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 PRELUDE_CHOICE_TOKEN,
108 FIRST_WARN_BAR_TOKEN,
109 LAST_WARN_BAR_TOKEN = (FIRST_WARN_BAR_TOKEN + NB_WARNING_TYPES - 1),
110};
111
112typedef enum {
113 REVIEW_NAV = 0,
114 SETTINGS_NAV,
115 GENERIC_NAV,
116 STREAMING_NAV
117} NavType_t;
118
119typedef struct DetailsContext_s {
120 uint8_t nbPages;
121 uint8_t currentPage;
122 uint8_t currentPairIdx;
123 bool wrapping;
124 const char *tag;
125 const char *value;
126 const char *nextPageStart;
127} DetailsContext_t;
128
129typedef struct AddressConfirmationContext_s {
130 nbgl_layoutTagValue_t tagValuePairs[ADDR_VERIF_NB_PAIRS];
131 nbgl_layout_t *modalLayout;
132 uint8_t nbPairs;
133} AddressConfirmationContext_t;
134
135#ifdef NBGL_KEYPAD
136typedef struct KeypadContext_s {
137 uint8_t pinEntry[KEYPAD_MAX_DIGITS];
138 uint8_t pinLen;
139 uint8_t pinMinDigits;
140 uint8_t pinMaxDigits;
141 nbgl_layout_t *layoutCtx;
142 bool hidden;
143} KeypadContext_t;
144#endif
145
146typedef struct ReviewWithWarningContext_s {
147 bool isStreaming;
148 nbgl_operationType_t operationType;
149 const nbgl_contentTagValueList_t *tagValueList;
150 const nbgl_icon_details_t *icon;
151 const char *reviewTitle;
152 const char *reviewSubTitle;
153 const char *finishTitle;
154 const nbgl_warning_t *warning;
155 nbgl_choiceCallback_t choiceCallback;
156 nbgl_layout_t *layoutCtx;
157 nbgl_layout_t *modalLayout;
158 uint8_t securityReportLevel; // level 1 is the first level of menus
159 bool isIntro; // set to true during intro (before actual review)
160} ReviewWithWarningContext_t;
161
162typedef struct ChoiceWithDetailsContext_s {
163 const nbgl_genericDetails_t *details;
164 nbgl_layout_t *layoutCtx;
165 nbgl_layout_t *modalLayout;
166 uint8_t level; // level 1 is the first level of menus
167} ChoiceWithDetailsContext_t;
168
169typedef enum {
170 SHARE_CTX_KEYPAD,
171 SHARE_CTX_REVIEW_WITH_WARNING,
172 SHARE_CTX_CHOICE_WITH_DETAILS,
173} SharedContextUsage_t;
174
175// this union is intended to save RAM for context storage
176// indeed, these 3 contexts cannot happen simultaneously
177typedef struct {
178 union {
179#ifdef NBGL_KEYPAD
180 KeypadContext_t keypad;
181#endif
182 ReviewWithWarningContext_t reviewWithWarning;
183 ChoiceWithDetailsContext_t choiceWithDetails;
184 };
185 SharedContextUsage_t usage;
186} SharedContext_t;
187
188typedef enum {
189 USE_CASE_GENERIC = 0,
190 USE_CASE_SPINNER
191} GenericContextType_t;
192
193typedef struct {
194 GenericContextType_t type; // type of Generic context usage
195 uint8_t spinnerPosition;
196 nbgl_genericContents_t genericContents;
197 int8_t currentContentIdx;
198 uint8_t currentContentElementNb;
199 uint8_t currentElementIdx;
200 bool hasStartingContent;
201 bool hasFinishingContent;
202 const char *detailsItem;
203 const char *detailsvalue;
204 bool detailsWrapping;
205 bool validWarningCtx; // set to true if the WarningContext is valid
207 *currentPairs; // to be used to retrieve the pairs with value alias
209 currentCallback; // to be used to retrieve the pairs with value alias
210 nbgl_layout_t modalLayout;
211 nbgl_layout_t backgroundLayout;
212 const nbgl_contentInfoList_t *currentInfos;
213 const nbgl_contentTagValueList_t *currentTagValues;
214 nbgl_tipBox_t tipBox;
215 SharedContext_t sharedCtx; // multi-purpose context shared for non-concurrent usages
216} GenericContext_t;
217
218typedef struct {
219 const char *appName;
220 const nbgl_icon_details_t *appIcon;
221 const char *tagline;
222 const nbgl_genericContents_t *settingContents;
223 const nbgl_contentInfoList_t *infosList;
224 nbgl_homeAction_t homeAction;
225 nbgl_callback_t quitCallback;
226} nbgl_homeAndSettingsContext_t;
227
228typedef struct {
229 nbgl_operationType_t operationType;
230 nbgl_choiceCallback_t choiceCallback;
231} nbgl_reviewContext_t;
232
233typedef struct {
234 nbgl_operationType_t operationType;
235 nbgl_choiceCallback_t choiceCallback;
236 nbgl_callback_t skipCallback;
237 const nbgl_icon_details_t *icon;
238 uint8_t stepPageNb;
239} nbgl_reviewStreamingContext_t;
240
241typedef union {
242 nbgl_homeAndSettingsContext_t homeAndSettings;
243 nbgl_reviewContext_t review;
244 nbgl_reviewStreamingContext_t reviewStreaming;
245} nbgl_BundleNavContext_t;
246
247typedef struct {
248 const nbgl_icon_details_t *icon;
249 const char *text;
250 const char *subText;
251} SecurityReportItem_t;
252
253/**********************
254 * STATIC VARIABLES
255 **********************/
256
257// char buffers to build some strings
258static char tmpString[W3C_DESCRIPTION_MAX_LEN];
259
260// multi-purposes callbacks
261static nbgl_callback_t onQuit;
262static nbgl_callback_t onContinue;
263static nbgl_callback_t onAction;
264static nbgl_navCallback_t onNav;
265static nbgl_layoutTouchCallback_t onControls;
266static nbgl_contentActionCallback_t onContentAction;
267static nbgl_choiceCallback_t onChoice;
268static nbgl_callback_t onModalConfirm;
269#ifdef NBGL_KEYPAD
270static nbgl_pinValidCallback_t onValidatePin;
271#endif
272
273// contexts for background and modal pages
274static nbgl_page_t *pageContext;
275static nbgl_page_t *modalPageContext;
276
277// context for pages
278static const char *pageTitle;
279
280// context for tip-box
281#define activeTipBox genericContext.tipBox
282
283// context for navigation use case
284static nbgl_pageNavigationInfo_t navInfo;
285static bool forwardNavOnly;
286static NavType_t navType;
287
288static DetailsContext_t detailsContext;
289
290// context for address review
291static AddressConfirmationContext_t addressConfirmationContext;
292
293// contexts for generic navigation
294static GenericContext_t genericContext;
295static nbgl_content_t
296 localContentsList[3]; // 3 needed for nbgl_useCaseReview (starting page / tags / final page)
297static uint8_t genericContextPagesInfo[MAX_PAGE_NB / PAGES_PER_UINT8];
298static uint8_t modalContextPagesInfo[MAX_MODAL_PAGE_NB / PAGES_PER_UINT8];
299
300// contexts for bundle navigation
301static nbgl_BundleNavContext_t bundleNavContext;
302
303// indexed by nbgl_contentType_t
304static const uint8_t nbMaxElementsPerContentType[] = {
305 1, // CENTERED_INFO
306 1, // EXTENDED_CENTER
307 1, // INFO_LONG_PRESS
308 1, // INFO_BUTTON
309 1, // TAG_VALUE_LIST (computed dynamically)
310 1, // TAG_VALUE_DETAILS
311 1, // TAG_VALUE_CONFIRM
312 3, // SWITCHES_LIST (computed dynamically)
313 3, // INFOS_LIST (computed dynamically)
314 5, // CHOICES_LIST (computed dynamically)
315 5, // BARS_LIST (computed dynamically)
316};
317
318// clang-format off
319static const SecurityReportItem_t securityReportItems[NB_WARNING_TYPES] = {
321 .icon = &WARNING_ICON,
322 .text = "Blind signing required",
323 .subText = "This transaction's details are not fully verifiable. If "
324 "you sign, you could lose all your assets."
325 },
326 [W3C_ISSUE_WARN] = {
327 .icon = &WARNING_ICON,
328 .text = "Transaction Check unavailable",
329 .subText = NULL
330 },
332 .icon = &WARNING_ICON,
333 .text = "Risk detected",
334 .subText = "This transaction was scanned as risky by Web3 Checks."
335 },
337 .icon = &WARNING_ICON,
338 .text = "Critical threat",
339 .subText = "This transaction was scanned as malicious by Web3 Checks."
340 },
342 .icon = NULL,
343 .text = "No threat detected",
344 .subText = "Transaction Check didn't find any threat, but always "
345 "review transaction details carefully."
346 }
347};
348// clang-format on
349
350// configuration of warning when using @ref nbgl_useCaseReviewBlindSigning()
351static const nbgl_warning_t blindSigningWarning = {.predefinedSet = (1 << BLIND_SIGNING_WARN)};
352
353#ifdef NBGL_QRCODE
354/* buffer to store reduced address under QR Code */
355static char reducedAddress[QRCODE_REDUCED_ADDR_LEN];
356#endif // NBGL_QRCODE
357
358/**********************
359 * STATIC FUNCTIONS
360 **********************/
361static void displayReviewPage(uint8_t page, bool forceFullRefresh);
362static void displayDetailsPage(uint8_t page, bool forceFullRefresh);
363static void displayTagValueListModalPage(uint8_t pageIdx, bool forceFullRefresh);
364static void displayFullValuePage(const char *backText,
365 const char *aliasText,
366 const nbgl_contentValueExt_t *extension);
367static void displayInfosListModal(const char *modalTitle,
368 const nbgl_contentInfoList_t *infos,
369 bool fromReview);
370static void displayTagValueListModal(const nbgl_contentTagValueList_t *tagValues);
371static void displaySettingsPage(uint8_t page, bool forceFullRefresh);
372static void displayGenericContextPage(uint8_t pageIdx, bool forceFullRefresh);
373static void pageCallback(int token, uint8_t index);
374#ifdef NBGL_QRCODE
375static void displayAddressQRCode(void);
376#endif // NBGL_QRCODE
377static void modalLayoutTouchCallback(int token, uint8_t index);
378static void displaySkipWarning(void);
379
380static void bundleNavStartHome(void);
381static void bundleNavStartSettingsAtPage(uint8_t initSettingPage);
382static void bundleNavStartSettings(void);
383
384static void bundleNavReviewStreamingChoice(bool confirm);
385static nbgl_layout_t *displayModalDetails(const nbgl_warningDetails_t *details, uint8_t token);
386static void displaySecurityReport(uint32_t set);
387static void displayCustomizedSecurityReport(const nbgl_warningDetails_t *details);
388static void displayInitialWarning(void);
389static void useCaseReview(nbgl_operationType_t operationType,
390 const nbgl_contentTagValueList_t *tagValueList,
391 const nbgl_icon_details_t *icon,
392 const char *reviewTitle,
393 const char *reviewSubTitle,
394 const char *finishTitle,
395 const nbgl_tipBox_t *tipBox,
396 nbgl_choiceCallback_t choiceCallback,
397 bool isLight,
398 bool playNotifSound);
399static void useCaseReviewStreamingStart(nbgl_operationType_t operationType,
400 const nbgl_icon_details_t *icon,
401 const char *reviewTitle,
402 const char *reviewSubTitle,
403 nbgl_choiceCallback_t choiceCallback,
404 bool playNotifSound);
405static void useCaseHomeExt(const char *appName,
406 const nbgl_icon_details_t *appIcon,
407 const char *tagline,
408 bool withSettings,
409 nbgl_homeAction_t *homeAction,
410 nbgl_callback_t topRightCallback,
411 nbgl_callback_t quitCallback);
412static void displayDetails(const char *tag, const char *value, bool wrapping);
413
414static void reset_callbacks_and_context(void)
415{
416 onQuit = NULL;
417 onContinue = NULL;
418 onAction = NULL;
419 onNav = NULL;
420 onControls = NULL;
421 onContentAction = NULL;
422 onChoice = NULL;
423#ifdef NBGL_KEYPAD
424 onValidatePin = NULL;
425#endif
426 memset(&genericContext, 0, sizeof(genericContext));
427}
428
429// Helper to set genericContext page info
430static void genericContextSetPageInfo(uint8_t pageIdx, uint8_t nbElements, bool flag)
431{
432 uint8_t pageData = SET_PAGE_NB_ELEMENTS(nbElements) + SET_PAGE_FLAG(flag);
433
434 genericContextPagesInfo[pageIdx / PAGES_PER_UINT8]
435 &= ~(0x0F << ((pageIdx % PAGES_PER_UINT8) * PAGE_DATA_BITS));
436 genericContextPagesInfo[pageIdx / PAGES_PER_UINT8]
437 |= pageData << ((pageIdx % PAGES_PER_UINT8) * PAGE_DATA_BITS);
438}
439
440// Helper to get genericContext page info
441static void genericContextGetPageInfo(uint8_t pageIdx, uint8_t *nbElements, bool *flag)
442{
443 uint8_t pageData = genericContextPagesInfo[pageIdx / PAGES_PER_UINT8]
444 >> ((pageIdx % PAGES_PER_UINT8) * PAGE_DATA_BITS);
445 if (nbElements != NULL) {
446 *nbElements = GET_PAGE_NB_ELEMENTS(pageData);
447 }
448 if (flag != NULL) {
449 *flag = GET_PAGE_FLAG(pageData);
450 }
451}
452
453// Helper to set modalContext page info
454static void modalContextSetPageInfo(uint8_t pageIdx, uint8_t nbElements)
455{
456 uint8_t pageData = SET_PAGE_NB_ELEMENTS(nbElements);
457
458 modalContextPagesInfo[pageIdx / PAGES_PER_UINT8]
459 &= ~(0x0F << ((pageIdx % PAGES_PER_UINT8) * PAGE_DATA_BITS));
460 modalContextPagesInfo[pageIdx / PAGES_PER_UINT8]
461 |= pageData << ((pageIdx % PAGES_PER_UINT8) * PAGE_DATA_BITS);
462}
463
464// Helper to get modalContext page info
465static void modalContextGetPageInfo(uint8_t pageIdx, uint8_t *nbElements)
466{
467 uint8_t pageData = modalContextPagesInfo[pageIdx / PAGES_PER_UINT8]
468 >> ((pageIdx % PAGES_PER_UINT8) * PAGE_DATA_BITS);
469 if (nbElements != NULL) {
470 *nbElements = GET_PAGE_NB_ELEMENTS(pageData);
471 }
472}
473
474// Simple helper to get the number of elements inside a nbgl_content_t
475static uint8_t getContentNbElement(const nbgl_content_t *content)
476{
477 switch (content->type) {
478 case TAG_VALUE_LIST:
479 return content->content.tagValueList.nbPairs;
482 case SWITCHES_LIST:
483 return content->content.switchesList.nbSwitches;
484 case INFOS_LIST:
485 return content->content.infosList.nbInfos;
486 case CHOICES_LIST:
487 return content->content.choicesList.nbChoices;
488 case BARS_LIST:
489 return content->content.barsList.nbBars;
490 default:
491 return 1;
492 }
493}
494
495// Helper to retrieve the content inside a nbgl_genericContents_t using
496// either the contentsList or using the contentGetterCallback
497static const nbgl_content_t *getContentAtIdx(const nbgl_genericContents_t *genericContents,
498 int8_t contentIdx,
499 nbgl_content_t *content)
500{
501 if (contentIdx < 0 || contentIdx >= genericContents->nbContents) {
502 LOG_DEBUG(USE_CASE_LOGGER, "No content available at %d\n", contentIdx);
503 return NULL;
504 }
505
506 if (genericContents->callbackCallNeeded) {
507 // Retrieve content through callback, but first memset the content.
508 memset(content, 0, sizeof(nbgl_content_t));
509 genericContents->contentGetterCallback(contentIdx, content);
510 return content;
511 }
512 else {
513 // Retrieve content through list
514 return PIC(&genericContents->contentsList[contentIdx]);
515 }
516}
517
518static void prepareNavInfo(bool isReview, uint8_t nbPages, const char *rejectText)
519{
520 memset(&navInfo, 0, sizeof(navInfo));
521
522 navInfo.nbPages = nbPages;
523 navInfo.tuneId = TUNE_TAP_CASUAL;
524 navInfo.progressIndicator = false;
525 navInfo.navType = NAV_WITH_BUTTONS;
526
527 if (isReview == false) {
528 navInfo.navWithButtons.navToken = NAV_TOKEN;
529 navInfo.navWithButtons.backButton = true;
530 }
531 else {
532 navInfo.quitToken = REJECT_TOKEN;
533 navInfo.navWithButtons.quitText = rejectText;
534 navInfo.navWithButtons.navToken = NAV_TOKEN;
536 = ((navType == STREAMING_NAV) && (nbPages < 2)) ? false : true;
537 navInfo.navWithButtons.visiblePageIndicator = (navType != STREAMING_NAV);
538 }
539}
540
541static void prepareReviewFirstPage(nbgl_contentCenter_t *contentCenter,
542 const nbgl_icon_details_t *icon,
543 const char *reviewTitle,
544 const char *reviewSubTitle)
545{
546 contentCenter->icon = icon;
547 contentCenter->title = reviewTitle;
548 contentCenter->description = reviewSubTitle;
549 contentCenter->subText = "Swipe to review";
550 contentCenter->smallTitle = NULL;
551 contentCenter->iconHug = 0;
552 contentCenter->padding = false;
553 contentCenter->illustrType = ICON_ILLUSTRATION;
554}
555
556static const char *getFinishTitle(nbgl_operationType_t operationType, const char *finishTitle)
557{
558 if (finishTitle != NULL) {
559 return finishTitle;
560 }
561 switch (operationType & REAL_TYPE_MASK) {
562 case TYPE_TRANSACTION:
563 return "Sign transaction";
564 case TYPE_MESSAGE:
565 return "Sign message";
566 default:
567 return "Sign operation";
568 }
569}
570
571static void prepareReviewLastPage(nbgl_operationType_t operationType,
572 nbgl_contentInfoLongPress_t *infoLongPress,
573 const nbgl_icon_details_t *icon,
574 const char *finishTitle)
575{
576 infoLongPress->text = getFinishTitle(operationType, finishTitle);
577 infoLongPress->icon = icon;
578 infoLongPress->longPressText = "Hold to sign";
579 infoLongPress->longPressToken = CONFIRM_TOKEN;
580}
581
582static void prepareReviewLightLastPage(nbgl_operationType_t operationType,
583 nbgl_contentInfoButton_t *infoButton,
584 const nbgl_icon_details_t *icon,
585 const char *finishTitle)
586{
587 infoButton->text = getFinishTitle(operationType, finishTitle);
588 infoButton->icon = icon;
589 infoButton->buttonText = "Approve";
590 infoButton->buttonToken = CONFIRM_TOKEN;
591}
592
593static const char *getRejectReviewText(nbgl_operationType_t operationType)
594{
595 UNUSED(operationType);
596 return "Reject";
597}
598
599// function called when navigating (or exiting) modal details pages
600// or when skip choice is displayed
601static void pageModalCallback(int token, uint8_t index)
602{
603 LOG_DEBUG(USE_CASE_LOGGER, "pageModalCallback, token = %d, index = %d\n", token, index);
604
605 if (token == INFOS_TIP_BOX_TOKEN) {
606 // the icon next to info type alias has been touched
607 displayFullValuePage(activeTipBox.infos.infoTypes[index],
608 activeTipBox.infos.infoContents[index],
609 &activeTipBox.infos.infoExtensions[index]);
610 return;
611 }
612 else if (token == INFO_ALIAS_TOKEN) {
613 // the icon next to info type alias has been touched, but coming from review
614 displayFullValuePage(genericContext.currentInfos->infoTypes[index],
615 genericContext.currentInfos->infoContents[index],
616 &genericContext.currentInfos->infoExtensions[index]);
617 return;
618 }
619 nbgl_pageRelease(modalPageContext);
620 modalPageContext = NULL;
621 if (token == NAV_TOKEN) {
622 if (index == EXIT_PAGE) {
623 // redraw the background layer
625 nbgl_refresh();
626 }
627 else {
628 displayDetailsPage(index, false);
629 }
630 }
631 if (token == MODAL_NAV_TOKEN) {
632 if (index == EXIT_PAGE) {
633 // redraw the background layer
635 nbgl_refresh();
636 }
637 else {
638 displayTagValueListModalPage(index, false);
639 }
640 }
641 else if (token == QUIT_TOKEN) {
642 // redraw the background layer
644 nbgl_refresh();
645 }
646 else if (token == QUIT_TIPBOX_MODAL_TOKEN) {
647 // if in "warning context", go back to security report
648 if (reviewWithWarnCtx.securityReportLevel == 2) {
649 reviewWithWarnCtx.securityReportLevel = 1;
650 displaySecurityReport(reviewWithWarnCtx.warning->predefinedSet);
651 }
652 else {
653 // redraw the background layer
655 nbgl_refresh();
656 }
657 }
658 else if (token == SKIP_TOKEN) {
659 if (index == 0) {
660 // display the last forward only review page, whatever it is
661 displayGenericContextPage(LAST_PAGE_FOR_REVIEW, true);
662 }
663 else {
664 // display background, which should be the page where skip has been touched
666 nbgl_refresh();
667 }
668 }
669 else if (token == CHOICE_TOKEN) {
670 if (index == 0) {
671 if (onModalConfirm != NULL) {
672 onModalConfirm();
673 onModalConfirm = NULL;
674 }
675 }
676 else {
677 // display background, which should be the page where skip has been touched
679 nbgl_refresh();
680 }
681 }
682}
683
684// generic callback for all pages except modal
685static void pageCallback(int token, uint8_t index)
686{
687 LOG_DEBUG(USE_CASE_LOGGER, "pageCallback, token = %d, index = %d\n", token, index);
688 if (token == QUIT_TOKEN) {
689 if (onQuit != NULL) {
690 onQuit();
691 }
692 }
693 else if (token == CONTINUE_TOKEN) {
694 if (onContinue != NULL) {
695 onContinue();
696 }
697 }
698 else if (token == CHOICE_TOKEN) {
699 if (onChoice != NULL) {
700 onChoice((index == 0) ? true : false);
701 }
702 }
703 else if (token == ACTION_BUTTON_TOKEN) {
704 if ((index == 0) && (onAction != NULL)) {
705 onAction();
706 }
707 else if ((index == 1) && (onQuit != NULL)) {
708 onQuit();
709 }
710 }
711#ifdef NBGL_QRCODE
712 else if (token == ADDRESS_QRCODE_BUTTON_TOKEN) {
713 displayAddressQRCode();
714 }
715#endif // NBGL_QRCODE
716 else if (token == CONFIRM_TOKEN) {
717 if (onChoice != NULL) {
718 onChoice(true);
719 }
720 }
721 else if (token == REJECT_TOKEN) {
722 if (onChoice != NULL) {
723 onChoice(false);
724 }
725 }
726 else if (token == DETAILS_BUTTON_TOKEN) {
727 displayDetails(genericContext.detailsItem,
728 genericContext.detailsvalue,
729 genericContext.detailsWrapping);
730 }
731 else if (token == NAV_TOKEN) {
732 if (index == EXIT_PAGE) {
733 if (onQuit != NULL) {
734 onQuit();
735 }
736 }
737 else {
738 if (navType == GENERIC_NAV || navType == STREAMING_NAV) {
739 displayGenericContextPage(index, false);
740 }
741 else if (navType == REVIEW_NAV) {
742 displayReviewPage(index, false);
743 }
744 else {
745 displaySettingsPage(index, false);
746 }
747 }
748 }
749 else if (token == NEXT_TOKEN) {
750 if (onNav != NULL) {
751 displayReviewPage(navInfo.activePage + 1, false);
752 }
753 else {
754 displayGenericContextPage(navInfo.activePage + 1, false);
755 }
756 }
757 else if (token == BACK_TOKEN) {
758 if (onNav != NULL) {
759 displayReviewPage(navInfo.activePage - 1, true);
760 }
761 else {
762 displayGenericContextPage(navInfo.activePage - 1, false);
763 }
764 }
765 else if (token == SKIP_TOKEN) {
766 // display a modal warning to confirm skip
767 displaySkipWarning();
768 }
769 else if (token == VALUE_ALIAS_TOKEN) {
770 // the icon next to value alias has been touched
771 const nbgl_contentTagValue_t *pair;
772 if (genericContext.currentPairs != NULL) {
773 pair = &genericContext.currentPairs[genericContext.currentElementIdx + index];
774 }
775 else {
776 pair = genericContext.currentCallback(genericContext.currentElementIdx + index);
777 }
778 displayFullValuePage(pair->item, pair->value, pair->extension);
779 }
780 else if (token == BLIND_WARNING_TOKEN) {
781 reviewWithWarnCtx.isIntro = false;
782 reviewWithWarnCtx.warning = NULL;
783 displaySecurityReport(1 << BLIND_SIGNING_WARN);
784 }
785 else if (token == WARNING_BUTTON_TOKEN) {
786 // top-right button, display the security modal
787 reviewWithWarnCtx.securityReportLevel = 1;
788 // if preset is used
789 if (reviewWithWarnCtx.warning->predefinedSet) {
790 displaySecurityReport(reviewWithWarnCtx.warning->predefinedSet);
791 }
792 else {
793 // use customized warning
794 if (reviewWithWarnCtx.isIntro) {
795 displayCustomizedSecurityReport(reviewWithWarnCtx.warning->introDetails);
796 }
797 else {
798 displayCustomizedSecurityReport(reviewWithWarnCtx.warning->reviewDetails);
799 }
800 }
801 }
802 else if (token == TIP_BOX_TOKEN) {
803 // if warning context is valid and if W3C directly display same as top-right
804 if (genericContext.validWarningCtx && (reviewWithWarnCtx.warning->predefinedSet != 0)) {
805 reviewWithWarnCtx.securityReportLevel = 1;
806 displaySecurityReport(reviewWithWarnCtx.warning->predefinedSet);
807 }
808 else {
809 displayInfosListModal(activeTipBox.modalTitle, &activeTipBox.infos, false);
810 }
811 }
812 else { // probably a control provided by caller
813 if (onContentAction != NULL) {
814 onContentAction(token, index, navInfo.activePage);
815 }
816 if (onControls != NULL) {
817 onControls(token, index);
818 }
819 }
820}
821
822// callback used for confirmation
823static void tickerCallback(void)
824{
825 nbgl_pageRelease(pageContext);
826 if (onQuit != NULL) {
827 onQuit();
828 }
829}
830
831// function used to display the current page in review
832static void displaySettingsPage(uint8_t page, bool forceFullRefresh)
833{
834 nbgl_pageContent_t content = {0};
835
836 if ((onNav == NULL) || (onNav(page, &content) == false)) {
837 return;
838 }
839
840 // override some fields
841 content.title = pageTitle;
842 content.isTouchableTitle = true;
843 content.titleToken = QUIT_TOKEN;
844 content.tuneId = TUNE_TAP_CASUAL;
845
846 navInfo.activePage = page;
847 pageContext = nbgl_pageDrawGenericContent(&pageCallback, &navInfo, &content);
848
849 if (forceFullRefresh) {
851 }
852 else {
854 }
855}
856
857// function used to display the current page in review
858static void displayReviewPage(uint8_t page, bool forceFullRefresh)
859{
860 nbgl_pageContent_t content = {0};
861
862 // ensure the page is valid
863 if ((navInfo.nbPages != 0) && (page >= (navInfo.nbPages))) {
864 return;
865 }
866 navInfo.activePage = page;
867 if ((onNav == NULL) || (onNav(navInfo.activePage, &content) == false)) {
868 return;
869 }
870
871 // override some fields
872 content.title = NULL;
873 content.isTouchableTitle = false;
874 content.tuneId = TUNE_TAP_CASUAL;
875
876 if (content.type == INFO_LONG_PRESS) { // last page
877 // for forward only review without known length...
878 // if we don't do that we cannot remove the '>' in the navigation bar at the last page
879 navInfo.nbPages = navInfo.activePage + 1;
880 content.infoLongPress.longPressToken = CONFIRM_TOKEN;
881 if (forwardNavOnly) {
882 // remove the "Skip" button
883 navInfo.skipText = NULL;
884 }
885 }
886
887 // override smallCaseForValue for tag/value types to false
888 if (content.type == TAG_VALUE_DETAILS) {
890 // the maximum displayable number of lines for value is NB_MAX_LINES_IN_REVIEW (without More
891 // button)
894 }
895 else if (content.type == TAG_VALUE_LIST) {
896 content.tagValueList.smallCaseForValue = false;
897 }
898 else if (content.type == TAG_VALUE_CONFIRM) {
900 // use confirm token for black button
901 content.tagValueConfirm.confirmationToken = CONFIRM_TOKEN;
902 }
903
904 pageContext = nbgl_pageDrawGenericContent(&pageCallback, &navInfo, &content);
905
906 if (forceFullRefresh) {
908 }
909 else {
911 }
912}
913
914// Helper that does the computing of which nbgl_content_t and which element in the content should be
915// displayed for the next generic context navigation page
916static const nbgl_content_t *genericContextComputeNextPageParams(uint8_t pageIdx,
917 nbgl_content_t *content,
918 uint8_t *p_nbElementsInNextPage,
919 bool *p_flag)
920{
921 int8_t nextContentIdx = genericContext.currentContentIdx;
922 int16_t nextElementIdx = genericContext.currentElementIdx;
923 uint8_t nbElementsInNextPage;
924
925 // Retrieve info on the next page
926 genericContextGetPageInfo(pageIdx, &nbElementsInNextPage, p_flag);
927 *p_nbElementsInNextPage = nbElementsInNextPage;
928
929 // Handle forward navigation:
930 // add to current index the number of pairs of the currently active page
931 if (pageIdx > navInfo.activePage) {
932 uint8_t nbElementsInCurrentPage;
933
934 genericContextGetPageInfo(navInfo.activePage, &nbElementsInCurrentPage, NULL);
935 nextElementIdx += nbElementsInCurrentPage;
936
937 // Handle case where the content to be displayed is in the next content
938 // In such case start at element index 0.
939 // If currentContentElementNb == 0, means not initialized, so skip
940 if ((nextElementIdx >= genericContext.currentContentElementNb)
941 && (genericContext.currentContentElementNb > 0)) {
942 nextContentIdx += 1;
943 nextElementIdx = 0;
944 }
945 }
946
947 // Handle backward navigation:
948 // add to current index the number of pairs of the currently active page
949 if (pageIdx < navInfo.activePage) {
950 // Backward navigation: remove to current index the number of pairs of the current page
951 nextElementIdx -= nbElementsInNextPage;
952
953 // Handle case where the content to be displayed is in the previous content
954 // In such case set a negative number as element index so that it is handled
955 // later once the previous content is accessible so that its elements number
956 // can be retrieved.
957 if (nextElementIdx < 0) {
958 nextContentIdx -= 1;
959 nextElementIdx = -nbElementsInNextPage;
960 }
961 }
962
963 const nbgl_content_t *p_content;
964 // Retrieve next content
965 if ((nextContentIdx == -1) && (genericContext.hasStartingContent)) {
966 p_content = &STARTING_CONTENT;
967 }
968 else if ((nextContentIdx == genericContext.genericContents.nbContents)
969 && (genericContext.hasFinishingContent)) {
970 p_content = &FINISHING_CONTENT;
971 }
972 else {
973 p_content = getContentAtIdx(&genericContext.genericContents, nextContentIdx, content);
974
975 if (p_content == NULL) {
976 LOG_DEBUG(USE_CASE_LOGGER, "Fail to retrieve content\n");
977 return NULL;
978 }
979 }
980
981 // Handle cases where we are going to display data from a new content:
982 // - navigation to a previous or to the next content
983 // - First page display (genericContext.currentContentElementNb == 0)
984 //
985 // In such case we need to:
986 // - Update genericContext.currentContentIdx
987 // - Update genericContext.currentContentElementNb
988 // - Update onContentAction callback
989 // - Update nextElementIdx in case it was previously not calculable
990 if ((nextContentIdx != genericContext.currentContentIdx)
991 || (genericContext.currentContentElementNb == 0)) {
992 genericContext.currentContentIdx = nextContentIdx;
993 genericContext.currentContentElementNb = getContentNbElement(p_content);
994 onContentAction = PIC(p_content->contentActionCallback);
995 if (nextElementIdx < 0) {
996 nextElementIdx = genericContext.currentContentElementNb + nextElementIdx;
997 }
998 }
999
1000 // Sanity check
1001 if ((nextElementIdx < 0) || (nextElementIdx >= genericContext.currentContentElementNb)) {
1003 "Invalid element index %d / %d\n",
1004 nextElementIdx,
1005 genericContext.currentContentElementNb);
1006 return NULL;
1007 }
1008
1009 // Update genericContext elements
1010 genericContext.currentElementIdx = nextElementIdx;
1011 navInfo.activePage = pageIdx;
1012
1013 return p_content;
1014}
1015
1016// Helper that generates a nbgl_pageContent_t to be displayed for the next generic context
1017// navigation page
1018static bool genericContextPreparePageContent(const nbgl_content_t *p_content,
1019 uint8_t nbElementsInPage,
1020 bool flag,
1021 nbgl_pageContent_t *pageContent)
1022{
1023 uint8_t nextElementIdx = genericContext.currentElementIdx;
1024
1025 pageContent->title = pageTitle;
1026 pageContent->isTouchableTitle = false;
1027 pageContent->titleToken = QUIT_TOKEN;
1028 pageContent->tuneId = TUNE_TAP_CASUAL;
1029
1030 pageContent->type = p_content->type;
1031 switch (pageContent->type) {
1032 case CENTERED_INFO:
1033 memcpy(&pageContent->centeredInfo,
1034 &p_content->content.centeredInfo,
1035 sizeof(pageContent->centeredInfo));
1036 break;
1037 case EXTENDED_CENTER:
1038 memcpy(&pageContent->extendedCenter,
1039 &p_content->content.extendedCenter,
1040 sizeof(pageContent->extendedCenter));
1041 break;
1042 case INFO_LONG_PRESS:
1043 memcpy(&pageContent->infoLongPress,
1044 &p_content->content.infoLongPress,
1045 sizeof(pageContent->infoLongPress));
1046 break;
1047
1048 case INFO_BUTTON:
1049 memcpy(&pageContent->infoButton,
1050 &p_content->content.infoButton,
1051 sizeof(pageContent->infoButton));
1052 break;
1053
1054 case TAG_VALUE_LIST: {
1055 nbgl_contentTagValueList_t *p_tagValueList = &pageContent->tagValueList;
1056
1057 // memorize pairs (or callback) for usage when alias is used
1058 genericContext.currentPairs = p_content->content.tagValueList.pairs;
1059 genericContext.currentCallback = p_content->content.tagValueList.callback;
1060
1061 if (flag) {
1062 // Flag can be set if the pair is too long to fit or because it needs
1063 // to be displayed as centered info.
1064
1065 // First retrieve the pair
1066 const nbgl_layoutTagValue_t *pair;
1067
1068 if (p_content->content.tagValueList.pairs != NULL) {
1069 pair = PIC(&p_content->content.tagValueList.pairs[nextElementIdx]);
1070 }
1071 else {
1072 pair = PIC(p_content->content.tagValueList.callback(nextElementIdx));
1073 }
1074
1075 if (pair->centeredInfo) {
1076 pageContent->type = EXTENDED_CENTER;
1077 prepareReviewFirstPage(&pageContent->extendedCenter.contentCenter,
1078 pair->valueIcon,
1079 pair->item,
1080 pair->value);
1081
1082 // Skip population of nbgl_contentTagValueList_t structure
1083 p_tagValueList = NULL;
1084 }
1085 else {
1086 // if the pair is too long to fit, we use a TAG_VALUE_DETAILS content
1087 pageContent->type = TAG_VALUE_DETAILS;
1088 pageContent->tagValueDetails.detailsButtonText = "More";
1089 pageContent->tagValueDetails.detailsButtonIcon = NULL;
1090 pageContent->tagValueDetails.detailsButtonToken = DETAILS_BUTTON_TOKEN;
1091
1092 p_tagValueList = &pageContent->tagValueDetails.tagValueList;
1093
1094 // Backup pair info for easy data access upon click on button
1095 genericContext.detailsItem = pair->item;
1096 genericContext.detailsvalue = pair->value;
1097 genericContext.detailsWrapping = p_content->content.tagValueList.wrapping;
1098 }
1099 }
1100
1101 if (p_tagValueList != NULL) {
1102 p_tagValueList->nbPairs = nbElementsInPage;
1103 p_tagValueList->token = p_content->content.tagValueList.token;
1104 if (p_content->content.tagValueList.pairs != NULL) {
1105 p_tagValueList->pairs
1106 = PIC(&p_content->content.tagValueList.pairs[nextElementIdx]);
1107 // parse pairs to check if any contains an alias for value
1108 for (uint8_t i = 0; i < nbElementsInPage; i++) {
1109 if (p_tagValueList->pairs[i].aliasValue) {
1110 p_tagValueList->token = VALUE_ALIAS_TOKEN;
1111 break;
1112 }
1113 }
1114 }
1115 else {
1116 p_tagValueList->pairs = NULL;
1117 p_tagValueList->callback = p_content->content.tagValueList.callback;
1118 p_tagValueList->startIndex = nextElementIdx;
1119 // parse pairs to check if any contains an alias for value
1120 for (uint8_t i = 0; i < nbElementsInPage; i++) {
1121 const nbgl_layoutTagValue_t *pair
1122 = PIC(p_content->content.tagValueList.callback(nextElementIdx + i));
1123 if (pair->aliasValue) {
1124 p_tagValueList->token = VALUE_ALIAS_TOKEN;
1125 break;
1126 }
1127 }
1128 }
1129 p_tagValueList->smallCaseForValue = false;
1131 p_tagValueList->hideEndOfLastLine = true;
1132 p_tagValueList->wrapping = p_content->content.tagValueList.wrapping;
1133 }
1134
1135 break;
1136 }
1137 case TAG_VALUE_CONFIRM: {
1138 nbgl_contentTagValueList_t *p_tagValueList;
1139 // only display a TAG_VALUE_CONFIRM if we are at the last page
1140 if ((nextElementIdx + nbElementsInPage)
1142 memcpy(&pageContent->tagValueConfirm,
1143 &p_content->content.tagValueConfirm,
1144 sizeof(pageContent->tagValueConfirm));
1145 p_tagValueList = &pageContent->tagValueConfirm.tagValueList;
1146 }
1147 else {
1148 // else display it as a TAG_VALUE_LIST
1149 pageContent->type = TAG_VALUE_LIST;
1150 p_tagValueList = &pageContent->tagValueList;
1151 }
1152 p_tagValueList->nbPairs = nbElementsInPage;
1153 p_tagValueList->pairs
1154 = PIC(&p_content->content.tagValueConfirm.tagValueList.pairs[nextElementIdx]);
1155 // parse pairs to check if any contains an alias for value
1156 for (uint8_t i = 0; i < nbElementsInPage; i++) {
1157 if (p_tagValueList->pairs[i].aliasValue) {
1158 p_tagValueList->token = VALUE_ALIAS_TOKEN;
1159 break;
1160 }
1161 }
1162 // memorize pairs (or callback) for usage when alias is used
1163 genericContext.currentPairs = p_tagValueList->pairs;
1164 genericContext.currentCallback = p_tagValueList->callback;
1165 break;
1166 }
1167 case SWITCHES_LIST:
1168 pageContent->switchesList.nbSwitches = nbElementsInPage;
1169 pageContent->switchesList.switches
1170 = PIC(&p_content->content.switchesList.switches[nextElementIdx]);
1171 break;
1172 case INFOS_LIST:
1173 pageContent->infosList.nbInfos = nbElementsInPage;
1174 pageContent->infosList.infoTypes
1175 = PIC(&p_content->content.infosList.infoTypes[nextElementIdx]);
1176 pageContent->infosList.infoContents
1177 = PIC(&p_content->content.infosList.infoContents[nextElementIdx]);
1178 break;
1179 case CHOICES_LIST:
1180 memcpy(&pageContent->choicesList,
1181 &p_content->content.choicesList,
1182 sizeof(pageContent->choicesList));
1183 pageContent->choicesList.nbChoices = nbElementsInPage;
1184 pageContent->choicesList.names
1185 = PIC(&p_content->content.choicesList.names[nextElementIdx]);
1186 if ((p_content->content.choicesList.initChoice >= nextElementIdx)
1187 && (p_content->content.choicesList.initChoice
1188 < nextElementIdx + nbElementsInPage)) {
1189 pageContent->choicesList.initChoice
1190 = p_content->content.choicesList.initChoice - nextElementIdx;
1191 }
1192 else {
1193 pageContent->choicesList.initChoice = nbElementsInPage;
1194 }
1195 break;
1196 case BARS_LIST:
1197 pageContent->barsList.nbBars = nbElementsInPage;
1198 pageContent->barsList.barTexts
1199 = PIC(&p_content->content.barsList.barTexts[nextElementIdx]);
1200 pageContent->barsList.tokens = PIC(&p_content->content.barsList.tokens[nextElementIdx]);
1201 pageContent->barsList.tuneId = p_content->content.barsList.tuneId;
1202 break;
1203 default:
1204 LOG_DEBUG(USE_CASE_LOGGER, "Unsupported type %d\n", pageContent->type);
1205 return false;
1206 }
1207
1208 bool isFirstOrLastPage
1209 = ((p_content->type == CENTERED_INFO) || (p_content->type == EXTENDED_CENTER))
1210 || (p_content->type == INFO_LONG_PRESS);
1211 nbgl_operationType_t operationType
1212 = (navType == STREAMING_NAV)
1213 ? bundleNavContext.reviewStreaming.operationType
1214 : ((navType == GENERIC_NAV) ? bundleNavContext.review.operationType : 0);
1215
1216 // if first or last page of review and blind/risky operation, add the warning top-right button
1217 if (isFirstOrLastPage
1218 && (operationType & (BLIND_OPERATION | RISKY_OPERATION | NO_THREAT_OPERATION))) {
1219 // if issue is only Web3Checks "no threat", use privacy icon
1220 if ((operationType & NO_THREAT_OPERATION)
1221 && !(reviewWithWarnCtx.warning->predefinedSet & (1 << BLIND_SIGNING_WARN))) {
1222 pageContent->topRightIcon = &PRIVACY_ICON;
1223 }
1224 else {
1225 pageContent->topRightIcon = &WARNING_ICON;
1226 }
1227
1228 pageContent->topRightToken
1229 = (operationType & BLIND_OPERATION) ? BLIND_WARNING_TOKEN : WARNING_BUTTON_TOKEN;
1230 }
1231
1232 return true;
1233}
1234
1235// function used to display the current page in generic context navigation mode
1236static void displayGenericContextPage(uint8_t pageIdx, bool forceFullRefresh)
1237{
1238 // Retrieve next page parameters
1239 nbgl_content_t content;
1240 uint8_t nbElementsInPage;
1241 bool flag;
1242 const nbgl_content_t *p_content = NULL;
1243
1244 if (navType == STREAMING_NAV) {
1245 if (pageIdx == LAST_PAGE_FOR_REVIEW) {
1246 if (bundleNavContext.reviewStreaming.skipCallback != NULL) {
1247 bundleNavContext.reviewStreaming.skipCallback();
1248 }
1249 return;
1250 }
1251 else if (pageIdx >= bundleNavContext.reviewStreaming.stepPageNb) {
1252 bundleNavReviewStreamingChoice(true);
1253 return;
1254 }
1255 }
1256 else {
1257 // if coming from Skip modal, let's say we are at the last page
1258 if (pageIdx == LAST_PAGE_FOR_REVIEW) {
1259 pageIdx = navInfo.nbPages - 1;
1260 }
1261 }
1262
1263 if (navInfo.activePage == pageIdx) {
1264 p_content
1265 = genericContextComputeNextPageParams(pageIdx, &content, &nbElementsInPage, &flag);
1266 }
1267 else if (navInfo.activePage < pageIdx) {
1268 // Support going more than one step forward.
1269 // It occurs when initializing a navigation on an arbitrary page
1270 for (int i = navInfo.activePage + 1; i <= pageIdx; i++) {
1271 p_content = genericContextComputeNextPageParams(i, &content, &nbElementsInPage, &flag);
1272 }
1273 }
1274 else {
1275 if (pageIdx - navInfo.activePage > 1) {
1276 // We don't support going more than one step backward as it doesn't occurs for now?
1277 LOG_DEBUG(USE_CASE_LOGGER, "Unsupported navigation\n");
1278 return;
1279 }
1280 p_content
1281 = genericContextComputeNextPageParams(pageIdx, &content, &nbElementsInPage, &flag);
1282 }
1283
1284 if (p_content == NULL) {
1285 return;
1286 }
1287 // if the operation is skippable
1288 if ((navType != STREAMING_NAV)
1289 && (bundleNavContext.review.operationType & SKIPPABLE_OPERATION)) {
1290 // only present the "Skip" header on actual tag/value pages
1291 if ((pageIdx > 0) && (pageIdx < (navInfo.nbPages - 1))) {
1292 navInfo.progressIndicator = false;
1293 navInfo.skipText = "Skip";
1294 navInfo.skipToken = SKIP_TOKEN;
1295 }
1296 else {
1297 navInfo.skipText = NULL;
1298 }
1299 }
1300
1301 // Create next page content
1302 nbgl_pageContent_t pageContent = {0};
1303 if (!genericContextPreparePageContent(p_content, nbElementsInPage, flag, &pageContent)) {
1304 return;
1305 }
1306
1307 pageContext = nbgl_pageDrawGenericContent(&pageCallback, &navInfo, &pageContent);
1308
1309 if (forceFullRefresh) {
1311 }
1312 else {
1314 }
1315}
1316
1317// from the current details context, return a pointer on the details at the given page
1318static const char *getDetailsPageAt(uint8_t detailsPage)
1319{
1320 uint8_t page = 0;
1321 const char *currentChar = detailsContext.value;
1322 while (page < detailsPage) {
1323 uint16_t nbLines
1324 = nbgl_getTextNbLinesInWidth(SMALL_BOLD_FONT, currentChar, AVAILABLE_WIDTH, false);
1325 if (nbLines > NB_MAX_LINES_IN_DETAILS) {
1326 uint16_t len;
1327 nbgl_getTextMaxLenInNbLines(SMALL_BOLD_FONT,
1328 currentChar,
1331 &len,
1332 detailsContext.wrapping);
1333 // don't start next page on a \n
1334 if (currentChar[len] == '\n') {
1335 len++;
1336 }
1337 else if (detailsContext.wrapping == false) {
1338 len -= 3;
1339 }
1340 currentChar = currentChar + len;
1341 }
1342 page++;
1343 }
1344 return currentChar;
1345}
1346
1347// function used to display the current page in details review mode
1348static void displayDetailsPage(uint8_t detailsPage, bool forceFullRefresh)
1349{
1350 static nbgl_layoutTagValue_t currentPair;
1351 nbgl_pageNavigationInfo_t info = {.activePage = detailsPage,
1352 .nbPages = detailsContext.nbPages,
1353 .navType = NAV_WITH_BUTTONS,
1354 .quitToken = QUIT_TOKEN,
1355 .navWithButtons.navToken = NAV_TOKEN,
1356 .navWithButtons.quitButton = true,
1357 .navWithButtons.backButton = true,
1358 .navWithButtons.quitText = NULL,
1359 .progressIndicator = false,
1360 .tuneId = TUNE_TAP_CASUAL};
1362 .topRightIcon = NULL,
1363 .tagValueList.nbPairs = 1,
1364 .tagValueList.pairs = &currentPair,
1365 .tagValueList.smallCaseForValue = true,
1366 .tagValueList.wrapping = detailsContext.wrapping};
1367
1368 if (modalPageContext != NULL) {
1369 nbgl_pageRelease(modalPageContext);
1370 }
1371 currentPair.item = detailsContext.tag;
1372 // if move backward or first page
1373 if (detailsPage <= detailsContext.currentPage) {
1374 // recompute current start from beginning
1375 currentPair.value = getDetailsPageAt(detailsPage);
1376 forceFullRefresh = true;
1377 }
1378 // else move forward
1379 else {
1380 currentPair.value = detailsContext.nextPageStart;
1381 }
1382 detailsContext.currentPage = detailsPage;
1383 uint16_t nbLines = nbgl_getTextNbLinesInWidth(
1384 SMALL_BOLD_FONT, currentPair.value, AVAILABLE_WIDTH, detailsContext.wrapping);
1385
1386 if (nbLines > NB_MAX_LINES_IN_DETAILS) {
1387 uint16_t len;
1388 nbgl_getTextMaxLenInNbLines(SMALL_BOLD_FONT,
1389 currentPair.value,
1392 &len,
1393 detailsContext.wrapping);
1395 // don't start next page on a \n
1396 if (currentPair.value[len] == '\n') {
1397 len++;
1398 }
1399 else if (!detailsContext.wrapping) {
1401 len -= 3;
1402 }
1403 // memorize next position to save processing
1404 detailsContext.nextPageStart = currentPair.value + len;
1405 // use special feature to keep only NB_MAX_LINES_IN_DETAILS lines and replace the last 3
1406 // chars by "...", only if the next char to display in next page is not '\n'
1408 }
1409 else {
1410 detailsContext.nextPageStart = NULL;
1411 content.tagValueList.nbMaxLinesForValue = 0;
1412 }
1413 if (info.nbPages == 1) {
1414 // if only one page, no navigation bar, and use a footer instead
1415 info.navWithButtons.quitText = "Close";
1416 }
1417 modalPageContext = nbgl_pageDrawGenericContentExt(&pageModalCallback, &info, &content, true);
1418
1419 if (forceFullRefresh) {
1421 }
1422 else {
1424 }
1425}
1426
1427// function used to display the content of a full value, when touching an alias of a tag/value pair
1428static void displayFullValuePage(const char *backText,
1429 const char *aliasText,
1430 const nbgl_contentValueExt_t *extension)
1431{
1432 const char *modalTitle
1433 = (extension->backText != NULL) ? PIC(extension->backText) : PIC(backText);
1434 if (extension->aliasType == INFO_LIST_ALIAS) {
1435 genericContext.currentInfos = extension->infolist;
1436 displayInfosListModal(modalTitle, extension->infolist, true);
1437 }
1438 else if (extension->aliasType == TAG_VALUE_LIST_ALIAS) {
1439 genericContext.currentTagValues = extension->tagValuelist;
1440 displayTagValueListModal(extension->tagValuelist);
1441 }
1442 else {
1443 nbgl_layoutDescription_t layoutDescription = {.modal = true,
1444 .withLeftBorder = true,
1445 .onActionCallback = &modalLayoutTouchCallback,
1446 .tapActionText = NULL};
1448 .separationLine = false,
1449 .backAndText.token = 0,
1450 .backAndText.tuneId = TUNE_TAP_CASUAL,
1451 .backAndText.text = modalTitle};
1452 genericContext.modalLayout = nbgl_layoutGet(&layoutDescription);
1453 // add header with the tag part of the pair, to go back
1454 nbgl_layoutAddHeader(genericContext.modalLayout, &headerDesc);
1455 // add either QR Code or full value text
1456 if (extension->aliasType == QR_CODE_ALIAS) {
1457#ifdef NBGL_QRCODE
1458 nbgl_layoutQRCode_t qrCode
1459 = {.url = extension->fullValue,
1460 .text1 = (extension->title != NULL) ? extension->title : extension->fullValue,
1461 .text2 = extension->explanation,
1462 .centered = true,
1463 .offsetY = 0};
1464
1465 nbgl_layoutAddQRCode(genericContext.modalLayout, &qrCode);
1466#endif // NBGL_QRCODE
1467 }
1468 else {
1469 nbgl_layoutTextContent_t content = {0};
1470 content.title = aliasText;
1471 if (extension->aliasType == ENS_ALIAS) {
1472 content.info = "ENS names are resolved by Ledger backend.";
1473 }
1474 else if ((extension->aliasType == ADDRESS_BOOK_ALIAS)
1475 && (extension->aliasSubName != NULL)) {
1476 content.descriptions[content.nbDescriptions] = extension->aliasSubName;
1477 content.nbDescriptions++;
1478 }
1479 else {
1480 content.info = extension->explanation;
1481 }
1482 // add full value text
1483 content.descriptions[content.nbDescriptions] = extension->fullValue;
1484 content.nbDescriptions++;
1485 nbgl_layoutAddTextContent(genericContext.modalLayout, &content);
1486 }
1487 // draw & refresh
1488 nbgl_layoutDraw(genericContext.modalLayout);
1489 nbgl_refresh();
1490 }
1491}
1492
1493// function used to display the modal containing tip-box infos
1494static void displayInfosListModal(const char *modalTitle,
1495 const nbgl_contentInfoList_t *infos,
1496 bool fromReview)
1497{
1499 .nbPages = 1,
1500 .navType = NAV_WITH_BUTTONS,
1501 .quitToken = QUIT_TIPBOX_MODAL_TOKEN,
1502 .navWithButtons.navToken = NAV_TOKEN,
1503 .navWithButtons.quitButton = false,
1504 .navWithButtons.backButton = true,
1505 .navWithButtons.quitText = NULL,
1506 .progressIndicator = false,
1507 .tuneId = TUNE_TAP_CASUAL};
1508 nbgl_pageContent_t content
1509 = {.type = INFOS_LIST,
1510 .topRightIcon = NULL,
1511 .infosList.nbInfos = infos->nbInfos,
1512 .infosList.withExtensions = infos->withExtensions,
1513 .infosList.infoTypes = infos->infoTypes,
1514 .infosList.infoContents = infos->infoContents,
1515 .infosList.infoExtensions = infos->infoExtensions,
1516 .infosList.token = fromReview ? INFO_ALIAS_TOKEN : INFOS_TIP_BOX_TOKEN,
1517 .title = modalTitle,
1518 .titleToken = QUIT_TIPBOX_MODAL_TOKEN,
1519 .tuneId = TUNE_TAP_CASUAL};
1520
1521 if (modalPageContext != NULL) {
1522 nbgl_pageRelease(modalPageContext);
1523 }
1524 modalPageContext = nbgl_pageDrawGenericContentExt(&pageModalCallback, &info, &content, true);
1525
1527}
1528
1529// function used to display the modal containing alias tag-value pairs
1530static void displayTagValueListModalPage(uint8_t pageIdx, bool forceFullRefresh)
1531{
1532 nbgl_pageNavigationInfo_t info = {.activePage = pageIdx,
1533 .nbPages = detailsContext.nbPages,
1534 .navType = NAV_WITH_BUTTONS,
1535 .quitToken = QUIT_TOKEN,
1536 .navWithButtons.navToken = MODAL_NAV_TOKEN,
1537 .navWithButtons.quitButton = true,
1538 .navWithButtons.backButton = true,
1539 .navWithButtons.quitText = NULL,
1540 .progressIndicator = false,
1541 .tuneId = TUNE_TAP_CASUAL};
1543 .topRightIcon = NULL,
1544 .tagValueList.smallCaseForValue = true,
1545 .tagValueList.wrapping = detailsContext.wrapping};
1546 uint8_t nbElementsInPage;
1547
1548 // if first page or forward
1549 if (detailsContext.currentPage <= pageIdx) {
1550 modalContextGetPageInfo(pageIdx, &nbElementsInPage);
1551 }
1552 else {
1553 // backward direction
1554 modalContextGetPageInfo(pageIdx + 1, &nbElementsInPage);
1555 detailsContext.currentPairIdx -= nbElementsInPage;
1556 modalContextGetPageInfo(pageIdx, &nbElementsInPage);
1557 detailsContext.currentPairIdx -= nbElementsInPage;
1558 }
1559 detailsContext.currentPage = pageIdx;
1560
1561 content.tagValueList.pairs
1562 = &genericContext.currentTagValues->pairs[detailsContext.currentPairIdx];
1563 content.tagValueList.nbPairs = nbElementsInPage;
1564 detailsContext.currentPairIdx += nbElementsInPage;
1565 if (info.nbPages == 1) {
1566 // if only one page, no navigation bar, and use a footer instead
1567 info.navWithButtons.quitText = "Close";
1568 }
1569
1570 if (modalPageContext != NULL) {
1571 nbgl_pageRelease(modalPageContext);
1572 }
1573 modalPageContext = nbgl_pageDrawGenericContentExt(&pageModalCallback, &info, &content, true);
1574
1575 if (forceFullRefresh) {
1577 }
1578 else {
1580 }
1581}
1582
1583#ifdef NBGL_QRCODE
1584static void displayAddressQRCode(void)
1585{
1586 // display the address as QR Code
1587 nbgl_layoutDescription_t layoutDescription = {.modal = true,
1588 .withLeftBorder = true,
1589 .onActionCallback = &modalLayoutTouchCallback,
1590 .tapActionText = NULL};
1591 nbgl_layoutHeader_t headerDesc = {
1592 .type = HEADER_EMPTY, .separationLine = false, .emptySpace.height = SMALL_CENTERING_HEADER};
1593 nbgl_layoutQRCode_t qrCode = {.url = addressConfirmationContext.tagValuePairs[0].value,
1594 .text1 = NULL,
1595 .centered = true,
1596 .offsetY = 0};
1597
1598 addressConfirmationContext.modalLayout = nbgl_layoutGet(&layoutDescription);
1599 // add empty header for better look
1600 nbgl_layoutAddHeader(addressConfirmationContext.modalLayout, &headerDesc);
1601 // compute nb lines to check whether it shall be shorten (max is 3 lines)
1602 uint16_t nbLines = nbgl_getTextNbLinesInWidth(SMALL_REGULAR_FONT,
1603 addressConfirmationContext.tagValuePairs[0].value,
1605 false);
1606
1607 if (nbLines <= QRCODE_NB_MAX_LINES) {
1608 qrCode.text2 = addressConfirmationContext.tagValuePairs[0].value; // in gray
1609 }
1610 else {
1611 // only keep beginning and end of text, and add ... in the middle
1612 nbgl_textReduceOnNbLines(SMALL_REGULAR_FONT,
1613 addressConfirmationContext.tagValuePairs[0].value,
1615 QRCODE_NB_MAX_LINES,
1616 reducedAddress,
1617 QRCODE_REDUCED_ADDR_LEN);
1618 qrCode.text2 = reducedAddress; // in gray
1619 }
1620
1621 nbgl_layoutAddQRCode(addressConfirmationContext.modalLayout, &qrCode);
1622
1624 addressConfirmationContext.modalLayout, "Close", DISMISS_QR_TOKEN, TUNE_TAP_CASUAL);
1625 nbgl_layoutDraw(addressConfirmationContext.modalLayout);
1626 nbgl_refresh();
1627}
1628
1629#endif // NBGL_QRCODE
1630
1631// called when header is touched on modal page, to dismiss it
1632static void modalLayoutTouchCallback(int token, uint8_t index)
1633{
1634 UNUSED(index);
1635 if (token == DISMISS_QR_TOKEN) {
1636 // dismiss modal
1637 nbgl_layoutRelease(addressConfirmationContext.modalLayout);
1638 addressConfirmationContext.modalLayout = NULL;
1640 }
1641 else if (token == DISMISS_WARNING_TOKEN) {
1642 // dismiss modal
1643 nbgl_layoutRelease(reviewWithWarnCtx.modalLayout);
1644 // if already at first level, simply redraw the background
1645 if (reviewWithWarnCtx.securityReportLevel <= 1) {
1646 reviewWithWarnCtx.modalLayout = NULL;
1648 }
1649 else {
1650 // We are at level 2 of warning modal, so re-display the level 1
1651 reviewWithWarnCtx.securityReportLevel = 1;
1652 // if preset is used
1653 if (reviewWithWarnCtx.warning->predefinedSet) {
1654 displaySecurityReport(reviewWithWarnCtx.warning->predefinedSet);
1655 }
1656 else {
1657 // use customized warning
1658 const nbgl_warningDetails_t *details
1659 = (reviewWithWarnCtx.isIntro) ? reviewWithWarnCtx.warning->introDetails
1660 : reviewWithWarnCtx.warning->reviewDetails;
1661 displayCustomizedSecurityReport(details);
1662 }
1663 return;
1664 }
1665 }
1666 else if (token == DISMISS_DETAILS_TOKEN) {
1667 // dismiss modal
1668 nbgl_layoutRelease(choiceWithDetailsCtx.modalLayout);
1669 // if already at first level, simply redraw the background
1670 if (choiceWithDetailsCtx.level <= 1) {
1671 choiceWithDetailsCtx.modalLayout = NULL;
1673 }
1674 else {
1675 // We are at level 2 of details modal, so re-display the level 1
1676 choiceWithDetailsCtx.level = 1;
1677 choiceWithDetailsCtx.modalLayout
1678 = displayModalDetails(choiceWithDetailsCtx.details, DISMISS_DETAILS_TOKEN);
1679 }
1680 }
1681 else if ((token >= FIRST_WARN_BAR_TOKEN) && (token <= LAST_WARN_BAR_TOKEN)) {
1682 if (sharedContext.usage == SHARE_CTX_REVIEW_WITH_WARNING) {
1683 // dismiss modal before creating a new one
1684 nbgl_layoutRelease(reviewWithWarnCtx.modalLayout);
1685 reviewWithWarnCtx.securityReportLevel = 2;
1686 // if preset is used
1687 if (reviewWithWarnCtx.warning->predefinedSet) {
1688 displaySecurityReport(1 << (token - FIRST_WARN_BAR_TOKEN));
1689 }
1690 else {
1691 // use customized warning
1692 const nbgl_warningDetails_t *details
1693 = (reviewWithWarnCtx.isIntro) ? reviewWithWarnCtx.warning->introDetails
1694 : reviewWithWarnCtx.warning->reviewDetails;
1695 if (details->type == BAR_LIST_WARNING) {
1696 displayCustomizedSecurityReport(
1697 &details->barList.details[token - FIRST_WARN_BAR_TOKEN]);
1698 }
1699 }
1700 }
1701 else if (sharedContext.usage == SHARE_CTX_CHOICE_WITH_DETAILS) {
1702 const nbgl_warningDetails_t *details
1703 = &choiceWithDetailsCtx.details->barList.details[token - FIRST_WARN_BAR_TOKEN];
1704 if (details->title != NO_TYPE_WARNING) {
1705 // dismiss modal before creating a new one
1706 nbgl_layoutRelease(choiceWithDetailsCtx.modalLayout);
1707 choiceWithDetailsCtx.level = 2;
1708 choiceWithDetailsCtx.modalLayout
1709 = displayModalDetails(details, DISMISS_DETAILS_TOKEN);
1710 }
1711 }
1712 return;
1713 }
1714 else {
1715 // dismiss modal
1716 nbgl_layoutRelease(genericContext.modalLayout);
1717 genericContext.modalLayout = NULL;
1719 }
1720 nbgl_refresh();
1721}
1722
1723// called when item are touched in layout
1724static void layoutTouchCallback(int token, uint8_t index)
1725{
1726 // choice buttons in initial warning page
1727 if (token == WARNING_CHOICE_TOKEN) {
1728 if (index == 0) { // top button to exit
1729 reviewWithWarnCtx.choiceCallback(false);
1730 }
1731 else { // bottom button to continue to review
1732 reviewWithWarnCtx.isIntro = false;
1733 if (reviewWithWarnCtx.isStreaming) {
1734 useCaseReviewStreamingStart(reviewWithWarnCtx.operationType,
1735 reviewWithWarnCtx.icon,
1736 reviewWithWarnCtx.reviewTitle,
1737 reviewWithWarnCtx.reviewSubTitle,
1738 reviewWithWarnCtx.choiceCallback,
1739 false);
1740 }
1741 else {
1742 useCaseReview(reviewWithWarnCtx.operationType,
1743 reviewWithWarnCtx.tagValueList,
1744 reviewWithWarnCtx.icon,
1745 reviewWithWarnCtx.reviewTitle,
1746 reviewWithWarnCtx.reviewSubTitle,
1747 reviewWithWarnCtx.finishTitle,
1748 &activeTipBox,
1749 reviewWithWarnCtx.choiceCallback,
1750 false,
1751 false);
1752 }
1753 }
1754 }
1755 // top-right button in initial warning page
1756 else if (token == WARNING_BUTTON_TOKEN) {
1757 // start directly at first level of security report
1758 reviewWithWarnCtx.securityReportLevel = 1;
1759 // if preset is used
1760 if (reviewWithWarnCtx.warning->predefinedSet) {
1761 displaySecurityReport(reviewWithWarnCtx.warning->predefinedSet);
1762 }
1763 else {
1764 // use customized warning
1765 const nbgl_warningDetails_t *details = (reviewWithWarnCtx.isIntro)
1766 ? reviewWithWarnCtx.warning->introDetails
1767 : reviewWithWarnCtx.warning->reviewDetails;
1768 displayCustomizedSecurityReport(details);
1769 }
1770 }
1771 // top-right button in choice with details page
1772 else if (token == CHOICE_DETAILS_TOKEN) {
1773 choiceWithDetailsCtx.level = 1;
1774 choiceWithDetailsCtx.modalLayout
1775 = displayModalDetails(choiceWithDetailsCtx.details, DISMISS_DETAILS_TOKEN);
1776 }
1777 // main prelude page
1778 else if (token == PRELUDE_CHOICE_TOKEN) {
1779 if (index == 0) { // top button to display details about prelude
1780 displayCustomizedSecurityReport(reviewWithWarnCtx.warning->prelude->details);
1781 }
1782 else { // footer to continue to review
1783 if (HAS_INITIAL_WARNING(reviewWithWarnCtx.warning)) {
1784 displayInitialWarning();
1785 }
1786 else {
1787 reviewWithWarnCtx.isIntro = false;
1788 if (reviewWithWarnCtx.isStreaming) {
1789 useCaseReviewStreamingStart(reviewWithWarnCtx.operationType,
1790 reviewWithWarnCtx.icon,
1791 reviewWithWarnCtx.reviewTitle,
1792 reviewWithWarnCtx.reviewSubTitle,
1793 reviewWithWarnCtx.choiceCallback,
1794 false);
1795 }
1796 else {
1797 useCaseReview(reviewWithWarnCtx.operationType,
1798 reviewWithWarnCtx.tagValueList,
1799 reviewWithWarnCtx.icon,
1800 reviewWithWarnCtx.reviewTitle,
1801 reviewWithWarnCtx.reviewSubTitle,
1802 reviewWithWarnCtx.finishTitle,
1803 &activeTipBox,
1804 reviewWithWarnCtx.choiceCallback,
1805 false,
1806 false);
1807 }
1808 }
1809 }
1810 }
1811 // choice buttons
1812 else if (token == CHOICE_TOKEN) {
1813 if (onChoice != NULL) {
1814 onChoice((index == 0) ? true : false);
1815 }
1816 }
1817}
1818
1819// called when skip button is touched in footer, during forward only review
1820static void displaySkipWarning(void)
1821{
1823 .cancelText = "Go back to review",
1824 .centeredInfo.text1 = "Skip review?",
1825 .centeredInfo.text2
1826 = "If you're sure you don't need to review all fields, you can skip straight to signing.",
1827 .centeredInfo.text3 = NULL,
1828 .centeredInfo.style = LARGE_CASE_INFO,
1829 .centeredInfo.icon = &IMPORTANT_CIRCLE_ICON,
1830 .centeredInfo.offsetY = 0,
1831 .confirmationText = "Yes, skip",
1832 .confirmationToken = SKIP_TOKEN,
1833 .tuneId = TUNE_TAP_CASUAL,
1834 .modal = true};
1835 if (modalPageContext != NULL) {
1836 nbgl_pageRelease(modalPageContext);
1837 }
1838 modalPageContext = nbgl_pageDrawConfirmation(&pageModalCallback, &info);
1840}
1841
1842#ifdef NBGL_KEYPAD
1843// called to update the keypad and automatically show / hide:
1844// - backspace key if no digits are present
1845// - validation key if the min digit is reached
1846// - keypad if the max number of digit is reached
1847static void updateKeyPad(bool add)
1848{
1849 bool enableValidate, enableBackspace, enableDigits;
1850 bool redrawKeypad = false;
1852
1853 enableDigits = (keypadContext.pinLen < keypadContext.pinMaxDigits);
1854 enableValidate = (keypadContext.pinLen >= keypadContext.pinMinDigits);
1855 enableBackspace = (keypadContext.pinLen > 0);
1856 if (add) {
1857 if ((keypadContext.pinLen == keypadContext.pinMinDigits)
1858 || // activate "validate" button on keypad
1859 (keypadContext.pinLen == keypadContext.pinMaxDigits)
1860 || // deactivate "digits" on keypad
1861 (keypadContext.pinLen == 1)) { // activate "backspace"
1862 redrawKeypad = true;
1863 }
1864 }
1865 else { // remove
1866 if ((keypadContext.pinLen == 0) || // deactivate "backspace" button on keypad
1867 (keypadContext.pinLen == (keypadContext.pinMinDigits - 1))
1868 || // deactivate "validate" button on keypad
1869 (keypadContext.pinLen
1870 == (keypadContext.pinMaxDigits - 1))) { // reactivate "digits" on keypad
1871 redrawKeypad = true;
1872 }
1873 }
1874 if (keypadContext.hidden == true) {
1875 nbgl_layoutUpdateKeypadContent(keypadContext.layoutCtx, true, keypadContext.pinLen, NULL);
1876 }
1877 else {
1879 keypadContext.layoutCtx, false, 0, (const char *) keypadContext.pinEntry);
1880 }
1881 if (redrawKeypad) {
1883 keypadContext.layoutCtx, 0, enableValidate, enableBackspace, enableDigits);
1884 }
1885
1886 if ((!add) && (keypadContext.pinLen == 0)) {
1887 // Full refresh to fully clean the bullets when reaching 0 digits
1888 mode = FULL_COLOR_REFRESH;
1889 }
1891}
1892
1893// called when a key is touched on the keypad
1894static void keypadCallback(char touchedKey)
1895{
1896 switch (touchedKey) {
1897 case BACKSPACE_KEY:
1898 if (keypadContext.pinLen > 0) {
1899 keypadContext.pinLen--;
1900 keypadContext.pinEntry[keypadContext.pinLen] = 0;
1901 }
1902 updateKeyPad(false);
1903 break;
1904
1905 case VALIDATE_KEY:
1906 // Gray out keyboard / buttons as a first user feedback
1907 nbgl_layoutUpdateKeypad(keypadContext.layoutCtx, 0, false, false, true);
1910
1911 onValidatePin(keypadContext.pinEntry, keypadContext.pinLen);
1912 break;
1913
1914 default:
1915 if ((touchedKey >= 0x30) && (touchedKey < 0x40)) {
1916 if (keypadContext.pinLen < keypadContext.pinMaxDigits) {
1917 keypadContext.pinEntry[keypadContext.pinLen] = touchedKey;
1918 keypadContext.pinLen++;
1919 }
1920 updateKeyPad(true);
1921 }
1922 break;
1923 }
1924}
1925
1926static void keypadGeneric_cb(int token, uint8_t index)
1927{
1928 UNUSED(index);
1929 if (token != BACK_TOKEN) {
1930 return;
1931 }
1932 onQuit();
1933}
1934#endif
1935
1950static uint8_t getNbTagValuesInPage(uint8_t nbPairs,
1951 const nbgl_contentTagValueList_t *tagValueList,
1952 uint8_t startIndex,
1953 bool isSkippable,
1954 bool hasConfirmationButton,
1955 bool hasDetailsButton,
1956 bool *requireSpecificDisplay)
1957{
1958 uint8_t nbPairsInPage = 0;
1959 uint16_t currentHeight = PRE_TAG_VALUE_MARGIN; // upper margin
1960 uint16_t maxUsableHeight = TAG_VALUE_AREA_HEIGHT;
1961
1962 // if the review is skippable, it means that there is less height for tag/value pairs
1963 // the small centering header becomes a touchable header
1964 if (isSkippable) {
1965 maxUsableHeight -= TOUCHABLE_HEADER_BAR_HEIGHT - SMALL_CENTERING_HEADER;
1966 }
1967
1968 *requireSpecificDisplay = false;
1969 while (nbPairsInPage < nbPairs) {
1970 const nbgl_layoutTagValue_t *pair;
1971 nbgl_font_id_e value_font;
1972 uint16_t nbLines;
1973
1974 // margin between pairs
1975 // 12 or 24 px between each tag/value pair
1976 if (nbPairsInPage > 0) {
1977 currentHeight += INTER_TAG_VALUE_MARGIN;
1978 }
1979 // fetch tag/value pair strings.
1980 if (tagValueList->pairs != NULL) {
1981 pair = PIC(&tagValueList->pairs[startIndex + nbPairsInPage]);
1982 }
1983 else {
1984 pair = PIC(tagValueList->callback(startIndex + nbPairsInPage));
1985 }
1986
1987 if (pair->forcePageStart && nbPairsInPage > 0) {
1988 // This pair must be at the top of a page
1989 break;
1990 }
1991
1992 if (pair->centeredInfo) {
1993 if (nbPairsInPage > 0) {
1994 // This pair must be at the top of a page
1995 break;
1996 }
1997 else {
1998 // This pair is the only one of the page and has a specific display behavior
1999 nbPairsInPage = 1;
2000 *requireSpecificDisplay = true;
2001 break;
2002 }
2003 }
2004
2005 // tag height
2006 currentHeight += nbgl_getTextHeightInWidth(
2007 SMALL_REGULAR_FONT, pair->item, AVAILABLE_WIDTH, tagValueList->wrapping);
2008 // space between tag and value
2009 currentHeight += TAG_VALUE_INTERVALE;
2010 // set value font
2011 if (tagValueList->smallCaseForValue) {
2012 value_font = SMALL_REGULAR_FONT;
2013 }
2014 else {
2015 value_font = LARGE_MEDIUM_FONT;
2016 }
2017 // value height
2018 currentHeight += nbgl_getTextHeightInWidth(
2019 value_font, pair->value, AVAILABLE_WIDTH, tagValueList->wrapping);
2020
2021 // potential subAlias text
2022 if ((pair->aliasValue) && (pair->extension->aliasSubName)) {
2023 currentHeight += TAG_VALUE_INTERVALE + nbgl_getFontLineHeight(SMALL_REGULAR_FONT);
2024 }
2025 // nb lines for value
2027 value_font, pair->value, AVAILABLE_WIDTH, tagValueList->wrapping);
2028 if ((currentHeight >= maxUsableHeight) || (nbLines > NB_MAX_LINES_IN_REVIEW)) {
2029 if (nbPairsInPage == 0) {
2030 // Pair too long to fit in a single screen
2031 // It will be the only one of the page and has a specific display behavior
2032 nbPairsInPage = 1;
2033 *requireSpecificDisplay = true;
2034 }
2035 break;
2036 }
2037 nbPairsInPage++;
2038 }
2039 // if this is a TAG_VALUE_CONFIRM and we have reached the last pairs,
2040 // let's check if it still fits with a CONFIRMATION button, and if not,
2041 // remove the last pair
2042 if (hasConfirmationButton && (nbPairsInPage == nbPairs)) {
2043 maxUsableHeight -= UP_FOOTER_BUTTON_HEIGHT;
2044 if (currentHeight > maxUsableHeight) {
2045 nbPairsInPage--;
2046 }
2047 }
2048 // do the same with just a details button
2049 else if (hasDetailsButton) {
2050 maxUsableHeight -= (SMALL_BUTTON_RADIUS * 2);
2051 if (currentHeight > maxUsableHeight) {
2052 nbPairsInPage--;
2053 }
2054 }
2055 return nbPairsInPage;
2056}
2057
2067static uint8_t getNbTagValuesInDetailsPage(uint8_t nbPairs,
2068 const nbgl_contentTagValueList_t *tagValueList,
2069 uint8_t startIndex)
2070{
2071 uint8_t nbPairsInPage = 0;
2072 uint16_t currentHeight = PRE_TAG_VALUE_MARGIN; // upper margin
2073 uint16_t maxUsableHeight = TAG_VALUE_AREA_HEIGHT;
2074
2075 while (nbPairsInPage < nbPairs) {
2076 const nbgl_layoutTagValue_t *pair;
2077
2078 // margin between pairs
2079 // 12 or 24 px between each tag/value pair
2080 if (nbPairsInPage > 0) {
2081 currentHeight += INTER_TAG_VALUE_MARGIN;
2082 }
2083 // fetch tag/value pair strings.
2084 if (tagValueList->pairs != NULL) {
2085 pair = PIC(&tagValueList->pairs[startIndex + nbPairsInPage]);
2086 }
2087 else {
2088 pair = PIC(tagValueList->callback(startIndex + nbPairsInPage));
2089 }
2090
2091 // tag height
2092 currentHeight += nbgl_getTextHeightInWidth(
2093 SMALL_REGULAR_FONT, pair->item, AVAILABLE_WIDTH, tagValueList->wrapping);
2094 // space between tag and value
2095 currentHeight += 4;
2096
2097 // value height
2098 currentHeight += nbgl_getTextHeightInWidth(
2099 SMALL_REGULAR_FONT, pair->value, AVAILABLE_WIDTH, tagValueList->wrapping);
2100
2101 // we have reached the maximum height, it means than there are to many pairs
2102 if (currentHeight >= maxUsableHeight) {
2103 break;
2104 }
2105 nbPairsInPage++;
2106 }
2107 return nbPairsInPage;
2108}
2109
2110static uint8_t getNbPagesForContent(const nbgl_content_t *content,
2111 uint8_t pageIdxStart,
2112 bool isLast,
2113 bool isSkippable)
2114{
2115 uint8_t nbElements = 0;
2116 uint8_t nbPages = 0;
2117 uint8_t nbElementsInPage;
2118 uint8_t elemIdx = 0;
2119 bool flag;
2120
2121 nbElements = getContentNbElement(content);
2122
2123 while (nbElements > 0) {
2124 flag = 0;
2125 // if the current page is not the first one (or last), a navigation bar exists
2126 bool hasNav = !isLast || (pageIdxStart > 0) || (elemIdx > 0);
2127 if (content->type == TAG_VALUE_LIST) {
2128 nbElementsInPage = getNbTagValuesInPage(nbElements,
2129 &content->content.tagValueList,
2130 elemIdx,
2131 isSkippable,
2132 false,
2133 false,
2134 &flag);
2135 }
2136 else if (content->type == TAG_VALUE_CONFIRM) {
2137 nbElementsInPage = getNbTagValuesInPage(nbElements,
2139 elemIdx,
2140 isSkippable,
2141 isLast,
2142 !isLast,
2143 &flag);
2144 }
2145 else if (content->type == INFOS_LIST) {
2146 nbElementsInPage = nbgl_useCaseGetNbInfosInPage(
2147 nbElements, &content->content.infosList, elemIdx, hasNav);
2148 }
2149 else if (content->type == SWITCHES_LIST) {
2150 nbElementsInPage = nbgl_useCaseGetNbSwitchesInPage(
2151 nbElements, &content->content.switchesList, elemIdx, hasNav);
2152 }
2153 else if (content->type == BARS_LIST) {
2154 nbElementsInPage = nbgl_useCaseGetNbBarsInPage(
2155 nbElements, &content->content.barsList, elemIdx, hasNav);
2156 }
2157 else if (content->type == CHOICES_LIST) {
2158 nbElementsInPage = nbgl_useCaseGetNbChoicesInPage(
2159 nbElements, &content->content.choicesList, elemIdx, hasNav);
2160 }
2161 else {
2162 nbElementsInPage = MIN(nbMaxElementsPerContentType[content->type], nbElements);
2163 }
2164
2165 elemIdx += nbElementsInPage;
2166 genericContextSetPageInfo(pageIdxStart + nbPages, nbElementsInPage, flag);
2167 nbElements -= nbElementsInPage;
2168 nbPages++;
2169 }
2170
2171 return nbPages;
2172}
2173
2174static uint8_t getNbPagesForGenericContents(const nbgl_genericContents_t *genericContents,
2175 uint8_t pageIdxStart,
2176 bool isSkippable)
2177{
2178 uint8_t nbPages = 0;
2179 nbgl_content_t content;
2180 const nbgl_content_t *p_content;
2181
2182 for (int i = 0; i < genericContents->nbContents; i++) {
2183 p_content = getContentAtIdx(genericContents, i, &content);
2184 if (p_content == NULL) {
2185 return 0;
2186 }
2187 nbPages += getNbPagesForContent(p_content,
2188 pageIdxStart + nbPages,
2189 (i == (genericContents->nbContents - 1)),
2190 isSkippable);
2191 }
2192
2193 return nbPages;
2194}
2195
2196static void prepareAddressConfirmationPages(const char *address,
2197 const nbgl_contentTagValueList_t *tagValueList,
2198 nbgl_content_t *firstPageContent,
2199 nbgl_content_t *secondPageContent)
2200{
2201 nbgl_contentTagValueConfirm_t *tagValueConfirm;
2202
2203 addressConfirmationContext.tagValuePairs[0].item = "Address";
2204 addressConfirmationContext.tagValuePairs[0].value = address;
2205 addressConfirmationContext.nbPairs = 1;
2206
2207 // First page
2208 firstPageContent->type = TAG_VALUE_CONFIRM;
2209 tagValueConfirm = &firstPageContent->content.tagValueConfirm;
2210
2211#ifdef NBGL_QRCODE
2212 tagValueConfirm->detailsButtonIcon = &QRCODE_ICON;
2213 // only use "Show as QR" when address & pairs are not fitting in a single page
2214 if ((tagValueList != NULL) && (tagValueList->nbPairs < ADDR_VERIF_NB_PAIRS)) {
2216 bool flag;
2217 // copy in intermediate structure
2218 for (uint8_t i = 0; i < tagValueList->nbPairs; i++) {
2219 memcpy(&addressConfirmationContext.tagValuePairs[1 + i],
2220 &tagValueList->pairs[i],
2221 sizeof(nbgl_contentTagValue_t));
2222 addressConfirmationContext.nbPairs++;
2223 }
2224 // check how many can fit in a page
2225 memcpy(&tmpList, tagValueList, sizeof(nbgl_contentTagValueList_t));
2226 tmpList.nbPairs = addressConfirmationContext.nbPairs;
2227 tmpList.pairs = addressConfirmationContext.tagValuePairs;
2228 addressConfirmationContext.nbPairs = getNbTagValuesInPage(
2229 addressConfirmationContext.nbPairs, &tmpList, 0, false, true, true, &flag);
2230 // if they don't all fit, keep only the address
2231 if (tmpList.nbPairs > addressConfirmationContext.nbPairs) {
2232 addressConfirmationContext.nbPairs = 1;
2233 }
2234 }
2235 else {
2236 tagValueConfirm->detailsButtonText = NULL;
2237 }
2238 tagValueConfirm->detailsButtonToken = ADDRESS_QRCODE_BUTTON_TOKEN;
2239#else // NBGL_QRCODE
2240 tagValueConfirm->detailsButtonText = NULL;
2241 tagValueConfirm->detailsButtonIcon = NULL;
2242#endif // NBGL_QRCODE
2243 tagValueConfirm->tuneId = TUNE_TAP_CASUAL;
2244 tagValueConfirm->tagValueList.nbPairs = addressConfirmationContext.nbPairs;
2245 tagValueConfirm->tagValueList.pairs = addressConfirmationContext.tagValuePairs;
2246 tagValueConfirm->tagValueList.smallCaseForValue = false;
2247 tagValueConfirm->tagValueList.nbMaxLinesForValue = 0;
2248 tagValueConfirm->tagValueList.wrapping = false;
2249 // if it's an extended address verif, it takes 2 pages, so display a "Tap to continue", and
2250 // no confirmation button
2251 if ((tagValueList != NULL)
2252 && (tagValueList->nbPairs > (addressConfirmationContext.nbPairs - 1))) {
2253 tagValueConfirm->detailsButtonText = "Show as QR";
2254 tagValueConfirm->confirmationText = NULL;
2255 // the second page is dedicated to the extended tag/value pairs
2256 secondPageContent->type = TAG_VALUE_CONFIRM;
2257 tagValueConfirm = &secondPageContent->content.tagValueConfirm;
2258 tagValueConfirm->confirmationText = "Confirm";
2259 tagValueConfirm->confirmationToken = CONFIRM_TOKEN;
2260 tagValueConfirm->detailsButtonText = NULL;
2261 tagValueConfirm->detailsButtonIcon = NULL;
2262 tagValueConfirm->tuneId = TUNE_TAP_CASUAL;
2263 memcpy(&tagValueConfirm->tagValueList, tagValueList, sizeof(nbgl_contentTagValueList_t));
2264 tagValueConfirm->tagValueList.nbPairs
2265 = tagValueList->nbPairs - (addressConfirmationContext.nbPairs - 1);
2266 tagValueConfirm->tagValueList.pairs
2267 = &tagValueList->pairs[addressConfirmationContext.nbPairs - 1];
2268 }
2269 else {
2270 // otherwise no tap to continue but a confirmation button
2271 tagValueConfirm->confirmationText = "Confirm";
2272 tagValueConfirm->confirmationToken = CONFIRM_TOKEN;
2273 }
2274}
2275
2276static void bundleNavStartHome(void)
2277{
2278 nbgl_homeAndSettingsContext_t *context = &bundleNavContext.homeAndSettings;
2279
2280 useCaseHomeExt(context->appName,
2281 context->appIcon,
2282 context->tagline,
2283 context->settingContents != NULL ? true : false,
2284 &context->homeAction,
2285 bundleNavStartSettings,
2286 context->quitCallback);
2287}
2288
2289static void bundleNavStartSettingsAtPage(uint8_t initSettingPage)
2290{
2291 nbgl_homeAndSettingsContext_t *context = &bundleNavContext.homeAndSettings;
2292
2293 nbgl_useCaseGenericSettings(context->appName,
2294 initSettingPage,
2295 context->settingContents,
2296 context->infosList,
2297 bundleNavStartHome);
2298}
2299
2300static void bundleNavStartSettings(void)
2301{
2302 bundleNavStartSettingsAtPage(0);
2303}
2304
2305static void bundleNavReviewConfirmRejection(void)
2306{
2307 bundleNavContext.review.choiceCallback(false);
2308}
2309
2310static void bundleNavReviewAskRejectionConfirmation(nbgl_operationType_t operationType,
2311 nbgl_callback_t callback)
2312{
2313 const char *title;
2314 const char *confirmText;
2315 // clear skip and blind bits
2316 operationType
2317 &= ~(SKIPPABLE_OPERATION | BLIND_OPERATION | RISKY_OPERATION | NO_THREAT_OPERATION);
2318 if (operationType == TYPE_TRANSACTION) {
2319 title = "Reject transaction?";
2320 confirmText = "Go back to transaction";
2321 }
2322 else if (operationType == TYPE_MESSAGE) {
2323 title = "Reject message?";
2324 confirmText = "Go back to message";
2325 }
2326 else {
2327 title = "Reject operation?";
2328 confirmText = "Go back to operation";
2329 }
2330
2331 // display a choice to confirm/cancel rejection
2332 nbgl_useCaseConfirm(title, NULL, "Yes, reject", confirmText, callback);
2333}
2334
2335static void bundleNavReviewChoice(bool confirm)
2336{
2337 if (confirm) {
2338 bundleNavContext.review.choiceCallback(true);
2339 }
2340 else {
2341 bundleNavReviewAskRejectionConfirmation(bundleNavContext.review.operationType,
2342 bundleNavReviewConfirmRejection);
2343 }
2344}
2345
2346static void bundleNavReviewStreamingConfirmRejection(void)
2347{
2348 bundleNavContext.reviewStreaming.choiceCallback(false);
2349}
2350
2351static void bundleNavReviewStreamingChoice(bool confirm)
2352{
2353 if (confirm) {
2354 bundleNavContext.reviewStreaming.choiceCallback(true);
2355 }
2356 else {
2357 bundleNavReviewAskRejectionConfirmation(bundleNavContext.reviewStreaming.operationType,
2358 bundleNavReviewStreamingConfirmRejection);
2359 }
2360}
2361
2362// function used to display the details in modal (from Choice with details or from Customized
2363// security report) it can be either a single page with text or QR code, or a list of touchable bars
2364// leading to single pages
2365static nbgl_layout_t *displayModalDetails(const nbgl_warningDetails_t *details, uint8_t token)
2366{
2367 nbgl_layoutDescription_t layoutDescription = {.modal = true,
2368 .withLeftBorder = true,
2369 .onActionCallback = modalLayoutTouchCallback,
2370 .tapActionText = NULL};
2372 .separationLine = true,
2373 .backAndText.icon = NULL,
2374 .backAndText.tuneId = TUNE_TAP_CASUAL,
2375 .backAndText.token = token};
2376 uint8_t i;
2377 nbgl_layout_t *layout;
2378
2379 layout = nbgl_layoutGet(&layoutDescription);
2380 headerDesc.backAndText.text = details->title;
2381 nbgl_layoutAddHeader(layout, &headerDesc);
2382 if (details->type == BAR_LIST_WARNING) {
2383 // if more than one warning, so use a list of touchable bars
2384 for (i = 0; i < details->barList.nbBars; i++) {
2385 nbgl_layoutBar_t bar;
2386 bar.text = details->barList.texts[i];
2387 bar.subText = details->barList.subTexts[i];
2388 bar.iconRight
2389 = (details->barList.details[i].type != NO_TYPE_WARNING) ? &PUSH_ICON : NULL;
2390 bar.iconLeft = details->barList.icons[i];
2391 bar.token = FIRST_WARN_BAR_TOKEN + i;
2392 bar.tuneId = TUNE_TAP_CASUAL;
2393 bar.large = false;
2394 bar.inactive = false;
2395 nbgl_layoutAddTouchableBar(layout, &bar);
2397 }
2398 }
2399 else if (details->type == QRCODE_WARNING) {
2400#ifdef NBGL_QRCODE
2401 // display a QR Code
2402 nbgl_layoutAddQRCode(layout, &details->qrCode);
2403#endif // NBGL_QRCODE
2404 headerDesc.backAndText.text = details->title;
2405 }
2406 else if (details->type == CENTERED_INFO_WARNING) {
2407 // display a centered info
2408 nbgl_layoutAddContentCenter(layout, &details->centeredInfo);
2409 headerDesc.separationLine = false;
2410 }
2411 nbgl_layoutDraw(layout);
2412 nbgl_refresh();
2413 return layout;
2414}
2415
2416// function used to display the security level page in modal
2417// it can be either a single page with text or QR code, or a list of touchable bars leading
2418// to single pages
2419static void displaySecurityReport(uint32_t set)
2420{
2421 nbgl_layoutDescription_t layoutDescription = {.modal = true,
2422 .withLeftBorder = true,
2423 .onActionCallback = modalLayoutTouchCallback,
2424 .tapActionText = NULL};
2426 .separationLine = true,
2427 .backAndText.icon = NULL,
2428 .backAndText.tuneId = TUNE_TAP_CASUAL,
2429 .backAndText.token = DISMISS_WARNING_TOKEN};
2430 nbgl_layoutFooter_t footerDesc
2431 = {.type = FOOTER_EMPTY, .separationLine = false, .emptySpace.height = 0};
2432 uint8_t i;
2433 const char *provider;
2434
2435 reviewWithWarnCtx.modalLayout = nbgl_layoutGet(&layoutDescription);
2436
2437 // display a list of touchable bars in some conditions:
2438 // - we must be in the first level of security report
2439 // - and be in the review itself (not from the intro/warning screen)
2440 //
2441 if ((reviewWithWarnCtx.securityReportLevel == 1) && (!reviewWithWarnCtx.isIntro)) {
2442 for (i = 0; i < NB_WARNING_TYPES; i++) {
2443 // Skip GATED_SIGNING_WARN from security report bars
2444 if ((i != GATED_SIGNING_WARN)
2445 && (reviewWithWarnCtx.warning->predefinedSet & (1 << i))) {
2446 nbgl_layoutBar_t bar = {0};
2447 if ((i == BLIND_SIGNING_WARN) || (i == W3C_NO_THREAT_WARN) || (i == W3C_ISSUE_WARN)
2448 || (reviewWithWarnCtx.warning->providerMessage == NULL)) {
2449 bar.subText = securityReportItems[i].subText;
2450 }
2451 else {
2452 bar.subText = reviewWithWarnCtx.warning->providerMessage;
2453 }
2454 bar.text = securityReportItems[i].text;
2455 if (i != W3C_NO_THREAT_WARN) {
2456 bar.iconRight = &PUSH_ICON;
2457 bar.token = FIRST_WARN_BAR_TOKEN + i;
2458 bar.tuneId = TUNE_TAP_CASUAL;
2459 }
2460 else {
2462 }
2463 bar.iconLeft = securityReportItems[i].icon;
2464 nbgl_layoutAddTouchableBar(reviewWithWarnCtx.modalLayout, &bar);
2465 nbgl_layoutAddSeparationLine(reviewWithWarnCtx.modalLayout);
2466 }
2467 }
2468 headerDesc.backAndText.text = "Security report";
2469 nbgl_layoutAddHeader(reviewWithWarnCtx.modalLayout, &headerDesc);
2470 nbgl_layoutDraw(reviewWithWarnCtx.modalLayout);
2471 nbgl_refresh();
2472 return;
2473 }
2474 if (reviewWithWarnCtx.warning && reviewWithWarnCtx.warning->reportProvider) {
2475 provider = reviewWithWarnCtx.warning->reportProvider;
2476 }
2477 else {
2478 provider = "[unknown]";
2479 }
2480 if ((set & (1 << W3C_THREAT_DETECTED_WARN)) || (set & (1 << W3C_RISK_DETECTED_WARN))) {
2481 size_t urlLen = 0;
2482 char *destStr
2483 = tmpString
2484 + W3C_DESCRIPTION_MAX_LEN / 2; // use the second half of tmpString for strings
2485#ifdef NBGL_QRCODE
2486 // display a QR Code
2487 nbgl_layoutQRCode_t qrCode = {.url = destStr,
2488 .text1 = reviewWithWarnCtx.warning->reportUrl,
2489 .text2 = "Scan to view full report",
2490 .centered = true,
2491 .offsetY = 0};
2492 // add "https://"" as prefix of the given URL
2493 snprintf(destStr,
2494 W3C_DESCRIPTION_MAX_LEN / 2,
2495 "https://%s",
2496 reviewWithWarnCtx.warning->reportUrl);
2497 urlLen = strlen(destStr) + 1;
2498 nbgl_layoutAddQRCode(reviewWithWarnCtx.modalLayout, &qrCode);
2499 footerDesc.emptySpace.height = 24;
2500#endif // NBGL_QRCODE
2501 // use the next part of destStr for back bar text
2502 snprintf(destStr + urlLen, W3C_DESCRIPTION_MAX_LEN / 2 - urlLen, "%s report", provider);
2503 headerDesc.backAndText.text = destStr + urlLen;
2504 }
2505 else if (set & (1 << BLIND_SIGNING_WARN)) {
2506 // display a centered if in review
2507 nbgl_contentCenter_t info = {0};
2508 info.icon = &LARGE_WARNING_ICON;
2509 info.title = "This transaction cannot be Clear Signed";
2510 info.description
2511 = "This transaction or message cannot be decoded fully. If you choose to sign, you "
2512 "could be authorizing malicious actions that can drain your wallet.\n\nLearn more: "
2513 "ledger.com/e8";
2514 nbgl_layoutAddContentCenter(reviewWithWarnCtx.modalLayout, &info);
2515 footerDesc.emptySpace.height = SMALL_CENTERING_HEADER;
2516 headerDesc.separationLine = false;
2517 }
2518 else if (set & (1 << W3C_ISSUE_WARN)) {
2519 // if W3 Checks issue, display a centered info
2520 nbgl_contentCenter_t info = {0};
2521 info.icon = &LARGE_WARNING_ICON;
2522 info.title = "Transaction Check unavailable";
2523 info.description
2524 = "If you're not using the Ledger Wallet app, Transaction Check might not work. If "
2525 "you "
2526 "are using Ledger Wallet, reject the transaction and try again.\n\nGet help at "
2527 "ledger.com/e11";
2528 nbgl_layoutAddContentCenter(reviewWithWarnCtx.modalLayout, &info);
2529 footerDesc.emptySpace.height = SMALL_CENTERING_HEADER;
2530 headerDesc.separationLine = false;
2531 }
2532 nbgl_layoutAddHeader(reviewWithWarnCtx.modalLayout, &headerDesc);
2533 if (footerDesc.emptySpace.height > 0) {
2534 nbgl_layoutAddExtendedFooter(reviewWithWarnCtx.modalLayout, &footerDesc);
2535 }
2536 nbgl_layoutDraw(reviewWithWarnCtx.modalLayout);
2537 nbgl_refresh();
2538}
2539
2540// function used to display the security level page in modal
2541// it can be either a single page with text or QR code, or a list of touchable bars leading
2542// to single pages
2543static void displayCustomizedSecurityReport(const nbgl_warningDetails_t *details)
2544{
2545 reviewWithWarnCtx.modalLayout = displayModalDetails(details, DISMISS_WARNING_TOKEN);
2546}
2547
2548// function used to display the initial warning page when starting a "review with warning"
2549static void displayInitialWarning(void)
2550{
2551 // Play notification sound
2552#ifdef HAVE_PIEZO_SOUND
2553 tune_index_e tune = TUNE_RESERVED;
2554#endif // HAVE_PIEZO_SOUND
2555 nbgl_layoutDescription_t layoutDescription;
2556 nbgl_layoutChoiceButtons_t buttonsInfo = {.bottomText = "Continue anyway",
2557 .token = WARNING_CHOICE_TOKEN,
2558 .topText = "Back to safety",
2559 .style = ROUNDED_AND_FOOTER_STYLE,
2560 .tuneId = TUNE_TAP_CASUAL};
2561 nbgl_layoutHeader_t headerDesc = {.type = HEADER_EMPTY,
2562 .separationLine = false,
2563 .emptySpace.height = MEDIUM_CENTERING_HEADER};
2564 uint32_t set = reviewWithWarnCtx.warning->predefinedSet
2565 & ~((1 << W3C_NO_THREAT_WARN) | (1 << W3C_ISSUE_WARN));
2566
2567 bool isBlindSigningOnly
2568 = (set != 0) && ((set & ~((1 << BLIND_SIGNING_WARN) | (1 << GATED_SIGNING_WARN))) == 0);
2569 reviewWithWarnCtx.isIntro = true;
2570
2571 layoutDescription.modal = false;
2572 layoutDescription.withLeftBorder = true;
2573
2574 layoutDescription.onActionCallback = layoutTouchCallback;
2575 layoutDescription.tapActionText = NULL;
2576
2577 layoutDescription.ticker.tickerCallback = NULL;
2578 reviewWithWarnCtx.layoutCtx = nbgl_layoutGet(&layoutDescription);
2579
2580 nbgl_layoutAddHeader(reviewWithWarnCtx.layoutCtx, &headerDesc);
2581 if (reviewWithWarnCtx.warning->predefinedSet != 0) {
2582 // icon is different in casee of Blind Siging or Gated Signing
2583 nbgl_layoutAddTopRightButton(reviewWithWarnCtx.layoutCtx,
2584 isBlindSigningOnly ? &INFO_I_ICON : &QRCODE_ICON,
2585 WARNING_BUTTON_TOKEN,
2586 TUNE_TAP_CASUAL);
2587 }
2588 else if (reviewWithWarnCtx.warning->introTopRightIcon != NULL) {
2589 nbgl_layoutAddTopRightButton(reviewWithWarnCtx.layoutCtx,
2590 reviewWithWarnCtx.warning->introTopRightIcon,
2591 WARNING_BUTTON_TOKEN,
2592 TUNE_TAP_CASUAL);
2593 }
2594 // add main content
2595 // if predefined content is configured, use it preferably
2596 if (reviewWithWarnCtx.warning->predefinedSet != 0) {
2597 nbgl_contentCenter_t info = {0};
2598
2599 const char *provider;
2600
2601 // default icon
2602 info.icon = &LARGE_WARNING_ICON;
2603
2604 // use small title only if not Blind signing and not Gated signing
2605 if (isBlindSigningOnly == false) {
2606 if (reviewWithWarnCtx.warning->reportProvider) {
2607 provider = reviewWithWarnCtx.warning->reportProvider;
2608 }
2609 else {
2610 provider = "[unknown]";
2611 }
2612 info.smallTitle = tmpString;
2613 snprintf(tmpString, W3C_DESCRIPTION_MAX_LEN, "Detected by %s", provider);
2614 }
2615
2616 // If Blind Signing or Gated Signing
2617 if (isBlindSigningOnly) {
2618 info.title = "Blind signing ahead";
2619 if (set & (1 << GATED_SIGNING_WARN)) {
2620 info.description
2621 = "If you sign this transaction, you could loose your assets. "
2622 "Explore safer alternatives: ledger.com/integrated-apps";
2623 buttonsInfo.bottomText = "Accept risk and continue";
2624 }
2625 else {
2626 info.description
2627 = "This transaction's details are not fully verifiable. If you sign, you could "
2628 "lose all your assets.";
2629 buttonsInfo.bottomText = "Continue anyway";
2630 }
2631#ifdef HAVE_PIEZO_SOUND
2632 tune = TUNE_NEUTRAL;
2633#endif // HAVE_PIEZO_SOUND
2634 }
2635 else if (set & (1 << W3C_RISK_DETECTED_WARN)) {
2636 info.title = "Potential risk";
2637 if (reviewWithWarnCtx.warning->providerMessage == NULL) {
2638 info.description = "Unidentified risk";
2639 }
2640 else {
2641 info.description = reviewWithWarnCtx.warning->providerMessage;
2642 }
2643 buttonsInfo.bottomText = "Accept risk and continue";
2644#ifdef HAVE_PIEZO_SOUND
2645 tune = TUNE_NEUTRAL;
2646#endif // HAVE_PIEZO_SOUND
2647 }
2648 else if (set & (1 << W3C_THREAT_DETECTED_WARN)) {
2649 info.title = "Critical threat";
2650 if (reviewWithWarnCtx.warning->providerMessage == NULL) {
2651 info.description = "Unidentified threat";
2652 }
2653 else {
2654 info.description = reviewWithWarnCtx.warning->providerMessage;
2655 }
2656 buttonsInfo.bottomText = "Accept threat and continue";
2657#ifdef HAVE_PIEZO_SOUND
2658 tune = TUNE_ERROR;
2659#endif // HAVE_PIEZO_SOUND
2660 }
2661 nbgl_layoutAddContentCenter(reviewWithWarnCtx.layoutCtx, &info);
2662 }
2663 else if (reviewWithWarnCtx.warning->info != NULL) {
2664 // if no predefined content, use custom one
2665 nbgl_layoutAddContentCenter(reviewWithWarnCtx.layoutCtx, reviewWithWarnCtx.warning->info);
2666#ifdef HAVE_PIEZO_SOUND
2667 tune = TUNE_LOOK_AT_ME;
2668#endif // HAVE_PIEZO_SOUND
2669 }
2670 // add button and footer on bottom
2671 nbgl_layoutAddChoiceButtons(reviewWithWarnCtx.layoutCtx, &buttonsInfo);
2672
2673#ifdef HAVE_PIEZO_SOUND
2674 if (tune != TUNE_RESERVED) {
2675 os_io_seph_cmd_piezo_play_tune(tune);
2676 }
2677#endif // HAVE_PIEZO_SOUND
2678 nbgl_layoutDraw(reviewWithWarnCtx.layoutCtx);
2679 nbgl_refresh();
2680}
2681
2682// function used to display the prelude when starting a "review with warning"
2683static void displayPrelude(void)
2684{
2685 nbgl_layoutDescription_t layoutDescription;
2686 nbgl_layoutChoiceButtons_t buttonsInfo
2687 = {.bottomText = reviewWithWarnCtx.warning->prelude->footerText,
2688 .token = PRELUDE_CHOICE_TOKEN,
2689 .topText = reviewWithWarnCtx.warning->prelude->buttonText,
2690 .style = ROUNDED_AND_FOOTER_STYLE,
2691 .tuneId = TUNE_TAP_CASUAL};
2692 nbgl_layoutHeader_t headerDesc = {.type = HEADER_EMPTY,
2693 .separationLine = false,
2694 .emptySpace.height = MEDIUM_CENTERING_HEADER};
2695
2696 reviewWithWarnCtx.isIntro = true;
2697
2698 layoutDescription.modal = false;
2699 layoutDescription.withLeftBorder = true;
2700 layoutDescription.onActionCallback = layoutTouchCallback;
2701 layoutDescription.tapActionText = NULL;
2702 layoutDescription.ticker.tickerCallback = NULL;
2703 reviewWithWarnCtx.layoutCtx = nbgl_layoutGet(&layoutDescription);
2704
2705 nbgl_layoutAddHeader(reviewWithWarnCtx.layoutCtx, &headerDesc);
2706 // add centered content
2707 nbgl_contentCenter_t info = {0};
2708
2709 // default icon
2710 info.icon = reviewWithWarnCtx.warning->prelude->icon;
2711
2712 info.title = reviewWithWarnCtx.warning->prelude->title;
2713 info.description = reviewWithWarnCtx.warning->prelude->description;
2714 nbgl_layoutAddContentCenter(reviewWithWarnCtx.layoutCtx, &info);
2715
2716 // add button and footer on bottom
2717 nbgl_layoutAddChoiceButtons(reviewWithWarnCtx.layoutCtx, &buttonsInfo);
2718
2719#ifdef HAVE_PIEZO_SOUND
2720 os_io_seph_cmd_piezo_play_tune(TUNE_LOOK_AT_ME);
2721#endif // HAVE_PIEZO_SOUND
2722 nbgl_layoutDraw(reviewWithWarnCtx.layoutCtx);
2723 nbgl_refresh();
2724}
2725
2726// function to factorize code for reviews tipbox
2727static void initWarningTipBox(const nbgl_tipBox_t *tipBox)
2728{
2729 const char *predefinedTipBoxText = NULL;
2730
2731 // if warning is valid and a warning requires a tip-box
2732 if (reviewWithWarnCtx.warning) {
2733 if (reviewWithWarnCtx.warning->predefinedSet & (1 << W3C_ISSUE_WARN)) {
2734 if (reviewWithWarnCtx.warning->predefinedSet & (1 << BLIND_SIGNING_WARN)) {
2735 predefinedTipBoxText = "Transaction Check unavailable.\nBlind signing required.";
2736 }
2737 else {
2738 predefinedTipBoxText = "Transaction Check unavailable";
2739 }
2740 }
2741 else if (reviewWithWarnCtx.warning->predefinedSet & (1 << W3C_THREAT_DETECTED_WARN)) {
2742 if (reviewWithWarnCtx.warning->predefinedSet & (1 << BLIND_SIGNING_WARN)) {
2743 predefinedTipBoxText = "Critical threat detected.\nBlind signing required.";
2744 }
2745 else {
2746 predefinedTipBoxText = "Critical threat detected.";
2747 }
2748 }
2749 else if (reviewWithWarnCtx.warning->predefinedSet & (1 << W3C_RISK_DETECTED_WARN)) {
2750 if (reviewWithWarnCtx.warning->predefinedSet & (1 << BLIND_SIGNING_WARN)) {
2751 predefinedTipBoxText = "Potential risk detected.\nBlind signing required.";
2752 }
2753 else {
2754 predefinedTipBoxText = "Potential risk detected.";
2755 }
2756 }
2757 else if (reviewWithWarnCtx.warning->predefinedSet & (1 << W3C_NO_THREAT_WARN)) {
2758 if (reviewWithWarnCtx.warning->predefinedSet & (1 << BLIND_SIGNING_WARN)) {
2759 predefinedTipBoxText
2760 = "No threat detected by Transaction Check but blind signing required.";
2761 }
2762 else {
2763 predefinedTipBoxText = "No threat detected by Transaction Check.";
2764 }
2765 }
2766 else if (reviewWithWarnCtx.warning->predefinedSet & (1 << BLIND_SIGNING_WARN)) {
2767 predefinedTipBoxText = "Blind signing required.";
2768 }
2769 }
2770
2771 if ((tipBox != NULL) || (predefinedTipBoxText != NULL)) {
2772 // do not display "Swipe to review" if a tip-box is displayed
2773 STARTING_CONTENT.content.extendedCenter.contentCenter.subText = NULL;
2774 if (predefinedTipBoxText != NULL) {
2775 genericContext.validWarningCtx = true;
2776 STARTING_CONTENT.content.extendedCenter.tipBox.icon = NULL;
2777 STARTING_CONTENT.content.extendedCenter.tipBox.text = predefinedTipBoxText;
2778 }
2779 else {
2780 STARTING_CONTENT.content.extendedCenter.tipBox.icon = tipBox->icon;
2781 STARTING_CONTENT.content.extendedCenter.tipBox.text = tipBox->text;
2782 }
2783 STARTING_CONTENT.content.extendedCenter.tipBox.token = TIP_BOX_TOKEN;
2784 STARTING_CONTENT.content.extendedCenter.tipBox.tuneId = TUNE_TAP_CASUAL;
2785 }
2786}
2787
2788// function to factorize code for all simple reviews
2789static void useCaseReview(nbgl_operationType_t operationType,
2790 const nbgl_contentTagValueList_t *tagValueList,
2791 const nbgl_icon_details_t *icon,
2792 const char *reviewTitle,
2793 const char *reviewSubTitle,
2794 const char *finishTitle,
2795 const nbgl_tipBox_t *tipBox,
2796 nbgl_choiceCallback_t choiceCallback,
2797 bool isLight,
2798 bool playNotifSound)
2799{
2800 bundleNavContext.review.operationType = operationType;
2801 bundleNavContext.review.choiceCallback = choiceCallback;
2802
2803 // memorize context
2804 onChoice = bundleNavReviewChoice;
2805 navType = GENERIC_NAV;
2806 pageTitle = NULL;
2807
2808 genericContext.genericContents.contentsList = localContentsList;
2809 genericContext.genericContents.nbContents = 3;
2810 memset(localContentsList, 0, 3 * sizeof(nbgl_content_t));
2811
2812 // First a centered info
2813 STARTING_CONTENT.type = EXTENDED_CENTER;
2814 prepareReviewFirstPage(
2815 &STARTING_CONTENT.content.extendedCenter.contentCenter, icon, reviewTitle, reviewSubTitle);
2816
2817 // Prepare un tipbox if needed
2818 initWarningTipBox(tipBox);
2819
2820 // Then the tag/value pairs
2821 localContentsList[1].type = TAG_VALUE_LIST;
2822 memcpy(&localContentsList[1].content.tagValueList,
2823 tagValueList,
2825 localContentsList[1].contentActionCallback = tagValueList->actionCallback;
2826
2827 // The last page
2828 if (isLight) {
2829 localContentsList[2].type = INFO_BUTTON;
2830 prepareReviewLightLastPage(
2831 operationType, &localContentsList[2].content.infoButton, icon, finishTitle);
2832 }
2833 else {
2834 localContentsList[2].type = INFO_LONG_PRESS;
2835 prepareReviewLastPage(
2836 operationType, &localContentsList[2].content.infoLongPress, icon, finishTitle);
2837 }
2838
2839 // compute number of pages & fill navigation structure
2840 uint8_t nbPages = getNbPagesForGenericContents(
2841 &genericContext.genericContents, 0, (operationType & SKIPPABLE_OPERATION));
2842 prepareNavInfo(true, nbPages, getRejectReviewText(operationType));
2843
2844 // Play notification sound if required
2845 if (playNotifSound) {
2846#ifdef HAVE_PIEZO_SOUND
2847 os_io_seph_cmd_piezo_play_tune(TUNE_LOOK_AT_ME);
2848#endif // HAVE_PIEZO_SOUND
2849 }
2850
2851 displayGenericContextPage(0, true);
2852}
2853
2854// function to factorize code for all streaming reviews
2855static void useCaseReviewStreamingStart(nbgl_operationType_t operationType,
2856 const nbgl_icon_details_t *icon,
2857 const char *reviewTitle,
2858 const char *reviewSubTitle,
2859 nbgl_choiceCallback_t choiceCallback,
2860 bool playNotifSound)
2861{
2862 bundleNavContext.reviewStreaming.operationType = operationType;
2863 bundleNavContext.reviewStreaming.choiceCallback = choiceCallback;
2864 bundleNavContext.reviewStreaming.icon = icon;
2865
2866 // memorize context
2867 onChoice = bundleNavReviewStreamingChoice;
2868 navType = STREAMING_NAV;
2869 pageTitle = NULL;
2870
2871 genericContext.genericContents.contentsList = localContentsList;
2872 genericContext.genericContents.nbContents = 1;
2873 memset(localContentsList, 0, 1 * sizeof(nbgl_content_t));
2874
2875 // First a centered info
2876 STARTING_CONTENT.type = EXTENDED_CENTER;
2877 prepareReviewFirstPage(
2878 &STARTING_CONTENT.content.extendedCenter.contentCenter, icon, reviewTitle, reviewSubTitle);
2879
2880 // Prepare un tipbox if needed
2881 initWarningTipBox(NULL);
2882
2883 // compute number of pages & fill navigation structure
2884 bundleNavContext.reviewStreaming.stepPageNb = getNbPagesForGenericContents(
2885 &genericContext.genericContents, 0, (operationType & SKIPPABLE_OPERATION));
2886 prepareNavInfo(true, NBGL_NO_PROGRESS_INDICATOR, getRejectReviewText(operationType));
2887 // no back button on first page
2888 navInfo.navWithButtons.backButton = false;
2889
2890 // Play notification sound if required
2891 if (playNotifSound) {
2892#ifdef HAVE_PIEZO_SOUND
2893 os_io_seph_cmd_piezo_play_tune(TUNE_LOOK_AT_ME);
2894#endif // HAVE_PIEZO_SOUND
2895 }
2896
2897 displayGenericContextPage(0, true);
2898}
2899
2916static void useCaseHomeExt(const char *appName,
2917 const nbgl_icon_details_t *appIcon,
2918 const char *tagline,
2919 bool withSettings,
2920 nbgl_homeAction_t *homeAction,
2921 nbgl_callback_t topRightCallback,
2922 nbgl_callback_t quitCallback)
2923{
2924 nbgl_pageInfoDescription_t info = {.centeredInfo.icon = appIcon,
2925 .centeredInfo.text1 = appName,
2926 .centeredInfo.text3 = NULL,
2927 .centeredInfo.style = LARGE_CASE_INFO,
2928 .centeredInfo.offsetY = 0,
2929 .footerText = NULL,
2930 .bottomButtonStyle = QUIT_APP_TEXT,
2931 .tapActionText = NULL,
2932 .topRightStyle = withSettings ? SETTINGS_ICON : INFO_ICON,
2933 .topRightToken = CONTINUE_TOKEN,
2934 .tuneId = TUNE_TAP_CASUAL};
2935 reset_callbacks_and_context();
2936
2937 if ((homeAction->text != NULL) || (homeAction->icon != NULL)) {
2938 // trick to use ACTION_BUTTON_TOKEN for action and quit, with index used to distinguish
2939 info.bottomButtonsToken = ACTION_BUTTON_TOKEN;
2940 onAction = homeAction->callback;
2941 info.actionButtonText = homeAction->text;
2942 info.actionButtonIcon = homeAction->icon;
2945 }
2946 else {
2947 info.bottomButtonsToken = QUIT_TOKEN;
2948 onAction = NULL;
2949 info.actionButtonText = NULL;
2950 info.actionButtonIcon = NULL;
2951 }
2952 if (tagline == NULL) {
2953 if (strlen(appName) > MAX_APP_NAME_FOR_SDK_TAGLINE) {
2954 snprintf(tmpString,
2956 "This app enables signing\ntransactions on its network.");
2957 }
2958 else {
2959 snprintf(tmpString,
2961 "%s %s\n%s",
2963 appName,
2965 }
2966
2967 // If there is more than 3 lines, it means the appName was split, so we put it on the next
2968 // line
2969 if (nbgl_getTextNbLinesInWidth(SMALL_REGULAR_FONT, tmpString, AVAILABLE_WIDTH, false) > 3) {
2970 snprintf(tmpString,
2972 "%s\n%s %s",
2974 appName,
2976 }
2977 info.centeredInfo.text2 = tmpString;
2978 }
2979 else {
2980 info.centeredInfo.text2 = tagline;
2981 }
2982
2983 onContinue = topRightCallback;
2984 onQuit = quitCallback;
2985
2986 pageContext = nbgl_pageDrawInfo(&pageCallback, NULL, &info);
2988}
2989
2998static void displayDetails(const char *tag, const char *value, bool wrapping)
2999{
3000 memset(&detailsContext, 0, sizeof(detailsContext));
3001
3002 uint16_t nbLines
3003 = nbgl_getTextNbLinesInWidth(SMALL_REGULAR_FONT, value, AVAILABLE_WIDTH, wrapping);
3004
3005 // initialize context
3006 detailsContext.tag = tag;
3007 detailsContext.value = value;
3008 detailsContext.nbPages = (nbLines + NB_MAX_LINES_IN_DETAILS - 1) / NB_MAX_LINES_IN_DETAILS;
3009 detailsContext.currentPage = 0;
3010 detailsContext.wrapping = wrapping;
3011 // add some spare for room lost with "..." substitution
3012 if (detailsContext.nbPages > 1) {
3013 uint16_t nbLostChars = (detailsContext.nbPages - 1) * 3;
3014 uint16_t nbLostLines = (nbLostChars + ((AVAILABLE_WIDTH) / 16) - 1)
3015 / ((AVAILABLE_WIDTH) / 16); // 16 for average char width
3016 uint8_t nbLinesInLastPage
3017 = nbLines - ((detailsContext.nbPages - 1) * NB_MAX_LINES_IN_DETAILS);
3018
3019 detailsContext.nbPages += nbLostLines / NB_MAX_LINES_IN_DETAILS;
3020 if ((nbLinesInLastPage + (nbLostLines % NB_MAX_LINES_IN_DETAILS))
3022 detailsContext.nbPages++;
3023 }
3024 }
3025
3026 displayDetailsPage(0, true);
3027}
3028
3029// function used to display the modal containing alias tag-value pairs
3030static void displayTagValueListModal(const nbgl_contentTagValueList_t *tagValues)
3031{
3032 uint8_t nbElements = 0;
3033 uint8_t nbElementsInPage;
3034 uint8_t elemIdx = 0;
3035
3036 // initialize context
3037 memset(&detailsContext, 0, sizeof(detailsContext));
3038 nbElements = tagValues->nbPairs;
3039
3040 while (nbElements > 0) {
3041 nbElementsInPage = getNbTagValuesInDetailsPage(nbElements, tagValues, elemIdx);
3042
3043 elemIdx += nbElementsInPage;
3044 modalContextSetPageInfo(detailsContext.nbPages, nbElementsInPage);
3045 nbElements -= nbElementsInPage;
3046 detailsContext.nbPages++;
3047 }
3048
3049 displayTagValueListModalPage(0, true);
3050}
3051
3052/**********************
3053 * GLOBAL FUNCTIONS
3054 **********************/
3055
3068uint8_t nbgl_useCaseGetNbTagValuesInPage(uint8_t nbPairs,
3069 const nbgl_contentTagValueList_t *tagValueList,
3070 uint8_t startIndex,
3071 bool *requireSpecificDisplay)
3072{
3073 return getNbTagValuesInPage(
3074 nbPairs, tagValueList, startIndex, false, false, false, requireSpecificDisplay);
3075}
3076
3090uint8_t nbgl_useCaseGetNbTagValuesInPageExt(uint8_t nbPairs,
3091 const nbgl_contentTagValueList_t *tagValueList,
3092 uint8_t startIndex,
3093 bool isSkippable,
3094 bool *requireSpecificDisplay)
3095{
3096 return getNbTagValuesInPage(
3097 nbPairs, tagValueList, startIndex, isSkippable, false, false, requireSpecificDisplay);
3098}
3099
3109uint8_t nbgl_useCaseGetNbInfosInPage(uint8_t nbInfos,
3110 const nbgl_contentInfoList_t *infosList,
3111 uint8_t startIndex,
3112 bool withNav)
3113{
3114 uint8_t nbInfosInPage = 0;
3115 uint16_t currentHeight = 0;
3116 uint16_t previousHeight;
3117 uint16_t navHeight = withNav ? SIMPLE_FOOTER_HEIGHT : 0;
3118 const char *const *infoContents = PIC(infosList->infoContents);
3119
3120 while (nbInfosInPage < nbInfos) {
3121 // The type string must be a 1 liner and its height is LIST_ITEM_MIN_TEXT_HEIGHT
3122 currentHeight
3123 += LIST_ITEM_MIN_TEXT_HEIGHT + 2 * LIST_ITEM_PRE_HEADING + LIST_ITEM_HEADING_SUB_TEXT;
3124
3125 // content height
3126 currentHeight += nbgl_getTextHeightInWidth(SMALL_REGULAR_FONT,
3127 PIC(infoContents[startIndex + nbInfosInPage]),
3129 true);
3130 // if height is over the limit
3131 if (currentHeight >= (INFOS_AREA_HEIGHT - navHeight)) {
3132 // if there was no nav, now there will be, so it can be necessary to remove the last
3133 // item
3134 if (!withNav && (previousHeight >= (INFOS_AREA_HEIGHT - SIMPLE_FOOTER_HEIGHT))) {
3135 nbInfosInPage--;
3136 }
3137 break;
3138 }
3139 previousHeight = currentHeight;
3140 nbInfosInPage++;
3141 }
3142 return nbInfosInPage;
3143}
3144
3154uint8_t nbgl_useCaseGetNbSwitchesInPage(uint8_t nbSwitches,
3155 const nbgl_contentSwitchesList_t *switchesList,
3156 uint8_t startIndex,
3157 bool withNav)
3158{
3159 uint8_t nbSwitchesInPage = 0;
3160 uint16_t currentHeight = 0;
3161 uint16_t previousHeight = 0;
3162 uint16_t navHeight = withNav ? SIMPLE_FOOTER_HEIGHT : 0;
3163 nbgl_contentSwitch_t *switchArray = (nbgl_contentSwitch_t *) PIC(switchesList->switches);
3164
3165 while (nbSwitchesInPage < nbSwitches) {
3166 nbgl_contentSwitch_t *curSwitch = &switchArray[startIndex + nbSwitchesInPage];
3167 // The text string is either a 1 liner and its height is LIST_ITEM_MIN_TEXT_HEIGHT
3168 // or we use its height directly
3169 uint16_t textHeight = MAX(
3170 LIST_ITEM_MIN_TEXT_HEIGHT,
3171 nbgl_getTextHeightInWidth(SMALL_BOLD_FONT, curSwitch->text, AVAILABLE_WIDTH, true));
3172 currentHeight += textHeight + 2 * LIST_ITEM_PRE_HEADING;
3173
3174 if (curSwitch->subText) {
3175 currentHeight += LIST_ITEM_HEADING_SUB_TEXT;
3176
3177 // sub-text height
3178 currentHeight += nbgl_getTextHeightInWidth(
3179 SMALL_REGULAR_FONT, curSwitch->subText, AVAILABLE_WIDTH, true);
3180 }
3181 // if height is over the limit
3182 if (currentHeight >= (INFOS_AREA_HEIGHT - navHeight)) {
3183 break;
3184 }
3185 previousHeight = currentHeight;
3186 nbSwitchesInPage++;
3187 }
3188 // if there was no nav, now there will be, so it can be necessary to remove the last
3189 // item
3190 if (!withNav && (previousHeight >= (INFOS_AREA_HEIGHT - SIMPLE_FOOTER_HEIGHT))) {
3191 nbSwitchesInPage--;
3192 }
3193 return nbSwitchesInPage;
3194}
3195
3205uint8_t nbgl_useCaseGetNbBarsInPage(uint8_t nbBars,
3206 const nbgl_contentBarsList_t *barsList,
3207 uint8_t startIndex,
3208 bool withNav)
3209{
3210 uint8_t nbBarsInPage = 0;
3211 uint16_t currentHeight = 0;
3212 uint16_t previousHeight;
3213 uint16_t navHeight = withNav ? SIMPLE_FOOTER_HEIGHT : 0;
3214
3215 UNUSED(barsList);
3216 UNUSED(startIndex);
3217
3218 while (nbBarsInPage < nbBars) {
3219 currentHeight += LIST_ITEM_MIN_TEXT_HEIGHT + 2 * LIST_ITEM_PRE_HEADING;
3220 // if height is over the limit
3221 if (currentHeight >= (INFOS_AREA_HEIGHT - navHeight)) {
3222 break;
3223 }
3224 previousHeight = currentHeight;
3225 nbBarsInPage++;
3226 }
3227 // if there was no nav, now there may will be, so it can be necessary to remove the last
3228 // item
3229 if (!withNav && (previousHeight >= (INFOS_AREA_HEIGHT - SIMPLE_FOOTER_HEIGHT))) {
3230 nbBarsInPage--;
3231 }
3232 return nbBarsInPage;
3233}
3234
3244uint8_t nbgl_useCaseGetNbChoicesInPage(uint8_t nbChoices,
3245 const nbgl_contentRadioChoice_t *choicesList,
3246 uint8_t startIndex,
3247 bool withNav)
3248{
3249 uint8_t nbChoicesInPage = 0;
3250 uint16_t currentHeight = 0;
3251 uint16_t previousHeight;
3252 uint16_t navHeight = withNav ? SIMPLE_FOOTER_HEIGHT : 0;
3253
3254 UNUSED(choicesList);
3255 UNUSED(startIndex);
3256
3257 while (nbChoicesInPage < nbChoices) {
3258 currentHeight += LIST_ITEM_MIN_TEXT_HEIGHT + 2 * LIST_ITEM_PRE_HEADING;
3259 // if height is over the limit
3260 if (currentHeight >= (INFOS_AREA_HEIGHT - navHeight)) {
3261 // if there was no nav, now there will be, so it can be necessary to remove the last
3262 // item
3263 if (!withNav && (previousHeight >= (INFOS_AREA_HEIGHT - SIMPLE_FOOTER_HEIGHT))) {
3264 nbChoicesInPage--;
3265 }
3266 break;
3267 }
3268 previousHeight = currentHeight;
3269 nbChoicesInPage++;
3270 }
3271 return nbChoicesInPage;
3272}
3273
3281{
3282 uint8_t nbPages = 0;
3283 uint8_t nbPairs = tagValueList->nbPairs;
3284 uint8_t nbPairsInPage;
3285 uint8_t i = 0;
3286 bool flag;
3287
3288 while (i < tagValueList->nbPairs) {
3289 // upper margin
3290 nbPairsInPage = nbgl_useCaseGetNbTagValuesInPageExt(nbPairs, tagValueList, i, false, &flag);
3291 i += nbPairsInPage;
3292 nbPairs -= nbPairsInPage;
3293 nbPages++;
3294 }
3295 return nbPages;
3296}
3297
3302void nbgl_useCaseHome(const char *appName,
3303 const nbgl_icon_details_t *appIcon,
3304 const char *tagline,
3305 bool withSettings,
3306 nbgl_callback_t topRightCallback,
3307 nbgl_callback_t quitCallback)
3308{
3309 nbgl_homeAction_t homeAction = {0};
3310 useCaseHomeExt(
3311 appName, appIcon, tagline, withSettings, &homeAction, topRightCallback, quitCallback);
3312}
3313
3318void nbgl_useCaseHomeExt(const char *appName,
3319 const nbgl_icon_details_t *appIcon,
3320 const char *tagline,
3321 bool withSettings,
3322 const char *actionButtonText,
3323 nbgl_callback_t actionCallback,
3324 nbgl_callback_t topRightCallback,
3325 nbgl_callback_t quitCallback)
3326{
3327 nbgl_homeAction_t homeAction = {.callback = actionCallback,
3328 .icon = NULL,
3329 .style = STRONG_HOME_ACTION,
3330 .text = actionButtonText};
3331
3332 useCaseHomeExt(
3333 appName, appIcon, tagline, withSettings, &homeAction, topRightCallback, quitCallback);
3334}
3335
3349void nbgl_useCaseNavigableContent(const char *title,
3350 uint8_t initPage,
3351 uint8_t nbPages,
3352 nbgl_callback_t quitCallback,
3353 nbgl_navCallback_t navCallback,
3354 nbgl_layoutTouchCallback_t controlsCallback)
3355{
3356 reset_callbacks_and_context();
3357
3358 // memorize context
3359 onQuit = quitCallback;
3360 onNav = navCallback;
3361 onControls = controlsCallback;
3362 pageTitle = title;
3363 navType = SETTINGS_NAV;
3364
3365 // fill navigation structure
3366 prepareNavInfo(false, nbPages, NULL);
3367
3368 displaySettingsPage(initPage, true);
3369}
3370
3376void nbgl_useCaseSettings(const char *title,
3377 uint8_t initPage,
3378 uint8_t nbPages,
3379 bool touchable,
3380 nbgl_callback_t quitCallback,
3381 nbgl_navCallback_t navCallback,
3382 nbgl_layoutTouchCallback_t controlsCallback)
3383{
3384 UNUSED(touchable);
3386 title, initPage, nbPages, quitCallback, navCallback, controlsCallback);
3387}
3388
3401void nbgl_useCaseGenericSettings(const char *appName,
3402 uint8_t initPage,
3403 const nbgl_genericContents_t *settingContents,
3404 const nbgl_contentInfoList_t *infosList,
3405 nbgl_callback_t quitCallback)
3406{
3407 reset_callbacks_and_context();
3408
3409 // memorize context
3410 onQuit = quitCallback;
3411 pageTitle = appName;
3412 navType = GENERIC_NAV;
3413
3414 if (settingContents != NULL) {
3415 memcpy(&genericContext.genericContents, settingContents, sizeof(nbgl_genericContents_t));
3416 }
3417 if (infosList != NULL) {
3418 genericContext.hasFinishingContent = true;
3419 memset(&FINISHING_CONTENT, 0, sizeof(nbgl_content_t));
3420 FINISHING_CONTENT.type = INFOS_LIST;
3421 memcpy(&FINISHING_CONTENT.content, infosList, sizeof(nbgl_content_u));
3422 }
3423
3424 // fill navigation structure
3425 uint8_t nbPages = getNbPagesForGenericContents(&genericContext.genericContents, 0, false);
3426 if (infosList != NULL) {
3427 nbPages += getNbPagesForContent(&FINISHING_CONTENT, nbPages, true, false);
3428 }
3429
3430 prepareNavInfo(false, nbPages, NULL);
3431
3432 displayGenericContextPage(initPage, true);
3433}
3434
3446void nbgl_useCaseGenericConfiguration(const char *title,
3447 uint8_t initPage,
3448 const nbgl_genericContents_t *contents,
3449 nbgl_callback_t quitCallback)
3450{
3451 nbgl_useCaseGenericSettings(title, initPage, contents, NULL, quitCallback);
3452}
3453
3471 const char *appName,
3472 const nbgl_icon_details_t *appIcon,
3473 const char *tagline,
3474 const uint8_t
3475 initSettingPage, // if not INIT_HOME_PAGE, start directly the corresponding setting page
3476 const nbgl_genericContents_t *settingContents,
3477 const nbgl_contentInfoList_t *infosList,
3478 const nbgl_homeAction_t *action, // Set to NULL if no additional action
3479 nbgl_callback_t quitCallback)
3480{
3481 nbgl_homeAndSettingsContext_t *context = &bundleNavContext.homeAndSettings;
3482
3483 reset_callbacks_and_context();
3484
3485 context->appName = appName;
3486 context->appIcon = appIcon;
3487 context->tagline = tagline;
3488 context->settingContents = settingContents;
3489 context->infosList = infosList;
3490 if (action != NULL) {
3491 memcpy(&context->homeAction, action, sizeof(nbgl_homeAction_t));
3492 }
3493 else {
3494 memset(&context->homeAction, 0, sizeof(nbgl_homeAction_t));
3495 }
3496 context->quitCallback = quitCallback;
3497
3498 if (initSettingPage != INIT_HOME_PAGE) {
3499 bundleNavStartSettingsAtPage(initSettingPage);
3500 }
3501 else {
3502 bundleNavStartHome();
3503 }
3504}
3505
3513void nbgl_useCaseStatus(const char *message, bool isSuccess, nbgl_callback_t quitCallback)
3514{
3515 nbgl_screenTickerConfiguration_t ticker = {.tickerCallback = &tickerCallback,
3516 .tickerIntervale = 0, // not periodic
3517 .tickerValue = STATUS_SCREEN_DURATION};
3518 nbgl_pageInfoDescription_t info = {0};
3519
3520 reset_callbacks_and_context();
3521
3522 onQuit = quitCallback;
3523 if (isSuccess) {
3524#ifdef HAVE_PIEZO_SOUND
3525 os_io_seph_cmd_piezo_play_tune(TUNE_LEDGER_MOMENT);
3526#endif // HAVE_PIEZO_SOUND
3527 }
3528 info.centeredInfo.icon = isSuccess ? &CHECK_CIRCLE_ICON : &DENIED_CIRCLE_ICON;
3530 info.centeredInfo.text1 = message;
3531 info.tapActionText = "";
3532 info.tapActionToken = QUIT_TOKEN;
3533 info.tuneId = TUNE_TAP_CASUAL;
3534 pageContext = nbgl_pageDrawInfo(&pageCallback, &ticker, &info);
3536}
3537
3545 nbgl_callback_t quitCallback)
3546{
3547 const char *msg;
3548 bool isSuccess;
3549 switch (reviewStatusType) {
3551 msg = "Operation signed";
3552 isSuccess = true;
3553 break;
3555 msg = "Operation rejected";
3556 isSuccess = false;
3557 break;
3559 msg = "Transaction signed";
3560 isSuccess = true;
3561 break;
3563 msg = "Transaction rejected";
3564 isSuccess = false;
3565 break;
3567 msg = "Message signed";
3568 isSuccess = true;
3569 break;
3571 msg = "Message rejected";
3572 isSuccess = false;
3573 break;
3575 msg = "Address verified";
3576 isSuccess = true;
3577 break;
3579 msg = "Address verification\ncancelled";
3580 isSuccess = false;
3581 break;
3582 default:
3583 return;
3584 }
3585 nbgl_useCaseStatus(msg, isSuccess, quitCallback);
3586}
3587
3601 const char *message,
3602 const char *subMessage,
3603 const char *confirmText,
3604 const char *cancelText,
3605 nbgl_choiceCallback_t callback)
3606{
3608 // check params
3609 if ((confirmText == NULL) || (cancelText == NULL)) {
3610 return;
3611 }
3612 reset_callbacks_and_context();
3613
3614 info.cancelText = cancelText;
3615 info.centeredInfo.text1 = message;
3616 info.centeredInfo.text2 = subMessage;
3618 info.centeredInfo.icon = icon;
3619 info.confirmationText = confirmText;
3620 info.confirmationToken = CHOICE_TOKEN;
3621 info.tuneId = TUNE_TAP_CASUAL;
3622
3623 onChoice = callback;
3624 pageContext = nbgl_pageDrawConfirmation(&pageCallback, &info);
3626}
3627
3643 const char *message,
3644 const char *subMessage,
3645 const char *confirmText,
3646 const char *cancelText,
3647 nbgl_genericDetails_t *details,
3648 nbgl_choiceCallback_t callback)
3649{
3650 nbgl_layoutDescription_t layoutDescription;
3651 nbgl_layoutChoiceButtons_t buttonsInfo = {.bottomText = cancelText,
3652 .token = CHOICE_TOKEN,
3653 .topText = confirmText,
3654 .style = ROUNDED_AND_FOOTER_STYLE,
3655 .tuneId = TUNE_TAP_CASUAL};
3656 nbgl_contentCenter_t centeredInfo = {0};
3657 nbgl_layoutHeader_t headerDesc = {.type = HEADER_EMPTY,
3658 .separationLine = false,
3659 .emptySpace.height = MEDIUM_CENTERING_HEADER};
3660
3661 // check params
3662 if ((confirmText == NULL) || (cancelText == NULL)) {
3663 return;
3664 }
3665
3666 reset_callbacks_and_context();
3667
3668 onChoice = callback;
3669 layoutDescription.modal = false;
3670 layoutDescription.withLeftBorder = true;
3671
3672 layoutDescription.onActionCallback = layoutTouchCallback;
3673 layoutDescription.tapActionText = NULL;
3674
3675 layoutDescription.ticker.tickerCallback = NULL;
3676 sharedContext.usage = SHARE_CTX_CHOICE_WITH_DETAILS;
3677 choiceWithDetailsCtx.layoutCtx = nbgl_layoutGet(&layoutDescription);
3678 choiceWithDetailsCtx.details = details;
3679
3680 nbgl_layoutAddHeader(choiceWithDetailsCtx.layoutCtx, &headerDesc);
3681 nbgl_layoutAddChoiceButtons(choiceWithDetailsCtx.layoutCtx, &buttonsInfo);
3682 centeredInfo.icon = icon;
3683 centeredInfo.title = message;
3684 centeredInfo.description = subMessage;
3685 nbgl_layoutAddContentCenter(choiceWithDetailsCtx.layoutCtx, &centeredInfo);
3686
3687 if (details != NULL) {
3689 choiceWithDetailsCtx.layoutCtx, &SEARCH_ICON, CHOICE_DETAILS_TOKEN, TUNE_TAP_CASUAL);
3690 }
3691
3692 nbgl_layoutDraw(choiceWithDetailsCtx.layoutCtx);
3694}
3695
3709void nbgl_useCaseConfirm(const char *message,
3710 const char *subMessage,
3711 const char *confirmText,
3712 const char *cancelText,
3713 nbgl_callback_t callback)
3714{
3715 // Don't reset callback or nav context as this is just a modal.
3716
3717 nbgl_pageConfirmationDescription_t info = {.cancelText = cancelText,
3718 .centeredInfo.text1 = message,
3719 .centeredInfo.text2 = subMessage,
3720 .centeredInfo.text3 = NULL,
3721 .centeredInfo.style = LARGE_CASE_INFO,
3722 .centeredInfo.icon = &IMPORTANT_CIRCLE_ICON,
3723 .centeredInfo.offsetY = 0,
3724 .confirmationText = confirmText,
3725 .confirmationToken = CHOICE_TOKEN,
3726 .tuneId = TUNE_TAP_CASUAL,
3727 .modal = true};
3728 onModalConfirm = callback;
3729 if (modalPageContext != NULL) {
3730 nbgl_pageRelease(modalPageContext);
3731 }
3732 modalPageContext = nbgl_pageDrawConfirmation(&pageModalCallback, &info);
3734}
3735
3747 const char *message,
3748 const char *actionText,
3749 nbgl_callback_t callback)
3750{
3751 nbgl_pageContent_t content = {0};
3752
3753 reset_callbacks_and_context();
3754 // memorize callback
3755 onAction = callback;
3756
3757 content.tuneId = TUNE_TAP_CASUAL;
3758 content.type = INFO_BUTTON;
3759 content.infoButton.buttonText = actionText;
3760 content.infoButton.text = message;
3761 content.infoButton.icon = icon;
3762 content.infoButton.buttonToken = ACTION_BUTTON_TOKEN;
3763
3764 pageContext = nbgl_pageDrawGenericContent(&pageCallback, NULL, &content);
3766}
3767
3780 const char *reviewTitle,
3781 const char *reviewSubTitle,
3782 const char *rejectText,
3783 nbgl_callback_t continueCallback,
3784 nbgl_callback_t rejectCallback)
3785{
3786 nbgl_pageInfoDescription_t info = {.footerText = rejectText,
3787 .footerToken = QUIT_TOKEN,
3788 .tapActionText = NULL,
3789 .isSwipeable = true,
3790 .tapActionToken = CONTINUE_TOKEN,
3791 .topRightStyle = NO_BUTTON_STYLE,
3792 .actionButtonText = NULL,
3793 .tuneId = TUNE_TAP_CASUAL};
3794
3795 reset_callbacks_and_context();
3796
3797 info.centeredInfo.icon = icon;
3798 info.centeredInfo.text1 = reviewTitle;
3799 info.centeredInfo.text2 = reviewSubTitle;
3800 info.centeredInfo.text3 = "Swipe to review";
3802 info.centeredInfo.offsetY = 0;
3803 onQuit = rejectCallback;
3804 onContinue = continueCallback;
3805
3806#ifdef HAVE_PIEZO_SOUND
3807 // Play notification sound
3808 os_io_seph_cmd_piezo_play_tune(TUNE_LOOK_AT_ME);
3809#endif // HAVE_PIEZO_SOUND
3810
3811 pageContext = nbgl_pageDrawInfo(&pageCallback, NULL, &info);
3812 nbgl_refresh();
3813}
3814
3819void nbgl_useCaseRegularReview(uint8_t initPage,
3820 uint8_t nbPages,
3821 const char *rejectText,
3822 nbgl_layoutTouchCallback_t buttonCallback,
3823 nbgl_navCallback_t navCallback,
3824 nbgl_choiceCallback_t choiceCallback)
3825{
3826 reset_callbacks_and_context();
3827
3828 // memorize context
3829 onChoice = choiceCallback;
3830 onNav = navCallback;
3831 onControls = buttonCallback;
3832 forwardNavOnly = false;
3833 navType = REVIEW_NAV;
3834
3835 // fill navigation structure
3836 UNUSED(rejectText);
3837 prepareNavInfo(true, nbPages, getRejectReviewText(TYPE_OPERATION));
3838
3839 displayReviewPage(initPage, true);
3840}
3841
3855 const nbgl_pageInfoLongPress_t *infoLongPress,
3856 const char *rejectText,
3857 nbgl_choiceCallback_t callback)
3858{
3859 uint8_t offset = 0;
3860
3861 reset_callbacks_and_context();
3862
3863 // memorize context
3864 onChoice = callback;
3865 navType = GENERIC_NAV;
3866 pageTitle = NULL;
3867 bundleNavContext.review.operationType = TYPE_OPERATION;
3868
3869 genericContext.genericContents.contentsList = localContentsList;
3870 memset(localContentsList, 0, 2 * sizeof(nbgl_content_t));
3871
3872 if (tagValueList != NULL && tagValueList->nbPairs != 0) {
3873 localContentsList[offset].type = TAG_VALUE_LIST;
3874 memcpy(&localContentsList[offset].content.tagValueList,
3875 tagValueList,
3877 offset++;
3878 }
3879
3880 localContentsList[offset].type = INFO_LONG_PRESS;
3881 memcpy(&localContentsList[offset].content.infoLongPress,
3882 infoLongPress,
3883 sizeof(nbgl_pageInfoLongPress_t));
3884 localContentsList[offset].content.infoLongPress.longPressToken = CONFIRM_TOKEN;
3885 offset++;
3886
3887 genericContext.genericContents.nbContents = offset;
3888
3889 // compute number of pages & fill navigation structure
3890 uint8_t nbPages = getNbPagesForGenericContents(&genericContext.genericContents, 0, false);
3891 UNUSED(rejectText);
3892 prepareNavInfo(true, nbPages, getRejectReviewText(TYPE_OPERATION));
3893
3894 displayGenericContextPage(0, true);
3895}
3896
3911 const nbgl_pageInfoLongPress_t *infoLongPress,
3912 const char *rejectText,
3913 nbgl_choiceCallback_t callback)
3914{
3915 uint8_t offset = 0;
3916
3917 reset_callbacks_and_context();
3918
3919 // memorize context
3920 onChoice = callback;
3921 navType = GENERIC_NAV;
3922 pageTitle = NULL;
3923
3924 genericContext.genericContents.contentsList = localContentsList;
3925 memset(localContentsList, 0, 2 * sizeof(nbgl_content_t));
3926
3927 if (tagValueList != NULL && tagValueList->nbPairs != 0) {
3928 localContentsList[offset].type = TAG_VALUE_LIST;
3929 memcpy(&localContentsList[offset].content.tagValueList,
3930 tagValueList,
3932 offset++;
3933 }
3934
3935 localContentsList[offset].type = INFO_BUTTON;
3936 localContentsList[offset].content.infoButton.text = infoLongPress->text;
3937 localContentsList[offset].content.infoButton.icon = infoLongPress->icon;
3938 localContentsList[offset].content.infoButton.buttonText = infoLongPress->longPressText;
3939 localContentsList[offset].content.infoButton.buttonToken = CONFIRM_TOKEN;
3940 localContentsList[offset].content.infoButton.tuneId = TUNE_TAP_CASUAL;
3941 offset++;
3942
3943 genericContext.genericContents.nbContents = offset;
3944
3945 // compute number of pages & fill navigation structure
3946 uint8_t nbPages = getNbPagesForGenericContents(&genericContext.genericContents, 0, false);
3947 UNUSED(rejectText);
3948 prepareNavInfo(true, nbPages, getRejectReviewText(TYPE_OPERATION));
3949
3950 displayGenericContextPage(0, true);
3951}
3952
3969void nbgl_useCaseReview(nbgl_operationType_t operationType,
3970 const nbgl_contentTagValueList_t *tagValueList,
3971 const nbgl_icon_details_t *icon,
3972 const char *reviewTitle,
3973 const char *reviewSubTitle,
3974 const char *finishTitle,
3975 nbgl_choiceCallback_t choiceCallback)
3976{
3977 reset_callbacks_and_context();
3978
3979 useCaseReview(operationType,
3980 tagValueList,
3981 icon,
3982 reviewTitle,
3983 reviewSubTitle,
3984 finishTitle,
3985 NULL,
3986 choiceCallback,
3987 false,
3988 true);
3989}
3990
4011 const nbgl_contentTagValueList_t *tagValueList,
4012 const nbgl_icon_details_t *icon,
4013 const char *reviewTitle,
4014 const char *reviewSubTitle,
4015 const char *finishTitle,
4016 const nbgl_tipBox_t *tipBox,
4017 nbgl_choiceCallback_t choiceCallback)
4018{
4019 nbgl_useCaseAdvancedReview(operationType,
4020 tagValueList,
4021 icon,
4022 reviewTitle,
4023 reviewSubTitle,
4024 finishTitle,
4025 tipBox,
4026 &blindSigningWarning,
4027 choiceCallback);
4028}
4029
4053 const nbgl_contentTagValueList_t *tagValueList,
4054 const nbgl_icon_details_t *icon,
4055 const char *reviewTitle,
4056 const char *reviewSubTitle,
4057 const char *finishTitle,
4058 const nbgl_tipBox_t *tipBox,
4059 const nbgl_warning_t *warning,
4060 nbgl_choiceCallback_t choiceCallback)
4061{
4062 reset_callbacks_and_context();
4063
4064 // memorize tipBox because it can be in the call stack of the caller
4065 if (tipBox != NULL) {
4066 memcpy(&activeTipBox, tipBox, sizeof(activeTipBox));
4067 }
4068 // if no warning at all, it's a simple review
4069 if ((warning == NULL)
4070 || ((warning->predefinedSet == 0) && (warning->introDetails == NULL)
4071 && (warning->reviewDetails == NULL) && (warning->prelude == NULL))) {
4072 useCaseReview(operationType,
4073 tagValueList,
4074 icon,
4075 reviewTitle,
4076 reviewSubTitle,
4077 finishTitle,
4078 tipBox,
4079 choiceCallback,
4080 false,
4081 true);
4082 return;
4083 }
4084 if (warning->predefinedSet == (1 << W3C_NO_THREAT_WARN)) {
4085 operationType |= NO_THREAT_OPERATION;
4086 }
4087 else if (warning->predefinedSet != 0) {
4088 operationType |= RISKY_OPERATION;
4089 }
4090 sharedContext.usage = SHARE_CTX_REVIEW_WITH_WARNING;
4091 reviewWithWarnCtx.isStreaming = false;
4092 reviewWithWarnCtx.operationType = operationType;
4093 reviewWithWarnCtx.tagValueList = tagValueList;
4094 reviewWithWarnCtx.icon = icon;
4095 reviewWithWarnCtx.reviewTitle = reviewTitle;
4096 reviewWithWarnCtx.reviewSubTitle = reviewSubTitle;
4097 reviewWithWarnCtx.finishTitle = finishTitle;
4098 reviewWithWarnCtx.warning = warning;
4099 reviewWithWarnCtx.choiceCallback = choiceCallback;
4100
4101 // if the warning contains a prelude, display it first
4102 if (reviewWithWarnCtx.warning->prelude) {
4103 displayPrelude();
4104 }
4105 // display the initial warning only of a risk/threat or blind signing or prelude
4106 else if (HAS_INITIAL_WARNING(warning)) {
4107 displayInitialWarning();
4108 }
4109 else {
4110 useCaseReview(operationType,
4111 tagValueList,
4112 icon,
4113 reviewTitle,
4114 reviewSubTitle,
4115 finishTitle,
4116 tipBox,
4117 choiceCallback,
4118 false,
4119 true);
4120 }
4121}
4122
4140 const nbgl_contentTagValueList_t *tagValueList,
4141 const nbgl_icon_details_t *icon,
4142 const char *reviewTitle,
4143 const char *reviewSubTitle,
4144 const char *finishTitle,
4145 nbgl_choiceCallback_t choiceCallback)
4146{
4147 reset_callbacks_and_context();
4148
4149 useCaseReview(operationType,
4150 tagValueList,
4151 icon,
4152 reviewTitle,
4153 reviewSubTitle,
4154 finishTitle,
4155 NULL,
4156 choiceCallback,
4157 true,
4158 true);
4159}
4160
4170 const char *rejectText,
4171 nbgl_callback_t rejectCallback)
4172{
4173 reset_callbacks_and_context();
4174
4175 // memorize context
4176 onQuit = rejectCallback;
4177 navType = GENERIC_NAV;
4178 pageTitle = NULL;
4179 bundleNavContext.review.operationType = TYPE_OPERATION;
4180
4181 memcpy(&genericContext.genericContents, contents, sizeof(nbgl_genericContents_t));
4182
4183 // compute number of pages & fill navigation structure
4184 uint8_t nbPages = getNbPagesForGenericContents(&genericContext.genericContents, 0, false);
4185 prepareNavInfo(true, nbPages, rejectText);
4186 navInfo.quitToken = QUIT_TOKEN;
4187
4188#ifdef HAVE_PIEZO_SOUND
4189 // Play notification sound
4190 os_io_seph_cmd_piezo_play_tune(TUNE_LOOK_AT_ME);
4191#endif // HAVE_PIEZO_SOUND
4192
4193 displayGenericContextPage(0, true);
4194}
4195
4209 const nbgl_icon_details_t *icon,
4210 const char *reviewTitle,
4211 const char *reviewSubTitle,
4212 nbgl_choiceCallback_t choiceCallback)
4213{
4214 reset_callbacks_and_context();
4215
4216 useCaseReviewStreamingStart(
4217 operationType, icon, reviewTitle, reviewSubTitle, choiceCallback, true);
4218}
4219
4234 const nbgl_icon_details_t *icon,
4235 const char *reviewTitle,
4236 const char *reviewSubTitle,
4237 nbgl_choiceCallback_t choiceCallback)
4238{
4240 operationType, icon, reviewTitle, reviewSubTitle, &blindSigningWarning, choiceCallback);
4241}
4242
4259 const nbgl_icon_details_t *icon,
4260 const char *reviewTitle,
4261 const char *reviewSubTitle,
4262 const nbgl_warning_t *warning,
4263 nbgl_choiceCallback_t choiceCallback)
4264{
4265 reset_callbacks_and_context();
4266
4267 // if no warning at all, it's a simple review
4268 if ((warning == NULL)
4269 || ((warning->predefinedSet == 0) && (warning->introDetails == NULL)
4270 && (warning->reviewDetails == NULL) && (warning->prelude == NULL))) {
4271 useCaseReviewStreamingStart(
4272 operationType, icon, reviewTitle, reviewSubTitle, choiceCallback, true);
4273 return;
4274 }
4275 if (warning->predefinedSet == (1 << W3C_NO_THREAT_WARN)) {
4276 operationType |= NO_THREAT_OPERATION;
4277 }
4278 else if (warning->predefinedSet != 0) {
4279 operationType |= RISKY_OPERATION;
4280 }
4281
4282 sharedContext.usage = SHARE_CTX_REVIEW_WITH_WARNING;
4283 reviewWithWarnCtx.isStreaming = true;
4284 reviewWithWarnCtx.operationType = operationType;
4285 reviewWithWarnCtx.icon = icon;
4286 reviewWithWarnCtx.reviewTitle = reviewTitle;
4287 reviewWithWarnCtx.reviewSubTitle = reviewSubTitle;
4288 reviewWithWarnCtx.choiceCallback = choiceCallback;
4289 reviewWithWarnCtx.warning = warning;
4290
4291 // if the warning contains a prelude, display it first
4292 if (reviewWithWarnCtx.warning->prelude) {
4293 displayPrelude();
4294 }
4295 // display the initial warning only of a risk/threat or blind signing
4296 else if (HAS_INITIAL_WARNING(warning)) {
4297 displayInitialWarning();
4298 }
4299 else {
4300 useCaseReviewStreamingStart(
4301 operationType, icon, reviewTitle, reviewSubTitle, choiceCallback, true);
4302 }
4303}
4304
4319 nbgl_choiceCallback_t choiceCallback,
4320 nbgl_callback_t skipCallback)
4321{
4322 // Should follow a call to nbgl_useCaseReviewStreamingStart
4323 memset(&genericContext, 0, sizeof(genericContext));
4324
4325 bundleNavContext.reviewStreaming.choiceCallback = choiceCallback;
4326 bundleNavContext.reviewStreaming.skipCallback = skipCallback;
4327
4328 // memorize context
4329 onChoice = bundleNavReviewStreamingChoice;
4330 navType = STREAMING_NAV;
4331 pageTitle = NULL;
4332
4333 genericContext.genericContents.contentsList = localContentsList;
4334 genericContext.genericContents.nbContents = 1;
4335 memset(localContentsList, 0, 1 * sizeof(nbgl_content_t));
4336
4337 // Then the tag/value pairs
4338 STARTING_CONTENT.type = TAG_VALUE_LIST;
4339 memcpy(
4340 &STARTING_CONTENT.content.tagValueList, tagValueList, sizeof(nbgl_contentTagValueList_t));
4341
4342 // compute number of pages & fill navigation structure
4343 bundleNavContext.reviewStreaming.stepPageNb = getNbPagesForGenericContents(
4344 &genericContext.genericContents,
4345 0,
4346 (bundleNavContext.reviewStreaming.operationType & SKIPPABLE_OPERATION));
4347 prepareNavInfo(true,
4349 getRejectReviewText(bundleNavContext.reviewStreaming.operationType));
4350 // if the operation is skippable
4351 if (bundleNavContext.reviewStreaming.operationType & SKIPPABLE_OPERATION) {
4352 navInfo.progressIndicator = false;
4353 navInfo.skipText = "Skip";
4354 navInfo.skipToken = SKIP_TOKEN;
4355 }
4356
4357 displayGenericContextPage(0, true);
4358}
4359
4371 nbgl_choiceCallback_t choiceCallback)
4372{
4373 nbgl_useCaseReviewStreamingContinueExt(tagValueList, choiceCallback, NULL);
4374}
4375
4384void nbgl_useCaseReviewStreamingFinish(const char *finishTitle,
4385 nbgl_choiceCallback_t choiceCallback)
4386{
4387 // Should follow a call to nbgl_useCaseReviewStreamingContinue
4388 memset(&genericContext, 0, sizeof(genericContext));
4389
4390 bundleNavContext.reviewStreaming.choiceCallback = choiceCallback;
4391
4392 // memorize context
4393 onChoice = bundleNavReviewStreamingChoice;
4394 navType = STREAMING_NAV;
4395 pageTitle = NULL;
4396
4397 genericContext.genericContents.contentsList = localContentsList;
4398 genericContext.genericContents.nbContents = 1;
4399 memset(localContentsList, 0, 1 * sizeof(nbgl_content_t));
4400
4401 // Eventually the long press page
4402 STARTING_CONTENT.type = INFO_LONG_PRESS;
4403 prepareReviewLastPage(bundleNavContext.reviewStreaming.operationType,
4404 &STARTING_CONTENT.content.infoLongPress,
4405 bundleNavContext.reviewStreaming.icon,
4406 finishTitle);
4407
4408 // compute number of pages & fill navigation structure
4409 bundleNavContext.reviewStreaming.stepPageNb = getNbPagesForGenericContents(
4410 &genericContext.genericContents,
4411 0,
4412 (bundleNavContext.reviewStreaming.operationType & SKIPPABLE_OPERATION));
4413 prepareNavInfo(true, 1, getRejectReviewText(bundleNavContext.reviewStreaming.operationType));
4414
4415 displayGenericContextPage(0, true);
4416}
4417
4422void nbgl_useCaseAddressConfirmationExt(const char *address,
4423 nbgl_choiceCallback_t callback,
4424 const nbgl_contentTagValueList_t *tagValueList)
4425{
4426 reset_callbacks_and_context();
4427 memset(&addressConfirmationContext, 0, sizeof(addressConfirmationContext));
4428
4429 // save context
4430 onChoice = callback;
4431 navType = GENERIC_NAV;
4432 pageTitle = NULL;
4433
4434 genericContext.genericContents.contentsList = localContentsList;
4435 genericContext.genericContents.nbContents = (tagValueList == NULL) ? 1 : 2;
4436 memset(localContentsList, 0, 2 * sizeof(nbgl_content_t));
4437 prepareAddressConfirmationPages(
4438 address, tagValueList, &STARTING_CONTENT, &localContentsList[1]);
4439
4440 // fill navigation structure, common to all pages
4441 uint8_t nbPages = getNbPagesForGenericContents(&genericContext.genericContents, 0, false);
4442
4443 prepareNavInfo(true, nbPages, "Cancel");
4444
4445#ifdef HAVE_PIEZO_SOUND
4446 // Play notification sound
4447 os_io_seph_cmd_piezo_play_tune(TUNE_LOOK_AT_ME);
4448#endif // HAVE_PIEZO_SOUND
4449
4450 displayGenericContextPage(0, true);
4451}
4452
4469void nbgl_useCaseAddressReview(const char *address,
4470 const nbgl_contentTagValueList_t *additionalTagValueList,
4471 const nbgl_icon_details_t *icon,
4472 const char *reviewTitle,
4473 const char *reviewSubTitle,
4474 nbgl_choiceCallback_t choiceCallback)
4475{
4476 reset_callbacks_and_context();
4477
4478 // release a potential modal
4479 if (addressConfirmationContext.modalLayout) {
4480 nbgl_layoutRelease(addressConfirmationContext.modalLayout);
4481 }
4482 memset(&addressConfirmationContext, 0, sizeof(addressConfirmationContext));
4483
4484 // save context
4485 onChoice = choiceCallback;
4486 navType = GENERIC_NAV;
4487 pageTitle = NULL;
4488 bundleNavContext.review.operationType = TYPE_OPERATION;
4489
4490 genericContext.genericContents.contentsList = localContentsList;
4491 memset(localContentsList, 0, 3 * sizeof(nbgl_content_t));
4492
4493 // First a centered info
4494 STARTING_CONTENT.type = EXTENDED_CENTER;
4495 prepareReviewFirstPage(
4496 &STARTING_CONTENT.content.extendedCenter.contentCenter, icon, reviewTitle, reviewSubTitle);
4497 STARTING_CONTENT.content.extendedCenter.contentCenter.subText = "Swipe to continue";
4498
4499 // Then the address confirmation pages
4500 prepareAddressConfirmationPages(
4501 address, additionalTagValueList, &localContentsList[1], &localContentsList[2]);
4502
4503 // fill navigation structure, common to all pages
4504 genericContext.genericContents.nbContents
4505 = (localContentsList[2].type == TAG_VALUE_CONFIRM) ? 3 : 2;
4506 uint8_t nbPages = getNbPagesForGenericContents(&genericContext.genericContents, 0, false);
4507
4508 prepareNavInfo(true, nbPages, "Cancel");
4509
4510#ifdef HAVE_PIEZO_SOUND
4511 // Play notification sound
4512 os_io_seph_cmd_piezo_play_tune(TUNE_LOOK_AT_ME);
4513#endif // HAVE_PIEZO_SOUND
4514
4515 displayGenericContextPage(0, true);
4516}
4517
4526void nbgl_useCaseSpinner(const char *text)
4527{
4528 // if the previous Use Case was not Spinner, fresh start
4529 if (genericContext.type != USE_CASE_SPINNER) {
4530 memset(&genericContext, 0, sizeof(genericContext));
4531 genericContext.type = USE_CASE_SPINNER;
4532 nbgl_layoutDescription_t layoutDescription = {0};
4533
4534 layoutDescription.withLeftBorder = true;
4535
4536 genericContext.backgroundLayout = nbgl_layoutGet(&layoutDescription);
4537
4539 genericContext.backgroundLayout, text, NULL, genericContext.spinnerPosition);
4540
4541 nbgl_layoutDraw(genericContext.backgroundLayout);
4543 }
4544 else {
4545 // otherwise increment spinner
4546 genericContext.spinnerPosition++;
4547 // there are only NB_SPINNER_POSITIONSpositions
4548 if (genericContext.spinnerPosition == NB_SPINNER_POSITIONS) {
4549 genericContext.spinnerPosition = 0;
4550 }
4551 int ret = nbgl_layoutUpdateSpinner(
4552 genericContext.backgroundLayout, text, NULL, genericContext.spinnerPosition);
4553 if (ret == 1) {
4555 }
4556 else if (ret == 2) {
4558 }
4559 }
4560}
4561
4562#ifdef NBGL_KEYPAD
4581void nbgl_useCaseKeypad(const char *title,
4582 uint8_t minDigits,
4583 uint8_t maxDigits,
4584 bool shuffled,
4585 bool hidden,
4586 nbgl_pinValidCallback_t validatePinCallback,
4587 nbgl_callback_t backCallback)
4588{
4589 nbgl_layoutDescription_t layoutDescription = {0};
4591 .separationLine = true,
4592 .backAndText.token = BACK_TOKEN,
4593 .backAndText.tuneId = TUNE_TAP_CASUAL,
4594 .backAndText.text = NULL};
4595 int status = -1;
4596
4597 if ((minDigits > KEYPAD_MAX_DIGITS) || (maxDigits > KEYPAD_MAX_DIGITS)) {
4598 return;
4599 }
4600
4601 reset_callbacks_and_context();
4602 // reset the keypad context
4603 memset(&keypadContext, 0, sizeof(KeypadContext_t));
4604
4605 // memorize context
4606 onQuit = backCallback;
4607
4608 // get a layout
4609 layoutDescription.onActionCallback = keypadGeneric_cb;
4610 layoutDescription.modal = false;
4611 layoutDescription.withLeftBorder = false;
4612 keypadContext.layoutCtx = nbgl_layoutGet(&layoutDescription);
4613 keypadContext.hidden = hidden;
4614
4615 // set back key in header
4616 nbgl_layoutAddHeader(keypadContext.layoutCtx, &headerDesc);
4617
4618 // add keypad
4619 status = nbgl_layoutAddKeypad(keypadContext.layoutCtx, keypadCallback, shuffled);
4620 if (status < 0) {
4621 return;
4622 }
4623 // add keypad content
4625 keypadContext.layoutCtx, title, keypadContext.hidden, maxDigits, "");
4626
4627 if (status < 0) {
4628 return;
4629 }
4630
4631 // validation pin callback
4632 onValidatePin = validatePinCallback;
4633 // pin code acceptable lengths
4634 keypadContext.pinMinDigits = minDigits;
4635 keypadContext.pinMaxDigits = maxDigits;
4636
4637 nbgl_layoutDraw(keypadContext.layoutCtx);
4639}
4640#endif // NBGL_KEYPAD
4641
4642#endif // HAVE_SE_TOUCH
4643#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)
nbgl_font_id_e
Definition nbgl_fonts.h:132
void nbgl_textReduceOnNbLines(nbgl_font_id_e fontId, const char *origText, uint16_t maxWidth, uint8_t nbLines, char *reducedText, uint16_t reducedTextLen)
uint16_t nbgl_getTextHeightInWidth(nbgl_font_id_e fontId, const char *text, uint16_t maxWidth, bool wrapping)
uint16_t nbgl_getTextNbLinesInWidth(nbgl_font_id_e fontId, const char *text, uint16_t maxWidth, bool wrapping)
uint8_t nbgl_getFontLineHeight(nbgl_font_id_e fontId)
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_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
int nbgl_layoutAddTextContent(nbgl_layout_t *layout, nbgl_layoutTextContent_t *content)
Creates in the main container three text areas:
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,...
#define TAG_VALUE_INTERVALE
@ 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)
#define KEYPAD_MAX_DIGITS
Definition nbgl_obj.h:67
void nbgl_refreshSpecial(nbgl_refresh_mode_t mode)
#define BACKSPACE_KEY
Definition nbgl_obj.h:26
void nbgl_refreshSpecialWithPostRefresh(nbgl_refresh_mode_t mode, nbgl_post_refresh_t post_refresh)
#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)
@ POST_REFRESH_FORCE_POWER_ON
Force screen power on after refresh.
Definition nbgl_types.h:353
#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:325
@ FULL_COLOR_CLEAN_REFRESH
to be used for lock screen display (cleaner but longer refresh)
Definition nbgl_types.h:328
@ BLACK_AND_WHITE_FAST_REFRESH
to be used for pure B&W area, when contrast is not priority
Definition nbgl_types.h:330
@ FULL_COLOR_PARTIAL_REFRESH
to be used for small partial refresh (radio buttons, switches)
Definition nbgl_types.h:327
@ FULL_COLOR_REFRESH
to be used for normal refresh
Definition nbgl_types.h:326
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.
@ GATED_SIGNING_WARN
Gated 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.
#define HAS_INITIAL_WARNING(_warning)
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 char * aliasSubName
string displayed under alias and in details view
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.
This structure contains info for Text content, to be set with nbgl_layoutAddTextContent.
const char * descriptions[NB_MAX_DESCRIPTIONS]
uint8_t nbDescriptions
number of used descriptions in above array
const char * info
description at bottom (in small gray)
const char * title
main text (in large bold font)
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:67
nbgl_contentSwitchesList_t switchesList
SWITCHES_LIST type
Definition nbgl_flow.h:65
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:68
nbgl_contentInfoButton_t infoButton
INFO_BUTTON type
Definition nbgl_flow.h:62
nbgl_contentInfoList_t infosList
INFOS_LIST type
Definition nbgl_flow.h:66
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_flow.h:64
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_preludeDetails_t * prelude
if not null, means that the review can start by a prelude
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