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 keyboardContext sharedContext.keyboard
59#define reviewWithWarnCtx sharedContext.reviewWithWarning
60#define choiceWithDetailsCtx sharedContext.choiceWithDetails
61
62/* max length of the string displaying the description of the Web3 Checks report */
63#define W3C_DESCRIPTION_MAX_LEN 128
64
70#define RISKY_OPERATION (1 << 6)
71
77#define NO_THREAT_OPERATION (1 << 7)
78
79/**********************
80 * TYPEDEFS
81 **********************/
82enum {
83 BACK_TOKEN = 0,
84 NEXT_TOKEN,
85 QUIT_TOKEN,
86 NAV_TOKEN,
87 MODAL_NAV_TOKEN,
88 SKIP_TOKEN,
89 CONTINUE_TOKEN,
90 ADDRESS_QRCODE_BUTTON_TOKEN,
91 ACTION_BUTTON_TOKEN,
92 CHOICE_TOKEN,
93 DETAILS_BUTTON_TOKEN,
94 CONFIRM_TOKEN,
95 REJECT_TOKEN,
96 VALUE_ALIAS_TOKEN,
97 INFO_ALIAS_TOKEN,
98 INFOS_TIP_BOX_TOKEN,
99 BLIND_WARNING_TOKEN,
100 WARNING_BUTTON_TOKEN,
101 CHOICE_DETAILS_TOKEN,
102 TIP_BOX_TOKEN,
103 QUIT_TIPBOX_MODAL_TOKEN,
104 WARNING_CHOICE_TOKEN,
105 DISMISS_QR_TOKEN,
106 DISMISS_WARNING_TOKEN,
107 DISMISS_DETAILS_TOKEN,
108 PRELUDE_CHOICE_TOKEN,
109 KEYBOARD_BUTTON_TOKEN,
110 KEYBOARD_CROSS_TOKEN,
111 FIRST_WARN_BAR_TOKEN,
112 LAST_WARN_BAR_TOKEN = (FIRST_WARN_BAR_TOKEN + NB_WARNING_TYPES - 1),
113};
114CCASSERT(NBGL_TOKENS, LAST_WARN_BAR_TOKEN + 1 < FIRST_USER_TOKEN);
115
116typedef enum {
117 REVIEW_NAV = 0,
118 SETTINGS_NAV,
119 GENERIC_NAV,
120 STREAMING_NAV
121} NavType_t;
122
123typedef struct DetailsContext_s {
124 uint8_t nbPages;
125 uint8_t currentPage;
126 uint8_t currentPairIdx;
127 bool wrapping;
128 const char *tag;
129 const char *value;
130 const char *nextPageStart;
131} DetailsContext_t;
132
133typedef struct AddressConfirmationContext_s {
134 nbgl_layoutTagValue_t tagValuePairs[ADDR_VERIF_NB_PAIRS];
135 nbgl_layout_t *modalLayout;
136 uint8_t nbPairs;
137} AddressConfirmationContext_t;
138
139#ifdef NBGL_KEYPAD
140typedef struct KeypadContext_s {
141 uint8_t pinEntry[KEYPAD_MAX_DIGITS];
142 uint8_t pinLen;
143 uint8_t pinMinDigits;
144 uint8_t pinMaxDigits;
145 nbgl_layout_t *layoutCtx;
146 bool hidden;
147 nbgl_pinValidCallback_t onValidatePin;
148} KeypadContext_t;
149#endif
150
151#ifdef NBGL_KEYBOARD
152typedef struct KeyboardContext_s {
153 nbgl_layoutKeyboardContent_t keyboardContent;
154 char *entryBuffer;
155 uint8_t entryMaxLen;
156 int keyboardIndex;
157 keyboardCase_t casing;
158 nbgl_layout_t *layoutCtx;
159 nbgl_keyboardButtonsCallback_t getSuggestionButtons;
160} KeyboardContext_t;
161#endif
162
163typedef struct ReviewWithWarningContext_s {
164 bool isStreaming;
165 nbgl_operationType_t operationType;
166 const nbgl_contentTagValueList_t *tagValueList;
167 const nbgl_icon_details_t *icon;
168 const char *reviewTitle;
169 const char *reviewSubTitle;
170 const char *finishTitle;
171 const nbgl_warning_t *warning;
172 nbgl_choiceCallback_t choiceCallback;
173 nbgl_layout_t *layoutCtx;
174 nbgl_layout_t *modalLayout;
175 uint8_t securityReportLevel; // level 1 is the first level of menus
176 bool isIntro; // set to true during intro (before actual review)
177} ReviewWithWarningContext_t;
178
179typedef struct ChoiceWithDetailsContext_s {
180 const nbgl_genericDetails_t *details;
181 nbgl_layout_t *layoutCtx;
182 nbgl_layout_t *modalLayout;
183 uint8_t level; // level 1 is the first level of menus
184} ChoiceWithDetailsContext_t;
185
186typedef enum {
187 SHARE_CTX_KEYPAD,
188 SHARE_CTX_REVIEW_WITH_WARNING,
189 SHARE_CTX_CHOICE_WITH_DETAILS,
190} SharedContextUsage_t;
191
192// this union is intended to save RAM for context storage
193// indeed, these 3 contexts cannot happen simultaneously
194typedef struct {
195 union {
196#ifdef NBGL_KEYPAD
197 KeypadContext_t keypad;
198#endif
199#ifdef NBGL_KEYBOARD
200 KeyboardContext_t keyboard;
201#endif
202 ReviewWithWarningContext_t reviewWithWarning;
203 ChoiceWithDetailsContext_t choiceWithDetails;
204 };
205 SharedContextUsage_t usage;
206} SharedContext_t;
207
208typedef enum {
209 USE_CASE_GENERIC = 0,
210 USE_CASE_SPINNER
211} GenericContextType_t;
212
213typedef struct {
214 GenericContextType_t type; // type of Generic context usage
215 uint8_t spinnerPosition;
216 nbgl_genericContents_t genericContents;
217 int8_t currentContentIdx;
218 uint8_t currentContentElementNb;
219 uint8_t currentElementIdx;
220 bool hasStartingContent;
221 bool hasFinishingContent;
222 const char *detailsItem;
223 const char *detailsvalue;
224 bool detailsWrapping;
225 bool validWarningCtx; // set to true if the WarningContext is valid
227 *currentPairs; // to be used to retrieve the pairs with value alias
229 currentCallback; // to be used to retrieve the pairs with value alias
230 nbgl_layout_t modalLayout;
231 nbgl_layout_t backgroundLayout;
232 const nbgl_contentInfoList_t *currentInfos;
233 const nbgl_contentTagValueList_t *currentTagValues;
234 nbgl_tipBox_t tipBox;
235 SharedContext_t sharedCtx; // multi-purpose context shared for non-concurrent usages
236} GenericContext_t;
237
238typedef struct {
239 const char *appName;
240 const nbgl_icon_details_t *appIcon;
241 const char *tagline;
242 const nbgl_genericContents_t *settingContents;
243 const nbgl_contentInfoList_t *infosList;
244 nbgl_homeAction_t homeAction;
245 nbgl_callback_t quitCallback;
246} nbgl_homeAndSettingsContext_t;
247
248typedef struct {
249 nbgl_operationType_t operationType;
250 nbgl_choiceCallback_t choiceCallback;
251} nbgl_reviewContext_t;
252
253typedef struct {
254 nbgl_operationType_t operationType;
255 nbgl_choiceCallback_t choiceCallback;
256 nbgl_callback_t skipCallback;
257 const nbgl_icon_details_t *icon;
258 uint8_t stepPageNb;
259} nbgl_reviewStreamingContext_t;
260
261typedef union {
262 nbgl_homeAndSettingsContext_t homeAndSettings;
263 nbgl_reviewContext_t review;
264 nbgl_reviewStreamingContext_t reviewStreaming;
265} nbgl_BundleNavContext_t;
266
267typedef struct {
268 const nbgl_icon_details_t *icon;
269 const char *text;
270 const char *subText;
271} SecurityReportItem_t;
272
273/**********************
274 * STATIC VARIABLES
275 **********************/
276
277// char buffers to build some strings
278static char tmpString[W3C_DESCRIPTION_MAX_LEN];
279
280// multi-purposes callbacks
281static nbgl_callback_t onQuit;
282static nbgl_callback_t onContinue;
283static nbgl_callback_t onAction;
284static nbgl_navCallback_t onNav;
285static nbgl_layoutTouchCallback_t onControls;
286static nbgl_contentActionCallback_t onContentAction;
287static nbgl_choiceCallback_t onChoice;
288static nbgl_callback_t onModalConfirm;
289
290// contexts for background and modal pages
291static nbgl_page_t *pageContext;
292static nbgl_page_t *modalPageContext;
293
294// context for pages
295static const char *pageTitle;
296
297// context for tip-box
298#define activeTipBox genericContext.tipBox
299
300// context for navigation use case
301static nbgl_pageNavigationInfo_t navInfo;
302static bool forwardNavOnly;
303static NavType_t navType;
304
305static DetailsContext_t detailsContext;
306
307// context for address review
308static AddressConfirmationContext_t addressConfirmationContext;
309
310// contexts for generic navigation
311static GenericContext_t genericContext;
312static nbgl_content_t
313 localContentsList[3]; // 3 needed for nbgl_useCaseReview (starting page / tags / final page)
314static uint8_t genericContextPagesInfo[MAX_PAGE_NB / PAGES_PER_UINT8];
315static uint8_t modalContextPagesInfo[MAX_MODAL_PAGE_NB / PAGES_PER_UINT8];
316
317// contexts for bundle navigation
318static nbgl_BundleNavContext_t bundleNavContext;
319
320// indexed by nbgl_contentType_t
321static const uint8_t nbMaxElementsPerContentType[] = {
322 1, // CENTERED_INFO
323 1, // EXTENDED_CENTER
324 1, // INFO_LONG_PRESS
325 1, // INFO_BUTTON
326 1, // TAG_VALUE_LIST (computed dynamically)
327 1, // TAG_VALUE_DETAILS
328 1, // TAG_VALUE_CONFIRM
329 3, // SWITCHES_LIST (computed dynamically)
330 3, // INFOS_LIST (computed dynamically)
331 5, // CHOICES_LIST (computed dynamically)
332 5, // BARS_LIST (computed dynamically)
333};
334
335// clang-format off
336static const SecurityReportItem_t securityReportItems[NB_WARNING_TYPES] = {
338 .icon = &WARNING_ICON,
339 .text = "Blind signing required",
340 .subText = "This transaction's details are not fully verifiable. If "
341 "you sign, you could lose all your assets."
342 },
343 [W3C_ISSUE_WARN] = {
344 .icon = &WARNING_ICON,
345 .text = "Transaction Check unavailable",
346 .subText = NULL
347 },
349 .icon = &WARNING_ICON,
350 .text = "Risk detected",
351 .subText = "This transaction was scanned as risky by Web3 Checks."
352 },
354 .icon = &WARNING_ICON,
355 .text = "Critical threat",
356 .subText = "This transaction was scanned as malicious by Web3 Checks."
357 },
359 .icon = NULL,
360 .text = "No threat detected",
361 .subText = "Transaction Check didn't find any threat, but always "
362 "review transaction details carefully."
363 }
364};
365// clang-format on
366
367// configuration of warning when using @ref nbgl_useCaseReviewBlindSigning()
368static const nbgl_warning_t blindSigningWarning = {.predefinedSet = (1 << BLIND_SIGNING_WARN)};
369
370#ifdef NBGL_QRCODE
371/* buffer to store reduced address under QR Code */
372static char reducedAddress[QRCODE_REDUCED_ADDR_LEN];
373#endif // NBGL_QRCODE
374
375/**********************
376 * STATIC FUNCTIONS
377 **********************/
378static void displayReviewPage(uint8_t page, bool forceFullRefresh);
379static void displayDetailsPage(uint8_t page, bool forceFullRefresh);
380static void displayTagValueListModalPage(uint8_t pageIdx, bool forceFullRefresh);
381static void displayFullValuePage(const char *backText,
382 const char *aliasText,
383 const nbgl_contentValueExt_t *extension);
384static void displayInfosListModal(const char *modalTitle,
385 const nbgl_contentInfoList_t *infos,
386 bool fromReview);
387static void displayTagValueListModal(const nbgl_contentTagValueList_t *tagValues);
388static void displaySettingsPage(uint8_t page, bool forceFullRefresh);
389static void displayGenericContextPage(uint8_t pageIdx, bool forceFullRefresh);
390static void pageCallback(int token, uint8_t index);
391#ifdef NBGL_QRCODE
392static void displayAddressQRCode(void);
393#endif // NBGL_QRCODE
394static void modalLayoutTouchCallback(int token, uint8_t index);
395static void displaySkipWarning(void);
396
397static void bundleNavStartHome(void);
398static void bundleNavStartSettingsAtPage(uint8_t initSettingPage);
399static void bundleNavStartSettings(void);
400
401static void bundleNavReviewStreamingChoice(bool confirm);
402static nbgl_layout_t *displayModalDetails(const nbgl_warningDetails_t *details, uint8_t token);
403static void displaySecurityReport(uint32_t set);
404static void displayCustomizedSecurityReport(const nbgl_warningDetails_t *details);
405static void displayInitialWarning(void);
406static void useCaseReview(nbgl_operationType_t operationType,
407 const nbgl_contentTagValueList_t *tagValueList,
408 const nbgl_icon_details_t *icon,
409 const char *reviewTitle,
410 const char *reviewSubTitle,
411 const char *finishTitle,
412 const nbgl_tipBox_t *tipBox,
413 nbgl_choiceCallback_t choiceCallback,
414 bool isLight,
415 bool playNotifSound);
416static void useCaseReviewStreamingStart(nbgl_operationType_t operationType,
417 const nbgl_icon_details_t *icon,
418 const char *reviewTitle,
419 const char *reviewSubTitle,
420 nbgl_choiceCallback_t choiceCallback,
421 bool playNotifSound);
422static void useCaseHomeExt(const char *appName,
423 const nbgl_icon_details_t *appIcon,
424 const char *tagline,
425 bool withSettings,
426 nbgl_homeAction_t *homeAction,
427 nbgl_callback_t topRightCallback,
428 nbgl_callback_t quitCallback);
429static void displayDetails(const char *tag, const char *value, bool wrapping);
430
431static void reset_callbacks_and_context(void)
432{
433 onQuit = NULL;
434 onContinue = NULL;
435 onAction = NULL;
436 onNav = NULL;
437 onControls = NULL;
438 onContentAction = NULL;
439 onChoice = NULL;
440 memset(&genericContext, 0, sizeof(genericContext));
441}
442
443// Helper to set genericContext page info
444static void genericContextSetPageInfo(uint8_t pageIdx, uint8_t nbElements, bool flag)
445{
446 uint8_t pageData = SET_PAGE_NB_ELEMENTS(nbElements) + SET_PAGE_FLAG(flag);
447
448 genericContextPagesInfo[pageIdx / PAGES_PER_UINT8]
449 &= ~(0x0F << ((pageIdx % PAGES_PER_UINT8) * PAGE_DATA_BITS));
450 genericContextPagesInfo[pageIdx / PAGES_PER_UINT8]
451 |= pageData << ((pageIdx % PAGES_PER_UINT8) * PAGE_DATA_BITS);
452}
453
454// Helper to get genericContext page info
455static void genericContextGetPageInfo(uint8_t pageIdx, uint8_t *nbElements, bool *flag)
456{
457 uint8_t pageData = genericContextPagesInfo[pageIdx / PAGES_PER_UINT8]
458 >> ((pageIdx % PAGES_PER_UINT8) * PAGE_DATA_BITS);
459 if (nbElements != NULL) {
460 *nbElements = GET_PAGE_NB_ELEMENTS(pageData);
461 }
462 if (flag != NULL) {
463 *flag = GET_PAGE_FLAG(pageData);
464 }
465}
466
467// Helper to set modalContext page info
468static void modalContextSetPageInfo(uint8_t pageIdx, uint8_t nbElements)
469{
470 uint8_t pageData = SET_PAGE_NB_ELEMENTS(nbElements);
471
472 modalContextPagesInfo[pageIdx / PAGES_PER_UINT8]
473 &= ~(0x0F << ((pageIdx % PAGES_PER_UINT8) * PAGE_DATA_BITS));
474 modalContextPagesInfo[pageIdx / PAGES_PER_UINT8]
475 |= pageData << ((pageIdx % PAGES_PER_UINT8) * PAGE_DATA_BITS);
476}
477
478// Helper to get modalContext page info
479static void modalContextGetPageInfo(uint8_t pageIdx, uint8_t *nbElements)
480{
481 uint8_t pageData = modalContextPagesInfo[pageIdx / PAGES_PER_UINT8]
482 >> ((pageIdx % PAGES_PER_UINT8) * PAGE_DATA_BITS);
483 if (nbElements != NULL) {
484 *nbElements = GET_PAGE_NB_ELEMENTS(pageData);
485 }
486}
487
488// Simple helper to get the number of elements inside a nbgl_content_t
489static uint8_t getContentNbElement(const nbgl_content_t *content)
490{
491 switch (content->type) {
492 case TAG_VALUE_LIST:
493 return content->content.tagValueList.nbPairs;
496 case SWITCHES_LIST:
497 return content->content.switchesList.nbSwitches;
498 case INFOS_LIST:
499 return content->content.infosList.nbInfos;
500 case CHOICES_LIST:
501 return content->content.choicesList.nbChoices;
502 case BARS_LIST:
503 return content->content.barsList.nbBars;
504 default:
505 return 1;
506 }
507}
508
509// Helper to retrieve the content inside a nbgl_genericContents_t using
510// either the contentsList or using the contentGetterCallback
511static const nbgl_content_t *getContentAtIdx(const nbgl_genericContents_t *genericContents,
512 int8_t contentIdx,
513 nbgl_content_t *content)
514{
515 if (contentIdx < 0 || contentIdx >= genericContents->nbContents) {
516 LOG_DEBUG(USE_CASE_LOGGER, "No content available at %d\n", contentIdx);
517 return NULL;
518 }
519
520 if (genericContents->callbackCallNeeded) {
521 // Retrieve content through callback, but first memset the content.
522 memset(content, 0, sizeof(nbgl_content_t));
523 genericContents->contentGetterCallback(contentIdx, content);
524 return content;
525 }
526 else {
527 // Retrieve content through list
528 return PIC(&genericContents->contentsList[contentIdx]);
529 }
530}
531
532static void prepareNavInfo(bool isReview, uint8_t nbPages, const char *rejectText)
533{
534 memset(&navInfo, 0, sizeof(navInfo));
535
536 navInfo.nbPages = nbPages;
537 navInfo.tuneId = TUNE_TAP_CASUAL;
538 navInfo.progressIndicator = false;
539 navInfo.navType = NAV_WITH_BUTTONS;
540
541 if (isReview == false) {
542 navInfo.navWithButtons.navToken = NAV_TOKEN;
543 navInfo.navWithButtons.backButton = true;
544 }
545 else {
546 navInfo.quitToken = REJECT_TOKEN;
547 navInfo.navWithButtons.quitText = rejectText;
548 navInfo.navWithButtons.navToken = NAV_TOKEN;
550 = ((navType == STREAMING_NAV) && (nbPages < 2)) ? false : true;
551 navInfo.navWithButtons.visiblePageIndicator = (navType != STREAMING_NAV);
552 }
553}
554
555static void prepareReviewFirstPage(nbgl_contentCenter_t *contentCenter,
556 const nbgl_icon_details_t *icon,
557 const char *reviewTitle,
558 const char *reviewSubTitle)
559{
560 contentCenter->icon = icon;
561 contentCenter->title = reviewTitle;
562 contentCenter->description = reviewSubTitle;
563 contentCenter->subText = "Swipe to review";
564 contentCenter->smallTitle = NULL;
565 contentCenter->iconHug = 0;
566 contentCenter->padding = false;
567 contentCenter->illustrType = ICON_ILLUSTRATION;
568}
569
570static const char *getFinishTitle(nbgl_operationType_t operationType, const char *finishTitle)
571{
572 if (finishTitle != NULL) {
573 return finishTitle;
574 }
575 switch (operationType & REAL_TYPE_MASK) {
576 case TYPE_TRANSACTION:
577 return "Sign transaction";
578 case TYPE_MESSAGE:
579 return "Sign message";
580 default:
581 return "Sign operation";
582 }
583}
584
585static void prepareReviewLastPage(nbgl_operationType_t operationType,
586 nbgl_contentInfoLongPress_t *infoLongPress,
587 const nbgl_icon_details_t *icon,
588 const char *finishTitle)
589{
590 infoLongPress->text = getFinishTitle(operationType, finishTitle);
591 infoLongPress->icon = icon;
592 infoLongPress->longPressText = "Hold to sign";
593 infoLongPress->longPressToken = CONFIRM_TOKEN;
594}
595
596static void prepareReviewLightLastPage(nbgl_operationType_t operationType,
597 nbgl_contentInfoButton_t *infoButton,
598 const nbgl_icon_details_t *icon,
599 const char *finishTitle)
600{
601 infoButton->text = getFinishTitle(operationType, finishTitle);
602 infoButton->icon = icon;
603 infoButton->buttonText = "Approve";
604 infoButton->buttonToken = CONFIRM_TOKEN;
605}
606
607static const char *getRejectReviewText(nbgl_operationType_t operationType)
608{
609 UNUSED(operationType);
610 return "Reject";
611}
612
613// function called when navigating (or exiting) modal details pages
614// or when skip choice is displayed
615static void pageModalCallback(int token, uint8_t index)
616{
617 LOG_DEBUG(USE_CASE_LOGGER, "pageModalCallback, token = %d, index = %d\n", token, index);
618
619 if (token == INFOS_TIP_BOX_TOKEN) {
620 // the icon next to info type alias has been touched
621 displayFullValuePage(activeTipBox.infos.infoTypes[index],
622 activeTipBox.infos.infoContents[index],
623 &activeTipBox.infos.infoExtensions[index]);
624 return;
625 }
626 else if (token == INFO_ALIAS_TOKEN) {
627 // the icon next to info type alias has been touched, but coming from review
628 displayFullValuePage(genericContext.currentInfos->infoTypes[index],
629 genericContext.currentInfos->infoContents[index],
630 &genericContext.currentInfos->infoExtensions[index]);
631 return;
632 }
633 nbgl_pageRelease(modalPageContext);
634 modalPageContext = NULL;
635 if (token == NAV_TOKEN) {
636 if (index == EXIT_PAGE) {
637 // redraw the background layer
639 nbgl_refresh();
640 }
641 else {
642 displayDetailsPage(index, false);
643 }
644 }
645 if (token == MODAL_NAV_TOKEN) {
646 if (index == EXIT_PAGE) {
647 // redraw the background layer
649 nbgl_refresh();
650 }
651 else {
652 displayTagValueListModalPage(index, false);
653 }
654 }
655 else if (token == QUIT_TOKEN) {
656 // redraw the background layer
658 nbgl_refresh();
659 }
660 else if (token == QUIT_TIPBOX_MODAL_TOKEN) {
661 // if in "warning context", go back to security report
662 if (reviewWithWarnCtx.securityReportLevel == 2) {
663 reviewWithWarnCtx.securityReportLevel = 1;
664 displaySecurityReport(reviewWithWarnCtx.warning->predefinedSet);
665 }
666 else {
667 // redraw the background layer
669 nbgl_refresh();
670 }
671 }
672 else if (token == SKIP_TOKEN) {
673 if (index == 0) {
674 // display the last forward only review page, whatever it is
675 displayGenericContextPage(LAST_PAGE_FOR_REVIEW, true);
676 }
677 else {
678 // display background, which should be the page where skip has been touched
680 nbgl_refresh();
681 }
682 }
683 else if (token == CHOICE_TOKEN) {
684 if (index == 0) {
685 if (onModalConfirm != NULL) {
686 onModalConfirm();
687 onModalConfirm = NULL;
688 }
689 }
690 else {
691 // display background, which should be the page where skip has been touched
693 nbgl_refresh();
694 }
695 }
696}
697
698// generic callback for all pages except modal
699static void pageCallback(int token, uint8_t index)
700{
701 LOG_DEBUG(USE_CASE_LOGGER, "pageCallback, token = %d, index = %d\n", token, index);
702 if (token == QUIT_TOKEN) {
703 if (onQuit != NULL) {
704 onQuit();
705 }
706 }
707 else if (token == CONTINUE_TOKEN) {
708 if (onContinue != NULL) {
709 onContinue();
710 }
711 }
712 else if (token == CHOICE_TOKEN) {
713 if (onChoice != NULL) {
714 onChoice((index == 0) ? true : false);
715 }
716 }
717 else if (token == ACTION_BUTTON_TOKEN) {
718 if ((index == 0) && (onAction != NULL)) {
719 onAction();
720 }
721 else if ((index == 1) && (onQuit != NULL)) {
722 onQuit();
723 }
724 }
725#ifdef NBGL_QRCODE
726 else if (token == ADDRESS_QRCODE_BUTTON_TOKEN) {
727 displayAddressQRCode();
728 }
729#endif // NBGL_QRCODE
730 else if (token == CONFIRM_TOKEN) {
731 if (onChoice != NULL) {
732 onChoice(true);
733 }
734 }
735 else if (token == REJECT_TOKEN) {
736 if (onChoice != NULL) {
737 onChoice(false);
738 }
739 }
740 else if (token == DETAILS_BUTTON_TOKEN) {
741 displayDetails(genericContext.detailsItem,
742 genericContext.detailsvalue,
743 genericContext.detailsWrapping);
744 }
745 else if (token == NAV_TOKEN) {
746 if (index == EXIT_PAGE) {
747 if (onQuit != NULL) {
748 onQuit();
749 }
750 }
751 else {
752 if (navType == GENERIC_NAV || navType == STREAMING_NAV) {
753 displayGenericContextPage(index, false);
754 }
755 else if (navType == REVIEW_NAV) {
756 displayReviewPage(index, false);
757 }
758 else {
759 displaySettingsPage(index, false);
760 }
761 }
762 }
763 else if (token == NEXT_TOKEN) {
764 if (onNav != NULL) {
765 displayReviewPage(navInfo.activePage + 1, false);
766 }
767 else {
768 displayGenericContextPage(navInfo.activePage + 1, false);
769 }
770 }
771 else if (token == BACK_TOKEN) {
772 if (onNav != NULL) {
773 displayReviewPage(navInfo.activePage - 1, true);
774 }
775 else {
776 displayGenericContextPage(navInfo.activePage - 1, false);
777 }
778 }
779 else if (token == SKIP_TOKEN) {
780 // display a modal warning to confirm skip
781 displaySkipWarning();
782 }
783 else if (token == VALUE_ALIAS_TOKEN) {
784 // the icon next to value alias has been touched
785 const nbgl_contentTagValue_t *pair;
786 if (genericContext.currentPairs != NULL) {
787 pair = &genericContext.currentPairs[genericContext.currentElementIdx + index];
788 }
789 else {
790 pair = genericContext.currentCallback(genericContext.currentElementIdx + index);
791 }
792 displayFullValuePage(pair->item, pair->value, pair->extension);
793 }
794 else if (token == BLIND_WARNING_TOKEN) {
795 reviewWithWarnCtx.isIntro = false;
796 reviewWithWarnCtx.warning = NULL;
797 displaySecurityReport(1 << BLIND_SIGNING_WARN);
798 }
799 else if (token == WARNING_BUTTON_TOKEN) {
800 // top-right button, display the security modal
801 reviewWithWarnCtx.securityReportLevel = 1;
802 // if preset is used
803 if (reviewWithWarnCtx.warning->predefinedSet) {
804 displaySecurityReport(reviewWithWarnCtx.warning->predefinedSet);
805 }
806 else {
807 // use customized warning
808 if (reviewWithWarnCtx.isIntro) {
809 displayCustomizedSecurityReport(reviewWithWarnCtx.warning->introDetails);
810 }
811 else {
812 displayCustomizedSecurityReport(reviewWithWarnCtx.warning->reviewDetails);
813 }
814 }
815 }
816 else if (token == TIP_BOX_TOKEN) {
817 // if warning context is valid and if W3C directly display same as top-right
818 if (genericContext.validWarningCtx && (reviewWithWarnCtx.warning->predefinedSet != 0)) {
819 reviewWithWarnCtx.securityReportLevel = 1;
820 displaySecurityReport(reviewWithWarnCtx.warning->predefinedSet);
821 }
822 else {
823 displayInfosListModal(activeTipBox.modalTitle, &activeTipBox.infos, false);
824 }
825 }
826 else { // probably a control provided by caller
827 if (onContentAction != NULL) {
828 onContentAction(token, index, navInfo.activePage);
829 }
830 if (onControls != NULL) {
831 onControls(token, index);
832 }
833 }
834}
835
836// callback used for confirmation
837static void tickerCallback(void)
838{
839 nbgl_pageRelease(pageContext);
840 if (onQuit != NULL) {
841 onQuit();
842 }
843}
844
845// function used to display the current page in review
846static void displaySettingsPage(uint8_t page, bool forceFullRefresh)
847{
848 nbgl_pageContent_t content = {0};
849
850 if ((onNav == NULL) || (onNav(page, &content) == false)) {
851 return;
852 }
853
854 // override some fields
855 content.title = pageTitle;
856 content.isTouchableTitle = true;
857 content.titleToken = QUIT_TOKEN;
858 content.tuneId = TUNE_TAP_CASUAL;
859
860 navInfo.activePage = page;
861 pageContext = nbgl_pageDrawGenericContent(&pageCallback, &navInfo, &content);
862
863 if (forceFullRefresh) {
865 }
866 else {
868 }
869}
870
871// function used to display the current page in review
872static void displayReviewPage(uint8_t page, bool forceFullRefresh)
873{
874 nbgl_pageContent_t content = {0};
875
876 // ensure the page is valid
877 if ((navInfo.nbPages != 0) && (page >= (navInfo.nbPages))) {
878 return;
879 }
880 navInfo.activePage = page;
881 if ((onNav == NULL) || (onNav(navInfo.activePage, &content) == false)) {
882 return;
883 }
884
885 // override some fields
886 content.title = NULL;
887 content.isTouchableTitle = false;
888 content.tuneId = TUNE_TAP_CASUAL;
889
890 if (content.type == INFO_LONG_PRESS) { // last page
891 // for forward only review without known length...
892 // if we don't do that we cannot remove the '>' in the navigation bar at the last page
893 navInfo.nbPages = navInfo.activePage + 1;
894 content.infoLongPress.longPressToken = CONFIRM_TOKEN;
895 if (forwardNavOnly) {
896 // remove the "Skip" button
897 navInfo.skipText = NULL;
898 }
899 }
900
901 // override smallCaseForValue for tag/value types to false
902 if (content.type == TAG_VALUE_DETAILS) {
904 // the maximum displayable number of lines for value is NB_MAX_LINES_IN_REVIEW (without More
905 // button)
908 }
909 else if (content.type == TAG_VALUE_LIST) {
910 content.tagValueList.smallCaseForValue = false;
911 }
912 else if (content.type == TAG_VALUE_CONFIRM) {
914 // use confirm token for black button
915 content.tagValueConfirm.confirmationToken = CONFIRM_TOKEN;
916 }
917
918 pageContext = nbgl_pageDrawGenericContent(&pageCallback, &navInfo, &content);
919
920 if (forceFullRefresh) {
922 }
923 else {
925 }
926}
927
928// Helper that does the computing of which nbgl_content_t and which element in the content should be
929// displayed for the next generic context navigation page
930static const nbgl_content_t *genericContextComputeNextPageParams(uint8_t pageIdx,
931 nbgl_content_t *content,
932 uint8_t *p_nbElementsInNextPage,
933 bool *p_flag)
934{
935 int8_t nextContentIdx = genericContext.currentContentIdx;
936 int16_t nextElementIdx = genericContext.currentElementIdx;
937 uint8_t nbElementsInNextPage;
938
939 // Retrieve info on the next page
940 genericContextGetPageInfo(pageIdx, &nbElementsInNextPage, p_flag);
941 *p_nbElementsInNextPage = nbElementsInNextPage;
942
943 // Handle forward navigation:
944 // add to current index the number of pairs of the currently active page
945 if (pageIdx > navInfo.activePage) {
946 uint8_t nbElementsInCurrentPage;
947
948 genericContextGetPageInfo(navInfo.activePage, &nbElementsInCurrentPage, NULL);
949 nextElementIdx += nbElementsInCurrentPage;
950
951 // Handle case where the content to be displayed is in the next content
952 // In such case start at element index 0.
953 // If currentContentElementNb == 0, means not initialized, so skip
954 if ((nextElementIdx >= genericContext.currentContentElementNb)
955 && (genericContext.currentContentElementNb > 0)) {
956 nextContentIdx += 1;
957 nextElementIdx = 0;
958 }
959 }
960
961 // Handle backward navigation:
962 // add to current index the number of pairs of the currently active page
963 if (pageIdx < navInfo.activePage) {
964 // Backward navigation: remove to current index the number of pairs of the current page
965 nextElementIdx -= nbElementsInNextPage;
966
967 // Handle case where the content to be displayed is in the previous content
968 // In such case set a negative number as element index so that it is handled
969 // later once the previous content is accessible so that its elements number
970 // can be retrieved.
971 if (nextElementIdx < 0) {
972 nextContentIdx -= 1;
973 nextElementIdx = -nbElementsInNextPage;
974 }
975 }
976
977 const nbgl_content_t *p_content;
978 // Retrieve next content
979 if ((nextContentIdx == -1) && (genericContext.hasStartingContent)) {
980 p_content = &STARTING_CONTENT;
981 }
982 else if ((nextContentIdx == genericContext.genericContents.nbContents)
983 && (genericContext.hasFinishingContent)) {
984 p_content = &FINISHING_CONTENT;
985 }
986 else {
987 p_content = getContentAtIdx(&genericContext.genericContents, nextContentIdx, content);
988
989 if (p_content == NULL) {
990 LOG_DEBUG(USE_CASE_LOGGER, "Fail to retrieve content\n");
991 return NULL;
992 }
993 }
994
995 // Handle cases where we are going to display data from a new content:
996 // - navigation to a previous or to the next content
997 // - First page display (genericContext.currentContentElementNb == 0)
998 //
999 // In such case we need to:
1000 // - Update genericContext.currentContentIdx
1001 // - Update genericContext.currentContentElementNb
1002 // - Update onContentAction callback
1003 // - Update nextElementIdx in case it was previously not calculable
1004 if ((nextContentIdx != genericContext.currentContentIdx)
1005 || (genericContext.currentContentElementNb == 0)) {
1006 genericContext.currentContentIdx = nextContentIdx;
1007 genericContext.currentContentElementNb = getContentNbElement(p_content);
1008 onContentAction = PIC(p_content->contentActionCallback);
1009 if (nextElementIdx < 0) {
1010 nextElementIdx = genericContext.currentContentElementNb + nextElementIdx;
1011 }
1012 }
1013
1014 // Sanity check
1015 if ((nextElementIdx < 0) || (nextElementIdx >= genericContext.currentContentElementNb)) {
1017 "Invalid element index %d / %d\n",
1018 nextElementIdx,
1019 genericContext.currentContentElementNb);
1020 return NULL;
1021 }
1022
1023 // Update genericContext elements
1024 genericContext.currentElementIdx = nextElementIdx;
1025 navInfo.activePage = pageIdx;
1026
1027 return p_content;
1028}
1029
1030// Helper that generates a nbgl_pageContent_t to be displayed for the next generic context
1031// navigation page
1032static bool genericContextPreparePageContent(const nbgl_content_t *p_content,
1033 uint8_t nbElementsInPage,
1034 bool flag,
1035 nbgl_pageContent_t *pageContent)
1036{
1037 uint8_t nextElementIdx = genericContext.currentElementIdx;
1038
1039 pageContent->title = pageTitle;
1040 pageContent->isTouchableTitle = false;
1041 pageContent->titleToken = QUIT_TOKEN;
1042 pageContent->tuneId = TUNE_TAP_CASUAL;
1043
1044 pageContent->type = p_content->type;
1045 switch (pageContent->type) {
1046 case CENTERED_INFO:
1047 memcpy(&pageContent->centeredInfo,
1048 &p_content->content.centeredInfo,
1049 sizeof(pageContent->centeredInfo));
1050 break;
1051 case EXTENDED_CENTER:
1052 memcpy(&pageContent->extendedCenter,
1053 &p_content->content.extendedCenter,
1054 sizeof(pageContent->extendedCenter));
1055 break;
1056 case INFO_LONG_PRESS:
1057 memcpy(&pageContent->infoLongPress,
1058 &p_content->content.infoLongPress,
1059 sizeof(pageContent->infoLongPress));
1060 break;
1061
1062 case INFO_BUTTON:
1063 memcpy(&pageContent->infoButton,
1064 &p_content->content.infoButton,
1065 sizeof(pageContent->infoButton));
1066 break;
1067
1068 case TAG_VALUE_LIST: {
1069 nbgl_contentTagValueList_t *p_tagValueList = &pageContent->tagValueList;
1070
1071 // memorize pairs (or callback) for usage when alias is used
1072 genericContext.currentPairs = p_content->content.tagValueList.pairs;
1073 genericContext.currentCallback = p_content->content.tagValueList.callback;
1074
1075 if (flag) {
1076 // Flag can be set if the pair is too long to fit or because it needs
1077 // to be displayed as centered info.
1078
1079 // First retrieve the pair
1080 const nbgl_layoutTagValue_t *pair;
1081
1082 if (p_content->content.tagValueList.pairs != NULL) {
1083 pair = PIC(&p_content->content.tagValueList.pairs[nextElementIdx]);
1084 }
1085 else {
1086 pair = PIC(p_content->content.tagValueList.callback(nextElementIdx));
1087 }
1088
1089 if (pair->centeredInfo) {
1090 pageContent->type = EXTENDED_CENTER;
1091 prepareReviewFirstPage(&pageContent->extendedCenter.contentCenter,
1092 pair->valueIcon,
1093 pair->item,
1094 pair->value);
1095
1096 // Skip population of nbgl_contentTagValueList_t structure
1097 p_tagValueList = NULL;
1098 }
1099 else {
1100 // if the pair is too long to fit, we use a TAG_VALUE_DETAILS content
1101 pageContent->type = TAG_VALUE_DETAILS;
1102 pageContent->tagValueDetails.detailsButtonText = "More";
1103 pageContent->tagValueDetails.detailsButtonIcon = NULL;
1104 pageContent->tagValueDetails.detailsButtonToken = DETAILS_BUTTON_TOKEN;
1105
1106 p_tagValueList = &pageContent->tagValueDetails.tagValueList;
1107
1108 // Backup pair info for easy data access upon click on button
1109 genericContext.detailsItem = pair->item;
1110 genericContext.detailsvalue = pair->value;
1111 genericContext.detailsWrapping = p_content->content.tagValueList.wrapping;
1112 }
1113 }
1114
1115 if (p_tagValueList != NULL) {
1116 p_tagValueList->nbPairs = nbElementsInPage;
1117 p_tagValueList->token = p_content->content.tagValueList.token;
1118 if (p_content->content.tagValueList.pairs != NULL) {
1119 p_tagValueList->pairs
1120 = PIC(&p_content->content.tagValueList.pairs[nextElementIdx]);
1121 // parse pairs to check if any contains an alias for value
1122 for (uint8_t i = 0; i < nbElementsInPage; i++) {
1123 if (p_tagValueList->pairs[i].aliasValue) {
1124 p_tagValueList->token = VALUE_ALIAS_TOKEN;
1125 break;
1126 }
1127 }
1128 }
1129 else {
1130 p_tagValueList->pairs = NULL;
1131 p_tagValueList->callback = p_content->content.tagValueList.callback;
1132 p_tagValueList->startIndex = nextElementIdx;
1133 // parse pairs to check if any contains an alias for value
1134 for (uint8_t i = 0; i < nbElementsInPage; i++) {
1135 const nbgl_layoutTagValue_t *pair
1136 = PIC(p_content->content.tagValueList.callback(nextElementIdx + i));
1137 if (pair->aliasValue) {
1138 p_tagValueList->token = VALUE_ALIAS_TOKEN;
1139 break;
1140 }
1141 }
1142 }
1143 p_tagValueList->smallCaseForValue = false;
1145 p_tagValueList->hideEndOfLastLine = true;
1146 p_tagValueList->wrapping = p_content->content.tagValueList.wrapping;
1147 }
1148
1149 break;
1150 }
1151 case TAG_VALUE_CONFIRM: {
1152 nbgl_contentTagValueList_t *p_tagValueList;
1153 // only display a TAG_VALUE_CONFIRM if we are at the last page
1154 if ((nextElementIdx + nbElementsInPage)
1156 memcpy(&pageContent->tagValueConfirm,
1157 &p_content->content.tagValueConfirm,
1158 sizeof(pageContent->tagValueConfirm));
1159 p_tagValueList = &pageContent->tagValueConfirm.tagValueList;
1160 }
1161 else {
1162 // else display it as a TAG_VALUE_LIST
1163 pageContent->type = TAG_VALUE_LIST;
1164 p_tagValueList = &pageContent->tagValueList;
1165 }
1166 p_tagValueList->nbPairs = nbElementsInPage;
1167 p_tagValueList->pairs
1168 = PIC(&p_content->content.tagValueConfirm.tagValueList.pairs[nextElementIdx]);
1169 // parse pairs to check if any contains an alias for value
1170 for (uint8_t i = 0; i < nbElementsInPage; i++) {
1171 if (p_tagValueList->pairs[i].aliasValue) {
1172 p_tagValueList->token = VALUE_ALIAS_TOKEN;
1173 break;
1174 }
1175 }
1176 // memorize pairs (or callback) for usage when alias is used
1177 genericContext.currentPairs = p_tagValueList->pairs;
1178 genericContext.currentCallback = p_tagValueList->callback;
1179 break;
1180 }
1181 case SWITCHES_LIST:
1182 pageContent->switchesList.nbSwitches = nbElementsInPage;
1183 pageContent->switchesList.switches
1184 = PIC(&p_content->content.switchesList.switches[nextElementIdx]);
1185 break;
1186 case INFOS_LIST:
1187 pageContent->infosList.nbInfos = nbElementsInPage;
1188 pageContent->infosList.infoTypes
1189 = PIC(&p_content->content.infosList.infoTypes[nextElementIdx]);
1190 pageContent->infosList.infoContents
1191 = PIC(&p_content->content.infosList.infoContents[nextElementIdx]);
1192 break;
1193 case CHOICES_LIST:
1194 memcpy(&pageContent->choicesList,
1195 &p_content->content.choicesList,
1196 sizeof(pageContent->choicesList));
1197 pageContent->choicesList.nbChoices = nbElementsInPage;
1198 pageContent->choicesList.names
1199 = PIC(&p_content->content.choicesList.names[nextElementIdx]);
1200 if ((p_content->content.choicesList.initChoice >= nextElementIdx)
1201 && (p_content->content.choicesList.initChoice
1202 < nextElementIdx + nbElementsInPage)) {
1203 pageContent->choicesList.initChoice
1204 = p_content->content.choicesList.initChoice - nextElementIdx;
1205 }
1206 else {
1207 pageContent->choicesList.initChoice = nbElementsInPage;
1208 }
1209 break;
1210 case BARS_LIST:
1211 pageContent->barsList.nbBars = nbElementsInPage;
1212 pageContent->barsList.barTexts
1213 = PIC(&p_content->content.barsList.barTexts[nextElementIdx]);
1214 pageContent->barsList.tokens = PIC(&p_content->content.barsList.tokens[nextElementIdx]);
1215 pageContent->barsList.tuneId = p_content->content.barsList.tuneId;
1216 break;
1217 default:
1218 LOG_DEBUG(USE_CASE_LOGGER, "Unsupported type %d\n", pageContent->type);
1219 return false;
1220 }
1221
1222 bool isFirstOrLastPage
1223 = ((p_content->type == CENTERED_INFO) || (p_content->type == EXTENDED_CENTER))
1224 || (p_content->type == INFO_LONG_PRESS);
1225 nbgl_operationType_t operationType
1226 = (navType == STREAMING_NAV)
1227 ? bundleNavContext.reviewStreaming.operationType
1228 : ((navType == GENERIC_NAV) ? bundleNavContext.review.operationType : 0);
1229
1230 // if first or last page of review and blind/risky operation, add the warning top-right button
1231 if (isFirstOrLastPage
1232 && (operationType & (BLIND_OPERATION | RISKY_OPERATION | NO_THREAT_OPERATION))) {
1233 // if issue is only Web3Checks "no threat", use privacy icon
1234 if ((operationType & NO_THREAT_OPERATION)
1235 && !(reviewWithWarnCtx.warning->predefinedSet & (1 << BLIND_SIGNING_WARN))) {
1236 pageContent->topRightIcon = &PRIVACY_ICON;
1237 }
1238 else {
1239 pageContent->topRightIcon = &WARNING_ICON;
1240 }
1241
1242 pageContent->topRightToken
1243 = (operationType & BLIND_OPERATION) ? BLIND_WARNING_TOKEN : WARNING_BUTTON_TOKEN;
1244 }
1245
1246 return true;
1247}
1248
1249// function used to display the current page in generic context navigation mode
1250static void displayGenericContextPage(uint8_t pageIdx, bool forceFullRefresh)
1251{
1252 // Retrieve next page parameters
1253 nbgl_content_t content;
1254 uint8_t nbElementsInPage;
1255 bool flag;
1256 const nbgl_content_t *p_content = NULL;
1257
1258 if (navType == STREAMING_NAV) {
1259 if (pageIdx == LAST_PAGE_FOR_REVIEW) {
1260 if (bundleNavContext.reviewStreaming.skipCallback != NULL) {
1261 bundleNavContext.reviewStreaming.skipCallback();
1262 }
1263 return;
1264 }
1265 else if (pageIdx >= bundleNavContext.reviewStreaming.stepPageNb) {
1266 bundleNavReviewStreamingChoice(true);
1267 return;
1268 }
1269 }
1270 else {
1271 // if coming from Skip modal, let's say we are at the last page
1272 if (pageIdx == LAST_PAGE_FOR_REVIEW) {
1273 pageIdx = navInfo.nbPages - 1;
1274 }
1275 }
1276
1277 if (navInfo.activePage == pageIdx) {
1278 p_content
1279 = genericContextComputeNextPageParams(pageIdx, &content, &nbElementsInPage, &flag);
1280 }
1281 else if (navInfo.activePage < pageIdx) {
1282 // Support going more than one step forward.
1283 // It occurs when initializing a navigation on an arbitrary page
1284 for (int i = navInfo.activePage + 1; i <= pageIdx; i++) {
1285 p_content = genericContextComputeNextPageParams(i, &content, &nbElementsInPage, &flag);
1286 }
1287 }
1288 else {
1289 if (pageIdx - navInfo.activePage > 1) {
1290 // We don't support going more than one step backward as it doesn't occurs for now?
1291 LOG_DEBUG(USE_CASE_LOGGER, "Unsupported navigation\n");
1292 return;
1293 }
1294 p_content
1295 = genericContextComputeNextPageParams(pageIdx, &content, &nbElementsInPage, &flag);
1296 }
1297
1298 if (p_content == NULL) {
1299 return;
1300 }
1301 // if the operation is skippable
1302 if ((navType != STREAMING_NAV)
1303 && (bundleNavContext.review.operationType & SKIPPABLE_OPERATION)) {
1304 // only present the "Skip" header on actual tag/value pages
1305 if ((pageIdx > 0) && (pageIdx < (navInfo.nbPages - 1))) {
1306 navInfo.progressIndicator = false;
1307 navInfo.skipText = "Skip";
1308 navInfo.skipToken = SKIP_TOKEN;
1309 }
1310 else {
1311 navInfo.skipText = NULL;
1312 }
1313 }
1314
1315 // Create next page content
1316 nbgl_pageContent_t pageContent = {0};
1317 if (!genericContextPreparePageContent(p_content, nbElementsInPage, flag, &pageContent)) {
1318 return;
1319 }
1320
1321 pageContext = nbgl_pageDrawGenericContent(&pageCallback, &navInfo, &pageContent);
1322
1323 if (forceFullRefresh) {
1325 }
1326 else {
1328 }
1329}
1330
1331// from the current details context, return a pointer on the details at the given page
1332static const char *getDetailsPageAt(uint8_t detailsPage)
1333{
1334 uint8_t page = 0;
1335 const char *currentChar = detailsContext.value;
1336 while (page < detailsPage) {
1337 uint16_t nbLines
1338 = nbgl_getTextNbLinesInWidth(SMALL_BOLD_FONT, currentChar, AVAILABLE_WIDTH, false);
1339 if (nbLines > NB_MAX_LINES_IN_DETAILS) {
1340 uint16_t len;
1341 nbgl_getTextMaxLenInNbLines(SMALL_BOLD_FONT,
1342 currentChar,
1345 &len,
1346 detailsContext.wrapping);
1347 // don't start next page on a \n
1348 if (currentChar[len] == '\n') {
1349 len++;
1350 }
1351 else if (detailsContext.wrapping == false) {
1352 len -= 3;
1353 }
1354 currentChar = currentChar + len;
1355 }
1356 page++;
1357 }
1358 return currentChar;
1359}
1360
1361// function used to display the current page in details review mode
1362static void displayDetailsPage(uint8_t detailsPage, bool forceFullRefresh)
1363{
1364 static nbgl_layoutTagValue_t currentPair;
1365 nbgl_pageNavigationInfo_t info = {.activePage = detailsPage,
1366 .nbPages = detailsContext.nbPages,
1367 .navType = NAV_WITH_BUTTONS,
1368 .quitToken = QUIT_TOKEN,
1369 .navWithButtons.navToken = NAV_TOKEN,
1370 .navWithButtons.quitButton = true,
1371 .navWithButtons.backButton = true,
1372 .navWithButtons.quitText = NULL,
1373 .progressIndicator = false,
1374 .tuneId = TUNE_TAP_CASUAL};
1376 .topRightIcon = NULL,
1377 .tagValueList.nbPairs = 1,
1378 .tagValueList.pairs = &currentPair,
1379 .tagValueList.smallCaseForValue = true,
1380 .tagValueList.wrapping = detailsContext.wrapping};
1381
1382 if (modalPageContext != NULL) {
1383 nbgl_pageRelease(modalPageContext);
1384 }
1385 currentPair.item = detailsContext.tag;
1386 // if move backward or first page
1387 if (detailsPage <= detailsContext.currentPage) {
1388 // recompute current start from beginning
1389 currentPair.value = getDetailsPageAt(detailsPage);
1390 forceFullRefresh = true;
1391 }
1392 // else move forward
1393 else {
1394 currentPair.value = detailsContext.nextPageStart;
1395 }
1396 detailsContext.currentPage = detailsPage;
1397 uint16_t nbLines = nbgl_getTextNbLinesInWidth(
1398 SMALL_BOLD_FONT, currentPair.value, AVAILABLE_WIDTH, detailsContext.wrapping);
1399
1400 if (nbLines > NB_MAX_LINES_IN_DETAILS) {
1401 uint16_t len;
1402 nbgl_getTextMaxLenInNbLines(SMALL_BOLD_FONT,
1403 currentPair.value,
1406 &len,
1407 detailsContext.wrapping);
1409 // don't start next page on a \n
1410 if (currentPair.value[len] == '\n') {
1411 len++;
1412 }
1413 else if (!detailsContext.wrapping) {
1415 len -= 3;
1416 }
1417 // memorize next position to save processing
1418 detailsContext.nextPageStart = currentPair.value + len;
1419 // use special feature to keep only NB_MAX_LINES_IN_DETAILS lines and replace the last 3
1420 // chars by "...", only if the next char to display in next page is not '\n'
1422 }
1423 else {
1424 detailsContext.nextPageStart = NULL;
1425 content.tagValueList.nbMaxLinesForValue = 0;
1426 }
1427 if (info.nbPages == 1) {
1428 // if only one page, no navigation bar, and use a footer instead
1429 info.navWithButtons.quitText = "Close";
1430 }
1431 modalPageContext = nbgl_pageDrawGenericContentExt(&pageModalCallback, &info, &content, true);
1432
1433 if (forceFullRefresh) {
1435 }
1436 else {
1438 }
1439}
1440
1441// function used to display the content of a full value, when touching an alias of a tag/value pair
1442static void displayFullValuePage(const char *backText,
1443 const char *aliasText,
1444 const nbgl_contentValueExt_t *extension)
1445{
1446 const char *modalTitle
1447 = (extension->backText != NULL) ? PIC(extension->backText) : PIC(backText);
1448 if (extension->aliasType == INFO_LIST_ALIAS) {
1449 genericContext.currentInfos = extension->infolist;
1450 displayInfosListModal(modalTitle, extension->infolist, true);
1451 }
1452 else if (extension->aliasType == TAG_VALUE_LIST_ALIAS) {
1453 genericContext.currentTagValues = extension->tagValuelist;
1454 displayTagValueListModal(extension->tagValuelist);
1455 }
1456 else {
1457 nbgl_layoutDescription_t layoutDescription = {.modal = true,
1458 .withLeftBorder = true,
1459 .onActionCallback = &modalLayoutTouchCallback,
1460 .tapActionText = NULL};
1462 .separationLine = false,
1463 .backAndText.token = 0,
1464 .backAndText.tuneId = TUNE_TAP_CASUAL,
1465 .backAndText.text = modalTitle};
1466 genericContext.modalLayout = nbgl_layoutGet(&layoutDescription);
1467 // add header with the tag part of the pair, to go back
1468 nbgl_layoutAddHeader(genericContext.modalLayout, &headerDesc);
1469 // add either QR Code or full value text
1470 if (extension->aliasType == QR_CODE_ALIAS) {
1471#ifdef NBGL_QRCODE
1472 nbgl_layoutQRCode_t qrCode
1473 = {.url = extension->fullValue,
1474 .text1 = (extension->title != NULL) ? extension->title : extension->fullValue,
1475 .text2 = extension->explanation,
1476 .centered = true,
1477 .offsetY = 0};
1478
1479 nbgl_layoutAddQRCode(genericContext.modalLayout, &qrCode);
1480#endif // NBGL_QRCODE
1481 }
1482 else {
1483 nbgl_layoutTextContent_t content = {0};
1484 content.title = aliasText;
1485 if (extension->aliasType == ENS_ALIAS) {
1486 content.info = "ENS names are resolved by Ledger backend.";
1487 }
1488 else if ((extension->aliasType == ADDRESS_BOOK_ALIAS)
1489 && (extension->aliasSubName != NULL)) {
1490 content.descriptions[content.nbDescriptions] = extension->aliasSubName;
1491 content.nbDescriptions++;
1492 }
1493 else {
1494 content.info = extension->explanation;
1495 }
1496 // add full value text
1497 content.descriptions[content.nbDescriptions] = extension->fullValue;
1498 content.nbDescriptions++;
1499 nbgl_layoutAddTextContent(genericContext.modalLayout, &content);
1500 }
1501 // draw & refresh
1502 nbgl_layoutDraw(genericContext.modalLayout);
1503 nbgl_refresh();
1504 }
1505}
1506
1507// function used to display the modal containing tip-box infos
1508static void displayInfosListModal(const char *modalTitle,
1509 const nbgl_contentInfoList_t *infos,
1510 bool fromReview)
1511{
1513 .nbPages = 1,
1514 .navType = NAV_WITH_BUTTONS,
1515 .quitToken = QUIT_TIPBOX_MODAL_TOKEN,
1516 .navWithButtons.navToken = NAV_TOKEN,
1517 .navWithButtons.quitButton = false,
1518 .navWithButtons.backButton = true,
1519 .navWithButtons.quitText = NULL,
1520 .progressIndicator = false,
1521 .tuneId = TUNE_TAP_CASUAL};
1522 nbgl_pageContent_t content
1523 = {.type = INFOS_LIST,
1524 .topRightIcon = NULL,
1525 .infosList.nbInfos = infos->nbInfos,
1526 .infosList.withExtensions = infos->withExtensions,
1527 .infosList.infoTypes = infos->infoTypes,
1528 .infosList.infoContents = infos->infoContents,
1529 .infosList.infoExtensions = infos->infoExtensions,
1530 .infosList.token = fromReview ? INFO_ALIAS_TOKEN : INFOS_TIP_BOX_TOKEN,
1531 .title = modalTitle,
1532 .titleToken = QUIT_TIPBOX_MODAL_TOKEN,
1533 .tuneId = TUNE_TAP_CASUAL};
1534
1535 if (modalPageContext != NULL) {
1536 nbgl_pageRelease(modalPageContext);
1537 }
1538 modalPageContext = nbgl_pageDrawGenericContentExt(&pageModalCallback, &info, &content, true);
1539
1541}
1542
1543// function used to display the modal containing alias tag-value pairs
1544static void displayTagValueListModalPage(uint8_t pageIdx, bool forceFullRefresh)
1545{
1546 nbgl_pageNavigationInfo_t info = {.activePage = pageIdx,
1547 .nbPages = detailsContext.nbPages,
1548 .navType = NAV_WITH_BUTTONS,
1549 .quitToken = QUIT_TOKEN,
1550 .navWithButtons.navToken = MODAL_NAV_TOKEN,
1551 .navWithButtons.quitButton = true,
1552 .navWithButtons.backButton = true,
1553 .navWithButtons.quitText = NULL,
1554 .progressIndicator = false,
1555 .tuneId = TUNE_TAP_CASUAL};
1557 .topRightIcon = NULL,
1558 .tagValueList.smallCaseForValue = true,
1559 .tagValueList.wrapping = detailsContext.wrapping};
1560 uint8_t nbElementsInPage;
1561
1562 // if first page or forward
1563 if (detailsContext.currentPage <= pageIdx) {
1564 modalContextGetPageInfo(pageIdx, &nbElementsInPage);
1565 }
1566 else {
1567 // backward direction
1568 modalContextGetPageInfo(pageIdx + 1, &nbElementsInPage);
1569 detailsContext.currentPairIdx -= nbElementsInPage;
1570 modalContextGetPageInfo(pageIdx, &nbElementsInPage);
1571 detailsContext.currentPairIdx -= nbElementsInPage;
1572 }
1573 detailsContext.currentPage = pageIdx;
1574
1575 content.tagValueList.pairs
1576 = &genericContext.currentTagValues->pairs[detailsContext.currentPairIdx];
1577 content.tagValueList.nbPairs = nbElementsInPage;
1578 detailsContext.currentPairIdx += nbElementsInPage;
1579 if (info.nbPages == 1) {
1580 // if only one page, no navigation bar, and use a footer instead
1581 info.navWithButtons.quitText = "Close";
1582 }
1583
1584 if (modalPageContext != NULL) {
1585 nbgl_pageRelease(modalPageContext);
1586 }
1587 modalPageContext = nbgl_pageDrawGenericContentExt(&pageModalCallback, &info, &content, true);
1588
1589 if (forceFullRefresh) {
1591 }
1592 else {
1594 }
1595}
1596
1597#ifdef NBGL_QRCODE
1598static void displayAddressQRCode(void)
1599{
1600 // display the address as QR Code
1601 nbgl_layoutDescription_t layoutDescription = {.modal = true,
1602 .withLeftBorder = true,
1603 .onActionCallback = &modalLayoutTouchCallback,
1604 .tapActionText = NULL};
1605 nbgl_layoutHeader_t headerDesc = {
1606 .type = HEADER_EMPTY, .separationLine = false, .emptySpace.height = SMALL_CENTERING_HEADER};
1607 nbgl_layoutQRCode_t qrCode = {.url = addressConfirmationContext.tagValuePairs[0].value,
1608 .text1 = NULL,
1609 .centered = true,
1610 .offsetY = 0};
1611
1612 addressConfirmationContext.modalLayout = nbgl_layoutGet(&layoutDescription);
1613 // add empty header for better look
1614 nbgl_layoutAddHeader(addressConfirmationContext.modalLayout, &headerDesc);
1615 // compute nb lines to check whether it shall be shorten (max is 3 lines)
1616 uint16_t nbLines = nbgl_getTextNbLinesInWidth(SMALL_REGULAR_FONT,
1617 addressConfirmationContext.tagValuePairs[0].value,
1619 false);
1620
1621 if (nbLines <= QRCODE_NB_MAX_LINES) {
1622 qrCode.text2 = addressConfirmationContext.tagValuePairs[0].value; // in gray
1623 }
1624 else {
1625 // only keep beginning and end of text, and add ... in the middle
1626 nbgl_textReduceOnNbLines(SMALL_REGULAR_FONT,
1627 addressConfirmationContext.tagValuePairs[0].value,
1629 QRCODE_NB_MAX_LINES,
1630 reducedAddress,
1631 QRCODE_REDUCED_ADDR_LEN);
1632 qrCode.text2 = reducedAddress; // in gray
1633 }
1634
1635 nbgl_layoutAddQRCode(addressConfirmationContext.modalLayout, &qrCode);
1636
1638 addressConfirmationContext.modalLayout, "Close", DISMISS_QR_TOKEN, TUNE_TAP_CASUAL);
1639 nbgl_layoutDraw(addressConfirmationContext.modalLayout);
1640 nbgl_refresh();
1641}
1642
1643#endif // NBGL_QRCODE
1644
1645// called when header is touched on modal page, to dismiss it
1646static void modalLayoutTouchCallback(int token, uint8_t index)
1647{
1648 UNUSED(index);
1649 if (token == DISMISS_QR_TOKEN) {
1650 // dismiss modal
1651 nbgl_layoutRelease(addressConfirmationContext.modalLayout);
1652 addressConfirmationContext.modalLayout = NULL;
1654 }
1655 else if (token == DISMISS_WARNING_TOKEN) {
1656 // dismiss modal
1657 nbgl_layoutRelease(reviewWithWarnCtx.modalLayout);
1658 // if already at first level, simply redraw the background
1659 if (reviewWithWarnCtx.securityReportLevel <= 1) {
1660 reviewWithWarnCtx.modalLayout = NULL;
1662 }
1663 else {
1664 // We are at level 2 of warning modal, so re-display the level 1
1665 reviewWithWarnCtx.securityReportLevel = 1;
1666 // if preset is used
1667 if (reviewWithWarnCtx.warning->predefinedSet) {
1668 displaySecurityReport(reviewWithWarnCtx.warning->predefinedSet);
1669 }
1670 else {
1671 // use customized warning
1672 const nbgl_warningDetails_t *details
1673 = (reviewWithWarnCtx.isIntro) ? reviewWithWarnCtx.warning->introDetails
1674 : reviewWithWarnCtx.warning->reviewDetails;
1675 displayCustomizedSecurityReport(details);
1676 }
1677 return;
1678 }
1679 }
1680 else if (token == DISMISS_DETAILS_TOKEN) {
1681 // dismiss modal
1682 nbgl_layoutRelease(choiceWithDetailsCtx.modalLayout);
1683 // if already at first level, simply redraw the background
1684 if (choiceWithDetailsCtx.level <= 1) {
1685 choiceWithDetailsCtx.modalLayout = NULL;
1687 }
1688 else {
1689 // We are at level 2 of details modal, so re-display the level 1
1690 choiceWithDetailsCtx.level = 1;
1691 choiceWithDetailsCtx.modalLayout
1692 = displayModalDetails(choiceWithDetailsCtx.details, DISMISS_DETAILS_TOKEN);
1693 }
1694 }
1695 else if ((token >= FIRST_WARN_BAR_TOKEN) && (token <= LAST_WARN_BAR_TOKEN)) {
1696 if (sharedContext.usage == SHARE_CTX_REVIEW_WITH_WARNING) {
1697 // dismiss modal before creating a new one
1698 nbgl_layoutRelease(reviewWithWarnCtx.modalLayout);
1699 reviewWithWarnCtx.securityReportLevel = 2;
1700 // if preset is used
1701 if (reviewWithWarnCtx.warning->predefinedSet) {
1702 displaySecurityReport(1 << (token - FIRST_WARN_BAR_TOKEN));
1703 }
1704 else {
1705 // use customized warning
1706 const nbgl_warningDetails_t *details
1707 = (reviewWithWarnCtx.isIntro) ? reviewWithWarnCtx.warning->introDetails
1708 : reviewWithWarnCtx.warning->reviewDetails;
1709 if (details->type == BAR_LIST_WARNING) {
1710 displayCustomizedSecurityReport(
1711 &details->barList.details[token - FIRST_WARN_BAR_TOKEN]);
1712 }
1713 }
1714 }
1715 else if (sharedContext.usage == SHARE_CTX_CHOICE_WITH_DETAILS) {
1716 const nbgl_warningDetails_t *details
1717 = &choiceWithDetailsCtx.details->barList.details[token - FIRST_WARN_BAR_TOKEN];
1718 if (details->title != NO_TYPE_WARNING) {
1719 // dismiss modal before creating a new one
1720 nbgl_layoutRelease(choiceWithDetailsCtx.modalLayout);
1721 choiceWithDetailsCtx.level = 2;
1722 choiceWithDetailsCtx.modalLayout
1723 = displayModalDetails(details, DISMISS_DETAILS_TOKEN);
1724 }
1725 }
1726 return;
1727 }
1728 else {
1729 // dismiss modal
1730 nbgl_layoutRelease(genericContext.modalLayout);
1731 genericContext.modalLayout = NULL;
1733 }
1734 nbgl_refresh();
1735}
1736
1737// called when item are touched in layout
1738static void layoutTouchCallback(int token, uint8_t index)
1739{
1740 // choice buttons in initial warning page
1741 if (token == WARNING_CHOICE_TOKEN) {
1742 if (index == 0) { // top button to exit
1743 reviewWithWarnCtx.choiceCallback(false);
1744 }
1745 else { // bottom button to continue to review
1746 reviewWithWarnCtx.isIntro = false;
1747 if (reviewWithWarnCtx.isStreaming) {
1748 useCaseReviewStreamingStart(reviewWithWarnCtx.operationType,
1749 reviewWithWarnCtx.icon,
1750 reviewWithWarnCtx.reviewTitle,
1751 reviewWithWarnCtx.reviewSubTitle,
1752 reviewWithWarnCtx.choiceCallback,
1753 false);
1754 }
1755 else {
1756 useCaseReview(reviewWithWarnCtx.operationType,
1757 reviewWithWarnCtx.tagValueList,
1758 reviewWithWarnCtx.icon,
1759 reviewWithWarnCtx.reviewTitle,
1760 reviewWithWarnCtx.reviewSubTitle,
1761 reviewWithWarnCtx.finishTitle,
1762 &activeTipBox,
1763 reviewWithWarnCtx.choiceCallback,
1764 false,
1765 false);
1766 }
1767 }
1768 }
1769 // top-right button in initial warning page
1770 else if (token == WARNING_BUTTON_TOKEN) {
1771 // start directly at first level of security report
1772 reviewWithWarnCtx.securityReportLevel = 1;
1773 // if preset is used
1774 if (reviewWithWarnCtx.warning->predefinedSet) {
1775 displaySecurityReport(reviewWithWarnCtx.warning->predefinedSet);
1776 }
1777 else {
1778 // use customized warning
1779 const nbgl_warningDetails_t *details = (reviewWithWarnCtx.isIntro)
1780 ? reviewWithWarnCtx.warning->introDetails
1781 : reviewWithWarnCtx.warning->reviewDetails;
1782 displayCustomizedSecurityReport(details);
1783 }
1784 }
1785 // top-right button in choice with details page
1786 else if (token == CHOICE_DETAILS_TOKEN) {
1787 choiceWithDetailsCtx.level = 1;
1788 choiceWithDetailsCtx.modalLayout
1789 = displayModalDetails(choiceWithDetailsCtx.details, DISMISS_DETAILS_TOKEN);
1790 }
1791 // main prelude page
1792 else if (token == PRELUDE_CHOICE_TOKEN) {
1793 if (index == 0) { // top button to display details about prelude
1794 displayCustomizedSecurityReport(reviewWithWarnCtx.warning->prelude->details);
1795 }
1796 else { // footer to continue to review
1797 if (HAS_INITIAL_WARNING(reviewWithWarnCtx.warning)) {
1798 displayInitialWarning();
1799 }
1800 else {
1801 reviewWithWarnCtx.isIntro = false;
1802 if (reviewWithWarnCtx.isStreaming) {
1803 useCaseReviewStreamingStart(reviewWithWarnCtx.operationType,
1804 reviewWithWarnCtx.icon,
1805 reviewWithWarnCtx.reviewTitle,
1806 reviewWithWarnCtx.reviewSubTitle,
1807 reviewWithWarnCtx.choiceCallback,
1808 false);
1809 }
1810 else {
1811 useCaseReview(reviewWithWarnCtx.operationType,
1812 reviewWithWarnCtx.tagValueList,
1813 reviewWithWarnCtx.icon,
1814 reviewWithWarnCtx.reviewTitle,
1815 reviewWithWarnCtx.reviewSubTitle,
1816 reviewWithWarnCtx.finishTitle,
1817 &activeTipBox,
1818 reviewWithWarnCtx.choiceCallback,
1819 false,
1820 false);
1821 }
1822 }
1823 }
1824 }
1825 // choice buttons
1826 else if (token == CHOICE_TOKEN) {
1827 if (onChoice != NULL) {
1828 onChoice((index == 0) ? true : false);
1829 }
1830 }
1831}
1832
1833// called when skip button is touched in footer, during forward only review
1834static void displaySkipWarning(void)
1835{
1837 .cancelText = "Go back to review",
1838 .centeredInfo.text1 = "Skip review?",
1839 .centeredInfo.text2
1840 = "If you're sure you don't need to review all fields, you can skip straight to signing.",
1841 .centeredInfo.text3 = NULL,
1842 .centeredInfo.style = LARGE_CASE_INFO,
1843 .centeredInfo.icon = &IMPORTANT_CIRCLE_ICON,
1844 .centeredInfo.offsetY = 0,
1845 .confirmationText = "Yes, skip",
1846 .confirmationToken = SKIP_TOKEN,
1847 .tuneId = TUNE_TAP_CASUAL,
1848 .modal = true};
1849 if (modalPageContext != NULL) {
1850 nbgl_pageRelease(modalPageContext);
1851 }
1852 modalPageContext = nbgl_pageDrawConfirmation(&pageModalCallback, &info);
1854}
1855
1856#ifdef NBGL_KEYPAD
1857// called to update the keypad and automatically show / hide:
1858// - backspace key if no digits are present
1859// - validation key if the min digit is reached
1860// - keypad if the max number of digit is reached
1861static void updateKeyPad(bool add)
1862{
1863 bool enableValidate, enableBackspace, enableDigits;
1864 bool redrawKeypad = false;
1866
1867 enableDigits = (keypadContext.pinLen < keypadContext.pinMaxDigits);
1868 enableValidate = (keypadContext.pinLen >= keypadContext.pinMinDigits);
1869 enableBackspace = (keypadContext.pinLen > 0);
1870 if (add) {
1871 if ((keypadContext.pinLen == keypadContext.pinMinDigits)
1872 || // activate "validate" button on keypad
1873 (keypadContext.pinLen == keypadContext.pinMaxDigits)
1874 || // deactivate "digits" on keypad
1875 (keypadContext.pinLen == 1)) { // activate "backspace"
1876 redrawKeypad = true;
1877 }
1878 }
1879 else { // remove
1880 if ((keypadContext.pinLen == 0) || // deactivate "backspace" button on keypad
1881 (keypadContext.pinLen == (keypadContext.pinMinDigits - 1))
1882 || // deactivate "validate" button on keypad
1883 (keypadContext.pinLen
1884 == (keypadContext.pinMaxDigits - 1))) { // reactivate "digits" on keypad
1885 redrawKeypad = true;
1886 }
1887 }
1888 if (keypadContext.hidden == true) {
1889 nbgl_layoutUpdateKeypadContent(keypadContext.layoutCtx, true, keypadContext.pinLen, NULL);
1890 }
1891 else {
1893 keypadContext.layoutCtx, false, 0, (const char *) keypadContext.pinEntry);
1894 }
1895 if (redrawKeypad) {
1897 keypadContext.layoutCtx, 0, enableValidate, enableBackspace, enableDigits);
1898 }
1899
1900 if ((!add) && (keypadContext.pinLen == 0)) {
1901 // Full refresh to fully clean the bullets when reaching 0 digits
1902 mode = FULL_COLOR_REFRESH;
1903 }
1905}
1906
1907// called when a key is touched on the keypad
1908static void keypadCallback(char touchedKey)
1909{
1910 switch (touchedKey) {
1911 case BACKSPACE_KEY:
1912 if (keypadContext.pinLen > 0) {
1913 keypadContext.pinLen--;
1914 keypadContext.pinEntry[keypadContext.pinLen] = 0;
1915 }
1916 updateKeyPad(false);
1917 break;
1918
1919 case VALIDATE_KEY:
1920 // Gray out keyboard / buttons as a first user feedback
1921 nbgl_layoutUpdateKeypad(keypadContext.layoutCtx, 0, false, false, true);
1924
1925 keypadContext.onValidatePin(keypadContext.pinEntry, keypadContext.pinLen);
1926 break;
1927
1928 default:
1929 if ((touchedKey >= 0x30) && (touchedKey < 0x40)) {
1930 if (keypadContext.pinLen < keypadContext.pinMaxDigits) {
1931 keypadContext.pinEntry[keypadContext.pinLen] = touchedKey;
1932 keypadContext.pinLen++;
1933 }
1934 updateKeyPad(true);
1935 }
1936 break;
1937 }
1938}
1939
1940static void keypadGeneric_cb(int token, uint8_t index)
1941{
1942 UNUSED(index);
1943 if (token != BACK_TOKEN) {
1944 return;
1945 }
1946 onQuit();
1947}
1948#endif
1949
1950#ifdef NBGL_KEYBOARD
1951// called when a key is touched on the keyboard
1952static void keyboardCallback(char touchedKey)
1953{
1954 uint32_t mask = 0;
1955 size_t textLen = strlen(keyboardContext.entryBuffer);
1956 if (touchedKey == BACKSPACE_KEY) {
1957 if (textLen == 0) {
1958 // No action
1959 return;
1960 }
1961 keyboardContext.entryBuffer[--textLen] = '\0';
1962 }
1963 else {
1964 keyboardContext.entryBuffer[textLen] = touchedKey;
1965 keyboardContext.entryBuffer[++textLen] = '\0';
1966 }
1967 if (keyboardContext.keyboardContent.type == KEYBOARD_WITH_SUGGESTIONS) {
1968 // if suggestions are displayed, we update them at each key press
1969 keyboardContext.getSuggestionButtons(&keyboardContext.keyboardContent, &mask);
1970 }
1971 else if (textLen >= keyboardContext.entryMaxLen) {
1972 // entry length can't be greater, so we mask every characters
1973 mask = -1;
1974 }
1975 nbgl_layoutUpdateKeyboardContent(keyboardContext.layoutCtx, &keyboardContext.keyboardContent);
1976 nbgl_layoutUpdateKeyboard(keyboardContext.layoutCtx,
1977 keyboardContext.keyboardIndex,
1978 mask,
1979 false,
1980 keyboardContext.casing);
1982}
1983
1984static void keyboardGeneric_cb(int token, uint8_t index)
1985{
1986 UNUSED(index);
1987 switch (token) {
1988 case BACK_TOKEN:
1989 onQuit();
1990 break;
1991 case KEYBOARD_CROSS_TOKEN:
1992 // Simulate a word of a single letter 'a' and trigger the backspace action
1993 keyboardContext.entryBuffer[0] = 'a';
1994 keyboardContext.entryBuffer[1] = '\0';
1995 keyboardCallback(BACKSPACE_KEY);
1996 break;
1997 case KEYBOARD_BUTTON_TOKEN:
1998 if (keyboardContext.keyboardContent.type == KEYBOARD_WITH_BUTTON) {
1999 onAction();
2000 }
2001 break;
2002 default:
2003 if ((keyboardContext.keyboardContent.type == KEYBOARD_WITH_SUGGESTIONS)
2004 && (token >= keyboardContext.keyboardContent.suggestionButtons.firstButtonToken)) {
2005 onControls(token, index);
2006 }
2007 break;
2008 }
2009}
2010#endif
2011
2026static uint8_t getNbTagValuesInPage(uint8_t nbPairs,
2027 const nbgl_contentTagValueList_t *tagValueList,
2028 uint8_t startIndex,
2029 bool isSkippable,
2030 bool hasConfirmationButton,
2031 bool hasDetailsButton,
2032 bool *requireSpecificDisplay)
2033{
2034 uint8_t nbPairsInPage = 0;
2035 uint16_t currentHeight = PRE_TAG_VALUE_MARGIN; // upper margin
2036 uint16_t maxUsableHeight = TAG_VALUE_AREA_HEIGHT;
2037
2038 // if the review is skippable, it means that there is less height for tag/value pairs
2039 // the small centering header becomes a touchable header
2040 if (isSkippable) {
2041 maxUsableHeight -= TOUCHABLE_HEADER_BAR_HEIGHT - SMALL_CENTERING_HEADER;
2042 }
2043
2044 *requireSpecificDisplay = false;
2045 while (nbPairsInPage < nbPairs) {
2046 const nbgl_layoutTagValue_t *pair;
2047 nbgl_font_id_e value_font;
2048 uint16_t nbLines;
2049
2050 // margin between pairs
2051 // 12 or 24 px between each tag/value pair
2052 if (nbPairsInPage > 0) {
2053 currentHeight += INTER_TAG_VALUE_MARGIN;
2054 }
2055 // fetch tag/value pair strings.
2056 if (tagValueList->pairs != NULL) {
2057 pair = PIC(&tagValueList->pairs[startIndex + nbPairsInPage]);
2058 }
2059 else {
2060 pair = PIC(tagValueList->callback(startIndex + nbPairsInPage));
2061 }
2062
2063 if (pair->forcePageStart && nbPairsInPage > 0) {
2064 // This pair must be at the top of a page
2065 break;
2066 }
2067
2068 if (pair->centeredInfo) {
2069 if (nbPairsInPage > 0) {
2070 // This pair must be at the top of a page
2071 break;
2072 }
2073 else {
2074 // This pair is the only one of the page and has a specific display behavior
2075 nbPairsInPage = 1;
2076 *requireSpecificDisplay = true;
2077 break;
2078 }
2079 }
2080
2081 // tag height
2082 currentHeight += nbgl_getTextHeightInWidth(
2083 SMALL_REGULAR_FONT, pair->item, AVAILABLE_WIDTH, tagValueList->wrapping);
2084 // space between tag and value
2085 currentHeight += TAG_VALUE_INTERVALE;
2086 // set value font
2087 if (tagValueList->smallCaseForValue) {
2088 value_font = SMALL_REGULAR_FONT;
2089 }
2090 else {
2091 value_font = LARGE_MEDIUM_FONT;
2092 }
2093 // value height
2094 currentHeight += nbgl_getTextHeightInWidth(
2095 value_font, pair->value, AVAILABLE_WIDTH, tagValueList->wrapping);
2096
2097 // potential subAlias text
2098 if ((pair->aliasValue) && (pair->extension->aliasSubName)) {
2099 currentHeight += TAG_VALUE_INTERVALE + nbgl_getFontLineHeight(SMALL_REGULAR_FONT);
2100 }
2101 // nb lines for value
2103 value_font, pair->value, AVAILABLE_WIDTH, tagValueList->wrapping);
2104 if ((currentHeight >= maxUsableHeight) || (nbLines > NB_MAX_LINES_IN_REVIEW)) {
2105 if (nbPairsInPage == 0) {
2106 // Pair too long to fit in a single screen
2107 // It will be the only one of the page and has a specific display behavior
2108 nbPairsInPage = 1;
2109 *requireSpecificDisplay = true;
2110 }
2111 break;
2112 }
2113 nbPairsInPage++;
2114 }
2115 // if this is a TAG_VALUE_CONFIRM and we have reached the last pairs,
2116 // let's check if it still fits with a CONFIRMATION button, and if not,
2117 // remove the last pair
2118 if (hasConfirmationButton && (nbPairsInPage == nbPairs)) {
2119 maxUsableHeight -= UP_FOOTER_BUTTON_HEIGHT;
2120 if (currentHeight > maxUsableHeight) {
2121 nbPairsInPage--;
2122 }
2123 }
2124 // do the same with just a details button
2125 else if (hasDetailsButton) {
2126 maxUsableHeight -= (SMALL_BUTTON_RADIUS * 2);
2127 if (currentHeight > maxUsableHeight) {
2128 nbPairsInPage--;
2129 }
2130 }
2131 return nbPairsInPage;
2132}
2133
2143static uint8_t getNbTagValuesInDetailsPage(uint8_t nbPairs,
2144 const nbgl_contentTagValueList_t *tagValueList,
2145 uint8_t startIndex)
2146{
2147 uint8_t nbPairsInPage = 0;
2148 uint16_t currentHeight = PRE_TAG_VALUE_MARGIN; // upper margin
2149 uint16_t maxUsableHeight = TAG_VALUE_AREA_HEIGHT;
2150
2151 while (nbPairsInPage < nbPairs) {
2152 const nbgl_layoutTagValue_t *pair;
2153
2154 // margin between pairs
2155 // 12 or 24 px between each tag/value pair
2156 if (nbPairsInPage > 0) {
2157 currentHeight += INTER_TAG_VALUE_MARGIN;
2158 }
2159 // fetch tag/value pair strings.
2160 if (tagValueList->pairs != NULL) {
2161 pair = PIC(&tagValueList->pairs[startIndex + nbPairsInPage]);
2162 }
2163 else {
2164 pair = PIC(tagValueList->callback(startIndex + nbPairsInPage));
2165 }
2166
2167 // tag height
2168 currentHeight += nbgl_getTextHeightInWidth(
2169 SMALL_REGULAR_FONT, pair->item, AVAILABLE_WIDTH, tagValueList->wrapping);
2170 // space between tag and value
2171 currentHeight += 4;
2172
2173 // value height
2174 currentHeight += nbgl_getTextHeightInWidth(
2175 SMALL_REGULAR_FONT, pair->value, AVAILABLE_WIDTH, tagValueList->wrapping);
2176
2177 // we have reached the maximum height, it means than there are to many pairs
2178 if (currentHeight >= maxUsableHeight) {
2179 break;
2180 }
2181 nbPairsInPage++;
2182 }
2183 return nbPairsInPage;
2184}
2185
2186static uint8_t getNbPagesForContent(const nbgl_content_t *content,
2187 uint8_t pageIdxStart,
2188 bool isLast,
2189 bool isSkippable)
2190{
2191 uint8_t nbElements = 0;
2192 uint8_t nbPages = 0;
2193 uint8_t nbElementsInPage;
2194 uint8_t elemIdx = 0;
2195 bool flag;
2196
2197 nbElements = getContentNbElement(content);
2198
2199 while (nbElements > 0) {
2200 flag = 0;
2201 // if the current page is not the first one (or last), a navigation bar exists
2202 bool hasNav = !isLast || (pageIdxStart > 0) || (elemIdx > 0);
2203 if (content->type == TAG_VALUE_LIST) {
2204 nbElementsInPage = getNbTagValuesInPage(nbElements,
2205 &content->content.tagValueList,
2206 elemIdx,
2207 isSkippable,
2208 false,
2209 false,
2210 &flag);
2211 }
2212 else if (content->type == TAG_VALUE_CONFIRM) {
2213 nbElementsInPage = getNbTagValuesInPage(nbElements,
2215 elemIdx,
2216 isSkippable,
2217 isLast,
2218 !isLast,
2219 &flag);
2220 }
2221 else if (content->type == INFOS_LIST) {
2222 nbElementsInPage = nbgl_useCaseGetNbInfosInPage(
2223 nbElements, &content->content.infosList, elemIdx, hasNav);
2224 }
2225 else if (content->type == SWITCHES_LIST) {
2226 nbElementsInPage = nbgl_useCaseGetNbSwitchesInPage(
2227 nbElements, &content->content.switchesList, elemIdx, hasNav);
2228 }
2229 else if (content->type == BARS_LIST) {
2230 nbElementsInPage = nbgl_useCaseGetNbBarsInPage(
2231 nbElements, &content->content.barsList, elemIdx, hasNav);
2232 }
2233 else if (content->type == CHOICES_LIST) {
2234 nbElementsInPage = nbgl_useCaseGetNbChoicesInPage(
2235 nbElements, &content->content.choicesList, elemIdx, hasNav);
2236 }
2237 else {
2238 nbElementsInPage = MIN(nbMaxElementsPerContentType[content->type], nbElements);
2239 }
2240
2241 elemIdx += nbElementsInPage;
2242 genericContextSetPageInfo(pageIdxStart + nbPages, nbElementsInPage, flag);
2243 nbElements -= nbElementsInPage;
2244 nbPages++;
2245 }
2246
2247 return nbPages;
2248}
2249
2250static uint8_t getNbPagesForGenericContents(const nbgl_genericContents_t *genericContents,
2251 uint8_t pageIdxStart,
2252 bool isSkippable)
2253{
2254 uint8_t nbPages = 0;
2255 nbgl_content_t content;
2256 const nbgl_content_t *p_content;
2257
2258 for (int i = 0; i < genericContents->nbContents; i++) {
2259 p_content = getContentAtIdx(genericContents, i, &content);
2260 if (p_content == NULL) {
2261 return 0;
2262 }
2263 nbPages += getNbPagesForContent(p_content,
2264 pageIdxStart + nbPages,
2265 (i == (genericContents->nbContents - 1)),
2266 isSkippable);
2267 }
2268
2269 return nbPages;
2270}
2271
2272static void prepareAddressConfirmationPages(const char *address,
2273 const nbgl_contentTagValueList_t *tagValueList,
2274 nbgl_content_t *firstPageContent,
2275 nbgl_content_t *secondPageContent)
2276{
2277 nbgl_contentTagValueConfirm_t *tagValueConfirm;
2278
2279 addressConfirmationContext.tagValuePairs[0].item = "Address";
2280 addressConfirmationContext.tagValuePairs[0].value = address;
2281 addressConfirmationContext.nbPairs = 1;
2282
2283 // First page
2284 firstPageContent->type = TAG_VALUE_CONFIRM;
2285 tagValueConfirm = &firstPageContent->content.tagValueConfirm;
2286
2287#ifdef NBGL_QRCODE
2288 tagValueConfirm->detailsButtonIcon = &QRCODE_ICON;
2289 // only use "Show as QR" when address & pairs are not fitting in a single page
2290 if ((tagValueList != NULL) && (tagValueList->nbPairs < ADDR_VERIF_NB_PAIRS)) {
2292 bool flag;
2293 // copy in intermediate structure
2294 for (uint8_t i = 0; i < tagValueList->nbPairs; i++) {
2295 memcpy(&addressConfirmationContext.tagValuePairs[1 + i],
2296 &tagValueList->pairs[i],
2297 sizeof(nbgl_contentTagValue_t));
2298 addressConfirmationContext.nbPairs++;
2299 }
2300 // check how many can fit in a page
2301 memcpy(&tmpList, tagValueList, sizeof(nbgl_contentTagValueList_t));
2302 tmpList.nbPairs = addressConfirmationContext.nbPairs;
2303 tmpList.pairs = addressConfirmationContext.tagValuePairs;
2304 addressConfirmationContext.nbPairs = getNbTagValuesInPage(
2305 addressConfirmationContext.nbPairs, &tmpList, 0, false, true, true, &flag);
2306 // if they don't all fit, keep only the address
2307 if (tmpList.nbPairs > addressConfirmationContext.nbPairs) {
2308 addressConfirmationContext.nbPairs = 1;
2309 }
2310 }
2311 else {
2312 tagValueConfirm->detailsButtonText = NULL;
2313 }
2314 tagValueConfirm->detailsButtonToken = ADDRESS_QRCODE_BUTTON_TOKEN;
2315#else // NBGL_QRCODE
2316 tagValueConfirm->detailsButtonText = NULL;
2317 tagValueConfirm->detailsButtonIcon = NULL;
2318#endif // NBGL_QRCODE
2319 tagValueConfirm->tuneId = TUNE_TAP_CASUAL;
2320 tagValueConfirm->tagValueList.nbPairs = addressConfirmationContext.nbPairs;
2321 tagValueConfirm->tagValueList.pairs = addressConfirmationContext.tagValuePairs;
2322 tagValueConfirm->tagValueList.smallCaseForValue = false;
2323 tagValueConfirm->tagValueList.nbMaxLinesForValue = 0;
2324 tagValueConfirm->tagValueList.wrapping = false;
2325 // if it's an extended address verif, it takes 2 pages, so display a "Tap to continue", and
2326 // no confirmation button
2327 if ((tagValueList != NULL)
2328 && (tagValueList->nbPairs > (addressConfirmationContext.nbPairs - 1))) {
2329 tagValueConfirm->detailsButtonText = "Show as QR";
2330 tagValueConfirm->confirmationText = NULL;
2331 // the second page is dedicated to the extended tag/value pairs
2332 secondPageContent->type = TAG_VALUE_CONFIRM;
2333 tagValueConfirm = &secondPageContent->content.tagValueConfirm;
2334 tagValueConfirm->confirmationText = "Confirm";
2335 tagValueConfirm->confirmationToken = CONFIRM_TOKEN;
2336 tagValueConfirm->detailsButtonText = NULL;
2337 tagValueConfirm->detailsButtonIcon = NULL;
2338 tagValueConfirm->tuneId = TUNE_TAP_CASUAL;
2339 memcpy(&tagValueConfirm->tagValueList, tagValueList, sizeof(nbgl_contentTagValueList_t));
2340 tagValueConfirm->tagValueList.nbPairs
2341 = tagValueList->nbPairs - (addressConfirmationContext.nbPairs - 1);
2342 tagValueConfirm->tagValueList.pairs
2343 = &tagValueList->pairs[addressConfirmationContext.nbPairs - 1];
2344 }
2345 else {
2346 // otherwise no tap to continue but a confirmation button
2347 tagValueConfirm->confirmationText = "Confirm";
2348 tagValueConfirm->confirmationToken = CONFIRM_TOKEN;
2349 }
2350}
2351
2352static void bundleNavStartHome(void)
2353{
2354 nbgl_homeAndSettingsContext_t *context = &bundleNavContext.homeAndSettings;
2355
2356 useCaseHomeExt(context->appName,
2357 context->appIcon,
2358 context->tagline,
2359 context->settingContents != NULL ? true : false,
2360 &context->homeAction,
2361 bundleNavStartSettings,
2362 context->quitCallback);
2363}
2364
2365static void bundleNavStartSettingsAtPage(uint8_t initSettingPage)
2366{
2367 nbgl_homeAndSettingsContext_t *context = &bundleNavContext.homeAndSettings;
2368
2369 nbgl_useCaseGenericSettings(context->appName,
2370 initSettingPage,
2371 context->settingContents,
2372 context->infosList,
2373 bundleNavStartHome);
2374}
2375
2376static void bundleNavStartSettings(void)
2377{
2378 bundleNavStartSettingsAtPage(0);
2379}
2380
2381static void bundleNavReviewConfirmRejection(void)
2382{
2383 bundleNavContext.review.choiceCallback(false);
2384}
2385
2386static void bundleNavReviewAskRejectionConfirmation(nbgl_operationType_t operationType,
2387 nbgl_callback_t callback)
2388{
2389 const char *title;
2390 const char *confirmText;
2391 // clear skip and blind bits
2392 operationType
2393 &= ~(SKIPPABLE_OPERATION | BLIND_OPERATION | RISKY_OPERATION | NO_THREAT_OPERATION);
2394 if (operationType == TYPE_TRANSACTION) {
2395 title = "Reject transaction?";
2396 confirmText = "Go back to transaction";
2397 }
2398 else if (operationType == TYPE_MESSAGE) {
2399 title = "Reject message?";
2400 confirmText = "Go back to message";
2401 }
2402 else {
2403 title = "Reject operation?";
2404 confirmText = "Go back to operation";
2405 }
2406
2407 // display a choice to confirm/cancel rejection
2408 nbgl_useCaseConfirm(title, NULL, "Yes, reject", confirmText, callback);
2409}
2410
2411static void bundleNavReviewChoice(bool confirm)
2412{
2413 if (confirm) {
2414 bundleNavContext.review.choiceCallback(true);
2415 }
2416 else {
2417 bundleNavReviewAskRejectionConfirmation(bundleNavContext.review.operationType,
2418 bundleNavReviewConfirmRejection);
2419 }
2420}
2421
2422static void bundleNavReviewStreamingConfirmRejection(void)
2423{
2424 bundleNavContext.reviewStreaming.choiceCallback(false);
2425}
2426
2427static void bundleNavReviewStreamingChoice(bool confirm)
2428{
2429 if (confirm) {
2430 bundleNavContext.reviewStreaming.choiceCallback(true);
2431 }
2432 else {
2433 bundleNavReviewAskRejectionConfirmation(bundleNavContext.reviewStreaming.operationType,
2434 bundleNavReviewStreamingConfirmRejection);
2435 }
2436}
2437
2438// function used to display the details in modal (from Choice with details or from Customized
2439// security report) it can be either a single page with text or QR code, or a list of touchable bars
2440// leading to single pages
2441static nbgl_layout_t *displayModalDetails(const nbgl_warningDetails_t *details, uint8_t token)
2442{
2443 nbgl_layoutDescription_t layoutDescription = {.modal = true,
2444 .withLeftBorder = true,
2445 .onActionCallback = modalLayoutTouchCallback,
2446 .tapActionText = NULL};
2448 .separationLine = true,
2449 .backAndText.icon = NULL,
2450 .backAndText.tuneId = TUNE_TAP_CASUAL,
2451 .backAndText.token = token};
2452 uint8_t i;
2453 nbgl_layout_t *layout;
2454
2455 layout = nbgl_layoutGet(&layoutDescription);
2456 headerDesc.backAndText.text = details->title;
2457 nbgl_layoutAddHeader(layout, &headerDesc);
2458 if (details->type == BAR_LIST_WARNING) {
2459 // if more than one warning, so use a list of touchable bars
2460 for (i = 0; i < details->barList.nbBars; i++) {
2461 nbgl_layoutBar_t bar;
2462 bar.text = details->barList.texts[i];
2463 bar.subText = details->barList.subTexts[i];
2464 bar.iconRight
2465 = (details->barList.details[i].type != NO_TYPE_WARNING) ? &PUSH_ICON : NULL;
2466 bar.iconLeft = details->barList.icons[i];
2467 bar.token = FIRST_WARN_BAR_TOKEN + i;
2468 bar.tuneId = TUNE_TAP_CASUAL;
2469 bar.large = false;
2470 bar.inactive = false;
2471 nbgl_layoutAddTouchableBar(layout, &bar);
2473 }
2474 }
2475 else if (details->type == QRCODE_WARNING) {
2476#ifdef NBGL_QRCODE
2477 // display a QR Code
2478 nbgl_layoutAddQRCode(layout, &details->qrCode);
2479#endif // NBGL_QRCODE
2480 headerDesc.backAndText.text = details->title;
2481 }
2482 else if (details->type == CENTERED_INFO_WARNING) {
2483 // display a centered info
2484 nbgl_layoutAddContentCenter(layout, &details->centeredInfo);
2485 headerDesc.separationLine = false;
2486 }
2487 nbgl_layoutDraw(layout);
2488 nbgl_refresh();
2489 return layout;
2490}
2491
2492// function used to display the security level page in modal
2493// it can be either a single page with text or QR code, or a list of touchable bars leading
2494// to single pages
2495static void displaySecurityReport(uint32_t set)
2496{
2497 nbgl_layoutDescription_t layoutDescription = {.modal = true,
2498 .withLeftBorder = true,
2499 .onActionCallback = modalLayoutTouchCallback,
2500 .tapActionText = NULL};
2502 .separationLine = true,
2503 .backAndText.icon = NULL,
2504 .backAndText.tuneId = TUNE_TAP_CASUAL,
2505 .backAndText.token = DISMISS_WARNING_TOKEN};
2506 nbgl_layoutFooter_t footerDesc
2507 = {.type = FOOTER_EMPTY, .separationLine = false, .emptySpace.height = 0};
2508 uint8_t i;
2509 const char *provider;
2510
2511 reviewWithWarnCtx.modalLayout = nbgl_layoutGet(&layoutDescription);
2512
2513 // display a list of touchable bars in some conditions:
2514 // - we must be in the first level of security report
2515 // - and be in the review itself (not from the intro/warning screen)
2516 //
2517 if ((reviewWithWarnCtx.securityReportLevel == 1) && (!reviewWithWarnCtx.isIntro)) {
2518 for (i = 0; i < NB_WARNING_TYPES; i++) {
2519 // Skip GATED_SIGNING_WARN from security report bars
2520 if ((i != GATED_SIGNING_WARN)
2521 && (reviewWithWarnCtx.warning->predefinedSet & (1 << i))) {
2522 nbgl_layoutBar_t bar = {0};
2523 if ((i == BLIND_SIGNING_WARN) || (i == W3C_NO_THREAT_WARN) || (i == W3C_ISSUE_WARN)
2524 || (reviewWithWarnCtx.warning->providerMessage == NULL)) {
2525 bar.subText = securityReportItems[i].subText;
2526 }
2527 else {
2528 bar.subText = reviewWithWarnCtx.warning->providerMessage;
2529 }
2530 bar.text = securityReportItems[i].text;
2531 if (i != W3C_NO_THREAT_WARN) {
2532 bar.iconRight = &PUSH_ICON;
2533 bar.token = FIRST_WARN_BAR_TOKEN + i;
2534 bar.tuneId = TUNE_TAP_CASUAL;
2535 }
2536 else {
2538 }
2539 bar.iconLeft = securityReportItems[i].icon;
2540 nbgl_layoutAddTouchableBar(reviewWithWarnCtx.modalLayout, &bar);
2541 nbgl_layoutAddSeparationLine(reviewWithWarnCtx.modalLayout);
2542 }
2543 }
2544 headerDesc.backAndText.text = "Security report";
2545 nbgl_layoutAddHeader(reviewWithWarnCtx.modalLayout, &headerDesc);
2546 nbgl_layoutDraw(reviewWithWarnCtx.modalLayout);
2547 nbgl_refresh();
2548 return;
2549 }
2550 if (reviewWithWarnCtx.warning && reviewWithWarnCtx.warning->reportProvider) {
2551 provider = reviewWithWarnCtx.warning->reportProvider;
2552 }
2553 else {
2554 provider = "[unknown]";
2555 }
2556 if ((set & (1 << W3C_THREAT_DETECTED_WARN)) || (set & (1 << W3C_RISK_DETECTED_WARN))) {
2557 size_t urlLen = 0;
2558 char *destStr
2559 = tmpString
2560 + W3C_DESCRIPTION_MAX_LEN / 2; // use the second half of tmpString for strings
2561#ifdef NBGL_QRCODE
2562 // display a QR Code
2563 nbgl_layoutQRCode_t qrCode = {.url = destStr,
2564 .text1 = reviewWithWarnCtx.warning->reportUrl,
2565 .text2 = "Scan to view full report",
2566 .centered = true,
2567 .offsetY = 0};
2568 // add "https://"" as prefix of the given URL
2569 snprintf(destStr,
2570 W3C_DESCRIPTION_MAX_LEN / 2,
2571 "https://%s",
2572 reviewWithWarnCtx.warning->reportUrl);
2573 urlLen = strlen(destStr) + 1;
2574 nbgl_layoutAddQRCode(reviewWithWarnCtx.modalLayout, &qrCode);
2575 footerDesc.emptySpace.height = 24;
2576#endif // NBGL_QRCODE
2577 // use the next part of destStr for back bar text
2578 snprintf(destStr + urlLen, W3C_DESCRIPTION_MAX_LEN / 2 - urlLen, "%s report", provider);
2579 headerDesc.backAndText.text = destStr + urlLen;
2580 }
2581 else if (set & (1 << BLIND_SIGNING_WARN)) {
2582 // display a centered if in review
2583 nbgl_contentCenter_t info = {0};
2584 info.icon = &LARGE_WARNING_ICON;
2585 info.title = "This transaction cannot be Clear Signed";
2586 info.description
2587 = "This transaction or message cannot be decoded fully. If you choose to sign, you "
2588 "could be authorizing malicious actions that can drain your wallet.\n\nLearn more: "
2589 "ledger.com/e8";
2590 nbgl_layoutAddContentCenter(reviewWithWarnCtx.modalLayout, &info);
2591 footerDesc.emptySpace.height = SMALL_CENTERING_HEADER;
2592 headerDesc.separationLine = false;
2593 }
2594 else if (set & (1 << W3C_ISSUE_WARN)) {
2595 // if W3 Checks issue, display a centered info
2596 nbgl_contentCenter_t info = {0};
2597 info.icon = &LARGE_WARNING_ICON;
2598 info.title = "Transaction Check unavailable";
2599 info.description
2600 = "If you're not using the Ledger Wallet app, Transaction Check might not work. If "
2601 "you "
2602 "are using Ledger Wallet, reject the transaction and try again.\n\nGet help at "
2603 "ledger.com/e11";
2604 nbgl_layoutAddContentCenter(reviewWithWarnCtx.modalLayout, &info);
2605 footerDesc.emptySpace.height = SMALL_CENTERING_HEADER;
2606 headerDesc.separationLine = false;
2607 }
2608 nbgl_layoutAddHeader(reviewWithWarnCtx.modalLayout, &headerDesc);
2609 if (footerDesc.emptySpace.height > 0) {
2610 nbgl_layoutAddExtendedFooter(reviewWithWarnCtx.modalLayout, &footerDesc);
2611 }
2612 nbgl_layoutDraw(reviewWithWarnCtx.modalLayout);
2613 nbgl_refresh();
2614}
2615
2616// function used to display the security level page in modal
2617// it can be either a single page with text or QR code, or a list of touchable bars leading
2618// to single pages
2619static void displayCustomizedSecurityReport(const nbgl_warningDetails_t *details)
2620{
2621 reviewWithWarnCtx.modalLayout = displayModalDetails(details, DISMISS_WARNING_TOKEN);
2622}
2623
2624// function used to display the initial warning page when starting a "review with warning"
2625static void displayInitialWarning(void)
2626{
2627 // Play notification sound
2628#ifdef HAVE_PIEZO_SOUND
2629 tune_index_e tune = TUNE_RESERVED;
2630#endif // HAVE_PIEZO_SOUND
2631 nbgl_layoutDescription_t layoutDescription;
2632 nbgl_layoutChoiceButtons_t buttonsInfo = {.bottomText = "Continue anyway",
2633 .token = WARNING_CHOICE_TOKEN,
2634 .topText = "Back to safety",
2635 .style = ROUNDED_AND_FOOTER_STYLE,
2636 .tuneId = TUNE_TAP_CASUAL};
2637 nbgl_layoutHeader_t headerDesc = {.type = HEADER_EMPTY,
2638 .separationLine = false,
2639 .emptySpace.height = MEDIUM_CENTERING_HEADER};
2640 uint32_t set = reviewWithWarnCtx.warning->predefinedSet
2641 & ~((1 << W3C_NO_THREAT_WARN) | (1 << W3C_ISSUE_WARN));
2642
2643 bool isBlindSigningOnly
2644 = (set != 0) && ((set & ~((1 << BLIND_SIGNING_WARN) | (1 << GATED_SIGNING_WARN))) == 0);
2645 reviewWithWarnCtx.isIntro = true;
2646
2647 layoutDescription.modal = false;
2648 layoutDescription.withLeftBorder = true;
2649
2650 layoutDescription.onActionCallback = layoutTouchCallback;
2651 layoutDescription.tapActionText = NULL;
2652
2653 layoutDescription.ticker.tickerCallback = NULL;
2654 reviewWithWarnCtx.layoutCtx = nbgl_layoutGet(&layoutDescription);
2655
2656 nbgl_layoutAddHeader(reviewWithWarnCtx.layoutCtx, &headerDesc);
2657 if (reviewWithWarnCtx.warning->predefinedSet != 0) {
2658 // icon is different in casee of Blind Siging or Gated Signing
2659 nbgl_layoutAddTopRightButton(reviewWithWarnCtx.layoutCtx,
2660 isBlindSigningOnly ? &INFO_I_ICON : &QRCODE_ICON,
2661 WARNING_BUTTON_TOKEN,
2662 TUNE_TAP_CASUAL);
2663 }
2664 else if (reviewWithWarnCtx.warning->introTopRightIcon != NULL) {
2665 nbgl_layoutAddTopRightButton(reviewWithWarnCtx.layoutCtx,
2666 reviewWithWarnCtx.warning->introTopRightIcon,
2667 WARNING_BUTTON_TOKEN,
2668 TUNE_TAP_CASUAL);
2669 }
2670 // add main content
2671 // if predefined content is configured, use it preferably
2672 if (reviewWithWarnCtx.warning->predefinedSet != 0) {
2673 nbgl_contentCenter_t info = {0};
2674
2675 const char *provider;
2676
2677 // default icon
2678 info.icon = &LARGE_WARNING_ICON;
2679
2680 // use small title only if not Blind signing and not Gated signing
2681 if (isBlindSigningOnly == false) {
2682 if (reviewWithWarnCtx.warning->reportProvider) {
2683 provider = reviewWithWarnCtx.warning->reportProvider;
2684 }
2685 else {
2686 provider = "[unknown]";
2687 }
2688 info.smallTitle = tmpString;
2689 snprintf(tmpString, W3C_DESCRIPTION_MAX_LEN, "Detected by %s", provider);
2690 }
2691
2692 // If Blind Signing or Gated Signing
2693 if (isBlindSigningOnly) {
2694 info.title = "Blind signing ahead";
2695 if (set & (1 << GATED_SIGNING_WARN)) {
2696 info.description
2697 = "If you sign this transaction, you could loose your assets. "
2698 "Explore safer alternatives: ledger.com/integrated-apps";
2699 buttonsInfo.bottomText = "Accept risk and continue";
2700 }
2701 else {
2702 info.description
2703 = "This transaction's details are not fully verifiable. If you sign, you could "
2704 "lose all your assets.";
2705 buttonsInfo.bottomText = "Continue anyway";
2706 }
2707#ifdef HAVE_PIEZO_SOUND
2708 tune = TUNE_NEUTRAL;
2709#endif // HAVE_PIEZO_SOUND
2710 }
2711 else if (set & (1 << W3C_RISK_DETECTED_WARN)) {
2712 info.title = "Potential risk";
2713 if (reviewWithWarnCtx.warning->providerMessage == NULL) {
2714 info.description = "Unidentified risk";
2715 }
2716 else {
2717 info.description = reviewWithWarnCtx.warning->providerMessage;
2718 }
2719 buttonsInfo.bottomText = "Accept risk and continue";
2720#ifdef HAVE_PIEZO_SOUND
2721 tune = TUNE_NEUTRAL;
2722#endif // HAVE_PIEZO_SOUND
2723 }
2724 else if (set & (1 << W3C_THREAT_DETECTED_WARN)) {
2725 info.title = "Critical threat";
2726 if (reviewWithWarnCtx.warning->providerMessage == NULL) {
2727 info.description = "Unidentified threat";
2728 }
2729 else {
2730 info.description = reviewWithWarnCtx.warning->providerMessage;
2731 }
2732 buttonsInfo.bottomText = "Accept threat and continue";
2733#ifdef HAVE_PIEZO_SOUND
2734 tune = TUNE_ERROR;
2735#endif // HAVE_PIEZO_SOUND
2736 }
2737 nbgl_layoutAddContentCenter(reviewWithWarnCtx.layoutCtx, &info);
2738 }
2739 else if (reviewWithWarnCtx.warning->info != NULL) {
2740 // if no predefined content, use custom one
2741 nbgl_layoutAddContentCenter(reviewWithWarnCtx.layoutCtx, reviewWithWarnCtx.warning->info);
2742#ifdef HAVE_PIEZO_SOUND
2743 tune = TUNE_LOOK_AT_ME;
2744#endif // HAVE_PIEZO_SOUND
2745 }
2746 // add button and footer on bottom
2747 nbgl_layoutAddChoiceButtons(reviewWithWarnCtx.layoutCtx, &buttonsInfo);
2748
2749#ifdef HAVE_PIEZO_SOUND
2750 if (tune != TUNE_RESERVED) {
2751 os_io_seph_cmd_piezo_play_tune(tune);
2752 }
2753#endif // HAVE_PIEZO_SOUND
2754 nbgl_layoutDraw(reviewWithWarnCtx.layoutCtx);
2755 nbgl_refresh();
2756}
2757
2758// function used to display the prelude when starting a "review with warning"
2759static void displayPrelude(void)
2760{
2761 nbgl_layoutDescription_t layoutDescription;
2762 nbgl_layoutChoiceButtons_t buttonsInfo
2763 = {.bottomText = reviewWithWarnCtx.warning->prelude->footerText,
2764 .token = PRELUDE_CHOICE_TOKEN,
2765 .topText = reviewWithWarnCtx.warning->prelude->buttonText,
2766 .style = ROUNDED_AND_FOOTER_STYLE,
2767 .tuneId = TUNE_TAP_CASUAL};
2768 nbgl_layoutHeader_t headerDesc = {.type = HEADER_EMPTY,
2769 .separationLine = false,
2770 .emptySpace.height = MEDIUM_CENTERING_HEADER};
2771
2772 reviewWithWarnCtx.isIntro = true;
2773
2774 layoutDescription.modal = false;
2775 layoutDescription.withLeftBorder = true;
2776 layoutDescription.onActionCallback = layoutTouchCallback;
2777 layoutDescription.tapActionText = NULL;
2778 layoutDescription.ticker.tickerCallback = NULL;
2779 reviewWithWarnCtx.layoutCtx = nbgl_layoutGet(&layoutDescription);
2780
2781 nbgl_layoutAddHeader(reviewWithWarnCtx.layoutCtx, &headerDesc);
2782 // add centered content
2783 nbgl_contentCenter_t info = {0};
2784
2785 // default icon
2786 info.icon = reviewWithWarnCtx.warning->prelude->icon;
2787
2788 info.title = reviewWithWarnCtx.warning->prelude->title;
2789 info.description = reviewWithWarnCtx.warning->prelude->description;
2790 nbgl_layoutAddContentCenter(reviewWithWarnCtx.layoutCtx, &info);
2791
2792 // add button and footer on bottom
2793 nbgl_layoutAddChoiceButtons(reviewWithWarnCtx.layoutCtx, &buttonsInfo);
2794
2795#ifdef HAVE_PIEZO_SOUND
2796 os_io_seph_cmd_piezo_play_tune(TUNE_LOOK_AT_ME);
2797#endif // HAVE_PIEZO_SOUND
2798 nbgl_layoutDraw(reviewWithWarnCtx.layoutCtx);
2799 nbgl_refresh();
2800}
2801
2802// function to factorize code for reviews tipbox
2803static void initWarningTipBox(const nbgl_tipBox_t *tipBox)
2804{
2805 const char *predefinedTipBoxText = NULL;
2806
2807 // if warning is valid and a warning requires a tip-box
2808 if (reviewWithWarnCtx.warning) {
2809 if (reviewWithWarnCtx.warning->predefinedSet & (1 << W3C_ISSUE_WARN)) {
2810 if (reviewWithWarnCtx.warning->predefinedSet & (1 << BLIND_SIGNING_WARN)) {
2811 predefinedTipBoxText = "Transaction Check unavailable.\nBlind signing required.";
2812 }
2813 else {
2814 predefinedTipBoxText = "Transaction Check unavailable";
2815 }
2816 }
2817 else if (reviewWithWarnCtx.warning->predefinedSet & (1 << W3C_THREAT_DETECTED_WARN)) {
2818 if (reviewWithWarnCtx.warning->predefinedSet & (1 << BLIND_SIGNING_WARN)) {
2819 predefinedTipBoxText = "Critical threat detected.\nBlind signing required.";
2820 }
2821 else {
2822 predefinedTipBoxText = "Critical threat detected.";
2823 }
2824 }
2825 else if (reviewWithWarnCtx.warning->predefinedSet & (1 << W3C_RISK_DETECTED_WARN)) {
2826 if (reviewWithWarnCtx.warning->predefinedSet & (1 << BLIND_SIGNING_WARN)) {
2827 predefinedTipBoxText = "Potential risk detected.\nBlind signing required.";
2828 }
2829 else {
2830 predefinedTipBoxText = "Potential risk detected.";
2831 }
2832 }
2833 else if (reviewWithWarnCtx.warning->predefinedSet & (1 << W3C_NO_THREAT_WARN)) {
2834 if (reviewWithWarnCtx.warning->predefinedSet & (1 << BLIND_SIGNING_WARN)) {
2835 predefinedTipBoxText
2836 = "No threat detected by Transaction Check but blind signing required.";
2837 }
2838 else {
2839 predefinedTipBoxText = "No threat detected by Transaction Check.";
2840 }
2841 }
2842 else if (reviewWithWarnCtx.warning->predefinedSet & (1 << BLIND_SIGNING_WARN)) {
2843 predefinedTipBoxText = "Blind signing required.";
2844 }
2845 }
2846
2847 if ((tipBox != NULL) || (predefinedTipBoxText != NULL)) {
2848 // do not display "Swipe to review" if a tip-box is displayed
2849 STARTING_CONTENT.content.extendedCenter.contentCenter.subText = NULL;
2850 if (predefinedTipBoxText != NULL) {
2851 genericContext.validWarningCtx = true;
2852 STARTING_CONTENT.content.extendedCenter.tipBox.icon = NULL;
2853 STARTING_CONTENT.content.extendedCenter.tipBox.text = predefinedTipBoxText;
2854 }
2855 else {
2856 STARTING_CONTENT.content.extendedCenter.tipBox.icon = tipBox->icon;
2857 STARTING_CONTENT.content.extendedCenter.tipBox.text = tipBox->text;
2858 }
2859 STARTING_CONTENT.content.extendedCenter.tipBox.token = TIP_BOX_TOKEN;
2860 STARTING_CONTENT.content.extendedCenter.tipBox.tuneId = TUNE_TAP_CASUAL;
2861 }
2862}
2863
2864// function to factorize code for all simple reviews
2865static void useCaseReview(nbgl_operationType_t operationType,
2866 const nbgl_contentTagValueList_t *tagValueList,
2867 const nbgl_icon_details_t *icon,
2868 const char *reviewTitle,
2869 const char *reviewSubTitle,
2870 const char *finishTitle,
2871 const nbgl_tipBox_t *tipBox,
2872 nbgl_choiceCallback_t choiceCallback,
2873 bool isLight,
2874 bool playNotifSound)
2875{
2876 bundleNavContext.review.operationType = operationType;
2877 bundleNavContext.review.choiceCallback = choiceCallback;
2878
2879 // memorize context
2880 onChoice = bundleNavReviewChoice;
2881 navType = GENERIC_NAV;
2882 pageTitle = NULL;
2883
2884 genericContext.genericContents.contentsList = localContentsList;
2885 genericContext.genericContents.nbContents = 3;
2886 memset(localContentsList, 0, 3 * sizeof(nbgl_content_t));
2887
2888 // First a centered info
2889 STARTING_CONTENT.type = EXTENDED_CENTER;
2890 prepareReviewFirstPage(
2891 &STARTING_CONTENT.content.extendedCenter.contentCenter, icon, reviewTitle, reviewSubTitle);
2892
2893 // Prepare un tipbox if needed
2894 initWarningTipBox(tipBox);
2895
2896 // Then the tag/value pairs
2897 localContentsList[1].type = TAG_VALUE_LIST;
2898 memcpy(&localContentsList[1].content.tagValueList,
2899 tagValueList,
2901 localContentsList[1].contentActionCallback = tagValueList->actionCallback;
2902
2903 // The last page
2904 if (isLight) {
2905 localContentsList[2].type = INFO_BUTTON;
2906 prepareReviewLightLastPage(
2907 operationType, &localContentsList[2].content.infoButton, icon, finishTitle);
2908 }
2909 else {
2910 localContentsList[2].type = INFO_LONG_PRESS;
2911 prepareReviewLastPage(
2912 operationType, &localContentsList[2].content.infoLongPress, icon, finishTitle);
2913 }
2914
2915 // compute number of pages & fill navigation structure
2916 uint8_t nbPages = getNbPagesForGenericContents(
2917 &genericContext.genericContents, 0, (operationType & SKIPPABLE_OPERATION));
2918 prepareNavInfo(true, nbPages, getRejectReviewText(operationType));
2919
2920 // Play notification sound if required
2921 if (playNotifSound) {
2922#ifdef HAVE_PIEZO_SOUND
2923 os_io_seph_cmd_piezo_play_tune(TUNE_LOOK_AT_ME);
2924#endif // HAVE_PIEZO_SOUND
2925 }
2926
2927 displayGenericContextPage(0, true);
2928}
2929
2930// function to factorize code for all streaming reviews
2931static void useCaseReviewStreamingStart(nbgl_operationType_t operationType,
2932 const nbgl_icon_details_t *icon,
2933 const char *reviewTitle,
2934 const char *reviewSubTitle,
2935 nbgl_choiceCallback_t choiceCallback,
2936 bool playNotifSound)
2937{
2938 bundleNavContext.reviewStreaming.operationType = operationType;
2939 bundleNavContext.reviewStreaming.choiceCallback = choiceCallback;
2940 bundleNavContext.reviewStreaming.icon = icon;
2941
2942 // memorize context
2943 onChoice = bundleNavReviewStreamingChoice;
2944 navType = STREAMING_NAV;
2945 pageTitle = NULL;
2946
2947 genericContext.genericContents.contentsList = localContentsList;
2948 genericContext.genericContents.nbContents = 1;
2949 memset(localContentsList, 0, 1 * sizeof(nbgl_content_t));
2950
2951 // First a centered info
2952 STARTING_CONTENT.type = EXTENDED_CENTER;
2953 prepareReviewFirstPage(
2954 &STARTING_CONTENT.content.extendedCenter.contentCenter, icon, reviewTitle, reviewSubTitle);
2955
2956 // Prepare un tipbox if needed
2957 initWarningTipBox(NULL);
2958
2959 // compute number of pages & fill navigation structure
2960 bundleNavContext.reviewStreaming.stepPageNb = getNbPagesForGenericContents(
2961 &genericContext.genericContents, 0, (operationType & SKIPPABLE_OPERATION));
2962 prepareNavInfo(true, NBGL_NO_PROGRESS_INDICATOR, getRejectReviewText(operationType));
2963 // no back button on first page
2964 navInfo.navWithButtons.backButton = false;
2965
2966 // Play notification sound if required
2967 if (playNotifSound) {
2968#ifdef HAVE_PIEZO_SOUND
2969 os_io_seph_cmd_piezo_play_tune(TUNE_LOOK_AT_ME);
2970#endif // HAVE_PIEZO_SOUND
2971 }
2972
2973 displayGenericContextPage(0, true);
2974}
2975
2992static void useCaseHomeExt(const char *appName,
2993 const nbgl_icon_details_t *appIcon,
2994 const char *tagline,
2995 bool withSettings,
2996 nbgl_homeAction_t *homeAction,
2997 nbgl_callback_t topRightCallback,
2998 nbgl_callback_t quitCallback)
2999{
3000 nbgl_pageInfoDescription_t info = {.centeredInfo.icon = appIcon,
3001 .centeredInfo.text1 = appName,
3002 .centeredInfo.text3 = NULL,
3003 .centeredInfo.style = LARGE_CASE_INFO,
3004 .centeredInfo.offsetY = 0,
3005 .footerText = NULL,
3006 .bottomButtonStyle = QUIT_APP_TEXT,
3007 .tapActionText = NULL,
3008 .topRightStyle = withSettings ? SETTINGS_ICON : INFO_ICON,
3009 .topRightToken = CONTINUE_TOKEN,
3010 .tuneId = TUNE_TAP_CASUAL};
3011 reset_callbacks_and_context();
3012
3013 if ((homeAction->text != NULL) || (homeAction->icon != NULL)) {
3014 // trick to use ACTION_BUTTON_TOKEN for action and quit, with index used to distinguish
3015 info.bottomButtonsToken = ACTION_BUTTON_TOKEN;
3016 onAction = homeAction->callback;
3017 info.actionButtonText = homeAction->text;
3018 info.actionButtonIcon = homeAction->icon;
3021 }
3022 else {
3023 info.bottomButtonsToken = QUIT_TOKEN;
3024 onAction = NULL;
3025 info.actionButtonText = NULL;
3026 info.actionButtonIcon = NULL;
3027 }
3028 if (tagline == NULL) {
3029 if (strlen(appName) > MAX_APP_NAME_FOR_SDK_TAGLINE) {
3030 snprintf(tmpString,
3032 "This app enables signing\ntransactions on its network.");
3033 }
3034 else {
3035 snprintf(tmpString,
3037 "%s %s\n%s",
3039 appName,
3041 }
3042
3043 // If there is more than 3 lines, it means the appName was split, so we put it on the next
3044 // line
3045 if (nbgl_getTextNbLinesInWidth(SMALL_REGULAR_FONT, tmpString, AVAILABLE_WIDTH, false) > 3) {
3046 snprintf(tmpString,
3048 "%s\n%s %s",
3050 appName,
3052 }
3053 info.centeredInfo.text2 = tmpString;
3054 }
3055 else {
3056 info.centeredInfo.text2 = tagline;
3057 }
3058
3059 onContinue = topRightCallback;
3060 onQuit = quitCallback;
3061
3062 pageContext = nbgl_pageDrawInfo(&pageCallback, NULL, &info);
3064}
3065
3074static void displayDetails(const char *tag, const char *value, bool wrapping)
3075{
3076 memset(&detailsContext, 0, sizeof(detailsContext));
3077
3078 uint16_t nbLines
3079 = nbgl_getTextNbLinesInWidth(SMALL_REGULAR_FONT, value, AVAILABLE_WIDTH, wrapping);
3080
3081 // initialize context
3082 detailsContext.tag = tag;
3083 detailsContext.value = value;
3084 detailsContext.nbPages = (nbLines + NB_MAX_LINES_IN_DETAILS - 1) / NB_MAX_LINES_IN_DETAILS;
3085 detailsContext.currentPage = 0;
3086 detailsContext.wrapping = wrapping;
3087 // add some spare for room lost with "..." substitution
3088 if (detailsContext.nbPages > 1) {
3089 uint16_t nbLostChars = (detailsContext.nbPages - 1) * 3;
3090 uint16_t nbLostLines = (nbLostChars + ((AVAILABLE_WIDTH) / 16) - 1)
3091 / ((AVAILABLE_WIDTH) / 16); // 16 for average char width
3092 uint8_t nbLinesInLastPage
3093 = nbLines - ((detailsContext.nbPages - 1) * NB_MAX_LINES_IN_DETAILS);
3094
3095 detailsContext.nbPages += nbLostLines / NB_MAX_LINES_IN_DETAILS;
3096 if ((nbLinesInLastPage + (nbLostLines % NB_MAX_LINES_IN_DETAILS))
3098 detailsContext.nbPages++;
3099 }
3100 }
3101
3102 displayDetailsPage(0, true);
3103}
3104
3105// function used to display the modal containing alias tag-value pairs
3106static void displayTagValueListModal(const nbgl_contentTagValueList_t *tagValues)
3107{
3108 uint8_t nbElements = 0;
3109 uint8_t nbElementsInPage;
3110 uint8_t elemIdx = 0;
3111
3112 // initialize context
3113 memset(&detailsContext, 0, sizeof(detailsContext));
3114 nbElements = tagValues->nbPairs;
3115
3116 while (nbElements > 0) {
3117 nbElementsInPage = getNbTagValuesInDetailsPage(nbElements, tagValues, elemIdx);
3118
3119 elemIdx += nbElementsInPage;
3120 modalContextSetPageInfo(detailsContext.nbPages, nbElementsInPage);
3121 nbElements -= nbElementsInPage;
3122 detailsContext.nbPages++;
3123 }
3124
3125 displayTagValueListModalPage(0, true);
3126}
3127
3128/**********************
3129 * GLOBAL FUNCTIONS
3130 **********************/
3131
3144uint8_t nbgl_useCaseGetNbTagValuesInPage(uint8_t nbPairs,
3145 const nbgl_contentTagValueList_t *tagValueList,
3146 uint8_t startIndex,
3147 bool *requireSpecificDisplay)
3148{
3149 return getNbTagValuesInPage(
3150 nbPairs, tagValueList, startIndex, false, false, false, requireSpecificDisplay);
3151}
3152
3166uint8_t nbgl_useCaseGetNbTagValuesInPageExt(uint8_t nbPairs,
3167 const nbgl_contentTagValueList_t *tagValueList,
3168 uint8_t startIndex,
3169 bool isSkippable,
3170 bool *requireSpecificDisplay)
3171{
3172 return getNbTagValuesInPage(
3173 nbPairs, tagValueList, startIndex, isSkippable, false, false, requireSpecificDisplay);
3174}
3175
3185uint8_t nbgl_useCaseGetNbInfosInPage(uint8_t nbInfos,
3186 const nbgl_contentInfoList_t *infosList,
3187 uint8_t startIndex,
3188 bool withNav)
3189{
3190 uint8_t nbInfosInPage = 0;
3191 uint16_t currentHeight = 0;
3192 uint16_t previousHeight;
3193 uint16_t navHeight = withNav ? SIMPLE_FOOTER_HEIGHT : 0;
3194 const char *const *infoContents = PIC(infosList->infoContents);
3195
3196 while (nbInfosInPage < nbInfos) {
3197 // The type string must be a 1 liner and its height is LIST_ITEM_MIN_TEXT_HEIGHT
3198 currentHeight
3199 += LIST_ITEM_MIN_TEXT_HEIGHT + 2 * LIST_ITEM_PRE_HEADING + LIST_ITEM_HEADING_SUB_TEXT;
3200
3201 // content height
3202 currentHeight += nbgl_getTextHeightInWidth(SMALL_REGULAR_FONT,
3203 PIC(infoContents[startIndex + nbInfosInPage]),
3205 true);
3206 // if height is over the limit
3207 if (currentHeight >= (INFOS_AREA_HEIGHT - navHeight)) {
3208 // if there was no nav, now there will be, so it can be necessary to remove the last
3209 // item
3210 if (!withNav && (previousHeight >= (INFOS_AREA_HEIGHT - SIMPLE_FOOTER_HEIGHT))) {
3211 nbInfosInPage--;
3212 }
3213 break;
3214 }
3215 previousHeight = currentHeight;
3216 nbInfosInPage++;
3217 }
3218 return nbInfosInPage;
3219}
3220
3230uint8_t nbgl_useCaseGetNbSwitchesInPage(uint8_t nbSwitches,
3231 const nbgl_contentSwitchesList_t *switchesList,
3232 uint8_t startIndex,
3233 bool withNav)
3234{
3235 uint8_t nbSwitchesInPage = 0;
3236 uint16_t currentHeight = 0;
3237 uint16_t previousHeight = 0;
3238 uint16_t navHeight = withNav ? SIMPLE_FOOTER_HEIGHT : 0;
3239 nbgl_contentSwitch_t *switchArray = (nbgl_contentSwitch_t *) PIC(switchesList->switches);
3240
3241 while (nbSwitchesInPage < nbSwitches) {
3242 nbgl_contentSwitch_t *curSwitch = &switchArray[startIndex + nbSwitchesInPage];
3243 // The text string is either a 1 liner and its height is LIST_ITEM_MIN_TEXT_HEIGHT
3244 // or we use its height directly
3245 uint16_t textHeight = MAX(
3246 LIST_ITEM_MIN_TEXT_HEIGHT,
3247 nbgl_getTextHeightInWidth(SMALL_BOLD_FONT, curSwitch->text, AVAILABLE_WIDTH, true));
3248 currentHeight += textHeight + 2 * LIST_ITEM_PRE_HEADING;
3249
3250 if (curSwitch->subText) {
3251 currentHeight += LIST_ITEM_HEADING_SUB_TEXT;
3252
3253 // sub-text height
3254 currentHeight += nbgl_getTextHeightInWidth(
3255 SMALL_REGULAR_FONT, curSwitch->subText, AVAILABLE_WIDTH, true);
3256 }
3257 // if height is over the limit
3258 if (currentHeight >= (INFOS_AREA_HEIGHT - navHeight)) {
3259 break;
3260 }
3261 previousHeight = currentHeight;
3262 nbSwitchesInPage++;
3263 }
3264 // if there was no nav, now there will be, so it can be necessary to remove the last
3265 // item
3266 if (!withNav && (previousHeight >= (INFOS_AREA_HEIGHT - SIMPLE_FOOTER_HEIGHT))) {
3267 nbSwitchesInPage--;
3268 }
3269 return nbSwitchesInPage;
3270}
3271
3281uint8_t nbgl_useCaseGetNbBarsInPage(uint8_t nbBars,
3282 const nbgl_contentBarsList_t *barsList,
3283 uint8_t startIndex,
3284 bool withNav)
3285{
3286 uint8_t nbBarsInPage = 0;
3287 uint16_t currentHeight = 0;
3288 uint16_t previousHeight;
3289 uint16_t navHeight = withNav ? SIMPLE_FOOTER_HEIGHT : 0;
3290
3291 UNUSED(barsList);
3292 UNUSED(startIndex);
3293
3294 while (nbBarsInPage < nbBars) {
3295 currentHeight += LIST_ITEM_MIN_TEXT_HEIGHT + 2 * LIST_ITEM_PRE_HEADING;
3296 // if height is over the limit
3297 if (currentHeight >= (INFOS_AREA_HEIGHT - navHeight)) {
3298 break;
3299 }
3300 previousHeight = currentHeight;
3301 nbBarsInPage++;
3302 }
3303 // if there was no nav, now there may will be, so it can be necessary to remove the last
3304 // item
3305 if (!withNav && (previousHeight >= (INFOS_AREA_HEIGHT - SIMPLE_FOOTER_HEIGHT))) {
3306 nbBarsInPage--;
3307 }
3308 return nbBarsInPage;
3309}
3310
3320uint8_t nbgl_useCaseGetNbChoicesInPage(uint8_t nbChoices,
3321 const nbgl_contentRadioChoice_t *choicesList,
3322 uint8_t startIndex,
3323 bool withNav)
3324{
3325 uint8_t nbChoicesInPage = 0;
3326 uint16_t currentHeight = 0;
3327 uint16_t previousHeight;
3328 uint16_t navHeight = withNav ? SIMPLE_FOOTER_HEIGHT : 0;
3329
3330 UNUSED(choicesList);
3331 UNUSED(startIndex);
3332
3333 while (nbChoicesInPage < nbChoices) {
3334 currentHeight += LIST_ITEM_MIN_TEXT_HEIGHT + 2 * LIST_ITEM_PRE_HEADING;
3335 // if height is over the limit
3336 if (currentHeight >= (INFOS_AREA_HEIGHT - navHeight)) {
3337 // if there was no nav, now there will be, so it can be necessary to remove the last
3338 // item
3339 if (!withNav && (previousHeight >= (INFOS_AREA_HEIGHT - SIMPLE_FOOTER_HEIGHT))) {
3340 nbChoicesInPage--;
3341 }
3342 break;
3343 }
3344 previousHeight = currentHeight;
3345 nbChoicesInPage++;
3346 }
3347 return nbChoicesInPage;
3348}
3349
3357{
3358 uint8_t nbPages = 0;
3359 uint8_t nbPairs = tagValueList->nbPairs;
3360 uint8_t nbPairsInPage;
3361 uint8_t i = 0;
3362 bool flag;
3363
3364 while (i < tagValueList->nbPairs) {
3365 // upper margin
3366 nbPairsInPage = nbgl_useCaseGetNbTagValuesInPageExt(nbPairs, tagValueList, i, false, &flag);
3367 i += nbPairsInPage;
3368 nbPairs -= nbPairsInPage;
3369 nbPages++;
3370 }
3371 return nbPages;
3372}
3373
3378void nbgl_useCaseHome(const char *appName,
3379 const nbgl_icon_details_t *appIcon,
3380 const char *tagline,
3381 bool withSettings,
3382 nbgl_callback_t topRightCallback,
3383 nbgl_callback_t quitCallback)
3384{
3385 nbgl_homeAction_t homeAction = {0};
3386 useCaseHomeExt(
3387 appName, appIcon, tagline, withSettings, &homeAction, topRightCallback, quitCallback);
3388}
3389
3394void nbgl_useCaseHomeExt(const char *appName,
3395 const nbgl_icon_details_t *appIcon,
3396 const char *tagline,
3397 bool withSettings,
3398 const char *actionButtonText,
3399 nbgl_callback_t actionCallback,
3400 nbgl_callback_t topRightCallback,
3401 nbgl_callback_t quitCallback)
3402{
3403 nbgl_homeAction_t homeAction = {.callback = actionCallback,
3404 .icon = NULL,
3405 .style = STRONG_HOME_ACTION,
3406 .text = actionButtonText};
3407
3408 useCaseHomeExt(
3409 appName, appIcon, tagline, withSettings, &homeAction, topRightCallback, quitCallback);
3410}
3411
3425void nbgl_useCaseNavigableContent(const char *title,
3426 uint8_t initPage,
3427 uint8_t nbPages,
3428 nbgl_callback_t quitCallback,
3429 nbgl_navCallback_t navCallback,
3430 nbgl_layoutTouchCallback_t controlsCallback)
3431{
3432 reset_callbacks_and_context();
3433
3434 // memorize context
3435 onQuit = quitCallback;
3436 onNav = navCallback;
3437 onControls = controlsCallback;
3438 pageTitle = title;
3439 navType = SETTINGS_NAV;
3440
3441 // fill navigation structure
3442 prepareNavInfo(false, nbPages, NULL);
3443
3444 displaySettingsPage(initPage, true);
3445}
3446
3452void nbgl_useCaseSettings(const char *title,
3453 uint8_t initPage,
3454 uint8_t nbPages,
3455 bool touchable,
3456 nbgl_callback_t quitCallback,
3457 nbgl_navCallback_t navCallback,
3458 nbgl_layoutTouchCallback_t controlsCallback)
3459{
3460 UNUSED(touchable);
3462 title, initPage, nbPages, quitCallback, navCallback, controlsCallback);
3463}
3464
3477void nbgl_useCaseGenericSettings(const char *appName,
3478 uint8_t initPage,
3479 const nbgl_genericContents_t *settingContents,
3480 const nbgl_contentInfoList_t *infosList,
3481 nbgl_callback_t quitCallback)
3482{
3483 reset_callbacks_and_context();
3484
3485 // memorize context
3486 onQuit = quitCallback;
3487 pageTitle = appName;
3488 navType = GENERIC_NAV;
3489
3490 if (settingContents != NULL) {
3491 memcpy(&genericContext.genericContents, settingContents, sizeof(nbgl_genericContents_t));
3492 }
3493 if (infosList != NULL) {
3494 genericContext.hasFinishingContent = true;
3495 memset(&FINISHING_CONTENT, 0, sizeof(nbgl_content_t));
3496 FINISHING_CONTENT.type = INFOS_LIST;
3497 memcpy(&FINISHING_CONTENT.content, infosList, sizeof(nbgl_content_u));
3498 }
3499
3500 // fill navigation structure
3501 uint8_t nbPages = getNbPagesForGenericContents(&genericContext.genericContents, 0, false);
3502 if (infosList != NULL) {
3503 nbPages += getNbPagesForContent(&FINISHING_CONTENT, nbPages, true, false);
3504 }
3505
3506 prepareNavInfo(false, nbPages, NULL);
3507
3508 displayGenericContextPage(initPage, true);
3509}
3510
3522void nbgl_useCaseGenericConfiguration(const char *title,
3523 uint8_t initPage,
3524 const nbgl_genericContents_t *contents,
3525 nbgl_callback_t quitCallback)
3526{
3527 nbgl_useCaseGenericSettings(title, initPage, contents, NULL, quitCallback);
3528}
3529
3547 const char *appName,
3548 const nbgl_icon_details_t *appIcon,
3549 const char *tagline,
3550 const uint8_t
3551 initSettingPage, // if not INIT_HOME_PAGE, start directly the corresponding setting page
3552 const nbgl_genericContents_t *settingContents,
3553 const nbgl_contentInfoList_t *infosList,
3554 const nbgl_homeAction_t *action, // Set to NULL if no additional action
3555 nbgl_callback_t quitCallback)
3556{
3557 nbgl_homeAndSettingsContext_t *context = &bundleNavContext.homeAndSettings;
3558
3559 reset_callbacks_and_context();
3560
3561 context->appName = appName;
3562 context->appIcon = appIcon;
3563 context->tagline = tagline;
3564 context->settingContents = settingContents;
3565 context->infosList = infosList;
3566 if (action != NULL) {
3567 memcpy(&context->homeAction, action, sizeof(nbgl_homeAction_t));
3568 }
3569 else {
3570 memset(&context->homeAction, 0, sizeof(nbgl_homeAction_t));
3571 }
3572 context->quitCallback = quitCallback;
3573
3574 if (initSettingPage != INIT_HOME_PAGE) {
3575 bundleNavStartSettingsAtPage(initSettingPage);
3576 }
3577 else {
3578 bundleNavStartHome();
3579 }
3580}
3581
3589void nbgl_useCaseStatus(const char *message, bool isSuccess, nbgl_callback_t quitCallback)
3590{
3591 nbgl_screenTickerConfiguration_t ticker = {.tickerCallback = &tickerCallback,
3592 .tickerIntervale = 0, // not periodic
3593 .tickerValue = STATUS_SCREEN_DURATION};
3594 nbgl_pageInfoDescription_t info = {0};
3595
3596 reset_callbacks_and_context();
3597
3598 onQuit = quitCallback;
3599 if (isSuccess) {
3600#ifdef HAVE_PIEZO_SOUND
3601 os_io_seph_cmd_piezo_play_tune(TUNE_LEDGER_MOMENT);
3602#endif // HAVE_PIEZO_SOUND
3603 }
3604 info.centeredInfo.icon = isSuccess ? &CHECK_CIRCLE_ICON : &DENIED_CIRCLE_ICON;
3606 info.centeredInfo.text1 = message;
3607 info.tapActionText = "";
3608 info.tapActionToken = QUIT_TOKEN;
3609 info.tuneId = TUNE_TAP_CASUAL;
3610 pageContext = nbgl_pageDrawInfo(&pageCallback, &ticker, &info);
3612}
3613
3621 nbgl_callback_t quitCallback)
3622{
3623 const char *msg;
3624 bool isSuccess;
3625 switch (reviewStatusType) {
3627 msg = "Operation signed";
3628 isSuccess = true;
3629 break;
3631 msg = "Operation rejected";
3632 isSuccess = false;
3633 break;
3635 msg = "Transaction signed";
3636 isSuccess = true;
3637 break;
3639 msg = "Transaction rejected";
3640 isSuccess = false;
3641 break;
3643 msg = "Message signed";
3644 isSuccess = true;
3645 break;
3647 msg = "Message rejected";
3648 isSuccess = false;
3649 break;
3651 msg = "Address verified";
3652 isSuccess = true;
3653 break;
3655 msg = "Address verification\ncancelled";
3656 isSuccess = false;
3657 break;
3658 default:
3659 return;
3660 }
3661 nbgl_useCaseStatus(msg, isSuccess, quitCallback);
3662}
3663
3677 const char *message,
3678 const char *subMessage,
3679 const char *confirmText,
3680 const char *cancelText,
3681 nbgl_choiceCallback_t callback)
3682{
3684 // check params
3685 if ((confirmText == NULL) || (cancelText == NULL)) {
3686 return;
3687 }
3688 reset_callbacks_and_context();
3689
3690 info.cancelText = cancelText;
3691 info.centeredInfo.text1 = message;
3692 info.centeredInfo.text2 = subMessage;
3694 info.centeredInfo.icon = icon;
3695 info.confirmationText = confirmText;
3696 info.confirmationToken = CHOICE_TOKEN;
3697 info.tuneId = TUNE_TAP_CASUAL;
3698
3699 onChoice = callback;
3700 pageContext = nbgl_pageDrawConfirmation(&pageCallback, &info);
3702}
3703
3719 const char *message,
3720 const char *subMessage,
3721 const char *confirmText,
3722 const char *cancelText,
3723 nbgl_genericDetails_t *details,
3724 nbgl_choiceCallback_t callback)
3725{
3726 nbgl_layoutDescription_t layoutDescription;
3727 nbgl_layoutChoiceButtons_t buttonsInfo = {.bottomText = cancelText,
3728 .token = CHOICE_TOKEN,
3729 .topText = confirmText,
3730 .style = ROUNDED_AND_FOOTER_STYLE,
3731 .tuneId = TUNE_TAP_CASUAL};
3732 nbgl_contentCenter_t centeredInfo = {0};
3733 nbgl_layoutHeader_t headerDesc = {.type = HEADER_EMPTY,
3734 .separationLine = false,
3735 .emptySpace.height = MEDIUM_CENTERING_HEADER};
3736
3737 // check params
3738 if ((confirmText == NULL) || (cancelText == NULL)) {
3739 return;
3740 }
3741
3742 reset_callbacks_and_context();
3743
3744 onChoice = callback;
3745 layoutDescription.modal = false;
3746 layoutDescription.withLeftBorder = true;
3747
3748 layoutDescription.onActionCallback = layoutTouchCallback;
3749 layoutDescription.tapActionText = NULL;
3750
3751 layoutDescription.ticker.tickerCallback = NULL;
3752 sharedContext.usage = SHARE_CTX_CHOICE_WITH_DETAILS;
3753 choiceWithDetailsCtx.layoutCtx = nbgl_layoutGet(&layoutDescription);
3754 choiceWithDetailsCtx.details = details;
3755
3756 nbgl_layoutAddHeader(choiceWithDetailsCtx.layoutCtx, &headerDesc);
3757 nbgl_layoutAddChoiceButtons(choiceWithDetailsCtx.layoutCtx, &buttonsInfo);
3758 centeredInfo.icon = icon;
3759 centeredInfo.title = message;
3760 centeredInfo.description = subMessage;
3761 nbgl_layoutAddContentCenter(choiceWithDetailsCtx.layoutCtx, &centeredInfo);
3762
3763 if (details != NULL) {
3765 choiceWithDetailsCtx.layoutCtx, &SEARCH_ICON, CHOICE_DETAILS_TOKEN, TUNE_TAP_CASUAL);
3766 }
3767
3768 nbgl_layoutDraw(choiceWithDetailsCtx.layoutCtx);
3770}
3771
3785void nbgl_useCaseConfirm(const char *message,
3786 const char *subMessage,
3787 const char *confirmText,
3788 const char *cancelText,
3789 nbgl_callback_t callback)
3790{
3791 // Don't reset callback or nav context as this is just a modal.
3792
3793 nbgl_pageConfirmationDescription_t info = {.cancelText = cancelText,
3794 .centeredInfo.text1 = message,
3795 .centeredInfo.text2 = subMessage,
3796 .centeredInfo.text3 = NULL,
3797 .centeredInfo.style = LARGE_CASE_INFO,
3798 .centeredInfo.icon = &IMPORTANT_CIRCLE_ICON,
3799 .centeredInfo.offsetY = 0,
3800 .confirmationText = confirmText,
3801 .confirmationToken = CHOICE_TOKEN,
3802 .tuneId = TUNE_TAP_CASUAL,
3803 .modal = true};
3804 onModalConfirm = callback;
3805 if (modalPageContext != NULL) {
3806 nbgl_pageRelease(modalPageContext);
3807 }
3808 modalPageContext = nbgl_pageDrawConfirmation(&pageModalCallback, &info);
3810}
3811
3823 const char *message,
3824 const char *actionText,
3825 nbgl_callback_t callback)
3826{
3827 nbgl_pageContent_t content = {0};
3828
3829 reset_callbacks_and_context();
3830 // memorize callback
3831 onAction = callback;
3832
3833 content.tuneId = TUNE_TAP_CASUAL;
3834 content.type = INFO_BUTTON;
3835 content.infoButton.buttonText = actionText;
3836 content.infoButton.text = message;
3837 content.infoButton.icon = icon;
3838 content.infoButton.buttonToken = ACTION_BUTTON_TOKEN;
3839
3840 pageContext = nbgl_pageDrawGenericContent(&pageCallback, NULL, &content);
3842}
3843
3856 const char *reviewTitle,
3857 const char *reviewSubTitle,
3858 const char *rejectText,
3859 nbgl_callback_t continueCallback,
3860 nbgl_callback_t rejectCallback)
3861{
3862 nbgl_pageInfoDescription_t info = {.footerText = rejectText,
3863 .footerToken = QUIT_TOKEN,
3864 .tapActionText = NULL,
3865 .isSwipeable = true,
3866 .tapActionToken = CONTINUE_TOKEN,
3867 .topRightStyle = NO_BUTTON_STYLE,
3868 .actionButtonText = NULL,
3869 .tuneId = TUNE_TAP_CASUAL};
3870
3871 reset_callbacks_and_context();
3872
3873 info.centeredInfo.icon = icon;
3874 info.centeredInfo.text1 = reviewTitle;
3875 info.centeredInfo.text2 = reviewSubTitle;
3876 info.centeredInfo.text3 = "Swipe to review";
3878 info.centeredInfo.offsetY = 0;
3879 onQuit = rejectCallback;
3880 onContinue = continueCallback;
3881
3882#ifdef HAVE_PIEZO_SOUND
3883 // Play notification sound
3884 os_io_seph_cmd_piezo_play_tune(TUNE_LOOK_AT_ME);
3885#endif // HAVE_PIEZO_SOUND
3886
3887 pageContext = nbgl_pageDrawInfo(&pageCallback, NULL, &info);
3888 nbgl_refresh();
3889}
3890
3895void nbgl_useCaseRegularReview(uint8_t initPage,
3896 uint8_t nbPages,
3897 const char *rejectText,
3898 nbgl_layoutTouchCallback_t buttonCallback,
3899 nbgl_navCallback_t navCallback,
3900 nbgl_choiceCallback_t choiceCallback)
3901{
3902 reset_callbacks_and_context();
3903
3904 // memorize context
3905 onChoice = choiceCallback;
3906 onNav = navCallback;
3907 onControls = buttonCallback;
3908 forwardNavOnly = false;
3909 navType = REVIEW_NAV;
3910
3911 // fill navigation structure
3912 UNUSED(rejectText);
3913 prepareNavInfo(true, nbPages, getRejectReviewText(TYPE_OPERATION));
3914
3915 displayReviewPage(initPage, true);
3916}
3917
3931 const nbgl_pageInfoLongPress_t *infoLongPress,
3932 const char *rejectText,
3933 nbgl_choiceCallback_t callback)
3934{
3935 uint8_t offset = 0;
3936
3937 reset_callbacks_and_context();
3938
3939 // memorize context
3940 onChoice = callback;
3941 navType = GENERIC_NAV;
3942 pageTitle = NULL;
3943 bundleNavContext.review.operationType = TYPE_OPERATION;
3944
3945 genericContext.genericContents.contentsList = localContentsList;
3946 memset(localContentsList, 0, 2 * sizeof(nbgl_content_t));
3947
3948 if (tagValueList != NULL && tagValueList->nbPairs != 0) {
3949 localContentsList[offset].type = TAG_VALUE_LIST;
3950 memcpy(&localContentsList[offset].content.tagValueList,
3951 tagValueList,
3953 offset++;
3954 }
3955
3956 localContentsList[offset].type = INFO_LONG_PRESS;
3957 memcpy(&localContentsList[offset].content.infoLongPress,
3958 infoLongPress,
3959 sizeof(nbgl_pageInfoLongPress_t));
3960 localContentsList[offset].content.infoLongPress.longPressToken = CONFIRM_TOKEN;
3961 offset++;
3962
3963 genericContext.genericContents.nbContents = offset;
3964
3965 // compute number of pages & fill navigation structure
3966 uint8_t nbPages = getNbPagesForGenericContents(&genericContext.genericContents, 0, false);
3967 UNUSED(rejectText);
3968 prepareNavInfo(true, nbPages, getRejectReviewText(TYPE_OPERATION));
3969
3970 displayGenericContextPage(0, true);
3971}
3972
3987 const nbgl_pageInfoLongPress_t *infoLongPress,
3988 const char *rejectText,
3989 nbgl_choiceCallback_t callback)
3990{
3991 uint8_t offset = 0;
3992
3993 reset_callbacks_and_context();
3994
3995 // memorize context
3996 onChoice = callback;
3997 navType = GENERIC_NAV;
3998 pageTitle = NULL;
3999
4000 genericContext.genericContents.contentsList = localContentsList;
4001 memset(localContentsList, 0, 2 * sizeof(nbgl_content_t));
4002
4003 if (tagValueList != NULL && tagValueList->nbPairs != 0) {
4004 localContentsList[offset].type = TAG_VALUE_LIST;
4005 memcpy(&localContentsList[offset].content.tagValueList,
4006 tagValueList,
4008 offset++;
4009 }
4010
4011 localContentsList[offset].type = INFO_BUTTON;
4012 localContentsList[offset].content.infoButton.text = infoLongPress->text;
4013 localContentsList[offset].content.infoButton.icon = infoLongPress->icon;
4014 localContentsList[offset].content.infoButton.buttonText = infoLongPress->longPressText;
4015 localContentsList[offset].content.infoButton.buttonToken = CONFIRM_TOKEN;
4016 localContentsList[offset].content.infoButton.tuneId = TUNE_TAP_CASUAL;
4017 offset++;
4018
4019 genericContext.genericContents.nbContents = offset;
4020
4021 // compute number of pages & fill navigation structure
4022 uint8_t nbPages = getNbPagesForGenericContents(&genericContext.genericContents, 0, false);
4023 UNUSED(rejectText);
4024 prepareNavInfo(true, nbPages, getRejectReviewText(TYPE_OPERATION));
4025
4026 displayGenericContextPage(0, true);
4027}
4028
4045void nbgl_useCaseReview(nbgl_operationType_t operationType,
4046 const nbgl_contentTagValueList_t *tagValueList,
4047 const nbgl_icon_details_t *icon,
4048 const char *reviewTitle,
4049 const char *reviewSubTitle,
4050 const char *finishTitle,
4051 nbgl_choiceCallback_t choiceCallback)
4052{
4053 reset_callbacks_and_context();
4054
4055 useCaseReview(operationType,
4056 tagValueList,
4057 icon,
4058 reviewTitle,
4059 reviewSubTitle,
4060 finishTitle,
4061 NULL,
4062 choiceCallback,
4063 false,
4064 true);
4065}
4066
4087 const nbgl_contentTagValueList_t *tagValueList,
4088 const nbgl_icon_details_t *icon,
4089 const char *reviewTitle,
4090 const char *reviewSubTitle,
4091 const char *finishTitle,
4092 const nbgl_tipBox_t *tipBox,
4093 nbgl_choiceCallback_t choiceCallback)
4094{
4095 nbgl_useCaseAdvancedReview(operationType,
4096 tagValueList,
4097 icon,
4098 reviewTitle,
4099 reviewSubTitle,
4100 finishTitle,
4101 tipBox,
4102 &blindSigningWarning,
4103 choiceCallback);
4104}
4105
4129 const nbgl_contentTagValueList_t *tagValueList,
4130 const nbgl_icon_details_t *icon,
4131 const char *reviewTitle,
4132 const char *reviewSubTitle,
4133 const char *finishTitle,
4134 const nbgl_tipBox_t *tipBox,
4135 const nbgl_warning_t *warning,
4136 nbgl_choiceCallback_t choiceCallback)
4137{
4138 reset_callbacks_and_context();
4139
4140 // memorize tipBox because it can be in the call stack of the caller
4141 if (tipBox != NULL) {
4142 memcpy(&activeTipBox, tipBox, sizeof(activeTipBox));
4143 }
4144 // if no warning at all, it's a simple review
4145 if ((warning == NULL)
4146 || ((warning->predefinedSet == 0) && (warning->introDetails == NULL)
4147 && (warning->reviewDetails == NULL) && (warning->prelude == NULL))) {
4148 useCaseReview(operationType,
4149 tagValueList,
4150 icon,
4151 reviewTitle,
4152 reviewSubTitle,
4153 finishTitle,
4154 tipBox,
4155 choiceCallback,
4156 false,
4157 true);
4158 return;
4159 }
4160 if (warning->predefinedSet == (1 << W3C_NO_THREAT_WARN)) {
4161 operationType |= NO_THREAT_OPERATION;
4162 }
4163 else if (warning->predefinedSet != 0) {
4164 operationType |= RISKY_OPERATION;
4165 }
4166 sharedContext.usage = SHARE_CTX_REVIEW_WITH_WARNING;
4167 reviewWithWarnCtx.isStreaming = false;
4168 reviewWithWarnCtx.operationType = operationType;
4169 reviewWithWarnCtx.tagValueList = tagValueList;
4170 reviewWithWarnCtx.icon = icon;
4171 reviewWithWarnCtx.reviewTitle = reviewTitle;
4172 reviewWithWarnCtx.reviewSubTitle = reviewSubTitle;
4173 reviewWithWarnCtx.finishTitle = finishTitle;
4174 reviewWithWarnCtx.warning = warning;
4175 reviewWithWarnCtx.choiceCallback = choiceCallback;
4176
4177 // if the warning contains a prelude, display it first
4178 if (reviewWithWarnCtx.warning->prelude) {
4179 displayPrelude();
4180 }
4181 // display the initial warning only of a risk/threat or blind signing or prelude
4182 else if (HAS_INITIAL_WARNING(warning)) {
4183 displayInitialWarning();
4184 }
4185 else {
4186 useCaseReview(operationType,
4187 tagValueList,
4188 icon,
4189 reviewTitle,
4190 reviewSubTitle,
4191 finishTitle,
4192 tipBox,
4193 choiceCallback,
4194 false,
4195 true);
4196 }
4197}
4198
4216 const nbgl_contentTagValueList_t *tagValueList,
4217 const nbgl_icon_details_t *icon,
4218 const char *reviewTitle,
4219 const char *reviewSubTitle,
4220 const char *finishTitle,
4221 nbgl_choiceCallback_t choiceCallback)
4222{
4223 reset_callbacks_and_context();
4224
4225 useCaseReview(operationType,
4226 tagValueList,
4227 icon,
4228 reviewTitle,
4229 reviewSubTitle,
4230 finishTitle,
4231 NULL,
4232 choiceCallback,
4233 true,
4234 true);
4235}
4236
4246 const char *rejectText,
4247 nbgl_callback_t rejectCallback)
4248{
4249 reset_callbacks_and_context();
4250
4251 // memorize context
4252 onQuit = rejectCallback;
4253 navType = GENERIC_NAV;
4254 pageTitle = NULL;
4255 bundleNavContext.review.operationType = TYPE_OPERATION;
4256
4257 memcpy(&genericContext.genericContents, contents, sizeof(nbgl_genericContents_t));
4258
4259 // compute number of pages & fill navigation structure
4260 uint8_t nbPages = getNbPagesForGenericContents(&genericContext.genericContents, 0, false);
4261 prepareNavInfo(true, nbPages, rejectText);
4262 navInfo.quitToken = QUIT_TOKEN;
4263
4264#ifdef HAVE_PIEZO_SOUND
4265 // Play notification sound
4266 os_io_seph_cmd_piezo_play_tune(TUNE_LOOK_AT_ME);
4267#endif // HAVE_PIEZO_SOUND
4268
4269 displayGenericContextPage(0, true);
4270}
4271
4285 const nbgl_icon_details_t *icon,
4286 const char *reviewTitle,
4287 const char *reviewSubTitle,
4288 nbgl_choiceCallback_t choiceCallback)
4289{
4290 reset_callbacks_and_context();
4291
4292 useCaseReviewStreamingStart(
4293 operationType, icon, reviewTitle, reviewSubTitle, choiceCallback, true);
4294}
4295
4310 const nbgl_icon_details_t *icon,
4311 const char *reviewTitle,
4312 const char *reviewSubTitle,
4313 nbgl_choiceCallback_t choiceCallback)
4314{
4316 operationType, icon, reviewTitle, reviewSubTitle, &blindSigningWarning, choiceCallback);
4317}
4318
4335 const nbgl_icon_details_t *icon,
4336 const char *reviewTitle,
4337 const char *reviewSubTitle,
4338 const nbgl_warning_t *warning,
4339 nbgl_choiceCallback_t choiceCallback)
4340{
4341 reset_callbacks_and_context();
4342
4343 // if no warning at all, it's a simple review
4344 if ((warning == NULL)
4345 || ((warning->predefinedSet == 0) && (warning->introDetails == NULL)
4346 && (warning->reviewDetails == NULL) && (warning->prelude == NULL))) {
4347 useCaseReviewStreamingStart(
4348 operationType, icon, reviewTitle, reviewSubTitle, choiceCallback, true);
4349 return;
4350 }
4351 if (warning->predefinedSet == (1 << W3C_NO_THREAT_WARN)) {
4352 operationType |= NO_THREAT_OPERATION;
4353 }
4354 else if (warning->predefinedSet != 0) {
4355 operationType |= RISKY_OPERATION;
4356 }
4357
4358 sharedContext.usage = SHARE_CTX_REVIEW_WITH_WARNING;
4359 reviewWithWarnCtx.isStreaming = true;
4360 reviewWithWarnCtx.operationType = operationType;
4361 reviewWithWarnCtx.icon = icon;
4362 reviewWithWarnCtx.reviewTitle = reviewTitle;
4363 reviewWithWarnCtx.reviewSubTitle = reviewSubTitle;
4364 reviewWithWarnCtx.choiceCallback = choiceCallback;
4365 reviewWithWarnCtx.warning = warning;
4366
4367 // if the warning contains a prelude, display it first
4368 if (reviewWithWarnCtx.warning->prelude) {
4369 displayPrelude();
4370 }
4371 // display the initial warning only of a risk/threat or blind signing
4372 else if (HAS_INITIAL_WARNING(warning)) {
4373 displayInitialWarning();
4374 }
4375 else {
4376 useCaseReviewStreamingStart(
4377 operationType, icon, reviewTitle, reviewSubTitle, choiceCallback, true);
4378 }
4379}
4380
4395 nbgl_choiceCallback_t choiceCallback,
4396 nbgl_callback_t skipCallback)
4397{
4398 // Should follow a call to nbgl_useCaseReviewStreamingStart
4399 memset(&genericContext, 0, sizeof(genericContext));
4400
4401 bundleNavContext.reviewStreaming.choiceCallback = choiceCallback;
4402 bundleNavContext.reviewStreaming.skipCallback = skipCallback;
4403
4404 // memorize context
4405 onChoice = bundleNavReviewStreamingChoice;
4406 navType = STREAMING_NAV;
4407 pageTitle = NULL;
4408
4409 genericContext.genericContents.contentsList = localContentsList;
4410 genericContext.genericContents.nbContents = 1;
4411 memset(localContentsList, 0, 1 * sizeof(nbgl_content_t));
4412
4413 // Then the tag/value pairs
4414 STARTING_CONTENT.type = TAG_VALUE_LIST;
4415 memcpy(
4416 &STARTING_CONTENT.content.tagValueList, tagValueList, sizeof(nbgl_contentTagValueList_t));
4417
4418 // compute number of pages & fill navigation structure
4419 bundleNavContext.reviewStreaming.stepPageNb = getNbPagesForGenericContents(
4420 &genericContext.genericContents,
4421 0,
4422 (bundleNavContext.reviewStreaming.operationType & SKIPPABLE_OPERATION));
4423 prepareNavInfo(true,
4425 getRejectReviewText(bundleNavContext.reviewStreaming.operationType));
4426 // if the operation is skippable
4427 if (bundleNavContext.reviewStreaming.operationType & SKIPPABLE_OPERATION) {
4428 navInfo.progressIndicator = false;
4429 navInfo.skipText = "Skip";
4430 navInfo.skipToken = SKIP_TOKEN;
4431 }
4432
4433 displayGenericContextPage(0, true);
4434}
4435
4447 nbgl_choiceCallback_t choiceCallback)
4448{
4449 nbgl_useCaseReviewStreamingContinueExt(tagValueList, choiceCallback, NULL);
4450}
4451
4460void nbgl_useCaseReviewStreamingFinish(const char *finishTitle,
4461 nbgl_choiceCallback_t choiceCallback)
4462{
4463 // Should follow a call to nbgl_useCaseReviewStreamingContinue
4464 memset(&genericContext, 0, sizeof(genericContext));
4465
4466 bundleNavContext.reviewStreaming.choiceCallback = choiceCallback;
4467
4468 // memorize context
4469 onChoice = bundleNavReviewStreamingChoice;
4470 navType = STREAMING_NAV;
4471 pageTitle = NULL;
4472
4473 genericContext.genericContents.contentsList = localContentsList;
4474 genericContext.genericContents.nbContents = 1;
4475 memset(localContentsList, 0, 1 * sizeof(nbgl_content_t));
4476
4477 // Eventually the long press page
4478 STARTING_CONTENT.type = INFO_LONG_PRESS;
4479 prepareReviewLastPage(bundleNavContext.reviewStreaming.operationType,
4480 &STARTING_CONTENT.content.infoLongPress,
4481 bundleNavContext.reviewStreaming.icon,
4482 finishTitle);
4483
4484 // compute number of pages & fill navigation structure
4485 bundleNavContext.reviewStreaming.stepPageNb = getNbPagesForGenericContents(
4486 &genericContext.genericContents,
4487 0,
4488 (bundleNavContext.reviewStreaming.operationType & SKIPPABLE_OPERATION));
4489 prepareNavInfo(true, 1, getRejectReviewText(bundleNavContext.reviewStreaming.operationType));
4490
4491 displayGenericContextPage(0, true);
4492}
4493
4498void nbgl_useCaseAddressConfirmationExt(const char *address,
4499 nbgl_choiceCallback_t callback,
4500 const nbgl_contentTagValueList_t *tagValueList)
4501{
4502 reset_callbacks_and_context();
4503 memset(&addressConfirmationContext, 0, sizeof(addressConfirmationContext));
4504
4505 // save context
4506 onChoice = callback;
4507 navType = GENERIC_NAV;
4508 pageTitle = NULL;
4509
4510 genericContext.genericContents.contentsList = localContentsList;
4511 genericContext.genericContents.nbContents = (tagValueList == NULL) ? 1 : 2;
4512 memset(localContentsList, 0, 2 * sizeof(nbgl_content_t));
4513 prepareAddressConfirmationPages(
4514 address, tagValueList, &STARTING_CONTENT, &localContentsList[1]);
4515
4516 // fill navigation structure, common to all pages
4517 uint8_t nbPages = getNbPagesForGenericContents(&genericContext.genericContents, 0, false);
4518
4519 prepareNavInfo(true, nbPages, "Cancel");
4520
4521#ifdef HAVE_PIEZO_SOUND
4522 // Play notification sound
4523 os_io_seph_cmd_piezo_play_tune(TUNE_LOOK_AT_ME);
4524#endif // HAVE_PIEZO_SOUND
4525
4526 displayGenericContextPage(0, true);
4527}
4528
4545void nbgl_useCaseAddressReview(const char *address,
4546 const nbgl_contentTagValueList_t *additionalTagValueList,
4547 const nbgl_icon_details_t *icon,
4548 const char *reviewTitle,
4549 const char *reviewSubTitle,
4550 nbgl_choiceCallback_t choiceCallback)
4551{
4552 reset_callbacks_and_context();
4553
4554 // release a potential modal
4555 if (addressConfirmationContext.modalLayout) {
4556 nbgl_layoutRelease(addressConfirmationContext.modalLayout);
4557 }
4558 memset(&addressConfirmationContext, 0, sizeof(addressConfirmationContext));
4559
4560 // save context
4561 onChoice = choiceCallback;
4562 navType = GENERIC_NAV;
4563 pageTitle = NULL;
4564 bundleNavContext.review.operationType = TYPE_OPERATION;
4565
4566 genericContext.genericContents.contentsList = localContentsList;
4567 memset(localContentsList, 0, 3 * sizeof(nbgl_content_t));
4568
4569 // First a centered info
4570 STARTING_CONTENT.type = EXTENDED_CENTER;
4571 prepareReviewFirstPage(
4572 &STARTING_CONTENT.content.extendedCenter.contentCenter, icon, reviewTitle, reviewSubTitle);
4573 STARTING_CONTENT.content.extendedCenter.contentCenter.subText = "Swipe to continue";
4574
4575 // Then the address confirmation pages
4576 prepareAddressConfirmationPages(
4577 address, additionalTagValueList, &localContentsList[1], &localContentsList[2]);
4578
4579 // fill navigation structure, common to all pages
4580 genericContext.genericContents.nbContents
4581 = (localContentsList[2].type == TAG_VALUE_CONFIRM) ? 3 : 2;
4582 uint8_t nbPages = getNbPagesForGenericContents(&genericContext.genericContents, 0, false);
4583
4584 prepareNavInfo(true, nbPages, "Cancel");
4585
4586#ifdef HAVE_PIEZO_SOUND
4587 // Play notification sound
4588 os_io_seph_cmd_piezo_play_tune(TUNE_LOOK_AT_ME);
4589#endif // HAVE_PIEZO_SOUND
4590
4591 displayGenericContextPage(0, true);
4592}
4593
4602void nbgl_useCaseSpinner(const char *text)
4603{
4604 // if the previous Use Case was not Spinner, fresh start
4605 if (genericContext.type != USE_CASE_SPINNER) {
4606 memset(&genericContext, 0, sizeof(genericContext));
4607 genericContext.type = USE_CASE_SPINNER;
4608 nbgl_layoutDescription_t layoutDescription = {0};
4609
4610 layoutDescription.withLeftBorder = true;
4611
4612 genericContext.backgroundLayout = nbgl_layoutGet(&layoutDescription);
4613
4615 genericContext.backgroundLayout, text, NULL, genericContext.spinnerPosition);
4616
4617 nbgl_layoutDraw(genericContext.backgroundLayout);
4619 }
4620 else {
4621 // otherwise increment spinner
4622 genericContext.spinnerPosition++;
4623 // there are only NB_SPINNER_POSITIONSpositions
4624 if (genericContext.spinnerPosition == NB_SPINNER_POSITIONS) {
4625 genericContext.spinnerPosition = 0;
4626 }
4627 int ret = nbgl_layoutUpdateSpinner(
4628 genericContext.backgroundLayout, text, NULL, genericContext.spinnerPosition);
4629 if (ret == 1) {
4631 }
4632 else if (ret == 2) {
4634 }
4635 }
4636}
4637
4638#ifdef NBGL_KEYPAD
4657void nbgl_useCaseKeypad(const char *title,
4658 uint8_t minDigits,
4659 uint8_t maxDigits,
4660 bool shuffled,
4661 bool hidden,
4662 nbgl_pinValidCallback_t validatePinCallback,
4663 nbgl_callback_t backCallback)
4664{
4665 nbgl_layoutDescription_t layoutDescription = {0};
4667 .separationLine = true,
4668 .backAndText.token = BACK_TOKEN,
4669 .backAndText.tuneId = TUNE_TAP_CASUAL};
4670 int status = -1;
4671
4672 if ((minDigits > KEYPAD_MAX_DIGITS) || (maxDigits > KEYPAD_MAX_DIGITS)) {
4673 return;
4674 }
4675
4676 reset_callbacks_and_context();
4677 // reset the keypad context
4678 memset(&keypadContext, 0, sizeof(KeypadContext_t));
4679
4680 // memorize context
4681 onQuit = backCallback;
4682
4683 // get a layout
4684 layoutDescription.onActionCallback = keypadGeneric_cb;
4685 layoutDescription.modal = false;
4686 layoutDescription.withLeftBorder = false;
4687 keypadContext.layoutCtx = nbgl_layoutGet(&layoutDescription);
4688 keypadContext.hidden = hidden;
4689
4690 // set back key in header
4691 nbgl_layoutAddHeader(keypadContext.layoutCtx, &headerDesc);
4692
4693 // add keypad
4694 status = nbgl_layoutAddKeypad(keypadContext.layoutCtx, keypadCallback, shuffled);
4695 if (status < 0) {
4696 return;
4697 }
4698 // add keypad content
4700 keypadContext.layoutCtx, title, keypadContext.hidden, maxDigits, "");
4701 if (status < 0) {
4702 return;
4703 }
4704
4705 // validation pin callback
4706 keypadContext.onValidatePin = validatePinCallback;
4707 // pin code acceptable lengths
4708 keypadContext.pinMinDigits = minDigits;
4709 keypadContext.pinMaxDigits = maxDigits;
4710
4711 nbgl_layoutDraw(keypadContext.layoutCtx);
4713}
4714#endif // NBGL_KEYPAD
4715
4716#ifdef NBGL_KEYBOARD
4743void nbgl_useCaseKeyboard(const nbgl_keyboardParams_t *params, nbgl_callback_t backCallback)
4744{
4745 // clang-format off
4746 nbgl_layoutDescription_t layoutDescription = {.onActionCallback = keyboardGeneric_cb};
4748 .backAndText.token = BACK_TOKEN,
4749 .backAndText.tuneId = TUNE_TAP_CASUAL};
4750 nbgl_layoutKbd_t kbdInfo = {.callback = &keyboardCallback,
4751 .lettersOnly = params->lettersOnly,
4752 .mode = params->mode,
4753 .casing = params->casing};
4754 int status = -1;
4755 // clang-format on
4756
4757 reset_callbacks_and_context();
4758 // reset the keyboard context
4759 memset(&keyboardContext, 0, sizeof(KeyboardContext_t));
4760
4761 // memorize context
4762 onQuit = backCallback;
4763
4764 // get a layout
4765 keyboardContext.layoutCtx = nbgl_layoutGet(&layoutDescription);
4766
4767 // set back key in header
4768 nbgl_layoutAddHeader(keyboardContext.layoutCtx, &headerDesc);
4769
4770 // Add keyboard
4771 status = nbgl_layoutAddKeyboard(keyboardContext.layoutCtx, &kbdInfo);
4772 if (status < 0) {
4773 return;
4774 }
4775 keyboardContext.keyboardIndex = status;
4776 keyboardContext.casing = params->casing;
4777 keyboardContext.entryBuffer = PIC(params->entryBuffer);
4778 keyboardContext.entryMaxLen = params->entryMaxLen;
4779 keyboardContext.entryBuffer[0] = '\0';
4780 // add keyboard content
4781 keyboardContext.keyboardContent = (nbgl_layoutKeyboardContent_t){
4782 .type = params->type,
4783 .title = PIC(params->title),
4784 .numbered = params->numbered,
4785 .number = params->number,
4786 .text = keyboardContext.entryBuffer,
4787 .textToken = KEYBOARD_CROSS_TOKEN,
4788 .tuneId = TUNE_TAP_CASUAL,
4789 };
4790 switch (params->type) {
4792 onAction = PIC(params->confirmationParams.onButtonCallback);
4793 keyboardContext.keyboardContent.confirmationButton = (nbgl_layoutConfirmationButton_t){
4794 .text = PIC(params->confirmationParams.buttonText),
4795 .token = KEYBOARD_BUTTON_TOKEN,
4796 .active = true,
4797 };
4798 break;
4800 onControls = PIC(params->suggestionParams.onButtonCallback);
4801 keyboardContext.getSuggestionButtons
4803 keyboardContext.keyboardContent.suggestionButtons = (nbgl_layoutSuggestionButtons_t){
4804 .buttons = PIC(params->suggestionParams.buttons),
4805 .firstButtonToken = params->suggestionParams.firstButtonToken,
4806 };
4807 break;
4808 default:
4809 return;
4810 }
4811 status = nbgl_layoutAddKeyboardContent(keyboardContext.layoutCtx,
4812 &keyboardContext.keyboardContent);
4813 if (status < 0) {
4814 return;
4815 }
4816
4817 nbgl_layoutDraw(keyboardContext.layoutCtx);
4819}
4820#endif // NBGL_KEYBOARD
4821
4822#endif // HAVE_SE_TOUCH
4823#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_layoutUpdateKeyboard(nbgl_layout_t *layout, uint8_t index, uint32_t keyMask, bool updateCasing, keyboardCase_t casing)
Updates an existing keyboard on bottom of the screen, with the given configuration.
int nbgl_layoutAddKeyboard(nbgl_layout_t *layout, const nbgl_layoutKbd_t *kbdInfo)
Creates a keyboard on bottom of the screen, with the given configuration.
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_layoutAddKeyboardContent(nbgl_layout_t *layout, nbgl_layoutKeyboardContent_t *content)
Adds an area containing a potential title, a text entry and either confirmation or suggestion buttons...
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_layoutUpdateKeyboardContent(nbgl_layout_t *layout, nbgl_layoutKeyboardContent_t *content)
Updates an area containing a potential title, a text entry and either confirmation or suggestion butt...
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,...
@ KEYBOARD_WITH_BUTTON
text entry area + confirmation button
@ KEYBOARD_WITH_SUGGESTIONS
text entry area + suggestion buttons
#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:268
void nbgl_refresh(void)
keyboardCase_t
Letters casing in which to open/set the keyboard.
Definition nbgl_obj.h:617
#define KEYPAD_MAX_DIGITS
Definition nbgl_obj.h:71
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
@ BLACK_AND_WHITE_REFRESH
to be used for pure B&W area, when contrast is important
Definition nbgl_types.h:329
@ 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 FIRST_USER_TOKEN
when using controls in page content (nbgl_pageContent_t), this is the first token value usable for th...
#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
void nbgl_useCaseKeyboard(const nbgl_keyboardParams_t *params, nbgl_callback_t backCallback)
#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)
void(* nbgl_keyboardButtonsCallback_t)(nbgl_layoutKeyboardContent_t *content, uint32_t *mask)
prototype of keyboard buttons callback function
#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
const char * buttonText
button title
nbgl_callback_t onButtonCallback
callback to call when the button is pressed
nbgl_keyboardButtonsCallback_t updateButtonsCallback
callback to call when a key is pressed to update suggestions
nbgl_layoutTouchCallback_t onButtonCallback
callback to call when one of the buttons is pressed
const char ** buttons
array of strings for buttons (last ones can be NULL)
int firstButtonToken
first token used for buttons, provided in onButtonCallback
Structure containing all parameters for keyboard use case.
keyboardMode_t mode
keyboard mode to start with
nbgl_kbdSuggestParams_t suggestionParams
nbgl_kbdButtonParams_t confirmationParams
used if type is KEYBOARD_WITH_SUGGESTIONS
bool lettersOnly
if true, only display letter keys and Backspace
uint8_t entryMaxLen
maximum length of text that can be entered
nbgl_layoutKeyboardContentType_t type
type of content
bool numbered
if set to true, the text is preceded on the left by 'number.'
keyboardCase_t casing
keyboard casing mode (lower, upper once or upper locked)
const char * title
centered title explaining the screen
char * entryBuffer
already entered text
uint8_t number
if numbered is true, number used to build 'number.' text
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)
This structure contains info to build a confirmation button.
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 keyboard with nbgl_layoutAddKeyboard()
keyboardCallback_t callback
function called when an active key is pressed
This structure contains info to build a keyboard content (controls that are linked to keyboard)
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 to build suggestion buttons.
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