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
65/**********************
66 * TYPEDEFS
67 **********************/
68enum {
69 BACK_TOKEN = 0,
70 NEXT_TOKEN,
71 QUIT_TOKEN,
72 NAV_TOKEN,
73 MODAL_NAV_TOKEN,
74 SKIP_TOKEN,
75 CONTINUE_TOKEN,
76 ADDRESS_QRCODE_BUTTON_TOKEN,
77 ACTION_BUTTON_TOKEN,
78 CHOICE_TOKEN,
79 DETAILS_BUTTON_TOKEN,
80 CONFIRM_TOKEN,
81 REJECT_TOKEN,
82 VALUE_ALIAS_TOKEN,
83 INFO_ALIAS_TOKEN,
84 INFOS_TIP_BOX_TOKEN,
85 BLIND_WARNING_TOKEN,
86 WARNING_BUTTON_TOKEN,
87 CHOICE_DETAILS_TOKEN,
88 TIP_BOX_TOKEN,
89 QUIT_TIPBOX_MODAL_TOKEN,
90 WARNING_CHOICE_TOKEN,
91 DISMISS_QR_TOKEN,
92 DISMISS_WARNING_TOKEN,
93 DISMISS_DETAILS_TOKEN,
94 PRELUDE_CHOICE_TOKEN,
95 KEYBOARD_BUTTON_TOKEN,
96 KEYBOARD_CROSS_TOKEN,
97 FIRST_WARN_BAR_TOKEN,
98 LAST_WARN_BAR_TOKEN = (FIRST_WARN_BAR_TOKEN + NB_WARNING_TYPES - 1),
99};
100CCASSERT(NBGL_TOKENS, LAST_WARN_BAR_TOKEN + 1 < FIRST_USER_TOKEN);
101
102typedef enum {
103 REVIEW_NAV = 0,
104 SETTINGS_NAV,
105 GENERIC_NAV,
106 STREAMING_NAV
107} NavType_t;
108
109typedef struct DetailsContext_s {
110 uint8_t nbPages;
111 uint8_t currentPage;
112 uint8_t currentPairIdx;
113 bool wrapping;
114 const char *tag;
115 const char *value;
116 const char *nextPageStart;
117} DetailsContext_t;
118
119typedef struct AddressConfirmationContext_s {
120 nbgl_layoutTagValue_t tagValuePairs[ADDR_VERIF_NB_PAIRS];
121 nbgl_layout_t *modalLayout;
122 uint8_t nbPairs;
123} AddressConfirmationContext_t;
124
125#ifdef NBGL_KEYPAD
126typedef struct KeypadContext_s {
127 uint8_t pinEntry[KEYPAD_MAX_DIGITS];
128 uint8_t pinLen;
129 uint8_t pinMinDigits;
130 uint8_t pinMaxDigits;
131 nbgl_layout_t *layoutCtx;
132 bool hidden;
133 nbgl_pinValidCallback_t onValidatePin;
134} KeypadContext_t;
135#endif
136
137#ifdef NBGL_KEYBOARD
138typedef struct KeyboardContext_s {
139 nbgl_layoutKeyboardContent_t keyboardContent;
140 char *entryBuffer;
141 uint8_t entryMaxLen;
142 int keyboardIndex;
143 keyboardCase_t casing;
144 nbgl_layout_t *layoutCtx;
145 nbgl_keyboardButtonsCallback_t getSuggestionButtons;
146} KeyboardContext_t;
147#endif
148
149typedef struct ReviewWithWarningContext_s {
150 bool isStreaming;
151 nbgl_operationType_t operationType;
152 const nbgl_contentTagValueList_t *tagValueList;
153 const nbgl_icon_details_t *icon;
154 const char *reviewTitle;
155 const char *reviewSubTitle;
156 const char *finishTitle;
157 const nbgl_warning_t *warning;
158 nbgl_choiceCallback_t choiceCallback;
159 nbgl_layout_t *layoutCtx;
160 nbgl_layout_t *modalLayout;
161 uint8_t securityReportLevel; // level 1 is the first level of menus
162 bool isIntro; // set to true during intro (before actual review)
163} ReviewWithWarningContext_t;
164
165typedef struct ChoiceWithDetailsContext_s {
166 const nbgl_genericDetails_t *details;
167 nbgl_layout_t *layoutCtx;
168 nbgl_layout_t *modalLayout;
169 uint8_t level; // level 1 is the first level of menus
170} ChoiceWithDetailsContext_t;
171
172typedef enum {
173 SHARE_CTX_KEYPAD,
174 SHARE_CTX_REVIEW_WITH_WARNING,
175 SHARE_CTX_CHOICE_WITH_DETAILS,
176} SharedContextUsage_t;
177
178// this union is intended to save RAM for context storage
179// indeed, these 3 contexts cannot happen simultaneously
180typedef struct {
181 union {
182#ifdef NBGL_KEYPAD
183 KeypadContext_t keypad;
184#endif
185#ifdef NBGL_KEYBOARD
186 KeyboardContext_t keyboard;
187#endif
188 ReviewWithWarningContext_t reviewWithWarning;
189 ChoiceWithDetailsContext_t choiceWithDetails;
190 };
191 SharedContextUsage_t usage;
192} SharedContext_t;
193
194typedef enum {
195 USE_CASE_GENERIC = 0,
196 USE_CASE_SPINNER
197} GenericContextType_t;
198
199typedef struct {
200 GenericContextType_t type; // type of Generic context usage
201 uint8_t spinnerPosition;
202 nbgl_genericContents_t genericContents;
203 int8_t currentContentIdx;
204 uint8_t currentContentElementNb;
205 uint8_t currentElementIdx;
206 bool hasStartingContent;
207 bool hasFinishingContent;
208 const char *detailsItem;
209 const char *detailsvalue;
210 bool detailsWrapping;
211 bool validWarningCtx; // set to true if the WarningContext is valid
213 *currentPairs; // to be used to retrieve the pairs with value alias
215 currentCallback; // to be used to retrieve the pairs with value alias
216 nbgl_layout_t modalLayout;
217 nbgl_layout_t backgroundLayout;
218 const nbgl_contentInfoList_t *currentInfos;
219 const nbgl_contentTagValueList_t *currentTagValues;
220 nbgl_tipBox_t tipBox;
221 SharedContext_t sharedCtx; // multi-purpose context shared for non-concurrent usages
222} GenericContext_t;
223
224typedef struct {
225 const char *appName;
226 const nbgl_icon_details_t *appIcon;
227 const char *tagline;
228 const nbgl_genericContents_t *settingContents;
229 const nbgl_contentInfoList_t *infosList;
230 nbgl_homeAction_t homeAction;
231 nbgl_callback_t quitCallback;
232} nbgl_homeAndSettingsContext_t;
233
234typedef struct {
235 nbgl_operationType_t operationType;
236 nbgl_choiceCallback_t choiceCallback;
237} nbgl_reviewContext_t;
238
239typedef struct {
240 nbgl_operationType_t operationType;
241 nbgl_choiceCallback_t choiceCallback;
242 nbgl_callback_t skipCallback;
243 const nbgl_icon_details_t *icon;
244 uint8_t stepPageNb;
245} nbgl_reviewStreamingContext_t;
246
247typedef union {
248 nbgl_homeAndSettingsContext_t homeAndSettings;
249 nbgl_reviewContext_t review;
250 nbgl_reviewStreamingContext_t reviewStreaming;
251} nbgl_BundleNavContext_t;
252
253typedef struct {
254 const nbgl_icon_details_t *icon;
255 const char *text;
256 const char *subText;
257} SecurityReportItem_t;
258
259/**********************
260 * STATIC VARIABLES
261 **********************/
262
263// char buffers to build some strings
264static char tmpString[W3C_DESCRIPTION_MAX_LEN];
265
266// multi-purposes callbacks
267static nbgl_callback_t onQuit;
268static nbgl_callback_t onContinue;
269static nbgl_callback_t onAction;
270static nbgl_navCallback_t onNav;
271static nbgl_layoutTouchCallback_t onControls;
272static nbgl_contentActionCallback_t onContentAction;
273static nbgl_choiceCallback_t onChoice;
274static nbgl_callback_t onModalConfirm;
275
276// contexts for background and modal pages
277static nbgl_page_t *pageContext;
278static nbgl_page_t *modalPageContext;
279
280// context for pages
281static const char *pageTitle;
282
283// context for tip-box
284#define activeTipBox genericContext.tipBox
285
286// context for navigation use case
287static nbgl_pageNavigationInfo_t navInfo;
288static bool forwardNavOnly;
289static NavType_t navType;
290
291static DetailsContext_t detailsContext;
292
293// context for address review
294static AddressConfirmationContext_t addressConfirmationContext;
295
296// contexts for generic navigation
297static GenericContext_t genericContext;
298static nbgl_content_t
299 localContentsList[3]; // 3 needed for nbgl_useCaseReview (starting page / tags / final page)
300static uint8_t genericContextPagesInfo[MAX_PAGE_NB / PAGES_PER_UINT8];
301static uint8_t modalContextPagesInfo[MAX_MODAL_PAGE_NB / PAGES_PER_UINT8];
302
303// contexts for bundle navigation
304static nbgl_BundleNavContext_t bundleNavContext;
305
306// indexed by nbgl_contentType_t
307static const uint8_t nbMaxElementsPerContentType[] = {
308 1, // CENTERED_INFO
309 1, // EXTENDED_CENTER
310 1, // INFO_LONG_PRESS
311 1, // INFO_BUTTON
312 1, // TAG_VALUE_LIST (computed dynamically)
313 1, // TAG_VALUE_DETAILS
314 1, // TAG_VALUE_CONFIRM
315 3, // SWITCHES_LIST (computed dynamically)
316 3, // INFOS_LIST (computed dynamically)
317 5, // CHOICES_LIST (computed dynamically)
318 5, // BARS_LIST (computed dynamically)
319};
320
321// clang-format off
322static const SecurityReportItem_t securityReportItems[NB_WARNING_TYPES] = {
324 .icon = &WARNING_ICON,
325 .text = "Blind signing required",
326 .subText = "This transaction's details are not fully verifiable. If "
327 "you sign, you could lose all your assets."
328 },
329 [W3C_ISSUE_WARN] = {
330 .icon = &WARNING_ICON,
331 .text = "Transaction Check unavailable",
332 .subText = NULL
333 },
335 .icon = &WARNING_ICON,
336 .text = "Risk detected",
337 .subText = "This transaction was scanned as risky by Web3 Checks."
338 },
340 .icon = &WARNING_ICON,
341 .text = "Critical threat",
342 .subText = "This transaction was scanned as malicious by Web3 Checks."
343 },
345 .icon = NULL,
346 .text = "No threat detected",
347 .subText = "Transaction Check didn't find any threat, but always "
348 "review transaction details carefully."
349 }
350};
351// clang-format on
352
353// configuration of warning when using @ref nbgl_useCaseReviewBlindSigning()
354static const nbgl_warning_t blindSigningWarning = {.predefinedSet = (1 << BLIND_SIGNING_WARN)};
355
356#ifdef NBGL_QRCODE
357/* buffer to store reduced address under QR Code */
358static char reducedAddress[QRCODE_REDUCED_ADDR_LEN];
359#endif // NBGL_QRCODE
360
361/**********************
362 * STATIC FUNCTIONS
363 **********************/
364static void displayReviewPage(uint8_t page, bool forceFullRefresh);
365static void displayDetailsPage(uint8_t page, bool forceFullRefresh);
366static void displayTagValueListModalPage(uint8_t pageIdx, bool forceFullRefresh);
367static void displayFullValuePage(const char *backText,
368 const char *aliasText,
369 const nbgl_contentValueExt_t *extension);
370static void displayInfosListModal(const char *modalTitle,
371 const nbgl_contentInfoList_t *infos,
372 bool fromReview);
373static void displayTagValueListModal(const nbgl_contentTagValueList_t *tagValues);
374static void displaySettingsPage(uint8_t page, bool forceFullRefresh);
375static void displayGenericContextPage(uint8_t pageIdx, bool forceFullRefresh);
376static void pageCallback(int token, uint8_t index);
377#ifdef NBGL_QRCODE
378static void displayAddressQRCode(void);
379#endif // NBGL_QRCODE
380static void modalLayoutTouchCallback(int token, uint8_t index);
381static void displaySkipWarning(void);
382
383static void bundleNavStartHome(void);
384static void bundleNavStartSettingsAtPage(uint8_t initSettingPage);
385static void bundleNavStartSettings(void);
386
387static void bundleNavReviewStreamingChoice(bool confirm);
388static nbgl_layout_t *displayModalDetails(const nbgl_warningDetails_t *details, uint8_t token);
389static void displaySecurityReport(uint32_t set);
390static void displayCustomizedSecurityReport(const nbgl_warningDetails_t *details);
391static void displayInitialWarning(void);
392static void useCaseReview(nbgl_operationType_t operationType,
393 const nbgl_contentTagValueList_t *tagValueList,
394 const nbgl_icon_details_t *icon,
395 const char *reviewTitle,
396 const char *reviewSubTitle,
397 const char *finishTitle,
398 const nbgl_tipBox_t *tipBox,
399 nbgl_choiceCallback_t choiceCallback,
400 bool isLight,
401 bool playNotifSound);
402static void useCaseReviewStreamingStart(nbgl_operationType_t operationType,
403 const nbgl_icon_details_t *icon,
404 const char *reviewTitle,
405 const char *reviewSubTitle,
406 nbgl_choiceCallback_t choiceCallback,
407 bool playNotifSound);
408static void useCaseHomeExt(const char *appName,
409 const nbgl_icon_details_t *appIcon,
410 const char *tagline,
411 bool withSettings,
412 nbgl_homeAction_t *homeAction,
413 nbgl_callback_t topRightCallback,
414 nbgl_callback_t quitCallback);
415static void displayDetails(const char *tag, const char *value, bool wrapping);
416
417static void reset_callbacks_and_context(void)
418{
419 onQuit = NULL;
420 onContinue = NULL;
421 onAction = NULL;
422 onNav = NULL;
423 onControls = NULL;
424 onContentAction = NULL;
425 onChoice = NULL;
426 memset(&genericContext, 0, sizeof(genericContext));
427}
428
429// Helper to set genericContext page info
430static void genericContextSetPageInfo(uint8_t pageIdx, uint8_t nbElements, bool flag)
431{
432 uint8_t pageData = SET_PAGE_NB_ELEMENTS(nbElements) + SET_PAGE_FLAG(flag);
433
434 genericContextPagesInfo[pageIdx / PAGES_PER_UINT8]
435 &= ~(0x0F << ((pageIdx % PAGES_PER_UINT8) * PAGE_DATA_BITS));
436 genericContextPagesInfo[pageIdx / PAGES_PER_UINT8]
437 |= pageData << ((pageIdx % PAGES_PER_UINT8) * PAGE_DATA_BITS);
438}
439
440// Helper to get genericContext page info
441static void genericContextGetPageInfo(uint8_t pageIdx, uint8_t *nbElements, bool *flag)
442{
443 uint8_t pageData = genericContextPagesInfo[pageIdx / PAGES_PER_UINT8]
444 >> ((pageIdx % PAGES_PER_UINT8) * PAGE_DATA_BITS);
445 if (nbElements != NULL) {
446 *nbElements = GET_PAGE_NB_ELEMENTS(pageData);
447 }
448 if (flag != NULL) {
449 *flag = GET_PAGE_FLAG(pageData);
450 }
451}
452
453// Helper to set modalContext page info
454static void modalContextSetPageInfo(uint8_t pageIdx, uint8_t nbElements)
455{
456 uint8_t pageData = SET_PAGE_NB_ELEMENTS(nbElements);
457
458 modalContextPagesInfo[pageIdx / PAGES_PER_UINT8]
459 &= ~(0x0F << ((pageIdx % PAGES_PER_UINT8) * PAGE_DATA_BITS));
460 modalContextPagesInfo[pageIdx / PAGES_PER_UINT8]
461 |= pageData << ((pageIdx % PAGES_PER_UINT8) * PAGE_DATA_BITS);
462}
463
464// Helper to get modalContext page info
465static void modalContextGetPageInfo(uint8_t pageIdx, uint8_t *nbElements)
466{
467 uint8_t pageData = modalContextPagesInfo[pageIdx / PAGES_PER_UINT8]
468 >> ((pageIdx % PAGES_PER_UINT8) * PAGE_DATA_BITS);
469 if (nbElements != NULL) {
470 *nbElements = GET_PAGE_NB_ELEMENTS(pageData);
471 }
472}
473
474// Simple helper to get the number of elements inside a nbgl_content_t
475static uint8_t getContentNbElement(const nbgl_content_t *content)
476{
477 switch (content->type) {
478 case TAG_VALUE_LIST:
479 return content->content.tagValueList.nbPairs;
482 case SWITCHES_LIST:
483 return content->content.switchesList.nbSwitches;
484 case INFOS_LIST:
485 return content->content.infosList.nbInfos;
486 case CHOICES_LIST:
487 return content->content.choicesList.nbChoices;
488 case BARS_LIST:
489 return content->content.barsList.nbBars;
490 default:
491 return 1;
492 }
493}
494
495// Helper to retrieve the content inside a nbgl_genericContents_t using
496// either the contentsList or using the contentGetterCallback
497static const nbgl_content_t *getContentAtIdx(const nbgl_genericContents_t *genericContents,
498 int8_t contentIdx,
499 nbgl_content_t *content)
500{
501 if (contentIdx < 0 || contentIdx >= genericContents->nbContents) {
502 LOG_DEBUG(USE_CASE_LOGGER, "No content available at %d\n", contentIdx);
503 return NULL;
504 }
505
506 if (genericContents->callbackCallNeeded) {
507 // Retrieve content through callback, but first memset the content.
508 memset(content, 0, sizeof(nbgl_content_t));
509 genericContents->contentGetterCallback(contentIdx, content);
510 return content;
511 }
512 else {
513 // Retrieve content through list
514 return PIC(&genericContents->contentsList[contentIdx]);
515 }
516}
517
518static void prepareNavInfo(bool isReview, uint8_t nbPages, const char *rejectText)
519{
520 memset(&navInfo, 0, sizeof(navInfo));
521
522 navInfo.nbPages = nbPages;
523 navInfo.tuneId = TUNE_TAP_CASUAL;
524 navInfo.progressIndicator = false;
525 navInfo.navType = NAV_WITH_BUTTONS;
526
527 if (isReview == false) {
528 navInfo.navWithButtons.navToken = NAV_TOKEN;
529 navInfo.navWithButtons.backButton = true;
530 }
531 else {
532 navInfo.quitToken = REJECT_TOKEN;
533 navInfo.navWithButtons.quitText = rejectText;
534 navInfo.navWithButtons.navToken = NAV_TOKEN;
536 = ((navType == STREAMING_NAV) && (nbPages < 2)) ? false : true;
537 navInfo.navWithButtons.visiblePageIndicator = (navType != STREAMING_NAV);
538 }
539}
540
541static void prepareReviewFirstPage(nbgl_contentCenter_t *contentCenter,
542 const nbgl_icon_details_t *icon,
543 const char *reviewTitle,
544 const char *reviewSubTitle)
545{
546 contentCenter->icon = icon;
547 contentCenter->title = reviewTitle;
548 contentCenter->description = reviewSubTitle;
549 contentCenter->subText = "Swipe to review";
550 contentCenter->smallTitle = NULL;
551 contentCenter->iconHug = 0;
552 contentCenter->padding = false;
553 contentCenter->illustrType = ICON_ILLUSTRATION;
554}
555
556static const char *getFinishTitle(nbgl_operationType_t operationType, const char *finishTitle)
557{
558 if (finishTitle != NULL) {
559 return finishTitle;
560 }
561 switch (operationType & REAL_TYPE_MASK) {
562 case TYPE_TRANSACTION:
563 return "Sign transaction";
564 case TYPE_MESSAGE:
565 return "Sign message";
566 default:
567 return "Sign operation";
568 }
569}
570
571static void prepareReviewLastPage(nbgl_operationType_t operationType,
572 nbgl_contentInfoLongPress_t *infoLongPress,
573 const nbgl_icon_details_t *icon,
574 const char *finishTitle)
575{
576 infoLongPress->text = getFinishTitle(operationType, finishTitle);
577 infoLongPress->icon = icon;
578 infoLongPress->longPressText = "Hold to sign";
579 infoLongPress->longPressToken = CONFIRM_TOKEN;
580}
581
582static void prepareReviewLightLastPage(nbgl_operationType_t operationType,
583 nbgl_contentInfoButton_t *infoButton,
584 const nbgl_icon_details_t *icon,
585 const char *finishTitle)
586{
587 infoButton->text = getFinishTitle(operationType, finishTitle);
588 infoButton->icon = icon;
589 if (operationType & ADDRESS_BOOK_OPERATION) {
590 infoButton->buttonText = "Confirm";
591 }
592 else {
593 infoButton->buttonText = "Approve";
594 }
595 infoButton->buttonToken = CONFIRM_TOKEN;
596}
597
598static const char *getRejectReviewText(nbgl_operationType_t operationType)
599{
600 if (operationType & ADDRESS_BOOK_OPERATION) {
601 return "Cancel";
602 }
603 return "Reject";
604}
605
606// function called when navigating (or exiting) modal details pages
607// or when skip choice is displayed
608static void pageModalCallback(int token, uint8_t index)
609{
610 LOG_DEBUG(USE_CASE_LOGGER, "pageModalCallback, token = %d, index = %d\n", token, index);
611
612 if (token == INFOS_TIP_BOX_TOKEN) {
613 // the icon next to info type alias has been touched
614 displayFullValuePage(activeTipBox.infos.infoTypes[index],
615 activeTipBox.infos.infoContents[index],
616 &activeTipBox.infos.infoExtensions[index]);
617 return;
618 }
619 else if (token == INFO_ALIAS_TOKEN) {
620 // the icon next to info type alias has been touched, but coming from review
621 displayFullValuePage(genericContext.currentInfos->infoTypes[index],
622 genericContext.currentInfos->infoContents[index],
623 &genericContext.currentInfos->infoExtensions[index]);
624 return;
625 }
626 nbgl_pageRelease(modalPageContext);
627 modalPageContext = NULL;
628 if (token == NAV_TOKEN) {
629 if (index == EXIT_PAGE) {
630 // redraw the background layer
632 nbgl_refresh();
633 }
634 else {
635 displayDetailsPage(index, false);
636 }
637 }
638 if (token == MODAL_NAV_TOKEN) {
639 if (index == EXIT_PAGE) {
640 // redraw the background layer
642 nbgl_refresh();
643 }
644 else {
645 displayTagValueListModalPage(index, false);
646 }
647 }
648 else if (token == QUIT_TOKEN) {
649 // redraw the background layer
651 nbgl_refresh();
652 }
653 else if (token == QUIT_TIPBOX_MODAL_TOKEN) {
654 // if in "warning context", go back to security report
655 if (reviewWithWarnCtx.securityReportLevel == 2) {
656 reviewWithWarnCtx.securityReportLevel = 1;
657 displaySecurityReport(reviewWithWarnCtx.warning->predefinedSet);
658 }
659 else {
660 // redraw the background layer
662 nbgl_refresh();
663 }
664 }
665 else if (token == SKIP_TOKEN) {
666 if (index == 0) {
667 // display the last forward only review page, whatever it is
668 displayGenericContextPage(LAST_PAGE_FOR_REVIEW, true);
669 }
670 else {
671 // display background, which should be the page where skip has been touched
673 nbgl_refresh();
674 }
675 }
676 else if (token == CHOICE_TOKEN) {
677 if (index == 0) {
678 if (onModalConfirm != NULL) {
679 onModalConfirm();
680 onModalConfirm = NULL;
681 }
682 }
683 else {
684 // display background, which should be the page where skip has been touched
686 nbgl_refresh();
687 }
688 }
689}
690
691// generic callback for all pages except modal
692static void pageCallback(int token, uint8_t index)
693{
694 LOG_DEBUG(USE_CASE_LOGGER, "pageCallback, token = %d, index = %d\n", token, index);
695 if (token == QUIT_TOKEN) {
696 if (onQuit != NULL) {
697 onQuit();
698 }
699 }
700 else if (token == CONTINUE_TOKEN) {
701 if (onContinue != NULL) {
702 onContinue();
703 }
704 }
705 else if (token == CHOICE_TOKEN) {
706 if (onChoice != NULL) {
707 onChoice((index == 0) ? true : false);
708 }
709 }
710 else if (token == ACTION_BUTTON_TOKEN) {
711 if ((index == 0) && (onAction != NULL)) {
712 onAction();
713 }
714 else if ((index == 1) && (onQuit != NULL)) {
715 onQuit();
716 }
717 }
718#ifdef NBGL_QRCODE
719 else if (token == ADDRESS_QRCODE_BUTTON_TOKEN) {
720 displayAddressQRCode();
721 }
722#endif // NBGL_QRCODE
723 else if (token == CONFIRM_TOKEN) {
724 if (onChoice != NULL) {
725 onChoice(true);
726 }
727 }
728 else if (token == REJECT_TOKEN) {
729 if (onChoice != NULL) {
730 onChoice(false);
731 }
732 }
733 else if (token == DETAILS_BUTTON_TOKEN) {
734 displayDetails(genericContext.detailsItem,
735 genericContext.detailsvalue,
736 genericContext.detailsWrapping);
737 }
738 else if (token == NAV_TOKEN) {
739 if (index == EXIT_PAGE) {
740 if (onQuit != NULL) {
741 onQuit();
742 }
743 }
744 else {
745 if (navType == GENERIC_NAV || navType == STREAMING_NAV) {
746 displayGenericContextPage(index, false);
747 }
748 else if (navType == REVIEW_NAV) {
749 displayReviewPage(index, false);
750 }
751 else {
752 displaySettingsPage(index, false);
753 }
754 }
755 }
756 else if (token == NEXT_TOKEN) {
757 if (onNav != NULL) {
758 displayReviewPage(navInfo.activePage + 1, false);
759 }
760 else {
761 displayGenericContextPage(navInfo.activePage + 1, false);
762 }
763 }
764 else if (token == BACK_TOKEN) {
765 if (onNav != NULL) {
766 displayReviewPage(navInfo.activePage - 1, true);
767 }
768 else {
769 displayGenericContextPage(navInfo.activePage - 1, false);
770 }
771 }
772 else if (token == SKIP_TOKEN) {
773 // display a modal warning to confirm skip
774 displaySkipWarning();
775 }
776 else if (token == VALUE_ALIAS_TOKEN) {
777 // the icon next to value alias has been touched
778 const nbgl_contentTagValue_t *pair;
779 if (genericContext.currentPairs != NULL) {
780 pair = &genericContext.currentPairs[genericContext.currentElementIdx + index];
781 }
782 else {
783 pair = genericContext.currentCallback(genericContext.currentElementIdx + index);
784 }
785 displayFullValuePage(pair->item, pair->value, pair->extension);
786 }
787 else if (token == BLIND_WARNING_TOKEN) {
788 reviewWithWarnCtx.isIntro = false;
789 reviewWithWarnCtx.warning = NULL;
790 displaySecurityReport(1 << BLIND_SIGNING_WARN);
791 }
792 else if (token == WARNING_BUTTON_TOKEN) {
793 // top-right button, display the security modal
794 reviewWithWarnCtx.securityReportLevel = 1;
795 // if preset is used
796 if (reviewWithWarnCtx.warning->predefinedSet) {
797 displaySecurityReport(reviewWithWarnCtx.warning->predefinedSet);
798 }
799 else {
800 // use customized warning
801 if (reviewWithWarnCtx.isIntro) {
802 displayCustomizedSecurityReport(reviewWithWarnCtx.warning->introDetails);
803 }
804 else {
805 displayCustomizedSecurityReport(reviewWithWarnCtx.warning->reviewDetails);
806 }
807 }
808 }
809 else if (token == TIP_BOX_TOKEN) {
810 // if warning context is valid and if W3C directly display same as top-right
811 if (genericContext.validWarningCtx && (reviewWithWarnCtx.warning->predefinedSet != 0)) {
812 reviewWithWarnCtx.securityReportLevel = 1;
813 displaySecurityReport(reviewWithWarnCtx.warning->predefinedSet);
814 }
815 else {
816 displayInfosListModal(activeTipBox.modalTitle, &activeTipBox.infos, false);
817 }
818 }
819 else { // probably a control provided by caller
820 if (onContentAction != NULL) {
821 onContentAction(token, index, navInfo.activePage);
822 }
823 if (onControls != NULL) {
824 onControls(token, index);
825 }
826 }
827}
828
829// callback used for confirmation
830static void tickerCallback(void)
831{
832 nbgl_pageRelease(pageContext);
833 if (onQuit != NULL) {
834 onQuit();
835 }
836}
837
838// function used to display the current page in review
839static void displaySettingsPage(uint8_t page, bool forceFullRefresh)
840{
841 nbgl_pageContent_t content = {0};
842
843 if (onNav == NULL) {
844 LOG_WARN(USE_CASE_LOGGER, "displaySettingsPage(): onNav callback is NULL\n");
845 return;
846 }
847 if (onNav(page, &content) == false) {
848 return;
849 }
850
851 // override some fields
852 content.title = pageTitle;
853 content.isTouchableTitle = true;
854 content.titleToken = QUIT_TOKEN;
855 content.tuneId = TUNE_TAP_CASUAL;
856
857 navInfo.activePage = page;
858 pageContext = nbgl_pageDrawGenericContent(&pageCallback, &navInfo, &content);
859
860 if (forceFullRefresh) {
862 }
863 else {
865 }
866}
867
868// function used to display the current page in review
869static void displayReviewPage(uint8_t page, bool forceFullRefresh)
870{
871 nbgl_pageContent_t content = {0};
872
873 // ensure the page is valid
874 if ((navInfo.nbPages != 0) && (page >= (navInfo.nbPages))) {
875 return;
876 }
877 navInfo.activePage = page;
878 if (onNav == NULL) {
879 LOG_WARN(USE_CASE_LOGGER, "displayReviewPage(): onNav callback is NULL\n");
880 return;
881 }
882 if (onNav(navInfo.activePage, &content) == false) {
883 return;
884 }
885
886 // override some fields
887 content.title = NULL;
888 content.isTouchableTitle = false;
889 content.tuneId = TUNE_TAP_CASUAL;
890
891 if (content.type == INFO_LONG_PRESS) { // last page
892 // for forward only review without known length...
893 // if we don't do that we cannot remove the '>' in the navigation bar at the last page
894 navInfo.nbPages = navInfo.activePage + 1;
895 content.infoLongPress.longPressToken = CONFIRM_TOKEN;
896 if (forwardNavOnly) {
897 // remove the "Skip" button
898 navInfo.skipText = NULL;
899 }
900 }
901
902 // override smallCaseForValue for tag/value types to false
903 if (content.type == TAG_VALUE_DETAILS) {
905 // the maximum displayable number of lines for value is NB_MAX_LINES_IN_REVIEW (without More
906 // button)
909 }
910 else if (content.type == TAG_VALUE_LIST) {
911 content.tagValueList.smallCaseForValue = false;
912 }
913 else if (content.type == TAG_VALUE_CONFIRM) {
915 // use confirm token for black button
916 content.tagValueConfirm.confirmationToken = CONFIRM_TOKEN;
917 }
918
919 pageContext = nbgl_pageDrawGenericContent(&pageCallback, &navInfo, &content);
920
921 if (forceFullRefresh) {
923 }
924 else {
926 }
927}
928
929// Helper that does the computing of which nbgl_content_t and which element in the content should be
930// displayed for the next generic context navigation page
931static const nbgl_content_t *genericContextComputeNextPageParams(uint8_t pageIdx,
932 nbgl_content_t *content,
933 uint8_t *p_nbElementsInNextPage,
934 bool *p_flag)
935{
936 int8_t nextContentIdx = genericContext.currentContentIdx;
937 int16_t nextElementIdx = genericContext.currentElementIdx;
938 uint8_t nbElementsInNextPage;
939
940 // Retrieve info on the next page
941 genericContextGetPageInfo(pageIdx, &nbElementsInNextPage, p_flag);
942 *p_nbElementsInNextPage = nbElementsInNextPage;
943
944 // Handle forward navigation:
945 // add to current index the number of pairs of the currently active page
946 if (pageIdx > navInfo.activePage) {
947 uint8_t nbElementsInCurrentPage;
948
949 genericContextGetPageInfo(navInfo.activePage, &nbElementsInCurrentPage, NULL);
950 nextElementIdx += nbElementsInCurrentPage;
951
952 // Handle case where the content to be displayed is in the next content
953 // In such case start at element index 0.
954 // If currentContentElementNb == 0, means not initialized, so skip
955 if ((nextElementIdx >= genericContext.currentContentElementNb)
956 && (genericContext.currentContentElementNb > 0)) {
957 nextContentIdx += 1;
958 nextElementIdx = 0;
959 }
960 }
961
962 // Handle backward navigation:
963 // add to current index the number of pairs of the currently active page
964 if (pageIdx < navInfo.activePage) {
965 // Backward navigation: remove to current index the number of pairs of the current page
966 nextElementIdx -= nbElementsInNextPage;
967
968 // Handle case where the content to be displayed is in the previous content
969 // In such case set a negative number as element index so that it is handled
970 // later once the previous content is accessible so that its elements number
971 // can be retrieved.
972 if (nextElementIdx < 0) {
973 nextContentIdx -= 1;
974 nextElementIdx = -nbElementsInNextPage;
975 }
976 }
977
978 const nbgl_content_t *p_content;
979 // Retrieve next content
980 if ((nextContentIdx == -1) && (genericContext.hasStartingContent)) {
981 p_content = &STARTING_CONTENT;
982 }
983 else if ((nextContentIdx == genericContext.genericContents.nbContents)
984 && (genericContext.hasFinishingContent)) {
985 p_content = &FINISHING_CONTENT;
986 }
987 else {
988 p_content = getContentAtIdx(&genericContext.genericContents, nextContentIdx, content);
989
990 if (p_content == NULL) {
991 LOG_DEBUG(USE_CASE_LOGGER, "Fail to retrieve content\n");
992 return NULL;
993 }
994 }
995
996 // Handle cases where we are going to display data from a new content:
997 // - navigation to a previous or to the next content
998 // - First page display (genericContext.currentContentElementNb == 0)
999 //
1000 // In such case we need to:
1001 // - Update genericContext.currentContentIdx
1002 // - Update genericContext.currentContentElementNb
1003 // - Update onContentAction callback
1004 // - Update nextElementIdx in case it was previously not calculable
1005 if ((nextContentIdx != genericContext.currentContentIdx)
1006 || (genericContext.currentContentElementNb == 0)) {
1007 genericContext.currentContentIdx = nextContentIdx;
1008 genericContext.currentContentElementNb = getContentNbElement(p_content);
1009 onContentAction = PIC(p_content->contentActionCallback);
1010 if (nextElementIdx < 0) {
1011 nextElementIdx = genericContext.currentContentElementNb + nextElementIdx;
1012 }
1013 }
1014
1015 // Sanity check
1016 if ((nextElementIdx < 0) || (nextElementIdx >= genericContext.currentContentElementNb)) {
1018 "Invalid element index %d / %d\n",
1019 nextElementIdx,
1020 genericContext.currentContentElementNb);
1021 return NULL;
1022 }
1023
1024 // Update genericContext elements
1025 genericContext.currentElementIdx = nextElementIdx;
1026 navInfo.activePage = pageIdx;
1027
1028 return p_content;
1029}
1030
1031// Helper that generates a nbgl_pageContent_t to be displayed for the next generic context
1032// navigation page
1033static bool genericContextPreparePageContent(const nbgl_content_t *p_content,
1034 uint8_t nbElementsInPage,
1035 bool flag,
1036 nbgl_pageContent_t *pageContent)
1037{
1038 uint8_t nextElementIdx = genericContext.currentElementIdx;
1039
1040 pageContent->title = pageTitle;
1041 pageContent->isTouchableTitle = false;
1042 pageContent->titleToken = QUIT_TOKEN;
1043 pageContent->tuneId = TUNE_TAP_CASUAL;
1044
1045 pageContent->type = p_content->type;
1046 switch (pageContent->type) {
1047 case CENTERED_INFO:
1048 memcpy(&pageContent->centeredInfo,
1049 &p_content->content.centeredInfo,
1050 sizeof(pageContent->centeredInfo));
1051 break;
1052 case EXTENDED_CENTER:
1053 memcpy(&pageContent->extendedCenter,
1054 &p_content->content.extendedCenter,
1055 sizeof(pageContent->extendedCenter));
1056 break;
1057 case INFO_LONG_PRESS:
1058 memcpy(&pageContent->infoLongPress,
1059 &p_content->content.infoLongPress,
1060 sizeof(pageContent->infoLongPress));
1061 break;
1062
1063 case INFO_BUTTON:
1064 memcpy(&pageContent->infoButton,
1065 &p_content->content.infoButton,
1066 sizeof(pageContent->infoButton));
1067 break;
1068
1069 case TAG_VALUE_LIST: {
1070 nbgl_contentTagValueList_t *p_tagValueList = &pageContent->tagValueList;
1071
1072 // memorize pairs (or callback) for usage when alias is used
1073 genericContext.currentPairs = p_content->content.tagValueList.pairs;
1074 genericContext.currentCallback = p_content->content.tagValueList.callback;
1075
1076 if (flag) {
1077 // Flag can be set if the pair is too long to fit or because it needs
1078 // to be displayed as centered info.
1079
1080 // First retrieve the pair
1081 const nbgl_layoutTagValue_t *pair;
1082
1083 if (p_content->content.tagValueList.pairs != NULL) {
1084 pair = PIC(&p_content->content.tagValueList.pairs[nextElementIdx]);
1085 }
1086 else {
1087 pair = PIC(p_content->content.tagValueList.callback(nextElementIdx));
1088 }
1089
1090 if (pair->centeredInfo) {
1091 pageContent->type = EXTENDED_CENTER;
1092 prepareReviewFirstPage(&pageContent->extendedCenter.contentCenter,
1093 pair->valueIcon,
1094 pair->item,
1095 pair->value);
1096
1097 // Skip population of nbgl_contentTagValueList_t structure
1098 p_tagValueList = NULL;
1099 }
1100 else {
1101 // if the pair is too long to fit, we use a TAG_VALUE_DETAILS content
1102 pageContent->type = TAG_VALUE_DETAILS;
1103 pageContent->tagValueDetails.detailsButtonText = "More";
1104 pageContent->tagValueDetails.detailsButtonIcon = NULL;
1105 pageContent->tagValueDetails.detailsButtonToken = DETAILS_BUTTON_TOKEN;
1106
1107 p_tagValueList = &pageContent->tagValueDetails.tagValueList;
1108
1109 // Backup pair info for easy data access upon click on button
1110 genericContext.detailsItem = pair->item;
1111 genericContext.detailsvalue = pair->value;
1112 genericContext.detailsWrapping = p_content->content.tagValueList.wrapping;
1113 }
1114 }
1115
1116 if (p_tagValueList != NULL) {
1117 p_tagValueList->nbPairs = nbElementsInPage;
1118 p_tagValueList->token = p_content->content.tagValueList.token;
1119 if (p_content->content.tagValueList.pairs != NULL) {
1120 p_tagValueList->pairs
1121 = PIC(&p_content->content.tagValueList.pairs[nextElementIdx]);
1122 // parse pairs to check if any contains an alias for value
1123 for (uint8_t i = 0; i < nbElementsInPage; i++) {
1124 if (p_tagValueList->pairs[i].aliasValue) {
1125 p_tagValueList->token = VALUE_ALIAS_TOKEN;
1126 break;
1127 }
1128 }
1129 }
1130 else {
1131 p_tagValueList->pairs = NULL;
1132 p_tagValueList->callback = p_content->content.tagValueList.callback;
1133 p_tagValueList->startIndex = nextElementIdx;
1134 // parse pairs to check if any contains an alias for value
1135 for (uint8_t i = 0; i < nbElementsInPage; i++) {
1136 const nbgl_layoutTagValue_t *pair
1137 = PIC(p_content->content.tagValueList.callback(nextElementIdx + i));
1138 if (pair->aliasValue) {
1139 p_tagValueList->token = VALUE_ALIAS_TOKEN;
1140 break;
1141 }
1142 }
1143 }
1144 p_tagValueList->smallCaseForValue = false;
1146 p_tagValueList->hideEndOfLastLine = true;
1147 p_tagValueList->wrapping = p_content->content.tagValueList.wrapping;
1148 }
1149
1150 break;
1151 }
1152 case TAG_VALUE_CONFIRM: {
1153 nbgl_contentTagValueList_t *p_tagValueList;
1154 // only display a TAG_VALUE_CONFIRM if we are at the last page
1155 if ((nextElementIdx + nbElementsInPage)
1157 memcpy(&pageContent->tagValueConfirm,
1158 &p_content->content.tagValueConfirm,
1159 sizeof(pageContent->tagValueConfirm));
1160 p_tagValueList = &pageContent->tagValueConfirm.tagValueList;
1161 }
1162 else {
1163 // else display it as a TAG_VALUE_LIST: preserve list-level
1164 // settings (nbMaxLinesForValue, wrapping, smallCaseForValue,
1165 // hideEndOfLastLine, token...) so intermediate pages render
1166 // identically to the last page
1167 pageContent->type = TAG_VALUE_LIST;
1168 pageContent->tagValueList = p_content->content.tagValueConfirm.tagValueList;
1169 p_tagValueList = &pageContent->tagValueList;
1170 }
1171 p_tagValueList->nbPairs = nbElementsInPage;
1172 p_tagValueList->pairs
1173 = PIC(&p_content->content.tagValueConfirm.tagValueList.pairs[nextElementIdx]);
1174 // parse pairs to check if any contains an alias for value
1175 for (uint8_t i = 0; i < nbElementsInPage; i++) {
1176 if (p_tagValueList->pairs[i].aliasValue) {
1177 p_tagValueList->token = VALUE_ALIAS_TOKEN;
1178 break;
1179 }
1180 }
1181 // memorize pairs (or callback) for usage when alias is used
1182 genericContext.currentPairs = p_tagValueList->pairs;
1183 genericContext.currentCallback = p_tagValueList->callback;
1184 break;
1185 }
1186 case SWITCHES_LIST:
1187 pageContent->switchesList.nbSwitches = nbElementsInPage;
1188 pageContent->switchesList.switches
1189 = PIC(&p_content->content.switchesList.switches[nextElementIdx]);
1190 break;
1191 case INFOS_LIST:
1192 pageContent->infosList.nbInfos = nbElementsInPage;
1193 pageContent->infosList.infoTypes
1194 = PIC(&p_content->content.infosList.infoTypes[nextElementIdx]);
1195 pageContent->infosList.infoContents
1196 = PIC(&p_content->content.infosList.infoContents[nextElementIdx]);
1197 break;
1198 case CHOICES_LIST:
1199 memcpy(&pageContent->choicesList,
1200 &p_content->content.choicesList,
1201 sizeof(pageContent->choicesList));
1202 pageContent->choicesList.nbChoices = nbElementsInPage;
1203 pageContent->choicesList.names
1204 = PIC(&p_content->content.choicesList.names[nextElementIdx]);
1205 if ((p_content->content.choicesList.initChoice >= nextElementIdx)
1206 && (p_content->content.choicesList.initChoice
1207 < nextElementIdx + nbElementsInPage)) {
1208 pageContent->choicesList.initChoice
1209 = p_content->content.choicesList.initChoice - nextElementIdx;
1210 }
1211 else {
1212 pageContent->choicesList.initChoice = nbElementsInPage;
1213 }
1214 break;
1215 case BARS_LIST:
1216 pageContent->barsList.nbBars = nbElementsInPage;
1217 pageContent->barsList.barTexts
1218 = PIC(&p_content->content.barsList.barTexts[nextElementIdx]);
1219 pageContent->barsList.tokens = PIC(&p_content->content.barsList.tokens[nextElementIdx]);
1220 pageContent->barsList.tuneId = p_content->content.barsList.tuneId;
1221 break;
1222 default:
1223 LOG_DEBUG(USE_CASE_LOGGER, "Unsupported type %d\n", pageContent->type);
1224 return false;
1225 }
1226
1227 bool isFirstOrLastPage
1228 = ((p_content->type == CENTERED_INFO) || (p_content->type == EXTENDED_CENTER))
1229 || (p_content->type == INFO_LONG_PRESS);
1230 nbgl_operationType_t operationType
1231 = (navType == STREAMING_NAV)
1232 ? bundleNavContext.reviewStreaming.operationType
1233 : ((navType == GENERIC_NAV) ? bundleNavContext.review.operationType : 0);
1234
1235 // if first or last page of review and blind/risky operation, add the warning top-right button
1236 if (isFirstOrLastPage
1237 && (operationType & (BLIND_OPERATION | RISKY_OPERATION | NO_THREAT_OPERATION))) {
1238 // if issue is only Web3Checks "no threat", use privacy icon
1239 if ((operationType & NO_THREAT_OPERATION)
1240 && !(reviewWithWarnCtx.warning->predefinedSet & (1 << BLIND_SIGNING_WARN))) {
1241 pageContent->topRightIcon = &PRIVACY_ICON;
1242 }
1243 else {
1244 pageContent->topRightIcon = &WARNING_ICON;
1245 }
1246
1247 pageContent->topRightToken
1248 = (operationType & BLIND_OPERATION) ? BLIND_WARNING_TOKEN : WARNING_BUTTON_TOKEN;
1249 }
1250
1251 return true;
1252}
1253
1254// function used to display the current page in generic context navigation mode
1255static void displayGenericContextPage(uint8_t pageIdx, bool forceFullRefresh)
1256{
1257 // Retrieve next page parameters
1258 nbgl_content_t content = {0};
1259 uint8_t nbElementsInPage;
1260 bool flag;
1261 const nbgl_content_t *p_content = NULL;
1262
1263 if (navType == STREAMING_NAV) {
1264 if (pageIdx == LAST_PAGE_FOR_REVIEW) {
1265 if (bundleNavContext.reviewStreaming.skipCallback != NULL) {
1266 bundleNavContext.reviewStreaming.skipCallback();
1267 }
1268 return;
1269 }
1270 else if (pageIdx >= bundleNavContext.reviewStreaming.stepPageNb) {
1271 bundleNavReviewStreamingChoice(true);
1272 return;
1273 }
1274 }
1275 else {
1276 // if coming from Skip modal, let's say we are at the last page
1277 if (pageIdx == LAST_PAGE_FOR_REVIEW) {
1278 pageIdx = navInfo.nbPages - 1;
1279 }
1280 }
1281
1282 if (navInfo.activePage == pageIdx) {
1283 p_content
1284 = genericContextComputeNextPageParams(pageIdx, &content, &nbElementsInPage, &flag);
1285 }
1286 else if (navInfo.activePage < pageIdx) {
1287 // Support going more than one step forward.
1288 // It occurs when initializing a navigation on an arbitrary page
1289 for (int i = navInfo.activePage + 1; i <= pageIdx; i++) {
1290 p_content = genericContextComputeNextPageParams(i, &content, &nbElementsInPage, &flag);
1291 }
1292 }
1293 else {
1294 if (pageIdx - navInfo.activePage > 1) {
1295 // We don't support going more than one step backward as it doesn't occurs for now?
1296 LOG_DEBUG(USE_CASE_LOGGER, "Unsupported navigation\n");
1297 return;
1298 }
1299 p_content
1300 = genericContextComputeNextPageParams(pageIdx, &content, &nbElementsInPage, &flag);
1301 }
1302
1303 if (p_content == NULL) {
1304 return;
1305 }
1306 // if the operation is skippable
1307 if ((navType != STREAMING_NAV)
1308 && (bundleNavContext.review.operationType & SKIPPABLE_OPERATION)) {
1309 // only present the "Skip" header on actual tag/value pages
1310 if ((pageIdx > 0) && (pageIdx < (navInfo.nbPages - 1))) {
1311 navInfo.progressIndicator = false;
1312 navInfo.skipText = "Skip";
1313 navInfo.skipToken = SKIP_TOKEN;
1314 }
1315 else {
1316 navInfo.skipText = NULL;
1317 }
1318 }
1319
1320 // Create next page content
1321 nbgl_pageContent_t pageContent = {0};
1322 if (!genericContextPreparePageContent(p_content, nbElementsInPage, flag, &pageContent)) {
1323 return;
1324 }
1325
1326 pageContext = nbgl_pageDrawGenericContent(&pageCallback, &navInfo, &pageContent);
1327
1328 if (forceFullRefresh) {
1330 }
1331 else {
1333 }
1334}
1335
1336// from the current details context, return a pointer on the details at the given page
1337static const char *getDetailsPageAt(uint8_t detailsPage)
1338{
1339 uint8_t page = 0;
1340 const char *currentChar = detailsContext.value;
1341 while (page < detailsPage) {
1342 uint16_t nbLines
1343 = nbgl_getTextNbLinesInWidth(SMALL_BOLD_FONT, currentChar, AVAILABLE_WIDTH, false);
1344 if (nbLines > NB_MAX_LINES_IN_DETAILS) {
1345 uint16_t len;
1346 nbgl_getTextMaxLenInNbLines(SMALL_BOLD_FONT,
1347 currentChar,
1350 &len,
1351 detailsContext.wrapping);
1352 // don't start next page on a \n
1353 if (currentChar[len] == '\n') {
1354 len++;
1355 }
1356 else if (detailsContext.wrapping == false) {
1357 len -= 3;
1358 }
1359 currentChar = currentChar + len;
1360 }
1361 page++;
1362 }
1363 return currentChar;
1364}
1365
1366// function used to display the current page in details review mode
1367static void displayDetailsPage(uint8_t detailsPage, bool forceFullRefresh)
1368{
1369 static nbgl_layoutTagValue_t currentPair;
1370 nbgl_pageNavigationInfo_t info = {.activePage = detailsPage,
1371 .nbPages = detailsContext.nbPages,
1372 .navType = NAV_WITH_BUTTONS,
1373 .quitToken = QUIT_TOKEN,
1374 .navWithButtons.navToken = NAV_TOKEN,
1375 .navWithButtons.quitButton = true,
1376 .navWithButtons.backButton = true,
1377 .navWithButtons.quitText = NULL,
1378 .progressIndicator = false,
1379 .tuneId = TUNE_TAP_CASUAL};
1381 .topRightIcon = NULL,
1382 .tagValueList.nbPairs = 1,
1383 .tagValueList.pairs = &currentPair,
1384 .tagValueList.smallCaseForValue = true,
1385 .tagValueList.wrapping = detailsContext.wrapping};
1386
1387 if (modalPageContext != NULL) {
1388 nbgl_pageRelease(modalPageContext);
1389 }
1390 currentPair.item = detailsContext.tag;
1391 // if move backward or first page
1392 if (detailsPage <= detailsContext.currentPage) {
1393 // recompute current start from beginning
1394 currentPair.value = getDetailsPageAt(detailsPage);
1395 forceFullRefresh = true;
1396 }
1397 // else move forward
1398 else {
1399 currentPair.value = detailsContext.nextPageStart;
1400 }
1401 detailsContext.currentPage = detailsPage;
1402 uint16_t nbLines = nbgl_getTextNbLinesInWidth(
1403 SMALL_BOLD_FONT, currentPair.value, AVAILABLE_WIDTH, detailsContext.wrapping);
1404
1405 if (nbLines > NB_MAX_LINES_IN_DETAILS) {
1406 uint16_t len;
1407 nbgl_getTextMaxLenInNbLines(SMALL_BOLD_FONT,
1408 currentPair.value,
1411 &len,
1412 detailsContext.wrapping);
1414 // don't start next page on a \n
1415 if (currentPair.value[len] == '\n') {
1416 len++;
1417 }
1418 else if (!detailsContext.wrapping) {
1420 len -= 3;
1421 }
1422 // memorize next position to save processing
1423 detailsContext.nextPageStart = currentPair.value + len;
1424 // use special feature to keep only NB_MAX_LINES_IN_DETAILS lines and replace the last 3
1425 // chars by "...", only if the next char to display in next page is not '\n'
1427 }
1428 else {
1429 detailsContext.nextPageStart = NULL;
1430 content.tagValueList.nbMaxLinesForValue = 0;
1431 }
1432 if (info.nbPages == 1) {
1433 // if only one page, no navigation bar, and use a footer instead
1434 info.navWithButtons.quitText = "Close";
1435 }
1436 modalPageContext = nbgl_pageDrawGenericContentExt(&pageModalCallback, &info, &content, true);
1437
1438 if (forceFullRefresh) {
1440 }
1441 else {
1443 }
1444}
1445
1446// function used to display the content of a full value, when touching an alias of a tag/value pair
1447static void displayFullValuePage(const char *backText,
1448 const char *aliasText,
1449 const nbgl_contentValueExt_t *extension)
1450{
1451 const char *modalTitle
1452 = (extension->backText != NULL) ? PIC(extension->backText) : PIC(backText);
1453 if (extension->aliasType == INFO_LIST_ALIAS) {
1454 genericContext.currentInfos = extension->infolist;
1455 displayInfosListModal(modalTitle, extension->infolist, true);
1456 }
1457 else if (extension->aliasType == TAG_VALUE_LIST_ALIAS) {
1458 genericContext.currentTagValues = extension->tagValuelist;
1459 displayTagValueListModal(extension->tagValuelist);
1460 }
1461 else {
1462 nbgl_layoutDescription_t layoutDescription = {.modal = true,
1463 .withLeftBorder = true,
1464 .onActionCallback = &modalLayoutTouchCallback,
1465 .tapActionText = NULL};
1466 nbgl_layoutHeader_t headerDesc = {.type = HEADER_BACK_AND_TEXT,
1467 .separationLine = false,
1468 .backAndText.token = 0,
1469 .backAndText.tuneId = TUNE_TAP_CASUAL,
1470 .backAndText.text = modalTitle};
1471 genericContext.modalLayout = nbgl_layoutGet(&layoutDescription);
1472 // add header with the tag part of the pair, to go back
1473 nbgl_layoutAddHeader(genericContext.modalLayout, &headerDesc);
1474 // add either QR Code or full value text
1475 if (extension->aliasType == QR_CODE_ALIAS) {
1476#ifdef NBGL_QRCODE
1477 nbgl_layoutQRCode_t qrCode
1478 = {.url = extension->fullValue,
1479 .text1 = (extension->title != NULL) ? extension->title : extension->fullValue,
1480 .text2 = extension->explanation,
1481 .centered = true,
1482 .offsetY = 0};
1483
1484 nbgl_layoutAddQRCode(genericContext.modalLayout, &qrCode);
1485#endif // NBGL_QRCODE
1486 }
1487 else {
1488 nbgl_layoutTextContent_t content = {0};
1489 content.title = aliasText;
1490 if (extension->aliasType == ENS_ALIAS) {
1491 content.info = "ENS names are resolved by Ledger backend.";
1492 }
1493 else if (extension->aliasType == ADDRESS_BOOK_ALIAS) {
1494 if (extension->aliasSubName != NULL) {
1495 content.descriptions[content.nbDescriptions] = extension->aliasSubName;
1496 content.nbDescriptions++;
1497 }
1498 }
1499 else {
1500 content.info = extension->explanation;
1501 }
1502 // add full value text
1503 content.descriptions[content.nbDescriptions] = extension->fullValue;
1504 content.nbDescriptions++;
1505 // add trusted name if present (combined Address Book+Trusted Name case)
1506 if ((extension->aliasType == ADDRESS_BOOK_ALIAS) && (extension->explanation != NULL)) {
1507 content.descriptions[content.nbDescriptions] = extension->explanation;
1508 content.nbDescriptions++;
1509 }
1510 nbgl_layoutAddTextContent(genericContext.modalLayout, &content);
1511 }
1512 // draw & refresh
1513 nbgl_layoutDraw(genericContext.modalLayout);
1514 nbgl_refresh();
1515 }
1516}
1517
1518// function used to display the modal containing tip-box infos
1519static void displayInfosListModal(const char *modalTitle,
1520 const nbgl_contentInfoList_t *infos,
1521 bool fromReview)
1522{
1524 .nbPages = 1,
1525 .navType = NAV_WITH_BUTTONS,
1526 .quitToken = QUIT_TIPBOX_MODAL_TOKEN,
1527 .navWithButtons.navToken = NAV_TOKEN,
1528 .navWithButtons.quitButton = false,
1529 .navWithButtons.backButton = true,
1530 .navWithButtons.quitText = NULL,
1531 .progressIndicator = false,
1532 .tuneId = TUNE_TAP_CASUAL};
1533 nbgl_pageContent_t content
1534 = {.type = INFOS_LIST,
1535 .topRightIcon = NULL,
1536 .infosList.nbInfos = infos->nbInfos,
1537 .infosList.withExtensions = infos->withExtensions,
1538 .infosList.infoTypes = infos->infoTypes,
1539 .infosList.infoContents = infos->infoContents,
1540 .infosList.infoExtensions = infos->infoExtensions,
1541 .infosList.token = fromReview ? INFO_ALIAS_TOKEN : INFOS_TIP_BOX_TOKEN,
1542 .title = modalTitle,
1543 .titleToken = QUIT_TIPBOX_MODAL_TOKEN,
1544 .tuneId = TUNE_TAP_CASUAL};
1545
1546 if (modalPageContext != NULL) {
1547 nbgl_pageRelease(modalPageContext);
1548 }
1549 modalPageContext = nbgl_pageDrawGenericContentExt(&pageModalCallback, &info, &content, true);
1550
1552}
1553
1554// function used to display the modal containing alias tag-value pairs
1555static void displayTagValueListModalPage(uint8_t pageIdx, bool forceFullRefresh)
1556{
1557 nbgl_pageNavigationInfo_t info = {.activePage = pageIdx,
1558 .nbPages = detailsContext.nbPages,
1559 .navType = NAV_WITH_BUTTONS,
1560 .quitToken = QUIT_TOKEN,
1561 .navWithButtons.navToken = MODAL_NAV_TOKEN,
1562 .navWithButtons.quitButton = true,
1563 .navWithButtons.backButton = true,
1564 .navWithButtons.quitText = NULL,
1565 .progressIndicator = false,
1566 .tuneId = TUNE_TAP_CASUAL};
1568 .topRightIcon = NULL,
1569 .tagValueList.smallCaseForValue = true,
1570 .tagValueList.wrapping = detailsContext.wrapping};
1571 uint8_t nbElementsInPage;
1572
1573 // if first page or forward
1574 if (detailsContext.currentPage <= pageIdx) {
1575 modalContextGetPageInfo(pageIdx, &nbElementsInPage);
1576 }
1577 else {
1578 // backward direction
1579 modalContextGetPageInfo(pageIdx + 1, &nbElementsInPage);
1580 detailsContext.currentPairIdx -= nbElementsInPage;
1581 modalContextGetPageInfo(pageIdx, &nbElementsInPage);
1582 detailsContext.currentPairIdx -= nbElementsInPage;
1583 }
1584 detailsContext.currentPage = pageIdx;
1585
1586 content.tagValueList.pairs
1587 = &genericContext.currentTagValues->pairs[detailsContext.currentPairIdx];
1588 content.tagValueList.nbPairs = nbElementsInPage;
1589 detailsContext.currentPairIdx += nbElementsInPage;
1590 if (info.nbPages == 1) {
1591 // if only one page, no navigation bar, and use a footer instead
1592 info.navWithButtons.quitText = "Close";
1593 }
1594
1595 if (modalPageContext != NULL) {
1596 nbgl_pageRelease(modalPageContext);
1597 }
1598 modalPageContext = nbgl_pageDrawGenericContentExt(&pageModalCallback, &info, &content, true);
1599
1600 if (forceFullRefresh) {
1602 }
1603 else {
1605 }
1606}
1607
1608#ifdef NBGL_QRCODE
1609static void displayAddressQRCode(void)
1610{
1611 // display the address as QR Code
1612 nbgl_layoutDescription_t layoutDescription = {.modal = true,
1613 .withLeftBorder = true,
1614 .onActionCallback = &modalLayoutTouchCallback,
1615 .tapActionText = NULL};
1616 nbgl_layoutHeader_t headerDesc = {
1617 .type = HEADER_EMPTY, .separationLine = false, .emptySpace.height = SMALL_CENTERING_HEADER};
1618 nbgl_layoutQRCode_t qrCode = {.url = addressConfirmationContext.tagValuePairs[0].value,
1619 .text1 = NULL,
1620 .centered = true,
1621 .offsetY = 0};
1622
1623 addressConfirmationContext.modalLayout = nbgl_layoutGet(&layoutDescription);
1624 // add empty header for better look
1625 nbgl_layoutAddHeader(addressConfirmationContext.modalLayout, &headerDesc);
1626 // compute nb lines to check whether it shall be shorten (max is 3 lines)
1627 uint16_t nbLines = nbgl_getTextNbLinesInWidth(SMALL_REGULAR_FONT,
1628 addressConfirmationContext.tagValuePairs[0].value,
1630 false);
1631
1632 if (nbLines <= QRCODE_NB_MAX_LINES) {
1633 qrCode.text2 = addressConfirmationContext.tagValuePairs[0].value; // in gray
1634 }
1635 else {
1636 // only keep beginning and end of text, and add ... in the middle
1637 nbgl_textReduceOnNbLines(SMALL_REGULAR_FONT,
1638 addressConfirmationContext.tagValuePairs[0].value,
1640 QRCODE_NB_MAX_LINES,
1641 reducedAddress,
1642 QRCODE_REDUCED_ADDR_LEN);
1643 qrCode.text2 = reducedAddress; // in gray
1644 }
1645
1646 nbgl_layoutAddQRCode(addressConfirmationContext.modalLayout, &qrCode);
1647
1648 nbgl_layoutAddFooter(
1649 addressConfirmationContext.modalLayout, "Close", DISMISS_QR_TOKEN, TUNE_TAP_CASUAL);
1650 nbgl_layoutDraw(addressConfirmationContext.modalLayout);
1651 nbgl_refresh();
1652}
1653
1654#endif // NBGL_QRCODE
1655
1656// called when header is touched on modal page, to dismiss it
1657static void modalLayoutTouchCallback(int token, uint8_t index)
1658{
1659 UNUSED(index);
1660 if (token == DISMISS_QR_TOKEN) {
1661 // dismiss modal
1662 nbgl_layoutRelease(addressConfirmationContext.modalLayout);
1663 addressConfirmationContext.modalLayout = NULL;
1665 }
1666 else if (token == DISMISS_WARNING_TOKEN) {
1667 // dismiss modal
1668 nbgl_layoutRelease(reviewWithWarnCtx.modalLayout);
1669 // if already at first level, simply redraw the background
1670 if (reviewWithWarnCtx.securityReportLevel <= 1) {
1671 reviewWithWarnCtx.modalLayout = NULL;
1673 }
1674 else {
1675 // We are at level 2 of warning modal, so re-display the level 1
1676 reviewWithWarnCtx.securityReportLevel = 1;
1677 // if preset is used
1678 if (reviewWithWarnCtx.warning->predefinedSet) {
1679 displaySecurityReport(reviewWithWarnCtx.warning->predefinedSet);
1680 }
1681 else {
1682 // use customized warning
1683 const nbgl_warningDetails_t *details
1684 = (reviewWithWarnCtx.isIntro) ? reviewWithWarnCtx.warning->introDetails
1685 : reviewWithWarnCtx.warning->reviewDetails;
1686 displayCustomizedSecurityReport(details);
1687 }
1688 return;
1689 }
1690 }
1691 else if (token == DISMISS_DETAILS_TOKEN) {
1692 // dismiss modal
1693 nbgl_layoutRelease(choiceWithDetailsCtx.modalLayout);
1694 // if already at first level, simply redraw the background
1695 if (choiceWithDetailsCtx.level <= 1) {
1696 choiceWithDetailsCtx.modalLayout = NULL;
1698 }
1699 else {
1700 // We are at level 2 of details modal, so re-display the level 1
1701 choiceWithDetailsCtx.level = 1;
1702 choiceWithDetailsCtx.modalLayout
1703 = displayModalDetails(choiceWithDetailsCtx.details, DISMISS_DETAILS_TOKEN);
1704 }
1705 }
1706 else if ((token >= FIRST_WARN_BAR_TOKEN) && (token <= LAST_WARN_BAR_TOKEN)) {
1707 if (sharedContext.usage == SHARE_CTX_REVIEW_WITH_WARNING) {
1708 // dismiss modal before creating a new one
1709 nbgl_layoutRelease(reviewWithWarnCtx.modalLayout);
1710 reviewWithWarnCtx.securityReportLevel = 2;
1711 // if preset is used
1712 if (reviewWithWarnCtx.warning->predefinedSet) {
1713 displaySecurityReport(1 << (token - FIRST_WARN_BAR_TOKEN));
1714 }
1715 else {
1716 // use customized warning
1717 const nbgl_warningDetails_t *details
1718 = (reviewWithWarnCtx.isIntro) ? reviewWithWarnCtx.warning->introDetails
1719 : reviewWithWarnCtx.warning->reviewDetails;
1720 if (details->type == BAR_LIST_WARNING) {
1721 displayCustomizedSecurityReport(
1722 &details->barList.details[token - FIRST_WARN_BAR_TOKEN]);
1723 }
1724 }
1725 }
1726 else if (sharedContext.usage == SHARE_CTX_CHOICE_WITH_DETAILS) {
1727 const nbgl_warningDetails_t *details
1728 = &choiceWithDetailsCtx.details->barList.details[token - FIRST_WARN_BAR_TOKEN];
1729 if (details->title != NO_TYPE_WARNING) {
1730 // dismiss modal before creating a new one
1731 nbgl_layoutRelease(choiceWithDetailsCtx.modalLayout);
1732 choiceWithDetailsCtx.level = 2;
1733 choiceWithDetailsCtx.modalLayout
1734 = displayModalDetails(details, DISMISS_DETAILS_TOKEN);
1735 }
1736 }
1737 return;
1738 }
1739 else {
1740 // dismiss modal
1741 nbgl_layoutRelease(genericContext.modalLayout);
1742 genericContext.modalLayout = NULL;
1744 }
1745 nbgl_refresh();
1746}
1747
1748// called when item are touched in layout
1749static void layoutTouchCallback(int token, uint8_t index)
1750{
1751 // choice buttons in initial warning page
1752 if (token == WARNING_CHOICE_TOKEN) {
1753 if (index == 0) { // top button to exit
1754 reviewWithWarnCtx.choiceCallback(false);
1755 }
1756 else { // bottom button to continue to review
1757 reviewWithWarnCtx.isIntro = false;
1758 if (reviewWithWarnCtx.isStreaming) {
1759 useCaseReviewStreamingStart(reviewWithWarnCtx.operationType,
1760 reviewWithWarnCtx.icon,
1761 reviewWithWarnCtx.reviewTitle,
1762 reviewWithWarnCtx.reviewSubTitle,
1763 reviewWithWarnCtx.choiceCallback,
1764 false);
1765 }
1766 else {
1767 useCaseReview(reviewWithWarnCtx.operationType,
1768 reviewWithWarnCtx.tagValueList,
1769 reviewWithWarnCtx.icon,
1770 reviewWithWarnCtx.reviewTitle,
1771 reviewWithWarnCtx.reviewSubTitle,
1772 reviewWithWarnCtx.finishTitle,
1773 &activeTipBox,
1774 reviewWithWarnCtx.choiceCallback,
1775 false,
1776 false);
1777 }
1778 }
1779 }
1780 // top-right button in initial warning page
1781 else if (token == WARNING_BUTTON_TOKEN) {
1782 // start directly at first level of security report
1783 reviewWithWarnCtx.securityReportLevel = 1;
1784 // if preset is used
1785 if (reviewWithWarnCtx.warning->predefinedSet) {
1786 displaySecurityReport(reviewWithWarnCtx.warning->predefinedSet);
1787 }
1788 else {
1789 // use customized warning
1790 const nbgl_warningDetails_t *details = (reviewWithWarnCtx.isIntro)
1791 ? reviewWithWarnCtx.warning->introDetails
1792 : reviewWithWarnCtx.warning->reviewDetails;
1793 displayCustomizedSecurityReport(details);
1794 }
1795 }
1796 // top-right button in choice with details page
1797 else if (token == CHOICE_DETAILS_TOKEN) {
1798 choiceWithDetailsCtx.level = 1;
1799 choiceWithDetailsCtx.modalLayout
1800 = displayModalDetails(choiceWithDetailsCtx.details, DISMISS_DETAILS_TOKEN);
1801 }
1802 // main prelude page
1803 else if (token == PRELUDE_CHOICE_TOKEN) {
1804 if (index == 0) { // top button to display details about prelude
1805 displayCustomizedSecurityReport(reviewWithWarnCtx.warning->prelude->details);
1806 }
1807 else { // footer to continue to review
1808 if (HAS_INITIAL_WARNING(reviewWithWarnCtx.warning)) {
1809 displayInitialWarning();
1810 }
1811 else {
1812 reviewWithWarnCtx.isIntro = false;
1813 if (reviewWithWarnCtx.isStreaming) {
1814 useCaseReviewStreamingStart(reviewWithWarnCtx.operationType,
1815 reviewWithWarnCtx.icon,
1816 reviewWithWarnCtx.reviewTitle,
1817 reviewWithWarnCtx.reviewSubTitle,
1818 reviewWithWarnCtx.choiceCallback,
1819 false);
1820 }
1821 else {
1822 useCaseReview(reviewWithWarnCtx.operationType,
1823 reviewWithWarnCtx.tagValueList,
1824 reviewWithWarnCtx.icon,
1825 reviewWithWarnCtx.reviewTitle,
1826 reviewWithWarnCtx.reviewSubTitle,
1827 reviewWithWarnCtx.finishTitle,
1828 &activeTipBox,
1829 reviewWithWarnCtx.choiceCallback,
1830 false,
1831 false);
1832 }
1833 }
1834 }
1835 }
1836 // choice buttons
1837 else if (token == CHOICE_TOKEN) {
1838 if (onChoice != NULL) {
1839 onChoice((index == 0) ? true : false);
1840 }
1841 }
1842}
1843
1844// called when skip button is touched in footer, during forward only review
1845static void displaySkipWarning(void)
1846{
1848 .cancelText = "Go back to review",
1849 .centeredInfo.text1 = "Skip review?",
1850 .centeredInfo.text2
1851 = "If you're sure you don't need to review all fields, you can skip straight to signing.",
1852 .centeredInfo.text3 = NULL,
1853 .centeredInfo.style = LARGE_CASE_INFO,
1854 .centeredInfo.icon = &IMPORTANT_CIRCLE_ICON,
1855 .centeredInfo.offsetY = 0,
1856 .confirmationText = "Yes, skip",
1857 .confirmationToken = SKIP_TOKEN,
1858 .tuneId = TUNE_TAP_CASUAL,
1859 .modal = true};
1860 if (modalPageContext != NULL) {
1861 nbgl_pageRelease(modalPageContext);
1862 }
1863 modalPageContext = nbgl_pageDrawConfirmation(&pageModalCallback, &info);
1865}
1866
1867#ifdef NBGL_KEYPAD
1868// called to update the keypad and automatically show / hide:
1869// - backspace key if no digits are present
1870// - validation key if the min digit is reached
1871// - keypad if the max number of digit is reached
1872static void updateKeyPad(bool add)
1873{
1874 bool enableValidate, enableBackspace, enableDigits;
1875 bool redrawKeypad = false;
1877
1878 enableDigits = (keypadContext.pinLen < keypadContext.pinMaxDigits);
1879 enableValidate = (keypadContext.pinLen >= keypadContext.pinMinDigits);
1880 enableBackspace = (keypadContext.pinLen > 0);
1881 if (add) {
1882 if ((keypadContext.pinLen == keypadContext.pinMinDigits)
1883 || // activate "validate" button on keypad
1884 (keypadContext.pinLen == keypadContext.pinMaxDigits)
1885 || // deactivate "digits" on keypad
1886 (keypadContext.pinLen == 1)) { // activate "backspace"
1887 redrawKeypad = true;
1888 }
1889 }
1890 else { // remove
1891 if ((keypadContext.pinLen == 0) || // deactivate "backspace" button on keypad
1892 (keypadContext.pinLen == (keypadContext.pinMinDigits - 1))
1893 || // deactivate "validate" button on keypad
1894 (keypadContext.pinLen
1895 == (keypadContext.pinMaxDigits - 1))) { // reactivate "digits" on keypad
1896 redrawKeypad = true;
1897 }
1898 }
1899 if (keypadContext.hidden == true) {
1900 nbgl_layoutUpdateKeypadContent(keypadContext.layoutCtx, true, keypadContext.pinLen, NULL);
1901 }
1902 else {
1904 keypadContext.layoutCtx, false, 0, (const char *) keypadContext.pinEntry);
1905 }
1906 if (redrawKeypad) {
1908 keypadContext.layoutCtx, 0, enableValidate, enableBackspace, enableDigits);
1909 }
1910
1911 if ((!add) && (keypadContext.pinLen == 0)) {
1912 // Full refresh to fully clean the bullets when reaching 0 digits
1913 mode = FULL_COLOR_REFRESH;
1914 }
1916}
1917
1918// called when a key is touched on the keypad
1919static void keypadCallback(char touchedKey)
1920{
1921 switch (touchedKey) {
1922 case BACKSPACE_KEY:
1923 if (keypadContext.pinLen > 0) {
1924 keypadContext.pinLen--;
1925 keypadContext.pinEntry[keypadContext.pinLen] = 0;
1926 }
1927 updateKeyPad(false);
1928 break;
1929
1930 case VALIDATE_KEY:
1931 // Gray out keyboard / buttons as a first user feedback
1932 nbgl_layoutUpdateKeypad(keypadContext.layoutCtx, 0, false, false, true);
1935
1936 keypadContext.onValidatePin(keypadContext.pinEntry, keypadContext.pinLen);
1937 break;
1938
1939 default:
1940 if ((touchedKey >= 0x30) && (touchedKey < 0x40)) {
1941 if (keypadContext.pinLen < keypadContext.pinMaxDigits) {
1942 keypadContext.pinEntry[keypadContext.pinLen] = touchedKey;
1943 keypadContext.pinLen++;
1944 }
1945 updateKeyPad(true);
1946 }
1947 break;
1948 }
1949}
1950
1951static void keypadGeneric_cb(int token, uint8_t index)
1952{
1953 UNUSED(index);
1954 if (token != BACK_TOKEN) {
1955 return;
1956 }
1957 onQuit();
1958}
1959#endif
1960
1961#ifdef NBGL_KEYBOARD
1962// called when a key is touched on the keyboard
1963static void keyboardCallback(char touchedKey)
1964{
1965 uint32_t mask = 0;
1966 size_t textLen = strlen(keyboardContext.entryBuffer);
1967 if (touchedKey == BACKSPACE_KEY) {
1968 if (textLen == 0) {
1969 // No action
1970 return;
1971 }
1972 keyboardContext.entryBuffer[--textLen] = '\0';
1973 }
1974 else {
1975 if (textLen >= keyboardContext.entryMaxLen) {
1976 // entry length can't be greater, so we mask every characters
1977 mask = -1;
1978 }
1979 else {
1980 keyboardContext.entryBuffer[textLen] = touchedKey;
1981 keyboardContext.entryBuffer[++textLen] = '\0';
1982 }
1983 }
1984 if (keyboardContext.keyboardContent.type == KEYBOARD_WITH_SUGGESTIONS) {
1985 // if suggestions are displayed, we update them at each key press
1986 keyboardContext.getSuggestionButtons(&keyboardContext.keyboardContent, &mask);
1987 }
1988 else if (textLen >= keyboardContext.entryMaxLen) {
1989 // entry length can't be greater, so we mask every characters
1990 mask = -1;
1991 }
1992 nbgl_layoutUpdateKeyboardContent(keyboardContext.layoutCtx, &keyboardContext.keyboardContent);
1993 nbgl_layoutUpdateKeyboard(keyboardContext.layoutCtx,
1994 keyboardContext.keyboardIndex,
1995 mask,
1996 false,
1997 keyboardContext.casing);
1999}
2000
2001static void keyboardGeneric_cb(int token, uint8_t index)
2002{
2003 UNUSED(index);
2004 switch (token) {
2005 case BACK_TOKEN:
2006 onQuit();
2007 break;
2008 case KEYBOARD_CROSS_TOKEN:
2009 // Simulate a word of a single letter 'a' and trigger the backspace action
2010 keyboardContext.entryBuffer[0] = 'a';
2011 keyboardContext.entryBuffer[1] = '\0';
2012 keyboardCallback(BACKSPACE_KEY);
2013 break;
2014 case KEYBOARD_BUTTON_TOKEN:
2015 if (keyboardContext.keyboardContent.type == KEYBOARD_WITH_BUTTON) {
2016 onAction();
2017 }
2018 break;
2019 default:
2020 if ((keyboardContext.keyboardContent.type == KEYBOARD_WITH_SUGGESTIONS)
2021 && (token >= keyboardContext.keyboardContent.suggestionButtons.firstButtonToken)) {
2022 onControls(token, index);
2023 }
2024 break;
2025 }
2026}
2027#endif
2028
2043static uint8_t getNbTagValuesInPage(uint8_t nbPairs,
2044 const nbgl_contentTagValueList_t *tagValueList,
2045 uint8_t startIndex,
2046 bool isSkippable,
2047 bool hasConfirmationButton,
2048 bool hasDetailsButton,
2049 bool *requireSpecificDisplay)
2050{
2051 uint8_t nbPairsInPage = 0;
2052 uint16_t currentHeight = PRE_TAG_VALUE_MARGIN; // upper margin
2053 uint16_t maxUsableHeight = TAG_VALUE_AREA_HEIGHT;
2054
2055 // if the review is skippable, it means that there is less height for tag/value pairs
2056 // the small centering header becomes a touchable header
2057 if (isSkippable) {
2058 maxUsableHeight -= TOUCHABLE_HEADER_BAR_HEIGHT - SMALL_CENTERING_HEADER;
2059 }
2060
2061 *requireSpecificDisplay = false;
2062 while (nbPairsInPage < nbPairs) {
2063 const nbgl_layoutTagValue_t *pair;
2064 nbgl_font_id_e value_font;
2065 uint16_t nbLines;
2066
2067 // margin between pairs
2068 // 12 or 24 px between each tag/value pair
2069 if (nbPairsInPage > 0) {
2070 currentHeight += INTER_TAG_VALUE_MARGIN;
2071 }
2072 // fetch tag/value pair strings.
2073 if (tagValueList->pairs != NULL) {
2074 pair = PIC(&tagValueList->pairs[startIndex + nbPairsInPage]);
2075 }
2076 else {
2077 pair = PIC(tagValueList->callback(startIndex + nbPairsInPage));
2078 }
2079
2080 if (pair->forcePageStart && nbPairsInPage > 0) {
2081 // This pair must be at the top of a page
2082 break;
2083 }
2084
2085 if (pair->centeredInfo) {
2086 if (nbPairsInPage > 0) {
2087 // This pair must be at the top of a page
2088 break;
2089 }
2090 else {
2091 // This pair is the only one of the page and has a specific display behavior
2092 nbPairsInPage = 1;
2093 *requireSpecificDisplay = true;
2094 break;
2095 }
2096 }
2097
2098 // tag height
2099 currentHeight += nbgl_getTextHeightInWidth(
2100 SMALL_REGULAR_FONT, pair->item, AVAILABLE_WIDTH, tagValueList->wrapping);
2101 // space between tag and value
2102 currentHeight += TAG_VALUE_INTERVALE;
2103 // set value font
2104 if (tagValueList->smallCaseForValue) {
2105 value_font = SMALL_REGULAR_FONT;
2106 }
2107 else {
2108 value_font = LARGE_MEDIUM_FONT;
2109 }
2110 // nb lines for value
2112 value_font, pair->value, AVAILABLE_WIDTH, tagValueList->wrapping);
2113 // honor list-level nbMaxLinesForValue: when set, the value will be
2114 // displayed truncated to that many lines, so account for its
2115 // truncated height rather than its full height.
2116 // Caveat: review TAG_VALUE_LIST/TAG_VALUE_DETAILS pages always render
2117 // up to NB_MAX_LINES_IN_REVIEW lines, because the display path forces
2118 // nbMaxLinesForValue back to NB_MAX_LINES_IN_REVIEW (see
2119 // genericContextPreparePageContent / displayReviewPage). So a list-level
2120 // value smaller than NB_MAX_LINES_IN_REVIEW must NOT shorten the height
2121 // estimate here, otherwise the page gets under-allocated and content is
2122 // drawn off-screen. Only honor a value that the display will actually
2123 // apply (i.e. >= NB_MAX_LINES_IN_REVIEW).
2124 uint8_t effectiveMaxLines = tagValueList->nbMaxLinesForValue;
2125 if ((effectiveMaxLines > 0) && (effectiveMaxLines < NB_MAX_LINES_IN_REVIEW)) {
2126 effectiveMaxLines = 0;
2127 }
2128 if ((effectiveMaxLines > 0) && (nbLines > effectiveMaxLines)) {
2129 nbLines = effectiveMaxLines;
2130 currentHeight += nbLines * nbgl_getFontLineHeight(value_font);
2131 }
2132 else {
2133 currentHeight += nbgl_getTextHeightInWidth(
2134 value_font, pair->value, AVAILABLE_WIDTH, tagValueList->wrapping);
2135 }
2136
2137 // potential subAlias text
2138 if ((pair->aliasValue) && (pair->extension->aliasSubName)) {
2139 currentHeight += TAG_VALUE_INTERVALE + nbgl_getFontLineHeight(SMALL_REGULAR_FONT);
2140 }
2141 if ((currentHeight >= maxUsableHeight) || (nbLines > NB_MAX_LINES_IN_REVIEW)) {
2142 if (nbPairsInPage == 0) {
2143 // Pair too long to fit in a single screen
2144 // It will be the only one of the page and has a specific display behavior
2145 nbPairsInPage = 1;
2146 *requireSpecificDisplay = true;
2147 }
2148 break;
2149 }
2150 nbPairsInPage++;
2151 }
2152 // if this is a TAG_VALUE_CONFIRM and we have reached the last pairs,
2153 // let's check if it still fits with a CONFIRMATION button, and if not,
2154 // remove the last pair (but never below 1, otherwise the pagination
2155 // loop in getNbPagesForContent would spin forever — the lone oversized
2156 // pair must still occupy this page and will be truncated at display time)
2157 if (hasConfirmationButton && (nbPairsInPage == nbPairs)) {
2158 maxUsableHeight -= UP_FOOTER_BUTTON_HEIGHT;
2159 if ((currentHeight > maxUsableHeight) && (nbPairsInPage > 1)) {
2160 nbPairsInPage--;
2161 }
2162 }
2163 // do the same with just a details button
2164 else if (hasDetailsButton) {
2165 maxUsableHeight -= (SMALL_BUTTON_RADIUS * 2);
2166 if ((currentHeight > maxUsableHeight) && (nbPairsInPage > 1)) {
2167 nbPairsInPage--;
2168 }
2169 }
2170 return nbPairsInPage;
2171}
2172
2182static uint8_t getNbTagValuesInDetailsPage(uint8_t nbPairs,
2183 const nbgl_contentTagValueList_t *tagValueList,
2184 uint8_t startIndex)
2185{
2186 uint8_t nbPairsInPage = 0;
2187 uint16_t currentHeight = PRE_TAG_VALUE_MARGIN; // upper margin
2188 uint16_t maxUsableHeight = TAG_VALUE_AREA_HEIGHT;
2189
2190 while (nbPairsInPage < nbPairs) {
2191 const nbgl_layoutTagValue_t *pair;
2192
2193 // margin between pairs
2194 // 12 or 24 px between each tag/value pair
2195 if (nbPairsInPage > 0) {
2196 currentHeight += INTER_TAG_VALUE_MARGIN;
2197 }
2198 // fetch tag/value pair strings.
2199 if (tagValueList->pairs != NULL) {
2200 pair = PIC(&tagValueList->pairs[startIndex + nbPairsInPage]);
2201 }
2202 else {
2203 pair = PIC(tagValueList->callback(startIndex + nbPairsInPage));
2204 }
2205
2206 // tag height
2207 currentHeight += nbgl_getTextHeightInWidth(
2208 SMALL_REGULAR_FONT, pair->item, AVAILABLE_WIDTH, tagValueList->wrapping);
2209 // space between tag and value
2210 currentHeight += 4;
2211
2212 // value height
2213 currentHeight += nbgl_getTextHeightInWidth(
2214 SMALL_REGULAR_FONT, pair->value, AVAILABLE_WIDTH, tagValueList->wrapping);
2215
2216 // we have reached the maximum height, it means than there are to many pairs
2217 if (currentHeight >= maxUsableHeight) {
2218 break;
2219 }
2220 nbPairsInPage++;
2221 }
2222 return nbPairsInPage;
2223}
2224
2225static uint8_t getNbPagesForContent(const nbgl_content_t *content,
2226 uint8_t pageIdxStart,
2227 bool isLast,
2228 bool isSkippable)
2229{
2230 uint8_t nbElements = 0;
2231 uint8_t nbPages = 0;
2232 uint8_t nbElementsInPage;
2233 uint8_t elemIdx = 0;
2234 bool flag;
2235
2236 nbElements = getContentNbElement(content);
2237
2238 while (nbElements > 0) {
2239 flag = 0;
2240 // if the current page is not the first one (or last), a navigation bar exists
2241 bool hasNav = !isLast || (pageIdxStart > 0) || (elemIdx > 0);
2242 if (content->type == TAG_VALUE_LIST) {
2243 nbElementsInPage = getNbTagValuesInPage(nbElements,
2244 &content->content.tagValueList,
2245 elemIdx,
2246 isSkippable,
2247 false,
2248 false,
2249 &flag);
2250 }
2251 else if (content->type == TAG_VALUE_CONFIRM) {
2252 nbElementsInPage = getNbTagValuesInPage(nbElements,
2254 elemIdx,
2255 isSkippable,
2256 isLast,
2257 !isLast,
2258 &flag);
2259 }
2260 else if (content->type == INFOS_LIST) {
2261 nbElementsInPage = nbgl_useCaseGetNbInfosInPage(
2262 nbElements, &content->content.infosList, elemIdx, hasNav);
2263 }
2264 else if (content->type == SWITCHES_LIST) {
2265 nbElementsInPage = nbgl_useCaseGetNbSwitchesInPage(
2266 nbElements, &content->content.switchesList, elemIdx, hasNav);
2267 }
2268 else if (content->type == BARS_LIST) {
2269 nbElementsInPage = nbgl_useCaseGetNbBarsInPage(
2270 nbElements, &content->content.barsList, elemIdx, hasNav);
2271 }
2272 else if (content->type == CHOICES_LIST) {
2273 nbElementsInPage = nbgl_useCaseGetNbChoicesInPage(
2274 nbElements, &content->content.choicesList, elemIdx, hasNav);
2275 }
2276 else {
2277 nbElementsInPage = MIN(nbMaxElementsPerContentType[content->type], nbElements);
2278 }
2279
2280 elemIdx += nbElementsInPage;
2281 genericContextSetPageInfo(pageIdxStart + nbPages, nbElementsInPage, flag);
2282 nbElements -= nbElementsInPage;
2283 nbPages++;
2284 }
2285
2286 return nbPages;
2287}
2288
2289static uint8_t getNbPagesForGenericContents(const nbgl_genericContents_t *genericContents,
2290 uint8_t pageIdxStart,
2291 bool isSkippable)
2292{
2293 uint8_t nbPages = 0;
2294 nbgl_content_t content = {0};
2295 const nbgl_content_t *p_content;
2296
2297 for (int i = 0; i < genericContents->nbContents; i++) {
2298 p_content = getContentAtIdx(genericContents, i, &content);
2299 if (p_content == NULL) {
2300 return 0;
2301 }
2302 nbPages += getNbPagesForContent(p_content,
2303 pageIdxStart + nbPages,
2304 (i == (genericContents->nbContents - 1)),
2305 isSkippable);
2306 }
2307
2308 return nbPages;
2309}
2310
2311static void prepareAddressConfirmationPages(const char *address,
2312 const nbgl_contentTagValueList_t *tagValueList,
2313 nbgl_content_t *firstPageContent,
2314 nbgl_content_t *secondPageContent)
2315{
2316 nbgl_contentTagValueConfirm_t *tagValueConfirm;
2317 uint8_t nbAddressChunks;
2318
2319 // Split the address into one or more chunks, each occupying its own page.
2320 // Each chunk-pair points into `address` at the chunk's start offset; the
2321 // list-level nbMaxLinesForValue (set further down when nbAddressChunks > 1)
2322 // bounds the rendered slice to NB_MAX_LINES_IN_REVIEW lines, so successive
2323 // chunks visually pick up where the previous left off. forcePageStart on
2324 // all but the first chunk ensures each chunk starts a fresh page.
2325 nbAddressChunks = nbgl_getTextNbPagesInWidth(
2326 LARGE_MEDIUM_FONT, address, NB_MAX_LINES_IN_REVIEW, AVAILABLE_WIDTH);
2327 bool addressTruncated = (nbAddressChunks > ADDR_VERIF_NB_PAIRS);
2328 if (addressTruncated) {
2329 nbAddressChunks = ADDR_VERIF_NB_PAIRS;
2330 }
2331 {
2332 const char *chunkStart = address;
2333 for (uint8_t i = 0; i < nbAddressChunks; i++) {
2334 addressConfirmationContext.tagValuePairs[i].item = "Address";
2335 addressConfirmationContext.tagValuePairs[i].value = chunkStart;
2336 addressConfirmationContext.tagValuePairs[i].forcePageStart = (i > 0);
2337 if (i + 1 < nbAddressChunks) {
2338 uint16_t len = 0;
2339 nbgl_getTextMaxLenInNbLines(LARGE_MEDIUM_FONT,
2340 chunkStart,
2343 &len,
2344 false);
2345 chunkStart += len;
2346 }
2347 }
2348 }
2349 addressConfirmationContext.nbPairs = nbAddressChunks;
2350
2351 // First page
2352 firstPageContent->type = TAG_VALUE_CONFIRM;
2353 tagValueConfirm = &firstPageContent->content.tagValueConfirm;
2354
2355#ifdef NBGL_QRCODE
2356 tagValueConfirm->detailsButtonIcon = &QRCODE_ICON;
2357 // only pack extras on the address page when the address itself fits on a
2358 // single page; for multi-chunk addresses, extras go to their own second page
2359 if ((tagValueList != NULL) && (nbAddressChunks == 1)
2360 && (tagValueList->nbPairs < ADDR_VERIF_NB_PAIRS)) {
2361 nbgl_contentTagValueList_t tmpList = {0};
2362 bool flag;
2363 // copy in intermediate structure
2364 for (uint8_t i = 0; i < tagValueList->nbPairs; i++) {
2365 memcpy(&addressConfirmationContext.tagValuePairs[1 + i],
2366 &tagValueList->pairs[i],
2367 sizeof(nbgl_contentTagValue_t));
2368 addressConfirmationContext.nbPairs++;
2369 }
2370 // check how many can fit in a page
2371 memcpy(&tmpList, tagValueList, sizeof(nbgl_contentTagValueList_t));
2372 tmpList.nbPairs = addressConfirmationContext.nbPairs;
2373 tmpList.pairs = addressConfirmationContext.tagValuePairs;
2374 addressConfirmationContext.nbPairs = getNbTagValuesInPage(
2375 addressConfirmationContext.nbPairs, &tmpList, 0, false, true, true, &flag);
2376 // if they don't all fit, keep only the address
2377 if (tmpList.nbPairs > addressConfirmationContext.nbPairs) {
2378 addressConfirmationContext.nbPairs = 1;
2379 }
2380 }
2381 else {
2382 tagValueConfirm->detailsButtonText = NULL;
2383 }
2384 tagValueConfirm->detailsButtonToken = ADDRESS_QRCODE_BUTTON_TOKEN;
2385#else // NBGL_QRCODE
2386 tagValueConfirm->detailsButtonText = NULL;
2387 tagValueConfirm->detailsButtonIcon = NULL;
2388#endif // NBGL_QRCODE
2389 tagValueConfirm->tuneId = TUNE_TAP_CASUAL;
2390 tagValueConfirm->tagValueList.nbPairs = addressConfirmationContext.nbPairs;
2391 tagValueConfirm->tagValueList.pairs = addressConfirmationContext.tagValuePairs;
2392 tagValueConfirm->tagValueList.smallCaseForValue = false;
2393 // when the address spans multiple chunks, cap each pair's rendered value
2394 // at NB_MAX_LINES_IN_REVIEW lines so each chunk-pair occupies exactly one page
2395 tagValueConfirm->tagValueList.nbMaxLinesForValue
2396 = (nbAddressChunks > 1) ? NB_MAX_LINES_IN_REVIEW : 0;
2397 tagValueConfirm->tagValueList.hideEndOfLastLine = addressTruncated;
2398 tagValueConfirm->tagValueList.wrapping = false;
2399
2400 // Number of extras left to place on a second page
2401 uint8_t extrasPlaced
2402 = (tagValueList != NULL) ? (addressConfirmationContext.nbPairs - nbAddressChunks) : 0;
2403 bool extrasNeedSecondPage = (tagValueList != NULL) && (tagValueList->nbPairs > extrasPlaced);
2404
2405 if (extrasNeedSecondPage) {
2406 tagValueConfirm->detailsButtonText = "Show as QR";
2407 tagValueConfirm->confirmationText = NULL;
2408 // the second page is dedicated to the extended tag/value pairs
2409 secondPageContent->type = TAG_VALUE_CONFIRM;
2410 tagValueConfirm = &secondPageContent->content.tagValueConfirm;
2411 tagValueConfirm->confirmationText = "Confirm";
2412 tagValueConfirm->confirmationToken = CONFIRM_TOKEN;
2413 tagValueConfirm->detailsButtonText = NULL;
2414 tagValueConfirm->detailsButtonIcon = NULL;
2415 tagValueConfirm->tuneId = TUNE_TAP_CASUAL;
2416 memcpy(&tagValueConfirm->tagValueList, tagValueList, sizeof(nbgl_contentTagValueList_t));
2417 tagValueConfirm->tagValueList.nbPairs = tagValueList->nbPairs - extrasPlaced;
2418 tagValueConfirm->tagValueList.pairs = &tagValueList->pairs[extrasPlaced];
2419 }
2420 else {
2421 // otherwise no tap to continue but a confirmation button
2422 tagValueConfirm->confirmationText = "Confirm";
2423 tagValueConfirm->confirmationToken = CONFIRM_TOKEN;
2424 }
2425}
2426
2427static void bundleNavStartHome(void)
2428{
2429 nbgl_homeAndSettingsContext_t *context = &bundleNavContext.homeAndSettings;
2430
2431 useCaseHomeExt(context->appName,
2432 context->appIcon,
2433 context->tagline,
2434 context->settingContents != NULL ? true : false,
2435 &context->homeAction,
2436 bundleNavStartSettings,
2437 context->quitCallback);
2438}
2439
2440static void bundleNavStartSettingsAtPage(uint8_t initSettingPage)
2441{
2442 nbgl_homeAndSettingsContext_t *context = &bundleNavContext.homeAndSettings;
2443
2444 nbgl_useCaseGenericSettings(context->appName,
2445 initSettingPage,
2446 context->settingContents,
2447 context->infosList,
2448 bundleNavStartHome);
2449}
2450
2451static void bundleNavStartSettings(void)
2452{
2453 bundleNavStartSettingsAtPage(0);
2454}
2455
2456static void bundleNavReviewConfirmRejection(void)
2457{
2458 bundleNavContext.review.choiceCallback(false);
2459}
2460
2461static void bundleNavReviewAskRejectionConfirmation(nbgl_operationType_t operationType,
2462 nbgl_callback_t callback)
2463{
2464 const char *title;
2465 const char *confirmText;
2466 // clear skip and blind bits
2467 operationType
2469 if (operationType == TYPE_TRANSACTION) {
2470 title = "Reject transaction?";
2471 confirmText = "Go back to transaction";
2472 }
2473 else if (operationType == TYPE_MESSAGE) {
2474 title = "Reject message?";
2475 confirmText = "Go back to message";
2476 }
2477 else {
2478 title = "Reject operation?";
2479 confirmText = "Go back to operation";
2480 }
2481
2482 // display a choice to confirm/cancel rejection
2483 nbgl_useCaseConfirm(title, NULL, "Yes, reject", confirmText, callback);
2484}
2485
2486static void bundleNavReviewChoice(bool confirm)
2487{
2488 if (confirm) {
2489 bundleNavContext.review.choiceCallback(true);
2490 }
2491 else {
2492 bundleNavReviewAskRejectionConfirmation(bundleNavContext.review.operationType,
2493 bundleNavReviewConfirmRejection);
2494 }
2495}
2496
2497static void bundleNavReviewStreamingConfirmRejection(void)
2498{
2499 bundleNavContext.reviewStreaming.choiceCallback(false);
2500}
2501
2502static void bundleNavReviewStreamingChoice(bool confirm)
2503{
2504 if (confirm) {
2505 bundleNavContext.reviewStreaming.choiceCallback(true);
2506 }
2507 else {
2508 bundleNavReviewAskRejectionConfirmation(bundleNavContext.reviewStreaming.operationType,
2509 bundleNavReviewStreamingConfirmRejection);
2510 }
2511}
2512
2513// function used to display the details in modal (from Choice with details or from Customized
2514// security report) it can be either a single page with text or QR code, or a list of touchable bars
2515// leading to single pages
2516static nbgl_layout_t *displayModalDetails(const nbgl_warningDetails_t *details, uint8_t token)
2517{
2518 nbgl_layoutDescription_t layoutDescription = {.modal = true,
2519 .withLeftBorder = true,
2520 .onActionCallback = modalLayoutTouchCallback,
2521 .tapActionText = NULL};
2522 nbgl_layoutHeader_t headerDesc = {.type = HEADER_BACK_AND_TEXT,
2523 .separationLine = true,
2524 .backAndText.icon = NULL,
2525 .backAndText.tuneId = TUNE_TAP_CASUAL,
2526 .backAndText.token = token};
2527 uint8_t i;
2528 nbgl_layout_t *layout;
2529
2530 layout = nbgl_layoutGet(&layoutDescription);
2531 headerDesc.backAndText.text = details->title;
2532 nbgl_layoutAddHeader(layout, &headerDesc);
2533 if (details->type == BAR_LIST_WARNING) {
2534 // if more than one warning, so use a list of touchable bars
2535 for (i = 0; i < details->barList.nbBars; i++) {
2536 nbgl_layoutBar_t bar = {0};
2537 bar.text = details->barList.texts[i];
2538 bar.subText = details->barList.subTexts[i];
2539 // Only assign a right icon if the bar is clickable (i.e. has details to display)
2540 if ((details->barList.details)
2541 && (details->barList.details[i].type != NO_TYPE_WARNING)) {
2542 bar.iconRight = &PUSH_ICON;
2543 }
2544 // Left icon is optional, only set if provided by the warning details
2545 if (details->barList.icons != NULL) {
2546 bar.iconLeft = details->barList.icons[i];
2547 }
2548 bar.token = FIRST_WARN_BAR_TOKEN + i;
2549 bar.tuneId = TUNE_TAP_CASUAL;
2550 nbgl_layoutAddTouchableBar(layout, &bar);
2551 nbgl_layoutAddSeparationLine(layout);
2552 }
2553 }
2554 else if (details->type == QRCODE_WARNING) {
2555#ifdef NBGL_QRCODE
2556 // display a QR Code
2557 nbgl_layoutAddQRCode(layout, &details->qrCode);
2558#endif // NBGL_QRCODE
2559 headerDesc.backAndText.text = details->title;
2560 }
2561 else if (details->type == CENTERED_INFO_WARNING) {
2562 // display a centered info
2563 nbgl_layoutAddContentCenter(layout, &details->centeredInfo);
2564 headerDesc.separationLine = false;
2565 }
2566 nbgl_layoutDraw(layout);
2567 nbgl_refresh();
2568 return layout;
2569}
2570
2571// function used to display the security level page in modal
2572// it can be either a single page with text or QR code, or a list of touchable bars leading
2573// to single pages
2574static void displaySecurityReport(uint32_t set)
2575{
2576 nbgl_layoutDescription_t layoutDescription = {.modal = true,
2577 .withLeftBorder = true,
2578 .onActionCallback = modalLayoutTouchCallback,
2579 .tapActionText = NULL};
2580 nbgl_layoutHeader_t headerDesc = {.type = HEADER_BACK_AND_TEXT,
2581 .separationLine = true,
2582 .backAndText.icon = NULL,
2583 .backAndText.tuneId = TUNE_TAP_CASUAL,
2584 .backAndText.token = DISMISS_WARNING_TOKEN};
2585 nbgl_layoutFooter_t footerDesc
2586 = {.type = FOOTER_EMPTY, .separationLine = false, .emptySpace.height = 0};
2587 uint8_t i;
2588 const char *provider;
2589
2590 reviewWithWarnCtx.modalLayout = nbgl_layoutGet(&layoutDescription);
2591
2592 // display a list of touchable bars in some conditions:
2593 // - we must be in the first level of security report
2594 // - and be in the review itself (not from the intro/warning screen)
2595 //
2596 if ((reviewWithWarnCtx.securityReportLevel == 1) && (!reviewWithWarnCtx.isIntro)) {
2597 for (i = 0; i < NB_WARNING_TYPES; i++) {
2598 // Skip GATED_SIGNING_WARN from security report bars
2599 if ((i != GATED_SIGNING_WARN)
2600 && (reviewWithWarnCtx.warning->predefinedSet & (1 << i))) {
2601 nbgl_layoutBar_t bar = {0};
2602 if ((i == BLIND_SIGNING_WARN) || (i == W3C_NO_THREAT_WARN) || (i == W3C_ISSUE_WARN)
2603 || (reviewWithWarnCtx.warning->providerMessage == NULL)) {
2604 bar.subText = securityReportItems[i].subText;
2605 }
2606 else {
2607 bar.subText = reviewWithWarnCtx.warning->providerMessage;
2608 }
2609 bar.text = securityReportItems[i].text;
2610 if (i != W3C_NO_THREAT_WARN) {
2611 bar.iconRight = &PUSH_ICON;
2612 bar.token = FIRST_WARN_BAR_TOKEN + i;
2613 bar.tuneId = TUNE_TAP_CASUAL;
2614 }
2615 else {
2617 }
2618 bar.iconLeft = securityReportItems[i].icon;
2619 nbgl_layoutAddTouchableBar(reviewWithWarnCtx.modalLayout, &bar);
2620 nbgl_layoutAddSeparationLine(reviewWithWarnCtx.modalLayout);
2621 }
2622 }
2623 headerDesc.backAndText.text = "Security report";
2624 nbgl_layoutAddHeader(reviewWithWarnCtx.modalLayout, &headerDesc);
2625 nbgl_layoutDraw(reviewWithWarnCtx.modalLayout);
2626 nbgl_refresh();
2627 return;
2628 }
2629 if (reviewWithWarnCtx.warning && reviewWithWarnCtx.warning->reportProvider) {
2630 provider = reviewWithWarnCtx.warning->reportProvider;
2631 }
2632 else {
2633 provider = "[unknown]";
2634 }
2635 if ((set & (1 << W3C_THREAT_DETECTED_WARN)) || (set & (1 << W3C_RISK_DETECTED_WARN))) {
2636 size_t urlLen = 0;
2637 char *destStr
2638 = tmpString
2639 + W3C_DESCRIPTION_MAX_LEN / 2; // use the second half of tmpString for strings
2640#ifdef NBGL_QRCODE
2641 // display a QR Code
2642 nbgl_layoutQRCode_t qrCode = {.url = destStr,
2643 .text1 = reviewWithWarnCtx.warning->reportUrl,
2644 .text2 = "Scan to view full report",
2645 .centered = true,
2646 .offsetY = 0};
2647 // add "https://"" as prefix of the given URL
2648 snprintf(destStr,
2649 W3C_DESCRIPTION_MAX_LEN / 2,
2650 "https://%s",
2651 reviewWithWarnCtx.warning->reportUrl);
2652 urlLen = strlen(destStr) + 1;
2653 nbgl_layoutAddQRCode(reviewWithWarnCtx.modalLayout, &qrCode);
2654 footerDesc.emptySpace.height = 24;
2655#endif // NBGL_QRCODE
2656 // use the next part of destStr for back bar text
2657 snprintf(destStr + urlLen, W3C_DESCRIPTION_MAX_LEN / 2 - urlLen, "%s report", provider);
2658 headerDesc.backAndText.text = destStr + urlLen;
2659 }
2660 else if (set & (1 << BLIND_SIGNING_WARN)) {
2661 // display a centered if in review
2662 nbgl_contentCenter_t info = {0};
2663 info.icon = &LARGE_WARNING_ICON;
2664 info.title = "This transaction cannot be Clear Signed";
2665 info.description
2666 = "This transaction or message cannot be decoded fully. If you choose to sign, you "
2667 "could be authorizing malicious actions that can drain your wallet.\n\nLearn more: "
2668 "ledger.com/e8";
2669 nbgl_layoutAddContentCenter(reviewWithWarnCtx.modalLayout, &info);
2670 footerDesc.emptySpace.height = SMALL_CENTERING_HEADER;
2671 headerDesc.separationLine = false;
2672 }
2673 else if (set & (1 << W3C_ISSUE_WARN)) {
2674 // if W3 Checks issue, display a centered info
2675 nbgl_contentCenter_t info = {0};
2676 info.icon = &LARGE_WARNING_ICON;
2677 info.title = "Transaction Check unavailable";
2678 info.description
2679 = "If you're not using the Ledger Wallet app, Transaction Check might not work. If "
2680 "you "
2681 "are using Ledger Wallet, reject the transaction and try again.\n\nGet help at "
2682 "ledger.com/e11";
2683 nbgl_layoutAddContentCenter(reviewWithWarnCtx.modalLayout, &info);
2684 footerDesc.emptySpace.height = SMALL_CENTERING_HEADER;
2685 headerDesc.separationLine = false;
2686 }
2687 nbgl_layoutAddHeader(reviewWithWarnCtx.modalLayout, &headerDesc);
2688 if (footerDesc.emptySpace.height > 0) {
2689 nbgl_layoutAddExtendedFooter(reviewWithWarnCtx.modalLayout, &footerDesc);
2690 }
2691 nbgl_layoutDraw(reviewWithWarnCtx.modalLayout);
2692 nbgl_refresh();
2693}
2694
2695// function used to display the security level page in modal
2696// it can be either a single page with text or QR code, or a list of touchable bars leading
2697// to single pages
2698static void displayCustomizedSecurityReport(const nbgl_warningDetails_t *details)
2699{
2700 reviewWithWarnCtx.modalLayout = displayModalDetails(details, DISMISS_WARNING_TOKEN);
2701}
2702
2703// function used to display the initial warning page when starting a "review with warning"
2704static void displayInitialWarning(void)
2705{
2706 // Play notification sound
2707#ifdef HAVE_PIEZO_SOUND
2708 tune_index_e tune = TUNE_RESERVED;
2709#endif // HAVE_PIEZO_SOUND
2710 // clang-format off
2711 nbgl_layoutDescription_t layoutDescription = {0};
2712 nbgl_layoutChoiceButtons_t buttonsInfo = {.bottomText = "Continue anyway",
2713 .token = WARNING_CHOICE_TOKEN,
2714 .topText = "Back to safety",
2715 .style = ROUNDED_AND_FOOTER_STYLE,
2716 .tuneId = TUNE_TAP_CASUAL};
2717 nbgl_layoutHeader_t headerDesc = {.type = HEADER_EMPTY,
2718 .emptySpace.height = MEDIUM_CENTERING_HEADER};
2719 uint32_t set = reviewWithWarnCtx.warning->predefinedSet & ~((1 << W3C_NO_THREAT_WARN) | (1 << W3C_ISSUE_WARN));
2720 // clang-format on
2721
2722 bool isBlindSigningOnly
2723 = (set != 0) && ((set & ~((1 << BLIND_SIGNING_WARN) | (1 << GATED_SIGNING_WARN))) == 0);
2724 reviewWithWarnCtx.isIntro = true;
2725
2726 layoutDescription.withLeftBorder = true;
2727 layoutDescription.onActionCallback = layoutTouchCallback;
2728 reviewWithWarnCtx.layoutCtx = nbgl_layoutGet(&layoutDescription);
2729
2730 nbgl_layoutAddHeader(reviewWithWarnCtx.layoutCtx, &headerDesc);
2731 if (reviewWithWarnCtx.warning->predefinedSet != 0) {
2732 // icon is different in casee of Blind Siging or Gated Signing
2733 nbgl_layoutAddTopRightButton(reviewWithWarnCtx.layoutCtx,
2734 isBlindSigningOnly ? &INFO_I_ICON : &QRCODE_ICON,
2735 WARNING_BUTTON_TOKEN,
2736 TUNE_TAP_CASUAL);
2737 }
2738 else if (reviewWithWarnCtx.warning->introTopRightIcon != NULL) {
2739 nbgl_layoutAddTopRightButton(reviewWithWarnCtx.layoutCtx,
2740 reviewWithWarnCtx.warning->introTopRightIcon,
2741 WARNING_BUTTON_TOKEN,
2742 TUNE_TAP_CASUAL);
2743 }
2744 // add main content
2745 // if predefined content is configured, use it preferably
2746 if (reviewWithWarnCtx.warning->predefinedSet != 0) {
2747 nbgl_contentCenter_t info = {0};
2748
2749 const char *provider;
2750
2751 // default icon
2752 info.icon = &LARGE_WARNING_ICON;
2753
2754 // use small title only if not Blind signing and not Gated signing
2755 if (isBlindSigningOnly == false) {
2756 if (reviewWithWarnCtx.warning->reportProvider) {
2757 provider = reviewWithWarnCtx.warning->reportProvider;
2758 }
2759 else {
2760 provider = "[unknown]";
2761 }
2762 info.smallTitle = tmpString;
2763 snprintf(tmpString, W3C_DESCRIPTION_MAX_LEN, "Detected by %s", provider);
2764 }
2765
2766 // If Blind Signing or Gated Signing
2767 if (isBlindSigningOnly) {
2768 info.title = "Blind signing ahead";
2769 if (set & (1 << GATED_SIGNING_WARN)) {
2770 info.description
2771 = "If you sign this transaction, you could lose your assets. "
2772 "Explore safer alternatives: ledger.com/integrated-apps";
2773 buttonsInfo.bottomText = "Accept risk and continue";
2774 }
2775 else {
2776 info.description
2777 = "This transaction's details are not fully verifiable. If you sign, you could "
2778 "lose all your assets.";
2779 buttonsInfo.bottomText = "Continue anyway";
2780 }
2781#ifdef HAVE_PIEZO_SOUND
2782 tune = TUNE_NEUTRAL;
2783#endif // HAVE_PIEZO_SOUND
2784 }
2785 else if (set & (1 << W3C_RISK_DETECTED_WARN)) {
2786 info.title = "Potential risk";
2787 if (reviewWithWarnCtx.warning->providerMessage == NULL) {
2788 info.description = "Unidentified risk";
2789 }
2790 else {
2791 info.description = reviewWithWarnCtx.warning->providerMessage;
2792 }
2793 buttonsInfo.bottomText = "Accept risk and continue";
2794#ifdef HAVE_PIEZO_SOUND
2795 tune = TUNE_NEUTRAL;
2796#endif // HAVE_PIEZO_SOUND
2797 }
2798 else if (set & (1 << W3C_THREAT_DETECTED_WARN)) {
2799 info.title = "Critical threat";
2800 if (reviewWithWarnCtx.warning->providerMessage == NULL) {
2801 info.description = "Unidentified threat";
2802 }
2803 else {
2804 info.description = reviewWithWarnCtx.warning->providerMessage;
2805 }
2806 buttonsInfo.bottomText = "Accept threat and continue";
2807#ifdef HAVE_PIEZO_SOUND
2808 tune = TUNE_ERROR;
2809#endif // HAVE_PIEZO_SOUND
2810 }
2811 nbgl_layoutAddContentCenter(reviewWithWarnCtx.layoutCtx, &info);
2812 }
2813 else if (reviewWithWarnCtx.warning->info != NULL) {
2814 // if no predefined content, use custom one
2815 nbgl_layoutAddContentCenter(reviewWithWarnCtx.layoutCtx, reviewWithWarnCtx.warning->info);
2816#ifdef HAVE_PIEZO_SOUND
2817 tune = TUNE_LOOK_AT_ME;
2818#endif // HAVE_PIEZO_SOUND
2819 }
2820 // add button and footer on bottom
2821 nbgl_layoutAddChoiceButtons(reviewWithWarnCtx.layoutCtx, &buttonsInfo);
2822
2823#ifdef HAVE_PIEZO_SOUND
2824 if (tune != TUNE_RESERVED) {
2825 os_io_seph_cmd_piezo_play_tune(tune);
2826 }
2827#endif // HAVE_PIEZO_SOUND
2828 nbgl_layoutDraw(reviewWithWarnCtx.layoutCtx);
2829 nbgl_refresh();
2830}
2831
2832// function used to display the prelude when starting a "review with warning"
2833static void displayPrelude(void)
2834{
2835 // clang-format off
2836 nbgl_layoutDescription_t layoutDescription = {0};
2837 nbgl_layoutChoiceButtons_t buttonsInfo
2838 = {.bottomText = reviewWithWarnCtx.warning->prelude->footerText,
2839 .token = PRELUDE_CHOICE_TOKEN,
2840 .topText = reviewWithWarnCtx.warning->prelude->buttonText,
2841 .style = ROUNDED_AND_FOOTER_STYLE,
2842 .tuneId = TUNE_TAP_CASUAL};
2843 nbgl_layoutHeader_t headerDesc = {.type = HEADER_EMPTY,
2844 .emptySpace.height = MEDIUM_CENTERING_HEADER};
2845 // clang-format on
2846
2847 reviewWithWarnCtx.isIntro = true;
2848
2849 layoutDescription.withLeftBorder = true;
2850 layoutDescription.onActionCallback = layoutTouchCallback;
2851 reviewWithWarnCtx.layoutCtx = nbgl_layoutGet(&layoutDescription);
2852
2853 nbgl_layoutAddHeader(reviewWithWarnCtx.layoutCtx, &headerDesc);
2854 // add centered content
2855 nbgl_contentCenter_t info = {0};
2856
2857 // default icon
2858 info.icon = reviewWithWarnCtx.warning->prelude->icon;
2859
2860 info.title = reviewWithWarnCtx.warning->prelude->title;
2861 info.description = reviewWithWarnCtx.warning->prelude->description;
2862 nbgl_layoutAddContentCenter(reviewWithWarnCtx.layoutCtx, &info);
2863
2864 // add button and footer on bottom
2865 nbgl_layoutAddChoiceButtons(reviewWithWarnCtx.layoutCtx, &buttonsInfo);
2866
2867#ifdef HAVE_PIEZO_SOUND
2868 os_io_seph_cmd_piezo_play_tune(TUNE_LOOK_AT_ME);
2869#endif // HAVE_PIEZO_SOUND
2870 nbgl_layoutDraw(reviewWithWarnCtx.layoutCtx);
2871 nbgl_refresh();
2872}
2873
2874// function to factorize code for reviews tipbox
2875static void initWarningTipBox(const nbgl_tipBox_t *tipBox)
2876{
2877 const char *predefinedTipBoxText = NULL;
2878
2879 // if warning is valid and a warning requires a tip-box
2880 if (reviewWithWarnCtx.warning) {
2881 if (reviewWithWarnCtx.warning->predefinedSet & (1 << W3C_ISSUE_WARN)) {
2882 if (reviewWithWarnCtx.warning->predefinedSet & (1 << BLIND_SIGNING_WARN)) {
2883 predefinedTipBoxText = "Transaction Check unavailable.\nBlind signing required.";
2884 }
2885 else {
2886 predefinedTipBoxText = "Transaction Check unavailable";
2887 }
2888 }
2889 else if (reviewWithWarnCtx.warning->predefinedSet & (1 << W3C_THREAT_DETECTED_WARN)) {
2890 if (reviewWithWarnCtx.warning->predefinedSet & (1 << BLIND_SIGNING_WARN)) {
2891 predefinedTipBoxText = "Critical threat detected.\nBlind signing required.";
2892 }
2893 else {
2894 predefinedTipBoxText = "Critical threat detected.";
2895 }
2896 }
2897 else if (reviewWithWarnCtx.warning->predefinedSet & (1 << W3C_RISK_DETECTED_WARN)) {
2898 if (reviewWithWarnCtx.warning->predefinedSet & (1 << BLIND_SIGNING_WARN)) {
2899 predefinedTipBoxText = "Potential risk detected.\nBlind signing required.";
2900 }
2901 else {
2902 predefinedTipBoxText = "Potential risk detected.";
2903 }
2904 }
2905 else if (reviewWithWarnCtx.warning->predefinedSet & (1 << W3C_NO_THREAT_WARN)) {
2906 if (reviewWithWarnCtx.warning->predefinedSet & (1 << BLIND_SIGNING_WARN)) {
2907 predefinedTipBoxText
2908 = "No threat detected by Transaction Check but blind signing required.";
2909 }
2910 else {
2911 predefinedTipBoxText = "No threat detected by Transaction Check.";
2912 }
2913 }
2914 else if (reviewWithWarnCtx.warning->predefinedSet & (1 << BLIND_SIGNING_WARN)) {
2915 predefinedTipBoxText = "Blind signing required.";
2916 }
2917 }
2918
2919 if ((tipBox != NULL) || (predefinedTipBoxText != NULL)) {
2920 // do not display "Swipe to review" if a tip-box is displayed
2921 STARTING_CONTENT.content.extendedCenter.contentCenter.subText = NULL;
2922 if (predefinedTipBoxText != NULL) {
2923 genericContext.validWarningCtx = true;
2924 STARTING_CONTENT.content.extendedCenter.tipBox.icon = NULL;
2925 STARTING_CONTENT.content.extendedCenter.tipBox.text = predefinedTipBoxText;
2926 }
2927 else {
2928 STARTING_CONTENT.content.extendedCenter.tipBox.icon = tipBox->icon;
2929 STARTING_CONTENT.content.extendedCenter.tipBox.text = tipBox->text;
2930 }
2931 STARTING_CONTENT.content.extendedCenter.tipBox.token = TIP_BOX_TOKEN;
2932 STARTING_CONTENT.content.extendedCenter.tipBox.tuneId = TUNE_TAP_CASUAL;
2933 }
2934}
2935
2936// function to factorize code for all simple reviews
2937static void useCaseReview(nbgl_operationType_t operationType,
2938 const nbgl_contentTagValueList_t *tagValueList,
2939 const nbgl_icon_details_t *icon,
2940 const char *reviewTitle,
2941 const char *reviewSubTitle,
2942 const char *finishTitle,
2943 const nbgl_tipBox_t *tipBox,
2944 nbgl_choiceCallback_t choiceCallback,
2945 bool isLight,
2946 bool playNotifSound)
2947{
2948 bundleNavContext.review.operationType = operationType;
2949 bundleNavContext.review.choiceCallback = choiceCallback;
2950
2951 // memorize context
2952 onChoice = bundleNavReviewChoice;
2953 navType = GENERIC_NAV;
2954 pageTitle = NULL;
2955
2956 genericContext.genericContents.contentsList = localContentsList;
2957 genericContext.genericContents.nbContents = 3;
2958 memset(localContentsList, 0, 3 * sizeof(nbgl_content_t));
2959
2960 // First a centered info
2961 STARTING_CONTENT.type = EXTENDED_CENTER;
2962 prepareReviewFirstPage(
2963 &STARTING_CONTENT.content.extendedCenter.contentCenter, icon, reviewTitle, reviewSubTitle);
2964
2965 // Prepare un tipbox if needed
2966 initWarningTipBox(tipBox);
2967
2968 // Then the tag/value pairs
2969 localContentsList[1].type = TAG_VALUE_LIST;
2970 memcpy(&localContentsList[1].content.tagValueList,
2971 tagValueList,
2973 localContentsList[1].contentActionCallback = tagValueList->actionCallback;
2974
2975 // The last page
2976 if (isLight) {
2977 localContentsList[2].type = INFO_BUTTON;
2978 prepareReviewLightLastPage(
2979 operationType, &localContentsList[2].content.infoButton, icon, finishTitle);
2980 }
2981 else {
2982 localContentsList[2].type = INFO_LONG_PRESS;
2983 prepareReviewLastPage(
2984 operationType, &localContentsList[2].content.infoLongPress, icon, finishTitle);
2985 }
2986
2987 // compute number of pages & fill navigation structure
2988 uint8_t nbPages = getNbPagesForGenericContents(
2989 &genericContext.genericContents, 0, (operationType & SKIPPABLE_OPERATION));
2990 prepareNavInfo(true, nbPages, getRejectReviewText(operationType));
2991
2992 // Play notification sound if required
2993 if (playNotifSound) {
2994#ifdef HAVE_PIEZO_SOUND
2995 os_io_seph_cmd_piezo_play_tune(TUNE_LOOK_AT_ME);
2996#endif // HAVE_PIEZO_SOUND
2997 }
2998
2999 displayGenericContextPage(0, true);
3000}
3001
3002// function to factorize code for all streaming reviews
3003static void useCaseReviewStreamingStart(nbgl_operationType_t operationType,
3004 const nbgl_icon_details_t *icon,
3005 const char *reviewTitle,
3006 const char *reviewSubTitle,
3007 nbgl_choiceCallback_t choiceCallback,
3008 bool playNotifSound)
3009{
3010 bundleNavContext.reviewStreaming.operationType = operationType;
3011 bundleNavContext.reviewStreaming.choiceCallback = choiceCallback;
3012 bundleNavContext.reviewStreaming.icon = icon;
3013
3014 // memorize context
3015 onChoice = bundleNavReviewStreamingChoice;
3016 navType = STREAMING_NAV;
3017 pageTitle = NULL;
3018
3019 genericContext.genericContents.contentsList = localContentsList;
3020 genericContext.genericContents.nbContents = 1;
3021 memset(localContentsList, 0, 1 * sizeof(nbgl_content_t));
3022
3023 // First a centered info
3024 STARTING_CONTENT.type = EXTENDED_CENTER;
3025 prepareReviewFirstPage(
3026 &STARTING_CONTENT.content.extendedCenter.contentCenter, icon, reviewTitle, reviewSubTitle);
3027
3028 // Prepare un tipbox if needed
3029 initWarningTipBox(NULL);
3030
3031 // compute number of pages & fill navigation structure
3032 bundleNavContext.reviewStreaming.stepPageNb = getNbPagesForGenericContents(
3033 &genericContext.genericContents, 0, (operationType & SKIPPABLE_OPERATION));
3034 prepareNavInfo(true, NBGL_NO_PROGRESS_INDICATOR, getRejectReviewText(operationType));
3035 // no back button on first page
3036 navInfo.navWithButtons.backButton = false;
3037
3038 // Play notification sound if required
3039 if (playNotifSound) {
3040#ifdef HAVE_PIEZO_SOUND
3041 os_io_seph_cmd_piezo_play_tune(TUNE_LOOK_AT_ME);
3042#endif // HAVE_PIEZO_SOUND
3043 }
3044
3045 displayGenericContextPage(0, true);
3046}
3047
3064static void useCaseHomeExt(const char *appName,
3065 const nbgl_icon_details_t *appIcon,
3066 const char *tagline,
3067 bool withSettings,
3068 nbgl_homeAction_t *homeAction,
3069 nbgl_callback_t topRightCallback,
3070 nbgl_callback_t quitCallback)
3071{
3072 nbgl_pageInfoDescription_t info = {.centeredInfo.icon = appIcon,
3073 .centeredInfo.text1 = appName,
3074 .centeredInfo.text3 = NULL,
3075 .centeredInfo.style = LARGE_CASE_INFO,
3076 .centeredInfo.offsetY = 0,
3077 .footerText = NULL,
3078 .bottomButtonStyle = QUIT_APP_TEXT,
3079 .tapActionText = NULL,
3080 .topRightStyle = withSettings ? SETTINGS_ICON : INFO_ICON,
3081 .topRightToken = CONTINUE_TOKEN,
3082 .tuneId = TUNE_TAP_CASUAL};
3083 reset_callbacks_and_context();
3084
3085 if ((homeAction->text != NULL) || (homeAction->icon != NULL)) {
3086 // trick to use ACTION_BUTTON_TOKEN for action and quit, with index used to distinguish
3087 info.bottomButtonsToken = ACTION_BUTTON_TOKEN;
3088 onAction = homeAction->callback;
3089 info.actionButtonText = homeAction->text;
3090 info.actionButtonIcon = homeAction->icon;
3092 = (homeAction->style == STRONG_HOME_ACTION) ? BLACK_BACKGROUND : WHITE_BACKGROUND;
3093 }
3094 else {
3095 info.bottomButtonsToken = QUIT_TOKEN;
3096 onAction = NULL;
3097 info.actionButtonText = NULL;
3098 info.actionButtonIcon = NULL;
3099 }
3100 if (tagline == NULL) {
3101 if (strlen(appName) > MAX_APP_NAME_FOR_SDK_TAGLINE) {
3102 snprintf(tmpString,
3104 "This app enables signing\ntransactions on its network.");
3105 }
3106 else {
3107 snprintf(tmpString,
3109 "%s %s\n%s",
3111 appName,
3113 }
3114
3115 // If there is more than 3 lines, it means the appName was split, so we put it on the next
3116 // line
3117 if (nbgl_getTextNbLinesInWidth(SMALL_REGULAR_FONT, tmpString, AVAILABLE_WIDTH, false) > 3) {
3118 snprintf(tmpString,
3120 "%s\n%s %s",
3122 appName,
3124 }
3125 info.centeredInfo.text2 = tmpString;
3126 }
3127 else {
3128 info.centeredInfo.text2 = tagline;
3129 }
3130
3131 onContinue = topRightCallback;
3132 onQuit = quitCallback;
3133
3134 pageContext = nbgl_pageDrawInfo(&pageCallback, NULL, &info);
3136}
3137
3146static void displayDetails(const char *tag, const char *value, bool wrapping)
3147{
3148 memset(&detailsContext, 0, sizeof(detailsContext));
3149
3150 uint16_t nbLines
3151 = nbgl_getTextNbLinesInWidth(SMALL_REGULAR_FONT, value, AVAILABLE_WIDTH, wrapping);
3152
3153 // initialize context
3154 detailsContext.tag = tag;
3155 detailsContext.value = value;
3156 detailsContext.nbPages = (nbLines + NB_MAX_LINES_IN_DETAILS - 1) / NB_MAX_LINES_IN_DETAILS;
3157 detailsContext.currentPage = 0;
3158 detailsContext.wrapping = wrapping;
3159 // add some spare for room lost with "..." substitution
3160 if (detailsContext.nbPages > 1) {
3161 uint16_t nbLostChars = (detailsContext.nbPages - 1) * 3;
3162 uint16_t nbLostLines = (nbLostChars + ((AVAILABLE_WIDTH) / 16) - 1)
3163 / ((AVAILABLE_WIDTH) / 16); // 16 for average char width
3164 uint8_t nbLinesInLastPage
3165 = nbLines - ((detailsContext.nbPages - 1) * NB_MAX_LINES_IN_DETAILS);
3166
3167 detailsContext.nbPages += nbLostLines / NB_MAX_LINES_IN_DETAILS;
3168 if ((nbLinesInLastPage + (nbLostLines % NB_MAX_LINES_IN_DETAILS))
3170 detailsContext.nbPages++;
3171 }
3172 }
3173
3174 displayDetailsPage(0, true);
3175}
3176
3177// function used to display the modal containing alias tag-value pairs
3178static void displayTagValueListModal(const nbgl_contentTagValueList_t *tagValues)
3179{
3180 uint8_t nbElements = 0;
3181 uint8_t nbElementsInPage;
3182 uint8_t elemIdx = 0;
3183
3184 // initialize context
3185 memset(&detailsContext, 0, sizeof(detailsContext));
3186 nbElements = tagValues->nbPairs;
3187
3188 while (nbElements > 0) {
3189 if (detailsContext.nbPages >= MAX_MODAL_PAGE_NB) {
3191 "displayTagValueListModal(): too many pages, truncating at %d\n",
3192 MAX_MODAL_PAGE_NB);
3193 break;
3194 }
3195 nbElementsInPage = getNbTagValuesInDetailsPage(nbElements, tagValues, elemIdx);
3196
3197 elemIdx += nbElementsInPage;
3198 modalContextSetPageInfo(detailsContext.nbPages, nbElementsInPage);
3199 nbElements -= nbElementsInPage;
3200 detailsContext.nbPages++;
3201 }
3202
3203 displayTagValueListModalPage(0, true);
3204}
3205
3206/**********************
3207 * GLOBAL FUNCTIONS
3208 **********************/
3209
3222uint8_t nbgl_useCaseGetNbTagValuesInPage(uint8_t nbPairs,
3223 const nbgl_contentTagValueList_t *tagValueList,
3224 uint8_t startIndex,
3225 bool *requireSpecificDisplay)
3226{
3227 return getNbTagValuesInPage(
3228 nbPairs, tagValueList, startIndex, false, false, false, requireSpecificDisplay);
3229}
3230
3244uint8_t nbgl_useCaseGetNbTagValuesInPageExt(uint8_t nbPairs,
3245 const nbgl_contentTagValueList_t *tagValueList,
3246 uint8_t startIndex,
3247 bool isSkippable,
3248 bool *requireSpecificDisplay)
3249{
3250 return getNbTagValuesInPage(
3251 nbPairs, tagValueList, startIndex, isSkippable, false, false, requireSpecificDisplay);
3252}
3253
3263uint8_t nbgl_useCaseGetNbInfosInPage(uint8_t nbInfos,
3264 const nbgl_contentInfoList_t *infosList,
3265 uint8_t startIndex,
3266 bool withNav)
3267{
3268 uint8_t nbInfosInPage = 0;
3269 uint16_t currentHeight = 0;
3270 uint16_t previousHeight = 0;
3271 uint16_t navHeight = withNav ? SIMPLE_FOOTER_HEIGHT : 0;
3272 const char *const *infoContents = PIC(infosList->infoContents);
3273
3274 while (nbInfosInPage < nbInfos) {
3275 // The type string must be a 1 liner and its height is LIST_ITEM_MIN_TEXT_HEIGHT
3276 currentHeight
3277 += LIST_ITEM_MIN_TEXT_HEIGHT + 2 * LIST_ITEM_PRE_HEADING + LIST_ITEM_HEADING_SUB_TEXT;
3278
3279 // content height
3280 currentHeight += nbgl_getTextHeightInWidth(SMALL_REGULAR_FONT,
3281 PIC(infoContents[startIndex + nbInfosInPage]),
3283 true);
3284 // if height is over the limit
3285 if (currentHeight >= (INFOS_AREA_HEIGHT - navHeight)) {
3286 // if there was no nav, now there will be, so it can be necessary to remove the last
3287 // item
3288 if (!withNav && (previousHeight >= (INFOS_AREA_HEIGHT - SIMPLE_FOOTER_HEIGHT))) {
3289 nbInfosInPage--;
3290 }
3291 break;
3292 }
3293 previousHeight = currentHeight;
3294 nbInfosInPage++;
3295 }
3296 return nbInfosInPage;
3297}
3298
3308uint8_t nbgl_useCaseGetNbSwitchesInPage(uint8_t nbSwitches,
3309 const nbgl_contentSwitchesList_t *switchesList,
3310 uint8_t startIndex,
3311 bool withNav)
3312{
3313 uint8_t nbSwitchesInPage = 0;
3314 uint16_t currentHeight = 0;
3315 uint16_t previousHeight = 0;
3316 uint16_t navHeight = withNav ? SIMPLE_FOOTER_HEIGHT : 0;
3317 nbgl_contentSwitch_t *switchArray = (nbgl_contentSwitch_t *) PIC(switchesList->switches);
3318
3319 while (nbSwitchesInPage < nbSwitches) {
3320 nbgl_contentSwitch_t *curSwitch = &switchArray[startIndex + nbSwitchesInPage];
3321 // The text string is either a 1 liner and its height is LIST_ITEM_MIN_TEXT_HEIGHT
3322 // or we use its height directly
3323 uint16_t textHeight = MAX(
3324 LIST_ITEM_MIN_TEXT_HEIGHT,
3325 nbgl_getTextHeightInWidth(SMALL_BOLD_FONT, curSwitch->text, AVAILABLE_WIDTH, true));
3326 currentHeight += textHeight + 2 * LIST_ITEM_PRE_HEADING;
3327
3328 if (curSwitch->subText) {
3329 currentHeight += LIST_ITEM_HEADING_SUB_TEXT;
3330
3331 // sub-text height
3332 currentHeight += nbgl_getTextHeightInWidth(
3333 SMALL_REGULAR_FONT, curSwitch->subText, AVAILABLE_WIDTH, true);
3334 }
3335 // if height is over the limit
3336 if (currentHeight >= (INFOS_AREA_HEIGHT - navHeight)) {
3337 break;
3338 }
3339 previousHeight = currentHeight;
3340 nbSwitchesInPage++;
3341 }
3342 // if there was no nav, now there will be, so it can be necessary to remove the last
3343 // item
3344 if (!withNav && (previousHeight >= (INFOS_AREA_HEIGHT - SIMPLE_FOOTER_HEIGHT))) {
3345 nbSwitchesInPage--;
3346 }
3347 return nbSwitchesInPage;
3348}
3349
3359uint8_t nbgl_useCaseGetNbBarsInPage(uint8_t nbBars,
3360 const nbgl_contentBarsList_t *barsList,
3361 uint8_t startIndex,
3362 bool withNav)
3363{
3364 uint8_t nbBarsInPage = 0;
3365 uint16_t currentHeight = 0;
3366 uint16_t previousHeight = 0;
3367 uint16_t navHeight = withNav ? SIMPLE_FOOTER_HEIGHT : 0;
3368
3369 UNUSED(barsList);
3370 UNUSED(startIndex);
3371
3372 while (nbBarsInPage < nbBars) {
3373 currentHeight += LIST_ITEM_MIN_TEXT_HEIGHT + 2 * LIST_ITEM_PRE_HEADING;
3374 // if height is over the limit
3375 if (currentHeight >= (INFOS_AREA_HEIGHT - navHeight)) {
3376 break;
3377 }
3378 previousHeight = currentHeight;
3379 nbBarsInPage++;
3380 }
3381 // if there was no nav, now there may will be, so it can be necessary to remove the last
3382 // item
3383 if (!withNav && (previousHeight >= (INFOS_AREA_HEIGHT - SIMPLE_FOOTER_HEIGHT))) {
3384 nbBarsInPage--;
3385 }
3386 return nbBarsInPage;
3387}
3388
3398uint8_t nbgl_useCaseGetNbChoicesInPage(uint8_t nbChoices,
3399 const nbgl_contentRadioChoice_t *choicesList,
3400 uint8_t startIndex,
3401 bool withNav)
3402{
3403 uint8_t nbChoicesInPage = 0;
3404 uint16_t currentHeight = 0;
3405 uint16_t previousHeight = 0;
3406 uint16_t navHeight = withNav ? SIMPLE_FOOTER_HEIGHT : 0;
3407
3408 UNUSED(choicesList);
3409 UNUSED(startIndex);
3410
3411 while (nbChoicesInPage < nbChoices) {
3412 currentHeight += LIST_ITEM_MIN_TEXT_HEIGHT + 2 * LIST_ITEM_PRE_HEADING;
3413 // if height is over the limit
3414 if (currentHeight >= (INFOS_AREA_HEIGHT - navHeight)) {
3415 // if there was no nav, now there will be, so it can be necessary to remove the last
3416 // item
3417 if (!withNav && (previousHeight >= (INFOS_AREA_HEIGHT - SIMPLE_FOOTER_HEIGHT))) {
3418 nbChoicesInPage--;
3419 }
3420 break;
3421 }
3422 previousHeight = currentHeight;
3423 nbChoicesInPage++;
3424 }
3425 return nbChoicesInPage;
3426}
3427
3435{
3436 uint8_t nbPages = 0;
3437 uint8_t nbPairs = tagValueList->nbPairs;
3438 uint8_t nbPairsInPage;
3439 uint8_t i = 0;
3440 bool flag;
3441
3442 while (i < tagValueList->nbPairs) {
3443 // upper margin
3444 nbPairsInPage = nbgl_useCaseGetNbTagValuesInPageExt(nbPairs, tagValueList, i, false, &flag);
3445 i += nbPairsInPage;
3446 nbPairs -= nbPairsInPage;
3447 nbPages++;
3448 }
3449 return nbPages;
3450}
3451
3456void nbgl_useCaseHome(const char *appName,
3457 const nbgl_icon_details_t *appIcon,
3458 const char *tagline,
3459 bool withSettings,
3460 nbgl_callback_t topRightCallback,
3461 nbgl_callback_t quitCallback)
3462{
3463 nbgl_homeAction_t homeAction = {0};
3464 useCaseHomeExt(
3465 appName, appIcon, tagline, withSettings, &homeAction, topRightCallback, quitCallback);
3466}
3467
3472void nbgl_useCaseHomeExt(const char *appName,
3473 const nbgl_icon_details_t *appIcon,
3474 const char *tagline,
3475 bool withSettings,
3476 const char *actionButtonText,
3478 nbgl_callback_t topRightCallback,
3479 nbgl_callback_t quitCallback)
3480{
3481 nbgl_homeAction_t homeAction = {.callback = actionCallback,
3482 .icon = NULL,
3483 .style = STRONG_HOME_ACTION,
3484 .text = actionButtonText};
3485
3486 useCaseHomeExt(
3487 appName, appIcon, tagline, withSettings, &homeAction, topRightCallback, quitCallback);
3488}
3489
3503void nbgl_useCaseNavigableContent(const char *title,
3504 uint8_t initPage,
3505 uint8_t nbPages,
3506 nbgl_callback_t quitCallback,
3507 nbgl_navCallback_t navCallback,
3508 nbgl_layoutTouchCallback_t controlsCallback)
3509{
3510 reset_callbacks_and_context();
3511
3512 // memorize context
3513 onQuit = quitCallback;
3514 onNav = navCallback;
3515 onControls = controlsCallback;
3516 pageTitle = title;
3517 navType = SETTINGS_NAV;
3518
3519 // fill navigation structure
3520 prepareNavInfo(false, nbPages, NULL);
3521
3522 displaySettingsPage(initPage, true);
3523}
3524
3530void nbgl_useCaseSettings(const char *title,
3531 uint8_t initPage,
3532 uint8_t nbPages,
3533 bool touchable,
3534 nbgl_callback_t quitCallback,
3535 nbgl_navCallback_t navCallback,
3536 nbgl_layoutTouchCallback_t controlsCallback)
3537{
3538 UNUSED(touchable);
3540 title, initPage, nbPages, quitCallback, navCallback, controlsCallback);
3541}
3542
3555void nbgl_useCaseGenericSettings(const char *appName,
3556 uint8_t initPage,
3557 const nbgl_genericContents_t *settingContents,
3558 const nbgl_contentInfoList_t *infosList,
3559 nbgl_callback_t quitCallback)
3560{
3561 reset_callbacks_and_context();
3562
3563 // memorize context
3564 onQuit = quitCallback;
3565 pageTitle = appName;
3566 navType = GENERIC_NAV;
3567
3568 if (settingContents != NULL) {
3569 memcpy(&genericContext.genericContents, settingContents, sizeof(nbgl_genericContents_t));
3570 }
3571 if (infosList != NULL) {
3572 genericContext.hasFinishingContent = true;
3573 memset(&FINISHING_CONTENT, 0, sizeof(nbgl_content_t));
3574 FINISHING_CONTENT.type = INFOS_LIST;
3575 memcpy(&FINISHING_CONTENT.content, infosList, sizeof(nbgl_content_u));
3576 }
3577
3578 // fill navigation structure
3579 uint8_t nbPages = getNbPagesForGenericContents(&genericContext.genericContents, 0, false);
3580 if (infosList != NULL) {
3581 nbPages += getNbPagesForContent(&FINISHING_CONTENT, nbPages, true, false);
3582 }
3583
3584 prepareNavInfo(false, nbPages, NULL);
3585
3586 displayGenericContextPage(initPage, true);
3587}
3588
3600void nbgl_useCaseGenericConfiguration(const char *title,
3601 uint8_t initPage,
3602 const nbgl_genericContents_t *contents,
3603 nbgl_callback_t quitCallback)
3604{
3605 nbgl_useCaseGenericSettings(title, initPage, contents, NULL, quitCallback);
3606}
3607
3625 const char *appName,
3626 const nbgl_icon_details_t *appIcon,
3627 const char *tagline,
3628 const uint8_t
3629 initSettingPage, // if not INIT_HOME_PAGE, start directly the corresponding setting page
3630 const nbgl_genericContents_t *settingContents,
3631 const nbgl_contentInfoList_t *infosList,
3632 const nbgl_homeAction_t *action, // Set to NULL if no additional action
3633 nbgl_callback_t quitCallback)
3634{
3635 nbgl_homeAndSettingsContext_t *context = &bundleNavContext.homeAndSettings;
3636
3637 reset_callbacks_and_context();
3638
3639 context->appName = appName;
3640 context->appIcon = appIcon;
3641 context->tagline = tagline;
3642 context->settingContents = settingContents;
3643 context->infosList = infosList;
3644 if (action != NULL) {
3645 memcpy(&context->homeAction, action, sizeof(nbgl_homeAction_t));
3646 }
3647 else {
3648 memset(&context->homeAction, 0, sizeof(nbgl_homeAction_t));
3649 }
3650 context->quitCallback = quitCallback;
3651
3652 if (initSettingPage != INIT_HOME_PAGE) {
3653 bundleNavStartSettingsAtPage(initSettingPage);
3654 }
3655 else {
3656 bundleNavStartHome();
3657 }
3658}
3659
3667void nbgl_useCaseStatus(const char *message, bool isSuccess, nbgl_callback_t quitCallback)
3668{
3669 nbgl_screenTickerConfiguration_t ticker = {.tickerCallback = &tickerCallback,
3670 .tickerIntervale = 0, // not periodic
3671 .tickerValue = STATUS_SCREEN_DURATION};
3672 nbgl_pageInfoDescription_t info = {0};
3673
3674 reset_callbacks_and_context();
3675
3676 onQuit = quitCallback;
3677 if (isSuccess) {
3678#ifdef HAVE_PIEZO_SOUND
3679 os_io_seph_cmd_piezo_play_tune(TUNE_LEDGER_MOMENT);
3680#endif // HAVE_PIEZO_SOUND
3681 }
3682 info.centeredInfo.icon = isSuccess ? &CHECK_CIRCLE_ICON : &DENIED_CIRCLE_ICON;
3683 info.centeredInfo.style = LARGE_CASE_INFO;
3684 info.centeredInfo.text1 = message;
3685 info.tapActionText = "";
3686 info.tapActionToken = QUIT_TOKEN;
3687 info.tuneId = TUNE_TAP_CASUAL;
3688 pageContext = nbgl_pageDrawInfo(&pageCallback, &ticker, &info);
3690}
3691
3699 nbgl_callback_t quitCallback)
3700{
3701 const char *msg;
3702 bool isSuccess;
3703 switch (reviewStatusType) {
3705 msg = "Operation signed";
3706 isSuccess = true;
3707 break;
3709 msg = "Operation rejected";
3710 isSuccess = false;
3711 break;
3713 msg = "Transaction signed";
3714 isSuccess = true;
3715 break;
3717 msg = "Transaction rejected";
3718 isSuccess = false;
3719 break;
3721 msg = "Message signed";
3722 isSuccess = true;
3723 break;
3725 msg = "Message rejected";
3726 isSuccess = false;
3727 break;
3729 msg = "Address verified";
3730 isSuccess = true;
3731 break;
3733 msg = "Address verification\ncancelled";
3734 isSuccess = false;
3735 break;
3736 default:
3737 return;
3738 }
3739 nbgl_useCaseStatus(msg, isSuccess, quitCallback);
3740}
3741
3755 const char *message,
3756 const char *subMessage,
3757 const char *confirmText,
3758 const char *cancelText,
3759 nbgl_choiceCallback_t callback)
3760{
3762 // check params
3763 if ((confirmText == NULL) || (cancelText == NULL)) {
3764 LOG_WARN(USE_CASE_LOGGER, "nbgl_useCaseChoice(): confirmText or cancelText is NULL\n");
3765 return;
3766 }
3767 reset_callbacks_and_context();
3768
3769 info.cancelText = cancelText;
3770 info.centeredInfo.text1 = message;
3771 info.centeredInfo.text2 = subMessage;
3772 info.centeredInfo.style = LARGE_CASE_INFO;
3773 info.centeredInfo.icon = icon;
3774 info.confirmationText = confirmText;
3775 info.confirmationToken = CHOICE_TOKEN;
3776 info.tuneId = TUNE_TAP_CASUAL;
3777
3778 onChoice = callback;
3779 pageContext = nbgl_pageDrawConfirmation(&pageCallback, &info);
3781}
3782
3798 const char *message,
3799 const char *subMessage,
3800 const char *confirmText,
3801 const char *cancelText,
3802 nbgl_genericDetails_t *details,
3803 nbgl_choiceCallback_t callback)
3804{
3806 (details != NULL) ? &SEARCH_ICON : NULL,
3807 message,
3808 subMessage,
3809 NULL,
3810 confirmText,
3811 cancelText,
3812 details,
3813 callback);
3814}
3815
3839 const nbgl_icon_details_t *headerIcon,
3840 const char *title,
3841 const char *message,
3842 const char *subMessage,
3843 const char *confirmText,
3844 const char *cancelText,
3845 nbgl_warningDetails_t *details,
3846 nbgl_choiceCallback_t callback)
3847{
3848 // clang-format off
3849 nbgl_layoutDescription_t layoutDescription = {0};
3850 nbgl_layoutChoiceButtons_t buttonsInfo = {.bottomText = cancelText,
3851 .token = CHOICE_TOKEN,
3852 .topText = confirmText,
3853 .style = ROUNDED_AND_FOOTER_STYLE,
3854 .tuneId = TUNE_TAP_CASUAL};
3855 nbgl_contentCenter_t centeredInfo = {0};
3856 nbgl_layoutHeader_t headerDesc = {.type = HEADER_EMPTY,
3857 .emptySpace.height = MEDIUM_CENTERING_HEADER};
3858 // clang-format on
3859
3860 if ((confirmText == NULL) || (cancelText == NULL)) {
3862 "nbgl_useCaseAdvancedChoiceWithDetails(): confirmText or cancelText is NULL\n");
3863 return;
3864 }
3865
3866 reset_callbacks_and_context();
3867
3868#ifdef HAVE_PIEZO_SOUND
3869 os_io_seph_cmd_piezo_play_tune(TUNE_LOOK_AT_ME);
3870#endif // HAVE_PIEZO_SOUND
3871
3872 onChoice = callback;
3873 layoutDescription.withLeftBorder = true;
3874 layoutDescription.onActionCallback = layoutTouchCallback;
3875 sharedContext.usage = SHARE_CTX_CHOICE_WITH_DETAILS;
3876 choiceWithDetailsCtx.layoutCtx = nbgl_layoutGet(&layoutDescription);
3877 choiceWithDetailsCtx.details = details;
3878
3879 nbgl_layoutAddHeader(choiceWithDetailsCtx.layoutCtx, &headerDesc);
3880 nbgl_layoutAddChoiceButtons(choiceWithDetailsCtx.layoutCtx, &buttonsInfo);
3881 centeredInfo.icon = centerIcon;
3882 centeredInfo.title = title;
3883 centeredInfo.description = message;
3884 centeredInfo.subText = subMessage;
3885 nbgl_layoutAddContentCenter(choiceWithDetailsCtx.layoutCtx, &centeredInfo);
3886
3887 if ((details != NULL) && (headerIcon != NULL)) {
3888 nbgl_layoutAddTopRightButton(
3889 choiceWithDetailsCtx.layoutCtx, headerIcon, CHOICE_DETAILS_TOKEN, TUNE_TAP_CASUAL);
3890 }
3891
3892 nbgl_layoutDraw(choiceWithDetailsCtx.layoutCtx);
3894}
3895
3909void nbgl_useCaseConfirm(const char *message,
3910 const char *subMessage,
3911 const char *confirmText,
3912 const char *cancelText,
3913 nbgl_callback_t callback)
3914{
3915 // Don't reset callback or nav context as this is just a modal.
3916
3917 nbgl_pageConfirmationDescription_t info = {.cancelText = cancelText,
3918 .centeredInfo.text1 = message,
3919 .centeredInfo.text2 = subMessage,
3920 .centeredInfo.text3 = NULL,
3921 .centeredInfo.style = LARGE_CASE_INFO,
3922 .centeredInfo.icon = &IMPORTANT_CIRCLE_ICON,
3923 .centeredInfo.offsetY = 0,
3924 .confirmationText = confirmText,
3925 .confirmationToken = CHOICE_TOKEN,
3926 .tuneId = TUNE_TAP_CASUAL,
3927 .modal = true};
3928 onModalConfirm = callback;
3929 if (modalPageContext != NULL) {
3930 nbgl_pageRelease(modalPageContext);
3931 }
3932 modalPageContext = nbgl_pageDrawConfirmation(&pageModalCallback, &info);
3934}
3935
3947 const char *message,
3948 const char *actionText,
3949 nbgl_callback_t callback)
3950{
3951 nbgl_pageContent_t content = {0};
3952
3953 reset_callbacks_and_context();
3954 // memorize callback
3955 onAction = callback;
3956
3957 content.tuneId = TUNE_TAP_CASUAL;
3958 content.type = INFO_BUTTON;
3959 content.infoButton.buttonText = actionText;
3960 content.infoButton.text = message;
3961 content.infoButton.icon = icon;
3962 content.infoButton.buttonToken = ACTION_BUTTON_TOKEN;
3963
3964 pageContext = nbgl_pageDrawGenericContent(&pageCallback, NULL, &content);
3966}
3967
3979void nbgl_useCaseReviewStart(const nbgl_icon_details_t *icon,
3980 const char *reviewTitle,
3981 const char *reviewSubTitle,
3982 const char *rejectText,
3983 nbgl_callback_t continueCallback,
3984 nbgl_callback_t rejectCallback)
3985{
3986 nbgl_pageInfoDescription_t info = {.footerText = rejectText,
3987 .footerToken = QUIT_TOKEN,
3988 .tapActionText = NULL,
3989 .isSwipeable = true,
3990 .tapActionToken = CONTINUE_TOKEN,
3991 .topRightStyle = NO_BUTTON_STYLE,
3992 .actionButtonText = NULL,
3993 .tuneId = TUNE_TAP_CASUAL};
3994
3995 reset_callbacks_and_context();
3996
3997 info.centeredInfo.icon = icon;
3998 info.centeredInfo.text1 = reviewTitle;
3999 info.centeredInfo.text2 = reviewSubTitle;
4000 info.centeredInfo.text3 = "Swipe to review";
4001 info.centeredInfo.style = LARGE_CASE_GRAY_INFO;
4002 info.centeredInfo.offsetY = 0;
4003 onQuit = rejectCallback;
4004 onContinue = continueCallback;
4005
4006#ifdef HAVE_PIEZO_SOUND
4007 // Play notification sound
4008 os_io_seph_cmd_piezo_play_tune(TUNE_LOOK_AT_ME);
4009#endif // HAVE_PIEZO_SOUND
4010
4011 pageContext = nbgl_pageDrawInfo(&pageCallback, NULL, &info);
4012 nbgl_refresh();
4013}
4014
4019void nbgl_useCaseRegularReview(uint8_t initPage,
4020 uint8_t nbPages,
4021 const char *rejectText,
4023 nbgl_navCallback_t navCallback,
4024 nbgl_choiceCallback_t choiceCallback)
4025{
4026 reset_callbacks_and_context();
4027
4028 // memorize context
4029 onChoice = choiceCallback;
4030 onNav = navCallback;
4031 onControls = buttonCallback;
4032 forwardNavOnly = false;
4033 navType = REVIEW_NAV;
4034
4035 // fill navigation structure
4036 UNUSED(rejectText);
4037 prepareNavInfo(true, nbPages, getRejectReviewText(TYPE_OPERATION));
4038
4039 displayReviewPage(initPage, true);
4040}
4041
4054void nbgl_useCaseStaticReview(const nbgl_contentTagValueList_t *tagValueList,
4055 const nbgl_pageInfoLongPress_t *infoLongPress,
4056 const char *rejectText,
4057 nbgl_choiceCallback_t callback)
4058{
4059 uint8_t offset = 0;
4060
4061 reset_callbacks_and_context();
4062
4063 // memorize context
4064 onChoice = callback;
4065 navType = GENERIC_NAV;
4066 pageTitle = NULL;
4067 bundleNavContext.review.operationType = TYPE_OPERATION;
4068
4069 genericContext.genericContents.contentsList = localContentsList;
4070 memset(localContentsList, 0, 2 * sizeof(nbgl_content_t));
4071
4072 if (tagValueList != NULL && tagValueList->nbPairs != 0) {
4073 localContentsList[offset].type = TAG_VALUE_LIST;
4074 memcpy(&localContentsList[offset].content.tagValueList,
4075 tagValueList,
4077 offset++;
4078 }
4079
4080 localContentsList[offset].type = INFO_LONG_PRESS;
4081 memcpy(&localContentsList[offset].content.infoLongPress,
4082 infoLongPress,
4083 sizeof(nbgl_pageInfoLongPress_t));
4084 localContentsList[offset].content.infoLongPress.longPressToken = CONFIRM_TOKEN;
4085 offset++;
4086
4087 genericContext.genericContents.nbContents = offset;
4088
4089 // compute number of pages & fill navigation structure
4090 uint8_t nbPages = getNbPagesForGenericContents(&genericContext.genericContents, 0, false);
4091 UNUSED(rejectText);
4092 prepareNavInfo(true, nbPages, getRejectReviewText(TYPE_OPERATION));
4093
4094 displayGenericContextPage(0, true);
4095}
4096
4110void nbgl_useCaseStaticReviewLight(const nbgl_contentTagValueList_t *tagValueList,
4111 const nbgl_pageInfoLongPress_t *infoLongPress,
4112 const char *rejectText,
4113 nbgl_choiceCallback_t callback)
4114{
4115 uint8_t offset = 0;
4116
4117 reset_callbacks_and_context();
4118
4119 // memorize context
4120 onChoice = callback;
4121 navType = GENERIC_NAV;
4122 pageTitle = NULL;
4123
4124 genericContext.genericContents.contentsList = localContentsList;
4125 memset(localContentsList, 0, 2 * sizeof(nbgl_content_t));
4126
4127 if (tagValueList != NULL && tagValueList->nbPairs != 0) {
4128 localContentsList[offset].type = TAG_VALUE_LIST;
4129 memcpy(&localContentsList[offset].content.tagValueList,
4130 tagValueList,
4132 offset++;
4133 }
4134
4135 localContentsList[offset].type = INFO_BUTTON;
4136 localContentsList[offset].content.infoButton.text = infoLongPress->text;
4137 localContentsList[offset].content.infoButton.icon = infoLongPress->icon;
4138 localContentsList[offset].content.infoButton.buttonText = infoLongPress->longPressText;
4139 localContentsList[offset].content.infoButton.buttonToken = CONFIRM_TOKEN;
4140 localContentsList[offset].content.infoButton.tuneId = TUNE_TAP_CASUAL;
4141 offset++;
4142
4143 genericContext.genericContents.nbContents = offset;
4144
4145 // compute number of pages & fill navigation structure
4146 uint8_t nbPages = getNbPagesForGenericContents(&genericContext.genericContents, 0, false);
4147 UNUSED(rejectText);
4148 prepareNavInfo(true, nbPages, getRejectReviewText(TYPE_OPERATION));
4149
4150 displayGenericContextPage(0, true);
4151}
4152
4169void nbgl_useCaseReview(nbgl_operationType_t operationType,
4170 const nbgl_contentTagValueList_t *tagValueList,
4171 const nbgl_icon_details_t *icon,
4172 const char *reviewTitle,
4173 const char *reviewSubTitle,
4174 const char *finishTitle,
4175 nbgl_choiceCallback_t choiceCallback)
4176{
4177 reset_callbacks_and_context();
4178
4179 useCaseReview(operationType,
4180 tagValueList,
4181 icon,
4182 reviewTitle,
4183 reviewSubTitle,
4184 finishTitle,
4185 NULL,
4186 choiceCallback,
4187 false,
4188 true);
4189}
4190
4211 const nbgl_contentTagValueList_t *tagValueList,
4212 const nbgl_icon_details_t *icon,
4213 const char *reviewTitle,
4214 const char *reviewSubTitle,
4215 const char *finishTitle,
4216 const nbgl_tipBox_t *tipBox,
4217 nbgl_choiceCallback_t choiceCallback)
4218{
4219 nbgl_useCaseAdvancedReview(operationType,
4220 tagValueList,
4221 icon,
4222 reviewTitle,
4223 reviewSubTitle,
4224 finishTitle,
4225 tipBox,
4226 &blindSigningWarning,
4227 choiceCallback);
4228}
4229
4253 const nbgl_contentTagValueList_t *tagValueList,
4254 const nbgl_icon_details_t *icon,
4255 const char *reviewTitle,
4256 const char *reviewSubTitle,
4257 const char *finishTitle,
4258 const nbgl_tipBox_t *tipBox,
4259 const nbgl_warning_t *warning,
4260 nbgl_choiceCallback_t choiceCallback)
4261{
4262 reset_callbacks_and_context();
4263
4264 // memorize tipBox because it can be in the call stack of the caller
4265 if (tipBox != NULL) {
4266 memcpy(&activeTipBox, tipBox, sizeof(activeTipBox));
4267 }
4268 // if no warning at all, it's a simple review
4269 if ((warning == NULL)
4270 || ((warning->predefinedSet == 0) && (warning->introDetails == NULL)
4271 && (warning->reviewDetails == NULL) && (warning->prelude == NULL))) {
4272 useCaseReview(operationType,
4273 tagValueList,
4274 icon,
4275 reviewTitle,
4276 reviewSubTitle,
4277 finishTitle,
4278 tipBox,
4279 choiceCallback,
4280 false,
4281 true);
4282 return;
4283 }
4284 if (warning->predefinedSet == (1 << W3C_NO_THREAT_WARN)) {
4285 operationType |= NO_THREAT_OPERATION;
4286 }
4287 else if (warning->predefinedSet != 0) {
4288 operationType |= RISKY_OPERATION;
4289 }
4290 sharedContext.usage = SHARE_CTX_REVIEW_WITH_WARNING;
4291 reviewWithWarnCtx.isStreaming = false;
4292 reviewWithWarnCtx.operationType = operationType;
4293 reviewWithWarnCtx.tagValueList = tagValueList;
4294 reviewWithWarnCtx.icon = icon;
4295 reviewWithWarnCtx.reviewTitle = reviewTitle;
4296 reviewWithWarnCtx.reviewSubTitle = reviewSubTitle;
4297 reviewWithWarnCtx.finishTitle = finishTitle;
4298 reviewWithWarnCtx.warning = warning;
4299 reviewWithWarnCtx.choiceCallback = choiceCallback;
4300
4301 // if the warning contains a prelude, display it first
4302 if (reviewWithWarnCtx.warning->prelude) {
4303 displayPrelude();
4304 }
4305 // display the initial warning only of a risk/threat or blind signing or prelude
4306 else if (HAS_INITIAL_WARNING(warning)) {
4307 displayInitialWarning();
4308 }
4309 else {
4310 useCaseReview(operationType,
4311 tagValueList,
4312 icon,
4313 reviewTitle,
4314 reviewSubTitle,
4315 finishTitle,
4316 tipBox,
4317 choiceCallback,
4318 false,
4319 true);
4320 }
4321}
4322
4340 const nbgl_contentTagValueList_t *tagValueList,
4341 const nbgl_icon_details_t *icon,
4342 const char *reviewTitle,
4343 const char *reviewSubTitle,
4344 const char *finishTitle,
4345 nbgl_choiceCallback_t choiceCallback)
4346{
4347 reset_callbacks_and_context();
4348
4349 useCaseReview(operationType,
4350 tagValueList,
4351 icon,
4352 reviewTitle,
4353 reviewSubTitle,
4354 finishTitle,
4355 NULL,
4356 choiceCallback,
4357 true,
4358 true);
4359}
4360
4370 const char *rejectText,
4371 nbgl_callback_t rejectCallback)
4372{
4373 reset_callbacks_and_context();
4374
4375 // memorize context
4376 onQuit = rejectCallback;
4377 navType = GENERIC_NAV;
4378 pageTitle = NULL;
4379 bundleNavContext.review.operationType = TYPE_OPERATION;
4380
4381 memcpy(&genericContext.genericContents, contents, sizeof(nbgl_genericContents_t));
4382
4383 // compute number of pages & fill navigation structure
4384 uint8_t nbPages = getNbPagesForGenericContents(&genericContext.genericContents, 0, false);
4385 prepareNavInfo(true, nbPages, rejectText);
4386 navInfo.quitToken = QUIT_TOKEN;
4387
4388#ifdef HAVE_PIEZO_SOUND
4389 // Play notification sound
4390 os_io_seph_cmd_piezo_play_tune(TUNE_LOOK_AT_ME);
4391#endif // HAVE_PIEZO_SOUND
4392
4393 displayGenericContextPage(0, true);
4394}
4395
4409 const nbgl_icon_details_t *icon,
4410 const char *reviewTitle,
4411 const char *reviewSubTitle,
4412 nbgl_choiceCallback_t choiceCallback)
4413{
4414 reset_callbacks_and_context();
4415
4416 useCaseReviewStreamingStart(
4417 operationType, icon, reviewTitle, reviewSubTitle, choiceCallback, true);
4418}
4419
4434 const nbgl_icon_details_t *icon,
4435 const char *reviewTitle,
4436 const char *reviewSubTitle,
4437 nbgl_choiceCallback_t choiceCallback)
4438{
4440 operationType, icon, reviewTitle, reviewSubTitle, &blindSigningWarning, choiceCallback);
4441}
4442
4459 const nbgl_icon_details_t *icon,
4460 const char *reviewTitle,
4461 const char *reviewSubTitle,
4462 const nbgl_warning_t *warning,
4463 nbgl_choiceCallback_t choiceCallback)
4464{
4465 reset_callbacks_and_context();
4466
4467 // if no warning at all, it's a simple review
4468 if ((warning == NULL)
4469 || ((warning->predefinedSet == 0) && (warning->introDetails == NULL)
4470 && (warning->reviewDetails == NULL) && (warning->prelude == NULL))) {
4471 useCaseReviewStreamingStart(
4472 operationType, icon, reviewTitle, reviewSubTitle, choiceCallback, true);
4473 return;
4474 }
4475 if (warning->predefinedSet == (1 << W3C_NO_THREAT_WARN)) {
4476 operationType |= NO_THREAT_OPERATION;
4477 }
4478 else if (warning->predefinedSet != 0) {
4479 operationType |= RISKY_OPERATION;
4480 }
4481
4482 sharedContext.usage = SHARE_CTX_REVIEW_WITH_WARNING;
4483 reviewWithWarnCtx.isStreaming = true;
4484 reviewWithWarnCtx.operationType = operationType;
4485 reviewWithWarnCtx.icon = icon;
4486 reviewWithWarnCtx.reviewTitle = reviewTitle;
4487 reviewWithWarnCtx.reviewSubTitle = reviewSubTitle;
4488 reviewWithWarnCtx.choiceCallback = choiceCallback;
4489 reviewWithWarnCtx.warning = warning;
4490
4491 // if the warning contains a prelude, display it first
4492 if (reviewWithWarnCtx.warning->prelude) {
4493 displayPrelude();
4494 }
4495 // display the initial warning only of a risk/threat or blind signing
4496 else if (HAS_INITIAL_WARNING(warning)) {
4497 displayInitialWarning();
4498 }
4499 else {
4500 useCaseReviewStreamingStart(
4501 operationType, icon, reviewTitle, reviewSubTitle, choiceCallback, true);
4502 }
4503}
4504
4519 nbgl_choiceCallback_t choiceCallback,
4520 nbgl_callback_t skipCallback)
4521{
4522 // Should follow a call to nbgl_useCaseReviewStreamingStart
4523 memset(&genericContext, 0, sizeof(genericContext));
4524
4525 bundleNavContext.reviewStreaming.choiceCallback = choiceCallback;
4526 bundleNavContext.reviewStreaming.skipCallback = skipCallback;
4527
4528 // memorize context
4529 onChoice = bundleNavReviewStreamingChoice;
4530 navType = STREAMING_NAV;
4531 pageTitle = NULL;
4532
4533 genericContext.genericContents.contentsList = localContentsList;
4534 genericContext.genericContents.nbContents = 1;
4535 memset(localContentsList, 0, 1 * sizeof(nbgl_content_t));
4536
4537 // Then the tag/value pairs
4538 STARTING_CONTENT.type = TAG_VALUE_LIST;
4539 memcpy(
4540 &STARTING_CONTENT.content.tagValueList, tagValueList, sizeof(nbgl_contentTagValueList_t));
4541
4542 // compute number of pages & fill navigation structure
4543 bundleNavContext.reviewStreaming.stepPageNb = getNbPagesForGenericContents(
4544 &genericContext.genericContents,
4545 0,
4546 (bundleNavContext.reviewStreaming.operationType & SKIPPABLE_OPERATION));
4547 prepareNavInfo(true,
4549 getRejectReviewText(bundleNavContext.reviewStreaming.operationType));
4550 // if the operation is skippable
4551 if (bundleNavContext.reviewStreaming.operationType & SKIPPABLE_OPERATION) {
4552 navInfo.progressIndicator = false;
4553 navInfo.skipText = "Skip";
4554 navInfo.skipToken = SKIP_TOKEN;
4555 }
4556
4557 displayGenericContextPage(0, true);
4558}
4559
4571 nbgl_choiceCallback_t choiceCallback)
4572{
4573 nbgl_useCaseReviewStreamingContinueExt(tagValueList, choiceCallback, NULL);
4574}
4575
4584void nbgl_useCaseReviewStreamingFinish(const char *finishTitle,
4585 nbgl_choiceCallback_t choiceCallback)
4586{
4587 // Should follow a call to nbgl_useCaseReviewStreamingContinue
4588 memset(&genericContext, 0, sizeof(genericContext));
4589
4590 bundleNavContext.reviewStreaming.choiceCallback = choiceCallback;
4591
4592 // memorize context
4593 onChoice = bundleNavReviewStreamingChoice;
4594 navType = STREAMING_NAV;
4595 pageTitle = NULL;
4596
4597 genericContext.genericContents.contentsList = localContentsList;
4598 genericContext.genericContents.nbContents = 1;
4599 memset(localContentsList, 0, 1 * sizeof(nbgl_content_t));
4600
4601 // Eventually the long press page
4602 STARTING_CONTENT.type = INFO_LONG_PRESS;
4603 prepareReviewLastPage(bundleNavContext.reviewStreaming.operationType,
4604 &STARTING_CONTENT.content.infoLongPress,
4605 bundleNavContext.reviewStreaming.icon,
4606 finishTitle);
4607
4608 // compute number of pages & fill navigation structure
4609 bundleNavContext.reviewStreaming.stepPageNb = getNbPagesForGenericContents(
4610 &genericContext.genericContents,
4611 0,
4612 (bundleNavContext.reviewStreaming.operationType & SKIPPABLE_OPERATION));
4613 prepareNavInfo(true, 1, getRejectReviewText(bundleNavContext.reviewStreaming.operationType));
4614
4615 displayGenericContextPage(0, true);
4616}
4617
4622void nbgl_useCaseAddressConfirmationExt(const char *address,
4623 nbgl_choiceCallback_t callback,
4624 const nbgl_contentTagValueList_t *tagValueList)
4625{
4626 reset_callbacks_and_context();
4627 memset(&addressConfirmationContext, 0, sizeof(addressConfirmationContext));
4628
4629 // save context
4630 onChoice = callback;
4631 navType = GENERIC_NAV;
4632 pageTitle = NULL;
4633
4634 genericContext.genericContents.contentsList = localContentsList;
4635 genericContext.genericContents.nbContents = (tagValueList == NULL) ? 1 : 2;
4636 memset(localContentsList, 0, 2 * sizeof(nbgl_content_t));
4637 prepareAddressConfirmationPages(
4638 address, tagValueList, &STARTING_CONTENT, &localContentsList[1]);
4639
4640 // fill navigation structure, common to all pages
4641 uint8_t nbPages = getNbPagesForGenericContents(&genericContext.genericContents, 0, false);
4642
4643 prepareNavInfo(true, nbPages, "Cancel");
4644
4645#ifdef HAVE_PIEZO_SOUND
4646 // Play notification sound
4647 os_io_seph_cmd_piezo_play_tune(TUNE_LOOK_AT_ME);
4648#endif // HAVE_PIEZO_SOUND
4649
4650 displayGenericContextPage(0, true);
4651}
4652
4669void nbgl_useCaseAddressReview(const char *address,
4670 const nbgl_contentTagValueList_t *additionalTagValueList,
4671 const nbgl_icon_details_t *icon,
4672 const char *reviewTitle,
4673 const char *reviewSubTitle,
4674 nbgl_choiceCallback_t choiceCallback)
4675{
4676 reset_callbacks_and_context();
4677
4678 // release a potential modal
4679 if (addressConfirmationContext.modalLayout) {
4680 nbgl_layoutRelease(addressConfirmationContext.modalLayout);
4681 }
4682 memset(&addressConfirmationContext, 0, sizeof(addressConfirmationContext));
4683
4684 // save context
4685 onChoice = choiceCallback;
4686 navType = GENERIC_NAV;
4687 pageTitle = NULL;
4688 bundleNavContext.review.operationType = TYPE_OPERATION;
4689
4690 genericContext.genericContents.contentsList = localContentsList;
4691 memset(localContentsList, 0, 3 * sizeof(nbgl_content_t));
4692
4693 // First a centered info
4694 STARTING_CONTENT.type = EXTENDED_CENTER;
4695 prepareReviewFirstPage(
4696 &STARTING_CONTENT.content.extendedCenter.contentCenter, icon, reviewTitle, reviewSubTitle);
4697 STARTING_CONTENT.content.extendedCenter.contentCenter.subText = "Swipe to continue";
4698
4699 // Then the address confirmation pages
4700 prepareAddressConfirmationPages(
4701 address, additionalTagValueList, &localContentsList[1], &localContentsList[2]);
4702
4703 // fill navigation structure, common to all pages
4704 genericContext.genericContents.nbContents
4705 = (localContentsList[2].type == TAG_VALUE_CONFIRM) ? 3 : 2;
4706 uint8_t nbPages = getNbPagesForGenericContents(&genericContext.genericContents, 0, false);
4707
4708 prepareNavInfo(true, nbPages, "Cancel");
4709
4710#ifdef HAVE_PIEZO_SOUND
4711 // Play notification sound
4712 os_io_seph_cmd_piezo_play_tune(TUNE_LOOK_AT_ME);
4713#endif // HAVE_PIEZO_SOUND
4714
4715 displayGenericContextPage(0, true);
4716}
4717
4726void nbgl_useCaseSpinner(const char *text)
4727{
4728 // if the previous Use Case was not Spinner, fresh start
4729 if (genericContext.type != USE_CASE_SPINNER) {
4730 memset(&genericContext, 0, sizeof(genericContext));
4731 genericContext.type = USE_CASE_SPINNER;
4732 nbgl_layoutDescription_t layoutDescription = {0};
4733
4734 layoutDescription.withLeftBorder = true;
4735
4736 genericContext.backgroundLayout = nbgl_layoutGet(&layoutDescription);
4737
4738 nbgl_layoutAddSpinner(
4739 genericContext.backgroundLayout, text, NULL, genericContext.spinnerPosition);
4740
4741 nbgl_layoutDraw(genericContext.backgroundLayout);
4743 }
4744 else {
4745 // otherwise increment spinner
4746 genericContext.spinnerPosition++;
4747 // there are only NB_SPINNER_POSITIONSpositions
4748 if (genericContext.spinnerPosition == NB_SPINNER_POSITIONS) {
4749 genericContext.spinnerPosition = 0;
4750 }
4751 int ret = nbgl_layoutUpdateSpinner(
4752 genericContext.backgroundLayout, text, NULL, genericContext.spinnerPosition);
4753 if (ret == 1) {
4755 }
4756 else if (ret == 2) {
4758 }
4759 }
4760}
4761
4762#ifdef NBGL_KEYPAD
4781void nbgl_useCaseKeypad(const char *title,
4782 uint8_t minDigits,
4783 uint8_t maxDigits,
4784 bool shuffled,
4785 bool hidden,
4786 nbgl_pinValidCallback_t validatePinCallback,
4787 nbgl_callback_t backCallback)
4788{
4789 nbgl_layoutDescription_t layoutDescription = {0};
4790 nbgl_layoutHeader_t headerDesc = {.type = HEADER_BACK_AND_TEXT,
4791 .separationLine = true,
4792 .backAndText.token = BACK_TOKEN,
4793 .backAndText.tuneId = TUNE_TAP_CASUAL};
4794 int status = -1;
4795
4796 if ((minDigits > KEYPAD_MAX_DIGITS) || (maxDigits > KEYPAD_MAX_DIGITS)) {
4798 "nbgl_useCaseKeypad(): digits out of range (%d/%d > %d)\n",
4799 minDigits,
4800 maxDigits,
4802 return;
4803 }
4804
4805 reset_callbacks_and_context();
4806 // reset the keypad context
4807 memset(&keypadContext, 0, sizeof(KeypadContext_t));
4808
4809 // memorize context
4810 onQuit = backCallback;
4811
4812 // get a layout
4813 layoutDescription.onActionCallback = keypadGeneric_cb;
4814 keypadContext.layoutCtx = nbgl_layoutGet(&layoutDescription);
4815 keypadContext.hidden = hidden;
4816
4817 // set back key in header
4818 nbgl_layoutAddHeader(keypadContext.layoutCtx, &headerDesc);
4819
4820 // add keypad
4821 status = nbgl_layoutAddKeypad(keypadContext.layoutCtx, keypadCallback, shuffled);
4822 if (status < 0) {
4823 LOG_WARN(
4824 USE_CASE_LOGGER, "nbgl_useCaseKeypad(): layout setup failed (status=%d)\n", status);
4825 return;
4826 }
4827 // add keypad content
4829 keypadContext.layoutCtx, title, keypadContext.hidden, maxDigits, "");
4830 if (status < 0) {
4831 LOG_WARN(
4832 USE_CASE_LOGGER, "nbgl_useCaseKeypad(): layout setup failed (status=%d)\n", status);
4833 return;
4834 }
4835
4836 // validation pin callback
4837 keypadContext.onValidatePin = validatePinCallback;
4838 // pin code acceptable lengths
4839 keypadContext.pinMinDigits = minDigits;
4840 keypadContext.pinMaxDigits = maxDigits;
4841
4842 nbgl_layoutDraw(keypadContext.layoutCtx);
4844}
4845#endif // NBGL_KEYPAD
4846
4847#ifdef NBGL_KEYBOARD
4875void nbgl_useCaseKeyboard(const nbgl_keyboardParams_t *params, nbgl_callback_t backCallback)
4876{
4877 // clang-format off
4878 nbgl_layoutDescription_t layoutDescription = {.onActionCallback = keyboardGeneric_cb};
4879 nbgl_layoutHeader_t headerDesc = {.type = HEADER_BACK_AND_TEXT,
4880 .backAndText.token = BACK_TOKEN,
4881 .backAndText.tuneId = TUNE_TAP_CASUAL};
4882 nbgl_layoutKbd_t kbdInfo = {.callback = &keyboardCallback,
4883 .lettersOnly = params->lettersOnly,
4884 .mode = params->mode,
4885 .casing = params->casing};
4886 int status = -1;
4887 // clang-format on
4888
4889 reset_callbacks_and_context();
4890 // reset the keyboard context
4891 memset(&keyboardContext, 0, sizeof(KeyboardContext_t));
4892
4893 // memorize context
4894 onQuit = backCallback;
4895
4896 // get a layout
4897 keyboardContext.layoutCtx = nbgl_layoutGet(&layoutDescription);
4898
4899 // set back key in header
4900 nbgl_layoutAddHeader(keyboardContext.layoutCtx, &headerDesc);
4901
4902 // Add keyboard
4903 status = nbgl_layoutAddKeyboard(keyboardContext.layoutCtx, &kbdInfo);
4904 if (status < 0) {
4905 LOG_WARN(
4906 USE_CASE_LOGGER, "nbgl_useCaseKeyboard(): layout setup failed (status=%d)\n", status);
4907 return;
4908 }
4909 keyboardContext.keyboardIndex = status;
4910 keyboardContext.casing = params->casing;
4911 keyboardContext.entryBuffer = PIC(params->entryBuffer);
4912 keyboardContext.entryMaxLen = params->entryMaxLen;
4913 keyboardContext.entryBuffer[0] = '\0';
4914 // add keyboard content
4915 keyboardContext.keyboardContent = (nbgl_layoutKeyboardContent_t){
4916 .type = params->type,
4917 .title = PIC(params->title),
4918 .numbered = params->numbered,
4919 .number = params->number,
4920 .obfuscated = params->obfuscated,
4921 .text = keyboardContext.entryBuffer,
4922 .textToken = KEYBOARD_CROSS_TOKEN,
4923 .tuneId = TUNE_TAP_CASUAL,
4924 };
4925 switch (params->type) {
4927 onAction = PIC(params->confirmationParams.onButtonCallback);
4928 keyboardContext.keyboardContent.confirmationButton = (nbgl_layoutConfirmationButton_t){
4929 .text = PIC(params->confirmationParams.buttonText),
4930 .token = KEYBOARD_BUTTON_TOKEN,
4931 .active = true,
4932 };
4933 break;
4935 onControls = PIC(params->suggestionParams.onButtonCallback);
4936 keyboardContext.getSuggestionButtons
4938 keyboardContext.keyboardContent.suggestionButtons = (nbgl_layoutSuggestionButtons_t){
4939 .buttons = PIC(params->suggestionParams.buttons),
4940 .firstButtonToken = params->suggestionParams.firstButtonToken,
4941 };
4942 break;
4943 default:
4944 return;
4945 }
4946 status = nbgl_layoutAddKeyboardContent(keyboardContext.layoutCtx,
4947 &keyboardContext.keyboardContent);
4948 if (status < 0) {
4949 LOG_WARN(
4950 USE_CASE_LOGGER, "nbgl_useCaseKeyboard(): layout setup failed (status=%d)\n", status);
4951 return;
4952 }
4953
4954 nbgl_layoutDraw(keyboardContext.layoutCtx);
4956}
4957#endif // NBGL_KEYBOARD
4958
4959#endif // HAVE_SE_TOUCH
4960#endif // NBGL_USE_CASE
nbgl_contentTagValue_t *(* nbgl_contentTagValueCallback_t)(uint8_t pairIndex)
prototype of tag/value pair retrieval callback
@ ICON_ILLUSTRATION
simple icon
@ 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_WARN(__logger,...)
Definition nbgl_debug.h:87
@ USE_CASE_LOGGER
Definition nbgl_debug.h:35
#define LOG_DEBUG(__logger,...)
Definition nbgl_debug.h:86
static void actionCallback(nbgl_step_t stepCtx, nbgl_buttonEvent_t event)
Definition nbgl_flow.c:133
uint8_t nbgl_getTextNbPagesInWidth(nbgl_font_id_e fontId, const char *text, uint8_t nbLinesPerPage, uint16_t maxWidth)
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:141
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)
#define WARNING_ICON
Definition nbgl_icons.h:206
#define LARGE_WARNING_ICON
Definition nbgl_icons.h:221
#define IMPORTANT_CIRCLE_ICON
Definition nbgl_icons.h:231
#define CHECK_CIRCLE_ICON
Definition nbgl_icons.h:230
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)
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_layoutUpdateKeyboard(nbgl_layout_t *layout, uint8_t index, uint32_t keyMask)
Updates an existing keyboard on bottom of the screen, with the given configuration.
int nbgl_layoutUpdateKeypad(nbgl_layout_t *layout, uint8_t index, bool enableValidate, bool enableBackspace)
Updates an existing keypad on bottom of the screen, with the given configuration.
int nbgl_layoutAddKeypadContent(nbgl_layout_t *layout, 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...
int nbgl_layoutDraw(nbgl_layout_t *layout)
Applies given layout. The screen will be redrawn.
@ WHITE_BACKGROUND
rounded bordered button, with text/icon in black, on white background
#define AVAILABLE_WIDTH
void * nbgl_layout_t
type shared externally
#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
#define NBGL_NO_PROGRESS_INDICATOR
To be used when a control token shall not be used.
Definition nbgl_layout.h:27
int nbgl_layoutAddKeypad(nbgl_layout_t *layout, keyboardCallback_t callback, const char *text, bool shuffled)
Adds a keypad on bottom of the screen, with the associated callback.
@ KEYBOARD_WITH_BUTTON
text entry area + confirmation button
@ KEYBOARD_WITH_SUGGESTIONS
text entry area + suggestion buttons
int nbgl_layoutRelease(nbgl_layout_t *layout)
Release the layout obtained with nbgl_layoutGet()
static void buttonCallback(nbgl_screen_t *screen, nbgl_buttonEvent_t buttonEvent)
void nbgl_refresh(void)
keyboardCase_t
Letters casing in which to open/set the keyboard.
Definition nbgl_obj.h:500
#define KEYPAD_MAX_DIGITS
Definition nbgl_obj.h:73
void nbgl_refreshSpecial(nbgl_refresh_mode_t mode)
#define BACKSPACE_KEY
Definition nbgl_obj.h:27
void nbgl_refreshSpecialWithPostRefresh(nbgl_refresh_mode_t mode, nbgl_post_refresh_t post_refresh)
#define VALIDATE_KEY
Definition nbgl_obj.h:28
nbgl_page_t * nbgl_pageDrawGenericContent(nbgl_layoutTouchCallback_t onActionCallback, const nbgl_pageNavigationInfo_t *nav, nbgl_pageContent_t *content)
nbgl_page_t * nbgl_pageDrawInfo(nbgl_layoutTouchCallback_t onActionCallback, const nbgl_screenTickerConfiguration_t *ticker, const nbgl_pageInfoDescription_t *info)
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)
int nbgl_pageRelease(nbgl_page_t *)
nbgl_page_t * nbgl_pageDrawGenericContentExt(nbgl_layoutTouchCallback_t onActionCallback, const nbgl_pageNavigationInfo_t *nav, nbgl_pageContent_t *content, bool modal)
@ 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:365
#define MIN(x, y)
Definition nbgl_types.h:130
struct PACKED__ nbgl_icon_details_s nbgl_icon_details_t
Represents all information about an icon.
#define MAX(x, y)
Definition nbgl_types.h:133
nbgl_refresh_mode_t
different modes of refresh for nbgl_refreshSpecial()
Definition nbgl_types.h:337
@ BLACK_AND_WHITE_REFRESH
to be used for pure B&W area, when contrast is important
Definition nbgl_types.h:341
@ FULL_COLOR_CLEAN_REFRESH
to be used for lock screen display (cleaner but longer refresh)
Definition nbgl_types.h:340
@ BLACK_AND_WHITE_FAST_REFRESH
to be used for pure B&W area, when contrast is not priority
Definition nbgl_types.h:342
@ FULL_COLOR_PARTIAL_REFRESH
to be used for small partial refresh (radio buttons, switches)
Definition nbgl_types.h:339
@ FULL_COLOR_REFRESH
to be used for normal refresh
Definition nbgl_types.h:338
API of the Advanced BOLOS Graphical Library, for typical application use-cases.
#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
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 to add a Skip bu...
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)
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)
#define RISKY_OPERATION
This is to use in nbgl_operationType_t when the operation is concerned by an internal warning....
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)
#define NO_THREAT_OPERATION
This is to use in nbgl_operationType_t when the operation is concerned by an internal information....
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_useCaseAdvancedChoiceWithDetails(const nbgl_icon_details_t *centerIcon, const nbgl_icon_details_t *headerIcon, const char *title, 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
#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
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_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
Mask to apply on nbgl_operationType_t to extract the base nbgl_opType_t. Bits 0-3 are reserved for th...
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,...
#define ADDRESS_BOOK_OPERATION
This is to use in nbgl_operationType_t when the operation is related to "Address Book" to adapt wordi...
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
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
const char * value
string giving the value name
const char * item
string giving the tag name
uint8_t centeredInfo
if set to 1, the tag will be displayed as a centered info
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_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
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
const char * title
centered title explaining the screen
char * entryBuffer
already entered text
This structure contains info to build a clickable "bar" with a text and an icon.
const char * text
text (can be NULL)
uint8_t token
the token that will be used as argument of the callback
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 confirmation button.
Structure containing all information when creating a layout. This structure must be passed as argumen...
nbgl_layoutButtonCallback_t onActionCallback
the callback to be called on any action on the layout
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 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