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 NavType_t navType;
289
290static DetailsContext_t detailsContext;
291
292// context for address review
293static AddressConfirmationContext_t addressConfirmationContext;
294
295// contexts for generic navigation
296static GenericContext_t genericContext;
297static nbgl_content_t
298 localContentsList[3]; // 3 needed for nbgl_useCaseReview (starting page / tags / final page)
299static uint8_t genericContextPagesInfo[MAX_PAGE_NB / PAGES_PER_UINT8];
300static uint8_t modalContextPagesInfo[MAX_MODAL_PAGE_NB / PAGES_PER_UINT8];
301
302// contexts for bundle navigation
303static nbgl_BundleNavContext_t bundleNavContext;
304
305// indexed by nbgl_contentType_t
306static const uint8_t nbMaxElementsPerContentType[] = {
307 1, // CENTERED_INFO
308 1, // EXTENDED_CENTER
309 1, // INFO_LONG_PRESS
310 1, // INFO_BUTTON
311 1, // TAG_VALUE_LIST (computed dynamically)
312 1, // TAG_VALUE_DETAILS
313 1, // TAG_VALUE_CONFIRM
314 3, // SWITCHES_LIST (computed dynamically)
315 3, // INFOS_LIST (computed dynamically)
316 5, // CHOICES_LIST (computed dynamically)
317 5, // BARS_LIST (computed dynamically)
318};
319
320// clang-format off
321static const SecurityReportItem_t securityReportItems[NB_WARNING_TYPES] = {
323 .icon = &WARNING_ICON,
324 .text = "Blind signing required",
325 .subText = "This transaction's details are not fully verifiable. If "
326 "you sign, you could lose all your assets."
327 },
328 [W3C_ISSUE_WARN] = {
329 .icon = &WARNING_ICON,
330 .text = "Transaction Check unavailable",
331 .subText = NULL
332 },
334 .icon = &WARNING_ICON,
335 .text = "Risk detected",
336 .subText = "This transaction was scanned as risky by Web3 Checks."
337 },
339 .icon = &WARNING_ICON,
340 .text = "Critical threat",
341 .subText = "This transaction was scanned as malicious by Web3 Checks."
342 },
344 .icon = NULL,
345 .text = "No threat detected",
346 .subText = "Transaction Check didn't find any threat, but always "
347 "review transaction details carefully."
348 }
349};
350// clang-format on
351
352// configuration of warning when using @ref nbgl_useCaseReviewBlindSigning()
353static const nbgl_warning_t blindSigningWarning = {.predefinedSet = (1 << BLIND_SIGNING_WARN)};
354
355#ifdef NBGL_QRCODE
356/* buffer to store reduced address under QR Code */
357static char reducedAddress[QRCODE_REDUCED_ADDR_LEN];
358#endif // NBGL_QRCODE
359
360/**********************
361 * STATIC FUNCTIONS
362 **********************/
363static void displayReviewPage(uint8_t page, bool forceFullRefresh);
364static void displayDetailsPage(uint8_t page, bool forceFullRefresh);
365static void displayTagValueListModalPage(uint8_t pageIdx, bool forceFullRefresh);
366static void displayFullValuePage(const char *backText,
367 const char *aliasText,
368 const nbgl_contentValueExt_t *extension);
369static void displayInfosListModal(const char *modalTitle,
370 const nbgl_contentInfoList_t *infos,
371 bool fromReview);
372static void displayTagValueListModal(const nbgl_contentTagValueList_t *tagValues);
373static void displaySettingsPage(uint8_t page, bool forceFullRefresh);
374static void displayGenericContextPage(uint8_t pageIdx, bool forceFullRefresh);
375static void pageCallback(int token, uint8_t index);
376
377static uint8_t getNbInfosInPage(uint8_t nbInfos,
378 const nbgl_contentInfoList_t *infosList,
379 uint8_t startIndex,
380 bool withNav);
381static uint8_t getNbSwitchesInPage(uint8_t nbSwitches,
382 const nbgl_contentSwitchesList_t *switchesList,
383 uint8_t startIndex,
384 bool withNav);
385static uint8_t getNbBarsInPage(uint8_t nbBars,
386 const nbgl_contentBarsList_t *barsList,
387 uint8_t startIndex,
388 bool withNav);
389#ifdef NBGL_QRCODE
390static void displayAddressQRCode(void);
391#endif // NBGL_QRCODE
392static void modalLayoutTouchCallback(int token, uint8_t index);
393static void displaySkipWarning(void);
394
395static void bundleNavStartHome(void);
396static void bundleNavStartSettingsAtPage(uint8_t initSettingPage);
397static void bundleNavStartSettings(void);
398
399static void bundleNavReviewStreamingChoice(bool confirm);
400static nbgl_layout_t *displayModalDetails(const nbgl_warningDetails_t *details, uint8_t token);
401static void displaySecurityReport(uint32_t set);
402static void displayCustomizedSecurityReport(const nbgl_warningDetails_t *details);
403static void displayInitialWarning(void);
404static void useCaseReview(nbgl_operationType_t operationType,
405 const nbgl_contentTagValueList_t *tagValueList,
406 const nbgl_icon_details_t *icon,
407 const char *reviewTitle,
408 const char *reviewSubTitle,
409 const char *finishTitle,
410 const nbgl_tipBox_t *tipBox,
411 nbgl_choiceCallback_t choiceCallback,
412 bool isLight,
413 bool playNotifSound);
414static void useCaseReviewStreamingStart(nbgl_operationType_t operationType,
415 const nbgl_icon_details_t *icon,
416 const char *reviewTitle,
417 const char *reviewSubTitle,
418 nbgl_choiceCallback_t choiceCallback,
419 bool playNotifSound);
420static void useCaseHomeExt(const char *appName,
421 const nbgl_icon_details_t *appIcon,
422 const char *tagline,
423 bool withSettings,
424 nbgl_homeAction_t *homeAction,
425 nbgl_callback_t topRightCallback,
426 nbgl_callback_t quitCallback);
427static void displayDetails(const char *tag, const char *value, bool wrapping);
428
429static void reset_callbacks_and_context(void)
430{
431 onQuit = NULL;
432 onContinue = NULL;
433 onAction = NULL;
434 onNav = NULL;
435 onControls = NULL;
436 onContentAction = NULL;
437 onChoice = NULL;
438 memset(&genericContext, 0, sizeof(genericContext));
439}
440
441// Helper to set genericContext page info
442static void genericContextSetPageInfo(uint8_t pageIdx, uint8_t nbElements, bool flag)
443{
444 uint8_t pageData = SET_PAGE_NB_ELEMENTS(nbElements) + SET_PAGE_FLAG(flag);
445
446 genericContextPagesInfo[pageIdx / PAGES_PER_UINT8]
447 &= ~(0x0F << ((pageIdx % PAGES_PER_UINT8) * PAGE_DATA_BITS));
448 genericContextPagesInfo[pageIdx / PAGES_PER_UINT8]
449 |= pageData << ((pageIdx % PAGES_PER_UINT8) * PAGE_DATA_BITS);
450}
451
452// Helper to get genericContext page info
453static void genericContextGetPageInfo(uint8_t pageIdx, uint8_t *nbElements, bool *flag)
454{
455 uint8_t pageData = genericContextPagesInfo[pageIdx / PAGES_PER_UINT8]
456 >> ((pageIdx % PAGES_PER_UINT8) * PAGE_DATA_BITS);
457 if (nbElements != NULL) {
458 *nbElements = GET_PAGE_NB_ELEMENTS(pageData);
459 }
460 if (flag != NULL) {
461 *flag = GET_PAGE_FLAG(pageData);
462 }
463}
464
465// Helper to set modalContext page info
466static void modalContextSetPageInfo(uint8_t pageIdx, uint8_t nbElements)
467{
468 uint8_t pageData = SET_PAGE_NB_ELEMENTS(nbElements);
469
470 modalContextPagesInfo[pageIdx / PAGES_PER_UINT8]
471 &= ~(0x0F << ((pageIdx % PAGES_PER_UINT8) * PAGE_DATA_BITS));
472 modalContextPagesInfo[pageIdx / PAGES_PER_UINT8]
473 |= pageData << ((pageIdx % PAGES_PER_UINT8) * PAGE_DATA_BITS);
474}
475
476// Helper to get modalContext page info
477static void modalContextGetPageInfo(uint8_t pageIdx, uint8_t *nbElements)
478{
479 uint8_t pageData = modalContextPagesInfo[pageIdx / PAGES_PER_UINT8]
480 >> ((pageIdx % PAGES_PER_UINT8) * PAGE_DATA_BITS);
481 if (nbElements != NULL) {
482 *nbElements = GET_PAGE_NB_ELEMENTS(pageData);
483 }
484}
485
486// Simple helper to get the number of elements inside a nbgl_content_t
487static uint8_t getContentNbElement(const nbgl_content_t *content)
488{
489 switch (content->type) {
490 case TAG_VALUE_LIST:
491 return content->content.tagValueList.nbPairs;
494 case SWITCHES_LIST:
495 return content->content.switchesList.nbSwitches;
496 case INFOS_LIST:
497 return content->content.infosList.nbInfos;
498 case CHOICES_LIST:
499 return content->content.choicesList.nbChoices;
500 case BARS_LIST:
501 return content->content.barsList.nbBars;
502 default:
503 return 1;
504 }
505}
506
507// Helper to retrieve the content inside a nbgl_genericContents_t using
508// either the contentsList or using the contentGetterCallback
509static const nbgl_content_t *getContentAtIdx(const nbgl_genericContents_t *genericContents,
510 int8_t contentIdx,
511 nbgl_content_t *content)
512{
513 if (contentIdx < 0 || contentIdx >= genericContents->nbContents) {
514 LOG_DEBUG(USE_CASE_LOGGER, "No content available at %d\n", contentIdx);
515 return NULL;
516 }
517
518 if (genericContents->callbackCallNeeded) {
519 // Retrieve content through callback, but first memset the content.
520 memset(content, 0, sizeof(nbgl_content_t));
521 genericContents->contentGetterCallback(contentIdx, content);
522 return content;
523 }
524 else {
525 // Retrieve content through list
526 return PIC(&genericContents->contentsList[contentIdx]);
527 }
528}
529
530static void prepareNavInfo(bool isReview, uint8_t nbPages, const char *rejectText)
531{
532 memset(&navInfo, 0, sizeof(navInfo));
533
534 navInfo.nbPages = nbPages;
535 navInfo.tuneId = TUNE_TAP_CASUAL;
536 navInfo.progressIndicator = false;
537 navInfo.navType = NAV_WITH_BUTTONS;
538
539 if (isReview == false) {
540 navInfo.navWithButtons.navToken = NAV_TOKEN;
541 navInfo.navWithButtons.backButton = true;
542 }
543 else {
544 navInfo.quitToken = REJECT_TOKEN;
545 navInfo.navWithButtons.quitText = rejectText;
546 navInfo.navWithButtons.navToken = NAV_TOKEN;
548 = ((navType == STREAMING_NAV) && (nbPages < 2)) ? false : true;
549 navInfo.navWithButtons.visiblePageIndicator = (navType != STREAMING_NAV);
550 }
551}
552
553static void prepareReviewFirstPage(nbgl_contentCenter_t *contentCenter,
554 const nbgl_icon_details_t *icon,
555 const char *reviewTitle,
556 const char *reviewSubTitle)
557{
558 contentCenter->icon = icon;
559 contentCenter->title = reviewTitle;
560 contentCenter->description = reviewSubTitle;
561 contentCenter->subText = "Swipe to review";
562 contentCenter->smallTitle = NULL;
563 contentCenter->iconHug = 0;
564 contentCenter->padding = false;
565 contentCenter->illustrType = ICON_ILLUSTRATION;
566}
567
568static const char *getFinishTitle(nbgl_operationType_t operationType, const char *finishTitle)
569{
570 if (finishTitle != NULL) {
571 return finishTitle;
572 }
573 switch (operationType & REAL_TYPE_MASK) {
574 case TYPE_TRANSACTION:
575 return "Sign transaction";
576 case TYPE_MESSAGE:
577 return "Sign message";
578 default:
579 return "Sign operation";
580 }
581}
582
583static void prepareReviewLastPage(nbgl_operationType_t operationType,
584 nbgl_contentInfoLongPress_t *infoLongPress,
585 const nbgl_icon_details_t *icon,
586 const char *finishTitle)
587{
588 infoLongPress->text = getFinishTitle(operationType, finishTitle);
589 infoLongPress->icon = icon;
590 infoLongPress->longPressText = "Hold to sign";
591 infoLongPress->longPressToken = CONFIRM_TOKEN;
592}
593
594static void prepareReviewLightLastPage(nbgl_operationType_t operationType,
595 nbgl_contentInfoButton_t *infoButton,
596 const nbgl_icon_details_t *icon,
597 const char *finishTitle)
598{
599 infoButton->text = getFinishTitle(operationType, finishTitle);
600 infoButton->icon = icon;
601 if (operationType & ADDRESS_BOOK_OPERATION) {
602 infoButton->buttonText = "Confirm";
603 }
604 else {
605 infoButton->buttonText = "Approve";
606 }
607 infoButton->buttonToken = CONFIRM_TOKEN;
608}
609
610static const char *getRejectReviewText(nbgl_operationType_t operationType)
611{
612 if (operationType & ADDRESS_BOOK_OPERATION) {
613 return "Cancel";
614 }
615 return "Reject";
616}
617
618// function called when navigating (or exiting) modal details pages
619// or when skip choice is displayed
620static void pageModalCallback(int token, uint8_t index)
621{
622 LOG_DEBUG(USE_CASE_LOGGER, "pageModalCallback, token = %d, index = %d\n", token, index);
623
624 if (token == INFOS_TIP_BOX_TOKEN) {
625 // the icon next to info type alias has been touched
626 displayFullValuePage(activeTipBox.infos.infoTypes[index],
627 activeTipBox.infos.infoContents[index],
628 &activeTipBox.infos.infoExtensions[index]);
629 return;
630 }
631 else if (token == INFO_ALIAS_TOKEN) {
632 // the icon next to info type alias has been touched, but coming from review
633 displayFullValuePage(genericContext.currentInfos->infoTypes[index],
634 genericContext.currentInfos->infoContents[index],
635 &genericContext.currentInfos->infoExtensions[index]);
636 return;
637 }
638 nbgl_pageRelease(modalPageContext);
639 modalPageContext = NULL;
640 if (token == NAV_TOKEN) {
641 if (index == EXIT_PAGE) {
642 // redraw the background layer
644 nbgl_refresh();
645 }
646 else {
647 displayDetailsPage(index, false);
648 }
649 }
650 if (token == MODAL_NAV_TOKEN) {
651 if (index == EXIT_PAGE) {
652 // redraw the background layer
654 nbgl_refresh();
655 }
656 else {
657 displayTagValueListModalPage(index, false);
658 }
659 }
660 else if (token == QUIT_TOKEN) {
661 // redraw the background layer
663 nbgl_refresh();
664 }
665 else if (token == QUIT_TIPBOX_MODAL_TOKEN) {
666 // if in "warning context", go back to security report
667 if (reviewWithWarnCtx.securityReportLevel == 2) {
668 reviewWithWarnCtx.securityReportLevel = 1;
669 displaySecurityReport(reviewWithWarnCtx.warning->predefinedSet);
670 }
671 else {
672 // redraw the background layer
674 nbgl_refresh();
675 }
676 }
677 else if (token == SKIP_TOKEN) {
678 if (index == 0) {
679 // display the last forward only review page, whatever it is
680 displayGenericContextPage(LAST_PAGE_FOR_REVIEW, true);
681 }
682 else {
683 // display background, which should be the page where skip has been touched
685 nbgl_refresh();
686 }
687 }
688 else if (token == CHOICE_TOKEN) {
689 if (index == 0) {
690 if (onModalConfirm != NULL) {
691 onModalConfirm();
692 onModalConfirm = NULL;
693 }
694 }
695 else {
696 // display background, which should be the page where skip has been touched
698 nbgl_refresh();
699 }
700 }
701}
702
703// generic callback for all pages except modal
704static void pageCallback(int token, uint8_t index)
705{
706 LOG_DEBUG(USE_CASE_LOGGER, "pageCallback, token = %d, index = %d\n", token, index);
707 if (token == QUIT_TOKEN) {
708 if (onQuit != NULL) {
709 onQuit();
710 }
711 }
712 else if (token == CONTINUE_TOKEN) {
713 if (onContinue != NULL) {
714 onContinue();
715 }
716 }
717 else if (token == CHOICE_TOKEN) {
718 if (onChoice != NULL) {
719 onChoice((index == 0) ? true : false);
720 }
721 }
722 else if (token == ACTION_BUTTON_TOKEN) {
723 if ((index == 0) && (onAction != NULL)) {
724 onAction();
725 }
726 else if ((index == 1) && (onQuit != NULL)) {
727 onQuit();
728 }
729 }
730#ifdef NBGL_QRCODE
731 else if (token == ADDRESS_QRCODE_BUTTON_TOKEN) {
732 displayAddressQRCode();
733 }
734#endif // NBGL_QRCODE
735 else if (token == CONFIRM_TOKEN) {
736 if (onChoice != NULL) {
737 onChoice(true);
738 }
739 }
740 else if (token == REJECT_TOKEN) {
741 if (onChoice != NULL) {
742 onChoice(false);
743 }
744 }
745 else if (token == DETAILS_BUTTON_TOKEN) {
746 displayDetails(genericContext.detailsItem,
747 genericContext.detailsvalue,
748 genericContext.detailsWrapping);
749 }
750 else if (token == NAV_TOKEN) {
751 if (index == EXIT_PAGE) {
752 if (onQuit != NULL) {
753 onQuit();
754 }
755 }
756 else {
757 if (navType == GENERIC_NAV || navType == STREAMING_NAV) {
758 displayGenericContextPage(index, false);
759 }
760 else if (navType == REVIEW_NAV) {
761 displayReviewPage(index, false);
762 }
763 else {
764 displaySettingsPage(index, false);
765 }
766 }
767 }
768 else if (token == NEXT_TOKEN) {
769 if (onNav != NULL) {
770 displayReviewPage(navInfo.activePage + 1, false);
771 }
772 else {
773 displayGenericContextPage(navInfo.activePage + 1, false);
774 }
775 }
776 else if (token == BACK_TOKEN) {
777 if (onNav != NULL) {
778 displayReviewPage(navInfo.activePage - 1, true);
779 }
780 else {
781 displayGenericContextPage(navInfo.activePage - 1, false);
782 }
783 }
784 else if (token == SKIP_TOKEN) {
785 // display a modal warning to confirm skip
786 displaySkipWarning();
787 }
788 else if (token == VALUE_ALIAS_TOKEN) {
789 // the icon next to value alias has been touched
790 const nbgl_contentTagValue_t *pair;
791 if (genericContext.currentPairs != NULL) {
792 pair = &genericContext.currentPairs[genericContext.currentElementIdx + index];
793 }
794 else {
795 pair = genericContext.currentCallback(genericContext.currentElementIdx + index);
796 }
797 displayFullValuePage(pair->item, pair->value, pair->extension);
798 }
799 else if (token == BLIND_WARNING_TOKEN) {
800 reviewWithWarnCtx.isIntro = false;
801 reviewWithWarnCtx.warning = NULL;
802 displaySecurityReport(1 << BLIND_SIGNING_WARN);
803 }
804 else if (token == WARNING_BUTTON_TOKEN) {
805 // top-right button, display the security modal
806 reviewWithWarnCtx.securityReportLevel = 1;
807 // if preset is used
808 if (reviewWithWarnCtx.warning->predefinedSet) {
809 displaySecurityReport(reviewWithWarnCtx.warning->predefinedSet);
810 }
811 else {
812 // use customized warning
813 if (reviewWithWarnCtx.isIntro) {
814 displayCustomizedSecurityReport(reviewWithWarnCtx.warning->introDetails);
815 }
816 else {
817 displayCustomizedSecurityReport(reviewWithWarnCtx.warning->reviewDetails);
818 }
819 }
820 }
821 else if (token == TIP_BOX_TOKEN) {
822 // if warning context is valid and if W3C directly display same as top-right
823 if (genericContext.validWarningCtx && (reviewWithWarnCtx.warning->predefinedSet != 0)) {
824 reviewWithWarnCtx.securityReportLevel = 1;
825 displaySecurityReport(reviewWithWarnCtx.warning->predefinedSet);
826 }
827 else {
828 displayInfosListModal(activeTipBox.modalTitle, &activeTipBox.infos, false);
829 }
830 }
831 else { // probably a control provided by caller
832 if (onContentAction != NULL) {
833 onContentAction(token, index, navInfo.activePage);
834 }
835 if (onControls != NULL) {
836 onControls(token, index);
837 }
838 }
839}
840
841// callback used for confirmation
842static void tickerCallback(void)
843{
844 nbgl_pageRelease(pageContext);
845 if (onQuit != NULL) {
846 onQuit();
847 }
848}
849
850// function used to display the current page in review
851static void displaySettingsPage(uint8_t page, bool forceFullRefresh)
852{
853 nbgl_pageContent_t content = {0};
854
855 if (onNav == NULL) {
856 LOG_WARN(USE_CASE_LOGGER, "displaySettingsPage(): onNav callback is NULL\n");
857 return;
858 }
859 if (onNav(page, &content) == false) {
860 return;
861 }
862
863 // override some fields
864 content.title = pageTitle;
865 content.isTouchableTitle = true;
866 content.titleToken = QUIT_TOKEN;
867 content.tuneId = TUNE_TAP_CASUAL;
868
869 navInfo.activePage = page;
870 pageContext = nbgl_pageDrawGenericContent(&pageCallback, &navInfo, &content);
871
872 if (forceFullRefresh) {
874 }
875 else {
877 }
878}
879
880// function used to display the current page in review
881static void displayReviewPage(uint8_t page, bool forceFullRefresh)
882{
883 nbgl_pageContent_t content = {0};
884
885 // ensure the page is valid
886 if ((navInfo.nbPages != 0) && (page >= (navInfo.nbPages))) {
887 return;
888 }
889 navInfo.activePage = page;
890 if (onNav == NULL) {
891 LOG_WARN(USE_CASE_LOGGER, "displayReviewPage(): onNav callback is NULL\n");
892 return;
893 }
894 if (onNav(navInfo.activePage, &content) == false) {
895 return;
896 }
897
898 // override some fields
899 content.title = NULL;
900 content.isTouchableTitle = false;
901 content.tuneId = TUNE_TAP_CASUAL;
902
903 if (content.type == INFO_LONG_PRESS) { // last page
904 // for forward only review without known length...
905 // if we don't do that we cannot remove the '>' in the navigation bar at the last page
906 navInfo.nbPages = navInfo.activePage + 1;
907 content.infoLongPress.longPressToken = CONFIRM_TOKEN;
908 }
909
910 // override smallCaseForValue for tag/value types to false
911 if (content.type == TAG_VALUE_DETAILS) {
913 // the maximum displayable number of lines for value is NB_MAX_LINES_IN_REVIEW (without More
914 // button)
917 }
918 else if (content.type == TAG_VALUE_LIST) {
919 content.tagValueList.smallCaseForValue = false;
920 }
921 else if (content.type == TAG_VALUE_CONFIRM) {
923 // use confirm token for black button
924 content.tagValueConfirm.confirmationToken = CONFIRM_TOKEN;
925 }
926
927 pageContext = nbgl_pageDrawGenericContent(&pageCallback, &navInfo, &content);
928
929 if (forceFullRefresh) {
931 }
932 else {
934 }
935}
936
937// Helper that does the computing of which nbgl_content_t and which element in the content should be
938// displayed for the next generic context navigation page
939static const nbgl_content_t *genericContextComputeNextPageParams(uint8_t pageIdx,
940 nbgl_content_t *content,
941 uint8_t *p_nbElementsInNextPage,
942 bool *p_flag)
943{
944 int8_t nextContentIdx = genericContext.currentContentIdx;
945 int16_t nextElementIdx = genericContext.currentElementIdx;
946 uint8_t nbElementsInNextPage;
947
948 // Retrieve info on the next page
949 genericContextGetPageInfo(pageIdx, &nbElementsInNextPage, p_flag);
950 *p_nbElementsInNextPage = nbElementsInNextPage;
951
952 // Handle forward navigation:
953 // add to current index the number of pairs of the currently active page
954 if (pageIdx > navInfo.activePage) {
955 uint8_t nbElementsInCurrentPage;
956
957 genericContextGetPageInfo(navInfo.activePage, &nbElementsInCurrentPage, NULL);
958 nextElementIdx += nbElementsInCurrentPage;
959
960 // Handle case where the content to be displayed is in the next content
961 // In such case start at element index 0.
962 // If currentContentElementNb == 0, means not initialized, so skip
963 if ((nextElementIdx >= genericContext.currentContentElementNb)
964 && (genericContext.currentContentElementNb > 0)) {
965 nextContentIdx += 1;
966 nextElementIdx = 0;
967 }
968 }
969
970 // Handle backward navigation:
971 // add to current index the number of pairs of the currently active page
972 if (pageIdx < navInfo.activePage) {
973 // Backward navigation: remove to current index the number of pairs of the current page
974 nextElementIdx -= nbElementsInNextPage;
975
976 // Handle case where the content to be displayed is in the previous content
977 // In such case set a negative number as element index so that it is handled
978 // later once the previous content is accessible so that its elements number
979 // can be retrieved.
980 if (nextElementIdx < 0) {
981 nextContentIdx -= 1;
982 nextElementIdx = -nbElementsInNextPage;
983 }
984 }
985
986 const nbgl_content_t *p_content;
987 // Retrieve next content
988 if ((nextContentIdx == -1) && (genericContext.hasStartingContent)) {
989 p_content = &STARTING_CONTENT;
990 }
991 else if ((nextContentIdx == genericContext.genericContents.nbContents)
992 && (genericContext.hasFinishingContent)) {
993 p_content = &FINISHING_CONTENT;
994 }
995 else {
996 p_content = getContentAtIdx(&genericContext.genericContents, nextContentIdx, content);
997
998 if (p_content == NULL) {
999 LOG_DEBUG(USE_CASE_LOGGER, "Fail to retrieve content\n");
1000 return NULL;
1001 }
1002 }
1003
1004 // Handle cases where we are going to display data from a new content:
1005 // - navigation to a previous or to the next content
1006 // - First page display (genericContext.currentContentElementNb == 0)
1007 //
1008 // In such case we need to:
1009 // - Update genericContext.currentContentIdx
1010 // - Update genericContext.currentContentElementNb
1011 // - Update onContentAction callback
1012 // - Update nextElementIdx in case it was previously not calculable
1013 if ((nextContentIdx != genericContext.currentContentIdx)
1014 || (genericContext.currentContentElementNb == 0)) {
1015 genericContext.currentContentIdx = nextContentIdx;
1016 genericContext.currentContentElementNb = getContentNbElement(p_content);
1017 onContentAction = PIC(p_content->contentActionCallback);
1018 if (nextElementIdx < 0) {
1019 nextElementIdx = genericContext.currentContentElementNb + nextElementIdx;
1020 }
1021 }
1022
1023 // Sanity check
1024 if ((nextElementIdx < 0) || (nextElementIdx >= genericContext.currentContentElementNb)) {
1026 "Invalid element index %d / %d\n",
1027 nextElementIdx,
1028 genericContext.currentContentElementNb);
1029 return NULL;
1030 }
1031
1032 // Update genericContext elements
1033 genericContext.currentElementIdx = nextElementIdx;
1034 navInfo.activePage = pageIdx;
1035
1036 return p_content;
1037}
1038
1039// Helper that generates a nbgl_pageContent_t to be displayed for the next generic context
1040// navigation page
1041static bool genericContextPreparePageContent(const nbgl_content_t *p_content,
1042 uint8_t nbElementsInPage,
1043 bool flag,
1044 nbgl_pageContent_t *pageContent)
1045{
1046 uint8_t nextElementIdx = genericContext.currentElementIdx;
1047
1048 pageContent->title = pageTitle;
1049 pageContent->isTouchableTitle = false;
1050 pageContent->titleToken = QUIT_TOKEN;
1051 pageContent->tuneId = TUNE_TAP_CASUAL;
1052
1053 pageContent->type = p_content->type;
1054 switch (pageContent->type) {
1055 case CENTERED_INFO:
1056 memcpy(&pageContent->centeredInfo,
1057 &p_content->content.centeredInfo,
1058 sizeof(pageContent->centeredInfo));
1059 break;
1060 case EXTENDED_CENTER:
1061 memcpy(&pageContent->extendedCenter,
1062 &p_content->content.extendedCenter,
1063 sizeof(pageContent->extendedCenter));
1064 break;
1065 case INFO_LONG_PRESS:
1066 memcpy(&pageContent->infoLongPress,
1067 &p_content->content.infoLongPress,
1068 sizeof(pageContent->infoLongPress));
1069 break;
1070
1071 case INFO_BUTTON:
1072 memcpy(&pageContent->infoButton,
1073 &p_content->content.infoButton,
1074 sizeof(pageContent->infoButton));
1075 break;
1076
1077 case TAG_VALUE_LIST: {
1078 nbgl_contentTagValueList_t *p_tagValueList = &pageContent->tagValueList;
1079
1080 // memorize pairs (or callback) for usage when alias is used
1081 genericContext.currentPairs = p_content->content.tagValueList.pairs;
1082 genericContext.currentCallback = p_content->content.tagValueList.callback;
1083
1084 if (flag) {
1085 // Flag can be set if the pair is too long to fit or because it needs
1086 // to be displayed as centered info.
1087
1088 // First retrieve the pair
1089 const nbgl_layoutTagValue_t *pair;
1090
1091 if (p_content->content.tagValueList.pairs != NULL) {
1092 pair = PIC(&p_content->content.tagValueList.pairs[nextElementIdx]);
1093 }
1094 else {
1095 pair = PIC(p_content->content.tagValueList.callback(nextElementIdx));
1096 }
1097
1098 if (pair->centeredInfo) {
1099 pageContent->type = EXTENDED_CENTER;
1100 prepareReviewFirstPage(&pageContent->extendedCenter.contentCenter,
1101 pair->valueIcon,
1102 pair->item,
1103 pair->value);
1104
1105 // Skip population of nbgl_contentTagValueList_t structure
1106 p_tagValueList = NULL;
1107 }
1108 else {
1109 // if the pair is too long to fit, we use a TAG_VALUE_DETAILS content
1110 pageContent->type = TAG_VALUE_DETAILS;
1111 pageContent->tagValueDetails.detailsButtonText = "More";
1112 pageContent->tagValueDetails.detailsButtonIcon = NULL;
1113 pageContent->tagValueDetails.detailsButtonToken = DETAILS_BUTTON_TOKEN;
1114
1115 p_tagValueList = &pageContent->tagValueDetails.tagValueList;
1116
1117 // Backup pair info for easy data access upon click on button
1118 genericContext.detailsItem = pair->item;
1119 genericContext.detailsvalue = pair->value;
1120 genericContext.detailsWrapping = p_content->content.tagValueList.wrapping;
1121 }
1122 }
1123
1124 if (p_tagValueList != NULL) {
1125 p_tagValueList->nbPairs = nbElementsInPage;
1126 p_tagValueList->token = p_content->content.tagValueList.token;
1127 if (p_content->content.tagValueList.pairs != NULL) {
1128 p_tagValueList->pairs
1129 = PIC(&p_content->content.tagValueList.pairs[nextElementIdx]);
1130 // parse pairs to check if any contains an alias for value
1131 for (uint8_t i = 0; i < nbElementsInPage; i++) {
1132 if (p_tagValueList->pairs[i].aliasValue) {
1133 p_tagValueList->token = VALUE_ALIAS_TOKEN;
1134 break;
1135 }
1136 }
1137 }
1138 else {
1139 p_tagValueList->pairs = NULL;
1140 p_tagValueList->callback = p_content->content.tagValueList.callback;
1141 p_tagValueList->startIndex = nextElementIdx;
1142 // parse pairs to check if any contains an alias for value
1143 for (uint8_t i = 0; i < nbElementsInPage; i++) {
1144 const nbgl_layoutTagValue_t *pair
1145 = PIC(p_content->content.tagValueList.callback(nextElementIdx + i));
1146 if (pair->aliasValue) {
1147 p_tagValueList->token = VALUE_ALIAS_TOKEN;
1148 break;
1149 }
1150 }
1151 }
1152 p_tagValueList->smallCaseForValue = false;
1154 p_tagValueList->hideEndOfLastLine = true;
1155 p_tagValueList->wrapping = p_content->content.tagValueList.wrapping;
1156 }
1157
1158 break;
1159 }
1160 case TAG_VALUE_CONFIRM: {
1161 nbgl_contentTagValueList_t *p_tagValueList;
1162 // only display a TAG_VALUE_CONFIRM if we are at the last page
1163 if ((nextElementIdx + nbElementsInPage)
1165 memcpy(&pageContent->tagValueConfirm,
1166 &p_content->content.tagValueConfirm,
1167 sizeof(pageContent->tagValueConfirm));
1168 p_tagValueList = &pageContent->tagValueConfirm.tagValueList;
1169 }
1170 else {
1171 // else display it as a TAG_VALUE_LIST: preserve list-level
1172 // settings (nbMaxLinesForValue, wrapping, smallCaseForValue,
1173 // hideEndOfLastLine, token...) so intermediate pages render
1174 // identically to the last page
1175 pageContent->type = TAG_VALUE_LIST;
1176 pageContent->tagValueList = p_content->content.tagValueConfirm.tagValueList;
1177 p_tagValueList = &pageContent->tagValueList;
1178 }
1179 p_tagValueList->nbPairs = nbElementsInPage;
1180 p_tagValueList->pairs
1181 = PIC(&p_content->content.tagValueConfirm.tagValueList.pairs[nextElementIdx]);
1182 // parse pairs to check if any contains an alias for value
1183 for (uint8_t i = 0; i < nbElementsInPage; i++) {
1184 if (p_tagValueList->pairs[i].aliasValue) {
1185 p_tagValueList->token = VALUE_ALIAS_TOKEN;
1186 break;
1187 }
1188 }
1189 // memorize pairs (or callback) for usage when alias is used
1190 genericContext.currentPairs = p_tagValueList->pairs;
1191 genericContext.currentCallback = p_tagValueList->callback;
1192 break;
1193 }
1194 case SWITCHES_LIST:
1195 pageContent->switchesList.nbSwitches = nbElementsInPage;
1196 pageContent->switchesList.switches
1197 = PIC(&p_content->content.switchesList.switches[nextElementIdx]);
1198 break;
1199 case INFOS_LIST:
1200 pageContent->infosList.nbInfos = nbElementsInPage;
1201 pageContent->infosList.infoTypes
1202 = PIC(&p_content->content.infosList.infoTypes[nextElementIdx]);
1203 pageContent->infosList.infoContents
1204 = PIC(&p_content->content.infosList.infoContents[nextElementIdx]);
1205 break;
1206 case CHOICES_LIST:
1207 memcpy(&pageContent->choicesList,
1208 &p_content->content.choicesList,
1209 sizeof(pageContent->choicesList));
1210 pageContent->choicesList.nbChoices = nbElementsInPage;
1211 pageContent->choicesList.names
1212 = PIC(&p_content->content.choicesList.names[nextElementIdx]);
1213 if ((p_content->content.choicesList.initChoice >= nextElementIdx)
1214 && (p_content->content.choicesList.initChoice
1215 < nextElementIdx + nbElementsInPage)) {
1216 pageContent->choicesList.initChoice
1217 = p_content->content.choicesList.initChoice - nextElementIdx;
1218 }
1219 else {
1220 pageContent->choicesList.initChoice = nbElementsInPage;
1221 }
1222 break;
1223 case BARS_LIST:
1224 pageContent->barsList.nbBars = nbElementsInPage;
1225 pageContent->barsList.barTexts
1226 = PIC(&p_content->content.barsList.barTexts[nextElementIdx]);
1227 pageContent->barsList.tokens = PIC(&p_content->content.barsList.tokens[nextElementIdx]);
1228 pageContent->barsList.tuneId = p_content->content.barsList.tuneId;
1229 break;
1230 default:
1231 LOG_DEBUG(USE_CASE_LOGGER, "Unsupported type %d\n", pageContent->type);
1232 return false;
1233 }
1234
1235 bool isFirstOrLastPage
1236 = ((p_content->type == CENTERED_INFO) || (p_content->type == EXTENDED_CENTER))
1237 || (p_content->type == INFO_LONG_PRESS);
1238 nbgl_operationType_t operationType
1239 = (navType == STREAMING_NAV)
1240 ? bundleNavContext.reviewStreaming.operationType
1241 : ((navType == GENERIC_NAV) ? bundleNavContext.review.operationType : 0);
1242
1243 // if first or last page of review and blind/risky operation, add the warning top-right button
1244 if (isFirstOrLastPage
1245 && (operationType & (BLIND_OPERATION | RISKY_OPERATION | NO_THREAT_OPERATION))) {
1246 // if issue is only Web3Checks "no threat", use privacy icon
1247 if ((operationType & NO_THREAT_OPERATION)
1248 && !(reviewWithWarnCtx.warning->predefinedSet & (1 << BLIND_SIGNING_WARN))) {
1249 pageContent->topRightIcon = &PRIVACY_ICON;
1250 }
1251 else {
1252 pageContent->topRightIcon = &WARNING_ICON;
1253 }
1254
1255 pageContent->topRightToken
1256 = (operationType & BLIND_OPERATION) ? BLIND_WARNING_TOKEN : WARNING_BUTTON_TOKEN;
1257 }
1258
1259 return true;
1260}
1261
1262// function used to display the current page in generic context navigation mode
1263static void displayGenericContextPage(uint8_t pageIdx, bool forceFullRefresh)
1264{
1265 // Retrieve next page parameters
1266 nbgl_content_t content = {0};
1267 uint8_t nbElementsInPage;
1268 bool flag;
1269 const nbgl_content_t *p_content = NULL;
1270
1271 if (navType == STREAMING_NAV) {
1272 if (pageIdx == LAST_PAGE_FOR_REVIEW) {
1273 if (bundleNavContext.reviewStreaming.skipCallback != NULL) {
1274 bundleNavContext.reviewStreaming.skipCallback();
1275 }
1276 return;
1277 }
1278 else if (pageIdx >= bundleNavContext.reviewStreaming.stepPageNb) {
1279 bundleNavReviewStreamingChoice(true);
1280 return;
1281 }
1282 }
1283 else {
1284 // if coming from Skip modal, let's say we are at the last page
1285 if (pageIdx == LAST_PAGE_FOR_REVIEW) {
1286 pageIdx = navInfo.nbPages - 1;
1287 }
1288 }
1289
1290 if (navInfo.activePage == pageIdx) {
1291 p_content
1292 = genericContextComputeNextPageParams(pageIdx, &content, &nbElementsInPage, &flag);
1293 }
1294 else if (navInfo.activePage < pageIdx) {
1295 // Support going more than one step forward.
1296 // It occurs when initializing a navigation on an arbitrary page
1297 for (int i = navInfo.activePage + 1; i <= pageIdx; i++) {
1298 p_content = genericContextComputeNextPageParams(i, &content, &nbElementsInPage, &flag);
1299 }
1300 }
1301 else {
1302 if (pageIdx - navInfo.activePage > 1) {
1303 // We don't support going more than one step backward as it doesn't occurs for now?
1304 LOG_DEBUG(USE_CASE_LOGGER, "Unsupported navigation\n");
1305 return;
1306 }
1307 p_content
1308 = genericContextComputeNextPageParams(pageIdx, &content, &nbElementsInPage, &flag);
1309 }
1310
1311 if (p_content == NULL) {
1312 return;
1313 }
1314 // if the operation is skippable
1315 if ((navType != STREAMING_NAV)
1316 && (bundleNavContext.review.operationType & SKIPPABLE_OPERATION)) {
1317 // only present the "Skip" header on actual tag/value pages
1318 if ((pageIdx > 0) && (pageIdx < (navInfo.nbPages - 1))) {
1319 navInfo.progressIndicator = false;
1320 navInfo.skipText = "Skip";
1321 navInfo.skipToken = SKIP_TOKEN;
1322 }
1323 else {
1324 navInfo.skipText = NULL;
1325 }
1326 }
1327
1328 // Create next page content
1329 nbgl_pageContent_t pageContent = {0};
1330 if (!genericContextPreparePageContent(p_content, nbElementsInPage, flag, &pageContent)) {
1331 return;
1332 }
1333
1334 pageContext = nbgl_pageDrawGenericContent(&pageCallback, &navInfo, &pageContent);
1335
1336 if (forceFullRefresh) {
1338 }
1339 else {
1341 }
1342}
1343
1344// from the current details context, return a pointer on the details at the given page
1345static const char *getDetailsPageAt(uint8_t detailsPage)
1346{
1347 uint8_t page = 0;
1348 const char *currentChar = detailsContext.value;
1349 while (page < detailsPage) {
1350 uint16_t nbLines
1351 = nbgl_getTextNbLinesInWidth(SMALL_BOLD_FONT, currentChar, AVAILABLE_WIDTH, false);
1352 if (nbLines > NB_MAX_LINES_IN_DETAILS) {
1353 uint16_t len;
1354 nbgl_getTextMaxLenInNbLines(SMALL_BOLD_FONT,
1355 currentChar,
1358 &len,
1359 detailsContext.wrapping);
1360 // don't start next page on a \n
1361 if (currentChar[len] == '\n') {
1362 len++;
1363 }
1364 else if (detailsContext.wrapping == false) {
1365 len -= 3;
1366 }
1367 currentChar = currentChar + len;
1368 }
1369 page++;
1370 }
1371 return currentChar;
1372}
1373
1374// function used to display the current page in details review mode
1375static void displayDetailsPage(uint8_t detailsPage, bool forceFullRefresh)
1376{
1377 static nbgl_layoutTagValue_t currentPair;
1378 nbgl_pageNavigationInfo_t info = {.activePage = detailsPage,
1379 .nbPages = detailsContext.nbPages,
1380 .navType = NAV_WITH_BUTTONS,
1381 .quitToken = QUIT_TOKEN,
1382 .navWithButtons.navToken = NAV_TOKEN,
1383 .navWithButtons.quitButton = true,
1384 .navWithButtons.backButton = true,
1385 .navWithButtons.quitText = NULL,
1386 .progressIndicator = false,
1387 .tuneId = TUNE_TAP_CASUAL};
1389 .topRightIcon = NULL,
1390 .tagValueList.nbPairs = 1,
1391 .tagValueList.pairs = &currentPair,
1392 .tagValueList.smallCaseForValue = true,
1393 .tagValueList.wrapping = detailsContext.wrapping};
1394
1395 if (modalPageContext != NULL) {
1396 nbgl_pageRelease(modalPageContext);
1397 }
1398 currentPair.item = detailsContext.tag;
1399 // if move backward or first page
1400 if (detailsPage <= detailsContext.currentPage) {
1401 // recompute current start from beginning
1402 currentPair.value = getDetailsPageAt(detailsPage);
1403 forceFullRefresh = true;
1404 }
1405 // else move forward
1406 else {
1407 currentPair.value = detailsContext.nextPageStart;
1408 }
1409 detailsContext.currentPage = detailsPage;
1410 uint16_t nbLines = nbgl_getTextNbLinesInWidth(
1411 SMALL_BOLD_FONT, currentPair.value, AVAILABLE_WIDTH, detailsContext.wrapping);
1412
1413 if (nbLines > NB_MAX_LINES_IN_DETAILS) {
1414 uint16_t len;
1415 nbgl_getTextMaxLenInNbLines(SMALL_BOLD_FONT,
1416 currentPair.value,
1419 &len,
1420 detailsContext.wrapping);
1422 // don't start next page on a \n
1423 if (currentPair.value[len] == '\n') {
1424 len++;
1425 }
1426 else if (!detailsContext.wrapping) {
1428 len -= 3;
1429 }
1430 // memorize next position to save processing
1431 detailsContext.nextPageStart = currentPair.value + len;
1432 // use special feature to keep only NB_MAX_LINES_IN_DETAILS lines and replace the last 3
1433 // chars by "...", only if the next char to display in next page is not '\n'
1435 }
1436 else {
1437 detailsContext.nextPageStart = NULL;
1438 content.tagValueList.nbMaxLinesForValue = 0;
1439 }
1440 if (info.nbPages == 1) {
1441 // if only one page, no navigation bar, and use a footer instead
1442 info.navWithButtons.quitText = "Close";
1443 }
1444 modalPageContext = nbgl_pageDrawGenericContentExt(&pageModalCallback, &info, &content, true);
1445
1446 if (forceFullRefresh) {
1448 }
1449 else {
1451 }
1452}
1453
1454// function used to display the content of a full value, when touching an alias of a tag/value pair
1455static void displayFullValuePage(const char *backText,
1456 const char *aliasText,
1457 const nbgl_contentValueExt_t *extension)
1458{
1459 const char *modalTitle
1460 = (extension->backText != NULL) ? PIC(extension->backText) : PIC(backText);
1461 if (extension->aliasType == INFO_LIST_ALIAS) {
1462 genericContext.currentInfos = extension->infolist;
1463 displayInfosListModal(modalTitle, extension->infolist, true);
1464 }
1465 else if (extension->aliasType == TAG_VALUE_LIST_ALIAS) {
1466 genericContext.currentTagValues = extension->tagValuelist;
1467 displayTagValueListModal(extension->tagValuelist);
1468 }
1469 else {
1470 nbgl_layoutDescription_t layoutDescription = {.modal = true,
1471 .withLeftBorder = true,
1472 .onActionCallback = &modalLayoutTouchCallback,
1473 .tapActionText = NULL};
1475 .separationLine = false,
1476 .backAndText.token = 0,
1477 .backAndText.tuneId = TUNE_TAP_CASUAL,
1478 .backAndText.text = modalTitle};
1479 genericContext.modalLayout = nbgl_layoutGet(&layoutDescription);
1480 // add header with the tag part of the pair, to go back
1481 nbgl_layoutAddHeader(genericContext.modalLayout, &headerDesc);
1482 // add either QR Code or full value text
1483 if (extension->aliasType == QR_CODE_ALIAS) {
1484#ifdef NBGL_QRCODE
1485 nbgl_layoutQRCode_t qrCode
1486 = {.url = extension->fullValue,
1487 .text1 = (extension->title != NULL) ? extension->title : extension->fullValue,
1488 .text2 = extension->explanation,
1489 .centered = true,
1490 .offsetY = 0};
1491
1492 nbgl_layoutAddQRCode(genericContext.modalLayout, &qrCode);
1493#endif // NBGL_QRCODE
1494 }
1495 else {
1496 nbgl_layoutTextContent_t content = {0};
1497 content.title = aliasText;
1498 if (extension->aliasType == ENS_ALIAS) {
1499 content.info = "ENS names are resolved by Ledger backend.";
1500 }
1501 else if (extension->aliasType == ADDRESS_BOOK_ALIAS) {
1502 if (extension->aliasSubName != NULL) {
1503 content.descriptions[content.nbDescriptions] = extension->aliasSubName;
1504 content.nbDescriptions++;
1505 }
1506 }
1507 else {
1508 content.info = extension->explanation;
1509 }
1510 // add full value text
1511 content.descriptions[content.nbDescriptions] = extension->fullValue;
1512 content.nbDescriptions++;
1513 // add trusted name if present (combined Address Book+Trusted Name case)
1514 if ((extension->aliasType == ADDRESS_BOOK_ALIAS) && (extension->explanation != NULL)) {
1515 content.descriptions[content.nbDescriptions] = extension->explanation;
1516 content.nbDescriptions++;
1517 }
1518 nbgl_layoutAddTextContent(genericContext.modalLayout, &content);
1519 }
1520 // draw & refresh
1521 nbgl_layoutDraw(genericContext.modalLayout);
1522 nbgl_refresh();
1523 }
1524}
1525
1526// function used to display the modal containing tip-box infos
1527static void displayInfosListModal(const char *modalTitle,
1528 const nbgl_contentInfoList_t *infos,
1529 bool fromReview)
1530{
1532 .nbPages = 1,
1533 .navType = NAV_WITH_BUTTONS,
1534 .quitToken = QUIT_TIPBOX_MODAL_TOKEN,
1535 .navWithButtons.navToken = NAV_TOKEN,
1536 .navWithButtons.quitButton = false,
1537 .navWithButtons.backButton = true,
1538 .navWithButtons.quitText = NULL,
1539 .progressIndicator = false,
1540 .tuneId = TUNE_TAP_CASUAL};
1541 nbgl_pageContent_t content
1542 = {.type = INFOS_LIST,
1543 .topRightIcon = NULL,
1544 .infosList.nbInfos = infos->nbInfos,
1545 .infosList.withExtensions = infos->withExtensions,
1546 .infosList.infoTypes = infos->infoTypes,
1547 .infosList.infoContents = infos->infoContents,
1548 .infosList.infoExtensions = infos->infoExtensions,
1549 .infosList.token = fromReview ? INFO_ALIAS_TOKEN : INFOS_TIP_BOX_TOKEN,
1550 .title = modalTitle,
1551 .titleToken = QUIT_TIPBOX_MODAL_TOKEN,
1552 .tuneId = TUNE_TAP_CASUAL};
1553
1554 if (modalPageContext != NULL) {
1555 nbgl_pageRelease(modalPageContext);
1556 }
1557 modalPageContext = nbgl_pageDrawGenericContentExt(&pageModalCallback, &info, &content, true);
1558
1560}
1561
1562// function used to display the modal containing alias tag-value pairs
1563static void displayTagValueListModalPage(uint8_t pageIdx, bool forceFullRefresh)
1564{
1565 nbgl_pageNavigationInfo_t info = {.activePage = pageIdx,
1566 .nbPages = detailsContext.nbPages,
1567 .navType = NAV_WITH_BUTTONS,
1568 .quitToken = QUIT_TOKEN,
1569 .navWithButtons.navToken = MODAL_NAV_TOKEN,
1570 .navWithButtons.quitButton = true,
1571 .navWithButtons.backButton = true,
1572 .navWithButtons.quitText = NULL,
1573 .progressIndicator = false,
1574 .tuneId = TUNE_TAP_CASUAL};
1576 .topRightIcon = NULL,
1577 .tagValueList.smallCaseForValue = true,
1578 .tagValueList.wrapping = detailsContext.wrapping};
1579 uint8_t nbElementsInPage;
1580
1581 // if first page or forward
1582 if (detailsContext.currentPage <= pageIdx) {
1583 modalContextGetPageInfo(pageIdx, &nbElementsInPage);
1584 }
1585 else {
1586 // backward direction
1587 modalContextGetPageInfo(pageIdx + 1, &nbElementsInPage);
1588 detailsContext.currentPairIdx -= nbElementsInPage;
1589 modalContextGetPageInfo(pageIdx, &nbElementsInPage);
1590 detailsContext.currentPairIdx -= nbElementsInPage;
1591 }
1592 detailsContext.currentPage = pageIdx;
1593
1594 content.tagValueList.pairs
1595 = &genericContext.currentTagValues->pairs[detailsContext.currentPairIdx];
1596 content.tagValueList.nbPairs = nbElementsInPage;
1597 detailsContext.currentPairIdx += nbElementsInPage;
1598 if (info.nbPages == 1) {
1599 // if only one page, no navigation bar, and use a footer instead
1600 info.navWithButtons.quitText = "Close";
1601 }
1602
1603 if (modalPageContext != NULL) {
1604 nbgl_pageRelease(modalPageContext);
1605 }
1606 modalPageContext = nbgl_pageDrawGenericContentExt(&pageModalCallback, &info, &content, true);
1607
1608 if (forceFullRefresh) {
1610 }
1611 else {
1613 }
1614}
1615
1616#ifdef NBGL_QRCODE
1617static void displayAddressQRCode(void)
1618{
1619 // display the address as QR Code
1620 nbgl_layoutDescription_t layoutDescription = {.modal = true,
1621 .withLeftBorder = true,
1622 .onActionCallback = &modalLayoutTouchCallback,
1623 .tapActionText = NULL};
1624 nbgl_layoutHeader_t headerDesc = {
1625 .type = HEADER_EMPTY, .separationLine = false, .emptySpace.height = SMALL_CENTERING_HEADER};
1626 nbgl_layoutQRCode_t qrCode = {.url = addressConfirmationContext.tagValuePairs[0].value,
1627 .text1 = NULL,
1628 .centered = true,
1629 .offsetY = 0};
1630
1631 addressConfirmationContext.modalLayout = nbgl_layoutGet(&layoutDescription);
1632 // add empty header for better look
1633 nbgl_layoutAddHeader(addressConfirmationContext.modalLayout, &headerDesc);
1634 // compute nb lines to check whether it shall be shorten (max is 3 lines)
1635 uint16_t nbLines = nbgl_getTextNbLinesInWidth(SMALL_REGULAR_FONT,
1636 addressConfirmationContext.tagValuePairs[0].value,
1638 false);
1639
1640 if (nbLines <= QRCODE_NB_MAX_LINES) {
1641 qrCode.text2 = addressConfirmationContext.tagValuePairs[0].value; // in gray
1642 }
1643 else {
1644 // only keep beginning and end of text, and add ... in the middle
1645 nbgl_textReduceOnNbLines(SMALL_REGULAR_FONT,
1646 addressConfirmationContext.tagValuePairs[0].value,
1648 QRCODE_NB_MAX_LINES,
1649 reducedAddress,
1650 QRCODE_REDUCED_ADDR_LEN);
1651 qrCode.text2 = reducedAddress; // in gray
1652 }
1653
1654 nbgl_layoutAddQRCode(addressConfirmationContext.modalLayout, &qrCode);
1655
1657 addressConfirmationContext.modalLayout, "Close", DISMISS_QR_TOKEN, TUNE_TAP_CASUAL);
1658 nbgl_layoutDraw(addressConfirmationContext.modalLayout);
1659 nbgl_refresh();
1660}
1661
1662#endif // NBGL_QRCODE
1663
1664// called when header is touched on modal page, to dismiss it
1665static void modalLayoutTouchCallback(int token, uint8_t index)
1666{
1667 UNUSED(index);
1668 if (token == DISMISS_QR_TOKEN) {
1669 // dismiss modal
1670 nbgl_layoutRelease(addressConfirmationContext.modalLayout);
1671 addressConfirmationContext.modalLayout = NULL;
1673 }
1674 else if (token == DISMISS_WARNING_TOKEN) {
1675 // dismiss modal
1676 nbgl_layoutRelease(reviewWithWarnCtx.modalLayout);
1677 // if already at first level, simply redraw the background
1678 if (reviewWithWarnCtx.securityReportLevel <= 1) {
1679 reviewWithWarnCtx.modalLayout = NULL;
1681 }
1682 else {
1683 // We are at level 2 of warning modal, so re-display the level 1
1684 reviewWithWarnCtx.securityReportLevel = 1;
1685 // if preset is used
1686 if (reviewWithWarnCtx.warning->predefinedSet) {
1687 displaySecurityReport(reviewWithWarnCtx.warning->predefinedSet);
1688 }
1689 else {
1690 // use customized warning
1691 const nbgl_warningDetails_t *details
1692 = (reviewWithWarnCtx.isIntro) ? reviewWithWarnCtx.warning->introDetails
1693 : reviewWithWarnCtx.warning->reviewDetails;
1694 displayCustomizedSecurityReport(details);
1695 }
1696 return;
1697 }
1698 }
1699 else if (token == DISMISS_DETAILS_TOKEN) {
1700 // dismiss modal
1701 nbgl_layoutRelease(choiceWithDetailsCtx.modalLayout);
1702 // if already at first level, simply redraw the background
1703 if (choiceWithDetailsCtx.level <= 1) {
1704 choiceWithDetailsCtx.modalLayout = NULL;
1706 }
1707 else {
1708 // We are at level 2 of details modal, so re-display the level 1
1709 choiceWithDetailsCtx.level = 1;
1710 choiceWithDetailsCtx.modalLayout
1711 = displayModalDetails(choiceWithDetailsCtx.details, DISMISS_DETAILS_TOKEN);
1712 }
1713 }
1714 else if ((token >= FIRST_WARN_BAR_TOKEN) && (token <= LAST_WARN_BAR_TOKEN)) {
1715 if (sharedContext.usage == SHARE_CTX_REVIEW_WITH_WARNING) {
1716 // dismiss modal before creating a new one
1717 nbgl_layoutRelease(reviewWithWarnCtx.modalLayout);
1718 reviewWithWarnCtx.securityReportLevel = 2;
1719 // if preset is used
1720 if (reviewWithWarnCtx.warning->predefinedSet) {
1721 displaySecurityReport(1 << (token - FIRST_WARN_BAR_TOKEN));
1722 }
1723 else {
1724 // use customized warning
1725 const nbgl_warningDetails_t *details
1726 = (reviewWithWarnCtx.isIntro) ? reviewWithWarnCtx.warning->introDetails
1727 : reviewWithWarnCtx.warning->reviewDetails;
1728 if (details->type == BAR_LIST_WARNING) {
1729 displayCustomizedSecurityReport(
1730 &details->barList.details[token - FIRST_WARN_BAR_TOKEN]);
1731 }
1732 }
1733 }
1734 else if (sharedContext.usage == SHARE_CTX_CHOICE_WITH_DETAILS) {
1735 const nbgl_warningDetails_t *details
1736 = &choiceWithDetailsCtx.details->barList.details[token - FIRST_WARN_BAR_TOKEN];
1737 if (details->title != NO_TYPE_WARNING) {
1738 // dismiss modal before creating a new one
1739 nbgl_layoutRelease(choiceWithDetailsCtx.modalLayout);
1740 choiceWithDetailsCtx.level = 2;
1741 choiceWithDetailsCtx.modalLayout
1742 = displayModalDetails(details, DISMISS_DETAILS_TOKEN);
1743 }
1744 }
1745 return;
1746 }
1747 else {
1748 // dismiss modal
1749 nbgl_layoutRelease(genericContext.modalLayout);
1750 genericContext.modalLayout = NULL;
1752 }
1753 nbgl_refresh();
1754}
1755
1756// called when item are touched in layout
1757static void layoutTouchCallback(int token, uint8_t index)
1758{
1759 // choice buttons in initial warning page
1760 if (token == WARNING_CHOICE_TOKEN) {
1761 if (index == 0) { // top button to exit
1762 reviewWithWarnCtx.choiceCallback(false);
1763 }
1764 else { // bottom button to continue to review
1765 reviewWithWarnCtx.isIntro = false;
1766 if (reviewWithWarnCtx.isStreaming) {
1767 useCaseReviewStreamingStart(reviewWithWarnCtx.operationType,
1768 reviewWithWarnCtx.icon,
1769 reviewWithWarnCtx.reviewTitle,
1770 reviewWithWarnCtx.reviewSubTitle,
1771 reviewWithWarnCtx.choiceCallback,
1772 false);
1773 }
1774 else {
1775 useCaseReview(reviewWithWarnCtx.operationType,
1776 reviewWithWarnCtx.tagValueList,
1777 reviewWithWarnCtx.icon,
1778 reviewWithWarnCtx.reviewTitle,
1779 reviewWithWarnCtx.reviewSubTitle,
1780 reviewWithWarnCtx.finishTitle,
1781 &activeTipBox,
1782 reviewWithWarnCtx.choiceCallback,
1783 false,
1784 false);
1785 }
1786 }
1787 }
1788 // top-right button in initial warning page
1789 else if (token == WARNING_BUTTON_TOKEN) {
1790 // start directly at first level of security report
1791 reviewWithWarnCtx.securityReportLevel = 1;
1792 // if preset is used
1793 if (reviewWithWarnCtx.warning->predefinedSet) {
1794 displaySecurityReport(reviewWithWarnCtx.warning->predefinedSet);
1795 }
1796 else {
1797 // use customized warning
1798 const nbgl_warningDetails_t *details = (reviewWithWarnCtx.isIntro)
1799 ? reviewWithWarnCtx.warning->introDetails
1800 : reviewWithWarnCtx.warning->reviewDetails;
1801 displayCustomizedSecurityReport(details);
1802 }
1803 }
1804 // top-right button in choice with details page
1805 else if (token == CHOICE_DETAILS_TOKEN) {
1806 choiceWithDetailsCtx.level = 1;
1807 choiceWithDetailsCtx.modalLayout
1808 = displayModalDetails(choiceWithDetailsCtx.details, DISMISS_DETAILS_TOKEN);
1809 }
1810 // main prelude page
1811 else if (token == PRELUDE_CHOICE_TOKEN) {
1812 if (index == 0) { // top button to display details about prelude
1813 displayCustomizedSecurityReport(reviewWithWarnCtx.warning->prelude->details);
1814 }
1815 else { // footer to continue to review
1816 if (HAS_INITIAL_WARNING(reviewWithWarnCtx.warning)) {
1817 displayInitialWarning();
1818 }
1819 else {
1820 reviewWithWarnCtx.isIntro = false;
1821 if (reviewWithWarnCtx.isStreaming) {
1822 useCaseReviewStreamingStart(reviewWithWarnCtx.operationType,
1823 reviewWithWarnCtx.icon,
1824 reviewWithWarnCtx.reviewTitle,
1825 reviewWithWarnCtx.reviewSubTitle,
1826 reviewWithWarnCtx.choiceCallback,
1827 false);
1828 }
1829 else {
1830 useCaseReview(reviewWithWarnCtx.operationType,
1831 reviewWithWarnCtx.tagValueList,
1832 reviewWithWarnCtx.icon,
1833 reviewWithWarnCtx.reviewTitle,
1834 reviewWithWarnCtx.reviewSubTitle,
1835 reviewWithWarnCtx.finishTitle,
1836 &activeTipBox,
1837 reviewWithWarnCtx.choiceCallback,
1838 false,
1839 false);
1840 }
1841 }
1842 }
1843 }
1844 // choice buttons
1845 else if (token == CHOICE_TOKEN) {
1846 if (onChoice != NULL) {
1847 onChoice((index == 0) ? true : false);
1848 }
1849 }
1850}
1851
1852// called when skip button is touched in footer, during forward only review
1853static void displaySkipWarning(void)
1854{
1856 .cancelText = "Go back to review",
1857 .centeredInfo.text1 = "Skip review?",
1858 .centeredInfo.text2
1859 = "If you're sure you don't need to review all fields, you can skip straight to signing.",
1860 .centeredInfo.text3 = NULL,
1861 .centeredInfo.style = LARGE_CASE_INFO,
1862 .centeredInfo.icon = &IMPORTANT_CIRCLE_ICON,
1863 .centeredInfo.offsetY = 0,
1864 .confirmationText = "Yes, skip",
1865 .confirmationToken = SKIP_TOKEN,
1866 .tuneId = TUNE_TAP_CASUAL,
1867 .modal = true};
1868 if (modalPageContext != NULL) {
1869 nbgl_pageRelease(modalPageContext);
1870 }
1871 modalPageContext = nbgl_pageDrawConfirmation(&pageModalCallback, &info);
1873}
1874
1875#ifdef NBGL_KEYPAD
1876// called to update the keypad and automatically show / hide:
1877// - backspace key if no digits are present
1878// - validation key if the min digit is reached
1879// - keypad if the max number of digit is reached
1880static void updateKeyPad(bool add)
1881{
1882 bool enableValidate, enableBackspace, enableDigits;
1883 bool redrawKeypad = false;
1885
1886 enableDigits = (keypadContext.pinLen < keypadContext.pinMaxDigits);
1887 enableValidate = (keypadContext.pinLen >= keypadContext.pinMinDigits);
1888 enableBackspace = (keypadContext.pinLen > 0);
1889 if (add) {
1890 if ((keypadContext.pinLen == keypadContext.pinMinDigits)
1891 || // activate "validate" button on keypad
1892 (keypadContext.pinLen == keypadContext.pinMaxDigits)
1893 || // deactivate "digits" on keypad
1894 (keypadContext.pinLen == 1)) { // activate "backspace"
1895 redrawKeypad = true;
1896 }
1897 }
1898 else { // remove
1899 if ((keypadContext.pinLen == 0) || // deactivate "backspace" button on keypad
1900 (keypadContext.pinLen == (keypadContext.pinMinDigits - 1))
1901 || // deactivate "validate" button on keypad
1902 (keypadContext.pinLen
1903 == (keypadContext.pinMaxDigits - 1))) { // reactivate "digits" on keypad
1904 redrawKeypad = true;
1905 }
1906 }
1907 if (keypadContext.hidden == true) {
1908 nbgl_layoutUpdateKeypadContent(keypadContext.layoutCtx, true, keypadContext.pinLen, NULL);
1909 }
1910 else {
1912 keypadContext.layoutCtx, false, 0, (const char *) keypadContext.pinEntry);
1913 }
1914 if (redrawKeypad) {
1916 keypadContext.layoutCtx, 0, enableValidate, enableBackspace, enableDigits);
1917 }
1918
1919 if ((!add) && (keypadContext.pinLen == 0)) {
1920 // Full refresh to fully clean the bullets when reaching 0 digits
1921 mode = FULL_COLOR_REFRESH;
1922 }
1924}
1925
1926// called when a key is touched on the keypad
1927static void keypadCallback(char touchedKey)
1928{
1929 switch (touchedKey) {
1930 case BACKSPACE_KEY:
1931 if (keypadContext.pinLen > 0) {
1932 keypadContext.pinLen--;
1933 keypadContext.pinEntry[keypadContext.pinLen] = 0;
1934 }
1935 updateKeyPad(false);
1936 break;
1937
1938 case VALIDATE_KEY:
1939 // Gray out keyboard / buttons as a first user feedback
1940 nbgl_layoutUpdateKeypad(keypadContext.layoutCtx, 0, false, false, true);
1943
1944 keypadContext.onValidatePin(keypadContext.pinEntry, keypadContext.pinLen);
1945 break;
1946
1947 default:
1948 if ((touchedKey >= 0x30) && (touchedKey < 0x40)) {
1949 if (keypadContext.pinLen < keypadContext.pinMaxDigits) {
1950 keypadContext.pinEntry[keypadContext.pinLen] = touchedKey;
1951 keypadContext.pinLen++;
1952 }
1953 updateKeyPad(true);
1954 }
1955 break;
1956 }
1957}
1958
1959static void keypadGeneric_cb(int token, uint8_t index)
1960{
1961 UNUSED(index);
1962 if (token != BACK_TOKEN) {
1963 return;
1964 }
1965 onQuit();
1966}
1967#endif
1968
1969#ifdef NBGL_KEYBOARD
1970// called when a key is touched on the keyboard
1971static void keyboardCallback(char touchedKey)
1972{
1973 uint32_t mask = 0;
1974 size_t textLen = strlen(keyboardContext.entryBuffer);
1975 if (touchedKey == BACKSPACE_KEY) {
1976 if (textLen == 0) {
1977 // No action
1978 return;
1979 }
1980 keyboardContext.entryBuffer[--textLen] = '\0';
1981 }
1982 else {
1983 if (textLen >= keyboardContext.entryMaxLen) {
1984 // entry length can't be greater, so we mask every characters
1985 mask = -1;
1986 }
1987 else {
1988 keyboardContext.entryBuffer[textLen] = touchedKey;
1989 keyboardContext.entryBuffer[++textLen] = '\0';
1990 }
1991 }
1992 if (keyboardContext.keyboardContent.type == KEYBOARD_WITH_SUGGESTIONS) {
1993 // if suggestions are displayed, we update them at each key press
1994 keyboardContext.getSuggestionButtons(&keyboardContext.keyboardContent, &mask);
1995 }
1996 else if (textLen >= keyboardContext.entryMaxLen) {
1997 // entry length can't be greater, so we mask every characters
1998 mask = -1;
1999 }
2000 nbgl_layoutUpdateKeyboardContent(keyboardContext.layoutCtx, &keyboardContext.keyboardContent);
2001 nbgl_layoutUpdateKeyboard(keyboardContext.layoutCtx,
2002 keyboardContext.keyboardIndex,
2003 mask,
2004 false,
2005 keyboardContext.casing);
2007}
2008
2009static void keyboardGeneric_cb(int token, uint8_t index)
2010{
2011 UNUSED(index);
2012 switch (token) {
2013 case BACK_TOKEN:
2014 onQuit();
2015 break;
2016 case KEYBOARD_CROSS_TOKEN:
2017 // Simulate a word of a single letter 'a' and trigger the backspace action
2018 keyboardContext.entryBuffer[0] = 'a';
2019 keyboardContext.entryBuffer[1] = '\0';
2020 keyboardCallback(BACKSPACE_KEY);
2021 break;
2022 case KEYBOARD_BUTTON_TOKEN:
2023 if (keyboardContext.keyboardContent.type == KEYBOARD_WITH_BUTTON) {
2024 onAction();
2025 }
2026 break;
2027 default:
2028 if ((keyboardContext.keyboardContent.type == KEYBOARD_WITH_SUGGESTIONS)
2029 && (token >= keyboardContext.keyboardContent.suggestionButtons.firstButtonToken)) {
2030 onControls(token, index);
2031 }
2032 break;
2033 }
2034}
2035#endif
2036
2051static uint8_t getNbTagValuesInPage(uint8_t nbPairs,
2052 const nbgl_contentTagValueList_t *tagValueList,
2053 uint8_t startIndex,
2054 bool isSkippable,
2055 bool hasConfirmationButton,
2056 bool hasDetailsButton,
2057 bool *requireSpecificDisplay)
2058{
2059 uint8_t nbPairsInPage = 0;
2060 uint16_t currentHeight = PRE_TAG_VALUE_MARGIN; // upper margin
2061 uint16_t maxUsableHeight = TAG_VALUE_AREA_HEIGHT;
2062
2063 // if the review is skippable, it means that there is less height for tag/value pairs
2064 // the small centering header becomes a touchable header
2065 if (isSkippable) {
2066 maxUsableHeight -= TOUCHABLE_HEADER_BAR_HEIGHT - SMALL_CENTERING_HEADER;
2067 }
2068
2069 *requireSpecificDisplay = false;
2070 while (nbPairsInPage < nbPairs) {
2071 const nbgl_layoutTagValue_t *pair;
2072 nbgl_font_id_e value_font;
2073 uint16_t nbLines;
2074
2075 // margin between pairs
2076 // 12 or 24 px between each tag/value pair
2077 if (nbPairsInPage > 0) {
2078 currentHeight += INTER_TAG_VALUE_MARGIN;
2079 }
2080 // fetch tag/value pair strings.
2081 if (tagValueList->pairs != NULL) {
2082 pair = PIC(&tagValueList->pairs[startIndex + nbPairsInPage]);
2083 }
2084 else {
2085 pair = PIC(tagValueList->callback(startIndex + nbPairsInPage));
2086 }
2087
2088 if (pair->forcePageStart && nbPairsInPage > 0) {
2089 // This pair must be at the top of a page
2090 break;
2091 }
2092
2093 if (pair->centeredInfo) {
2094 if (nbPairsInPage > 0) {
2095 // This pair must be at the top of a page
2096 break;
2097 }
2098 else {
2099 // This pair is the only one of the page and has a specific display behavior
2100 nbPairsInPage = 1;
2101 *requireSpecificDisplay = true;
2102 break;
2103 }
2104 }
2105
2106 // tag height
2107 currentHeight += nbgl_getTextHeightInWidth(
2108 SMALL_REGULAR_FONT, pair->item, AVAILABLE_WIDTH, tagValueList->wrapping);
2109 // space between tag and value
2110 currentHeight += TAG_VALUE_INTERVALE;
2111 // set value font
2112 if (tagValueList->smallCaseForValue) {
2113 value_font = SMALL_REGULAR_FONT;
2114 }
2115 else {
2116 value_font = LARGE_MEDIUM_FONT;
2117 }
2118 // nb lines for value
2120 value_font, pair->value, AVAILABLE_WIDTH, tagValueList->wrapping);
2121 // honor list-level nbMaxLinesForValue: when set, the value will be
2122 // displayed truncated to that many lines, so account for its
2123 // truncated height rather than its full height.
2124 // Caveat: review TAG_VALUE_LIST/TAG_VALUE_DETAILS pages always render
2125 // up to NB_MAX_LINES_IN_REVIEW lines, because the display path forces
2126 // nbMaxLinesForValue back to NB_MAX_LINES_IN_REVIEW (see
2127 // genericContextPreparePageContent / displayReviewPage). So a list-level
2128 // value smaller than NB_MAX_LINES_IN_REVIEW must NOT shorten the height
2129 // estimate here, otherwise the page gets under-allocated and content is
2130 // drawn off-screen. Only honor a value that the display will actually
2131 // apply (i.e. >= NB_MAX_LINES_IN_REVIEW).
2132 uint8_t effectiveMaxLines = tagValueList->nbMaxLinesForValue;
2133 if ((effectiveMaxLines > 0) && (effectiveMaxLines < NB_MAX_LINES_IN_REVIEW)) {
2134 effectiveMaxLines = 0;
2135 }
2136 if ((effectiveMaxLines > 0) && (nbLines > effectiveMaxLines)) {
2137 nbLines = effectiveMaxLines;
2138 currentHeight += nbLines * nbgl_getFontLineHeight(value_font);
2139 }
2140 else {
2141 currentHeight += nbgl_getTextHeightInWidth(
2142 value_font, pair->value, AVAILABLE_WIDTH, tagValueList->wrapping);
2143 }
2144
2145 // potential subAlias text
2146 if ((pair->aliasValue) && (pair->extension->aliasSubName)) {
2147 currentHeight += TAG_VALUE_INTERVALE + nbgl_getFontLineHeight(SMALL_REGULAR_FONT);
2148 }
2149 if ((currentHeight >= maxUsableHeight) || (nbLines > NB_MAX_LINES_IN_REVIEW)) {
2150 if (nbPairsInPage == 0) {
2151 // Pair too long to fit in a single screen
2152 // It will be the only one of the page and has a specific display behavior
2153 nbPairsInPage = 1;
2154 *requireSpecificDisplay = true;
2155 }
2156 break;
2157 }
2158 nbPairsInPage++;
2159 }
2160 // if this is a TAG_VALUE_CONFIRM and we have reached the last pairs,
2161 // let's check if it still fits with a CONFIRMATION button, and if not,
2162 // remove the last pair (but never below 1, otherwise the pagination
2163 // loop in getNbPagesForContent would spin forever — the lone oversized
2164 // pair must still occupy this page and will be truncated at display time)
2165 if (hasConfirmationButton && (nbPairsInPage == nbPairs)) {
2166 maxUsableHeight -= UP_FOOTER_BUTTON_HEIGHT;
2167 if ((currentHeight > maxUsableHeight) && (nbPairsInPage > 1)) {
2168 nbPairsInPage--;
2169 }
2170 }
2171 // do the same with just a details button
2172 else if (hasDetailsButton) {
2173 maxUsableHeight -= (SMALL_BUTTON_RADIUS * 2);
2174 if ((currentHeight > maxUsableHeight) && (nbPairsInPage > 1)) {
2175 nbPairsInPage--;
2176 }
2177 }
2178 return nbPairsInPage;
2179}
2180
2190static uint8_t getNbTagValuesInDetailsPage(uint8_t nbPairs,
2191 const nbgl_contentTagValueList_t *tagValueList,
2192 uint8_t startIndex)
2193{
2194 uint8_t nbPairsInPage = 0;
2195 uint16_t currentHeight = PRE_TAG_VALUE_MARGIN; // upper margin
2196 uint16_t maxUsableHeight = TAG_VALUE_AREA_HEIGHT;
2197
2198 while (nbPairsInPage < nbPairs) {
2199 const nbgl_layoutTagValue_t *pair;
2200
2201 // margin between pairs
2202 // 12 or 24 px between each tag/value pair
2203 if (nbPairsInPage > 0) {
2204 currentHeight += INTER_TAG_VALUE_MARGIN;
2205 }
2206 // fetch tag/value pair strings.
2207 if (tagValueList->pairs != NULL) {
2208 pair = PIC(&tagValueList->pairs[startIndex + nbPairsInPage]);
2209 }
2210 else {
2211 pair = PIC(tagValueList->callback(startIndex + nbPairsInPage));
2212 }
2213
2214 // tag height
2215 currentHeight += nbgl_getTextHeightInWidth(
2216 SMALL_REGULAR_FONT, pair->item, AVAILABLE_WIDTH, tagValueList->wrapping);
2217 // space between tag and value
2218 currentHeight += 4;
2219
2220 // value height
2221 currentHeight += nbgl_getTextHeightInWidth(
2222 SMALL_REGULAR_FONT, pair->value, AVAILABLE_WIDTH, tagValueList->wrapping);
2223
2224 // we have reached the maximum height, it means than there are to many pairs
2225 if (currentHeight >= maxUsableHeight) {
2226 break;
2227 }
2228 nbPairsInPage++;
2229 }
2230 return nbPairsInPage;
2231}
2232
2233static uint8_t getNbPagesForContent(const nbgl_content_t *content,
2234 uint8_t pageIdxStart,
2235 bool isLast,
2236 bool isSkippable)
2237{
2238 uint8_t nbElements = 0;
2239 uint8_t nbPages = 0;
2240 uint8_t nbElementsInPage;
2241 uint8_t elemIdx = 0;
2242 bool flag;
2243
2244 nbElements = getContentNbElement(content);
2245
2246 while (nbElements > 0) {
2247 flag = 0;
2248 // if the current page is not the first one (or last), a navigation bar exists
2249 bool hasNav = !isLast || (pageIdxStart > 0) || (elemIdx > 0);
2250 if (content->type == TAG_VALUE_LIST) {
2251 nbElementsInPage = getNbTagValuesInPage(nbElements,
2252 &content->content.tagValueList,
2253 elemIdx,
2254 isSkippable,
2255 false,
2256 false,
2257 &flag);
2258 }
2259 else if (content->type == TAG_VALUE_CONFIRM) {
2260 nbElementsInPage = getNbTagValuesInPage(nbElements,
2262 elemIdx,
2263 isSkippable,
2264 isLast,
2265 !isLast,
2266 &flag);
2267 }
2268 else if (content->type == INFOS_LIST) {
2269 nbElementsInPage
2270 = getNbInfosInPage(nbElements, &content->content.infosList, elemIdx, hasNav);
2271 }
2272 else if (content->type == SWITCHES_LIST) {
2273 nbElementsInPage
2274 = getNbSwitchesInPage(nbElements, &content->content.switchesList, elemIdx, hasNav);
2275 }
2276 else if (content->type == BARS_LIST) {
2277 nbElementsInPage
2278 = getNbBarsInPage(nbElements, &content->content.barsList, elemIdx, hasNav);
2279 }
2280 else if (content->type == CHOICES_LIST) {
2281 nbElementsInPage = nbgl_useCaseGetNbChoicesInPage(
2282 nbElements, &content->content.choicesList, elemIdx, hasNav);
2283 }
2284 else {
2285 nbElementsInPage = MIN(nbMaxElementsPerContentType[content->type], nbElements);
2286 }
2287
2288 elemIdx += nbElementsInPage;
2289 genericContextSetPageInfo(pageIdxStart + nbPages, nbElementsInPage, flag);
2290 nbElements -= nbElementsInPage;
2291 nbPages++;
2292 }
2293
2294 return nbPages;
2295}
2296
2297static uint8_t getNbPagesForGenericContents(const nbgl_genericContents_t *genericContents,
2298 uint8_t pageIdxStart,
2299 bool isSkippable)
2300{
2301 uint8_t nbPages = 0;
2302 nbgl_content_t content = {0};
2303 const nbgl_content_t *p_content;
2304
2305 for (int i = 0; i < genericContents->nbContents; i++) {
2306 p_content = getContentAtIdx(genericContents, i, &content);
2307 if (p_content == NULL) {
2308 return 0;
2309 }
2310 nbPages += getNbPagesForContent(p_content,
2311 pageIdxStart + nbPages,
2312 (i == (genericContents->nbContents - 1)),
2313 isSkippable);
2314 }
2315
2316 return nbPages;
2317}
2318
2319static void prepareAddressConfirmationPages(const char *address,
2320 const nbgl_contentTagValueList_t *tagValueList,
2321 nbgl_content_t *firstPageContent,
2322 nbgl_content_t *secondPageContent)
2323{
2324 nbgl_contentTagValueConfirm_t *tagValueConfirm;
2325 uint8_t nbAddressChunks;
2326
2327 // Split the address into one or more chunks, each occupying its own page.
2328 // Each chunk-pair points into `address` at the chunk's start offset; the
2329 // list-level nbMaxLinesForValue (set further down when nbAddressChunks > 1)
2330 // bounds the rendered slice to NB_MAX_LINES_IN_REVIEW lines, so successive
2331 // chunks visually pick up where the previous left off. forcePageStart on
2332 // all but the first chunk ensures each chunk starts a fresh page.
2333 nbAddressChunks = nbgl_getTextNbPagesInWidth(
2334 LARGE_MEDIUM_FONT, address, NB_MAX_LINES_IN_REVIEW, AVAILABLE_WIDTH);
2335 bool addressTruncated = (nbAddressChunks > ADDR_VERIF_NB_PAIRS);
2336 if (addressTruncated) {
2337 nbAddressChunks = ADDR_VERIF_NB_PAIRS;
2338 }
2339 {
2340 const char *chunkStart = address;
2341 for (uint8_t i = 0; i < nbAddressChunks; i++) {
2342 addressConfirmationContext.tagValuePairs[i].item = "Address";
2343 addressConfirmationContext.tagValuePairs[i].value = chunkStart;
2344 addressConfirmationContext.tagValuePairs[i].forcePageStart = (i > 0);
2345 if (i + 1 < nbAddressChunks) {
2346 uint16_t len = 0;
2347 nbgl_getTextMaxLenInNbLines(LARGE_MEDIUM_FONT,
2348 chunkStart,
2351 &len,
2352 false);
2353 chunkStart += len;
2354 }
2355 }
2356 }
2357 addressConfirmationContext.nbPairs = nbAddressChunks;
2358
2359 // First page
2360 firstPageContent->type = TAG_VALUE_CONFIRM;
2361 tagValueConfirm = &firstPageContent->content.tagValueConfirm;
2362
2363#ifdef NBGL_QRCODE
2364 tagValueConfirm->detailsButtonIcon = &QRCODE_ICON;
2365 // only pack extras on the address page when the address itself fits on a
2366 // single page; for multi-chunk addresses, extras go to their own second page
2367 if ((tagValueList != NULL) && (nbAddressChunks == 1)
2368 && (tagValueList->nbPairs < ADDR_VERIF_NB_PAIRS)) {
2369 nbgl_contentTagValueList_t tmpList = {0};
2370 bool flag;
2371 // copy in intermediate structure
2372 for (uint8_t i = 0; i < tagValueList->nbPairs; i++) {
2373 memcpy(&addressConfirmationContext.tagValuePairs[1 + i],
2374 &tagValueList->pairs[i],
2375 sizeof(nbgl_contentTagValue_t));
2376 addressConfirmationContext.nbPairs++;
2377 }
2378 // check how many can fit in a page
2379 memcpy(&tmpList, tagValueList, sizeof(nbgl_contentTagValueList_t));
2380 tmpList.nbPairs = addressConfirmationContext.nbPairs;
2381 tmpList.pairs = addressConfirmationContext.tagValuePairs;
2382 addressConfirmationContext.nbPairs = getNbTagValuesInPage(
2383 addressConfirmationContext.nbPairs, &tmpList, 0, false, true, true, &flag);
2384 // if they don't all fit, keep only the address
2385 if (tmpList.nbPairs > addressConfirmationContext.nbPairs) {
2386 addressConfirmationContext.nbPairs = 1;
2387 }
2388 }
2389 else {
2390 tagValueConfirm->detailsButtonText = NULL;
2391 }
2392 tagValueConfirm->detailsButtonToken = ADDRESS_QRCODE_BUTTON_TOKEN;
2393#else // NBGL_QRCODE
2394 tagValueConfirm->detailsButtonText = NULL;
2395 tagValueConfirm->detailsButtonIcon = NULL;
2396#endif // NBGL_QRCODE
2397 tagValueConfirm->tuneId = TUNE_TAP_CASUAL;
2398 tagValueConfirm->tagValueList.nbPairs = addressConfirmationContext.nbPairs;
2399 tagValueConfirm->tagValueList.pairs = addressConfirmationContext.tagValuePairs;
2400 tagValueConfirm->tagValueList.smallCaseForValue = false;
2401 // when the address spans multiple chunks, cap each pair's rendered value
2402 // at NB_MAX_LINES_IN_REVIEW lines so each chunk-pair occupies exactly one page
2403 tagValueConfirm->tagValueList.nbMaxLinesForValue
2404 = (nbAddressChunks > 1) ? NB_MAX_LINES_IN_REVIEW : 0;
2405 tagValueConfirm->tagValueList.hideEndOfLastLine = addressTruncated;
2406 tagValueConfirm->tagValueList.wrapping = false;
2407
2408 // Number of extras left to place on a second page
2409 uint8_t extrasPlaced
2410 = (tagValueList != NULL) ? (addressConfirmationContext.nbPairs - nbAddressChunks) : 0;
2411 bool extrasNeedSecondPage = (tagValueList != NULL) && (tagValueList->nbPairs > extrasPlaced);
2412
2413 if (extrasNeedSecondPage) {
2414 tagValueConfirm->detailsButtonText = "Show as QR";
2415 tagValueConfirm->confirmationText = NULL;
2416 // the second page is dedicated to the extended tag/value pairs
2417 secondPageContent->type = TAG_VALUE_CONFIRM;
2418 tagValueConfirm = &secondPageContent->content.tagValueConfirm;
2419 tagValueConfirm->confirmationText = "Confirm";
2420 tagValueConfirm->confirmationToken = CONFIRM_TOKEN;
2421 tagValueConfirm->detailsButtonText = NULL;
2422 tagValueConfirm->detailsButtonIcon = NULL;
2423 tagValueConfirm->tuneId = TUNE_TAP_CASUAL;
2424 memcpy(&tagValueConfirm->tagValueList, tagValueList, sizeof(nbgl_contentTagValueList_t));
2425 tagValueConfirm->tagValueList.nbPairs = tagValueList->nbPairs - extrasPlaced;
2426 tagValueConfirm->tagValueList.pairs = &tagValueList->pairs[extrasPlaced];
2427 }
2428 else {
2429 // otherwise no tap to continue but a confirmation button
2430 tagValueConfirm->confirmationText = "Confirm";
2431 tagValueConfirm->confirmationToken = CONFIRM_TOKEN;
2432 }
2433}
2434
2435static void bundleNavStartHome(void)
2436{
2437 nbgl_homeAndSettingsContext_t *context = &bundleNavContext.homeAndSettings;
2438
2439 useCaseHomeExt(context->appName,
2440 context->appIcon,
2441 context->tagline,
2442 context->settingContents != NULL ? true : false,
2443 &context->homeAction,
2444 bundleNavStartSettings,
2445 context->quitCallback);
2446}
2447
2448static void bundleNavStartSettingsAtPage(uint8_t initSettingPage)
2449{
2450 nbgl_homeAndSettingsContext_t *context = &bundleNavContext.homeAndSettings;
2451
2452 nbgl_useCaseGenericSettings(context->appName,
2453 initSettingPage,
2454 context->settingContents,
2455 context->infosList,
2456 bundleNavStartHome);
2457}
2458
2459static void bundleNavStartSettings(void)
2460{
2461 bundleNavStartSettingsAtPage(0);
2462}
2463
2464static void bundleNavReviewConfirmRejection(void)
2465{
2466 bundleNavContext.review.choiceCallback(false);
2467}
2468
2469static void bundleNavReviewAskRejectionConfirmation(nbgl_operationType_t operationType,
2470 nbgl_callback_t callback)
2471{
2472 const char *title;
2473 const char *confirmText;
2474 // clear skip and blind bits
2475 operationType
2477 if (operationType == TYPE_TRANSACTION) {
2478 title = "Reject transaction?";
2479 confirmText = "Go back to transaction";
2480 }
2481 else if (operationType == TYPE_MESSAGE) {
2482 title = "Reject message?";
2483 confirmText = "Go back to message";
2484 }
2485 else {
2486 title = "Reject operation?";
2487 confirmText = "Go back to operation";
2488 }
2489
2490 // display a choice to confirm/cancel rejection
2491 nbgl_useCaseConfirm(title, NULL, "Yes, reject", confirmText, callback);
2492}
2493
2494static void bundleNavReviewChoice(bool confirm)
2495{
2496 if (confirm) {
2497 bundleNavContext.review.choiceCallback(true);
2498 }
2499 else {
2500 bundleNavReviewAskRejectionConfirmation(bundleNavContext.review.operationType,
2501 bundleNavReviewConfirmRejection);
2502 }
2503}
2504
2505static void bundleNavReviewStreamingConfirmRejection(void)
2506{
2507 bundleNavContext.reviewStreaming.choiceCallback(false);
2508}
2509
2510static void bundleNavReviewStreamingChoice(bool confirm)
2511{
2512 if (confirm) {
2513 bundleNavContext.reviewStreaming.choiceCallback(true);
2514 }
2515 else {
2516 bundleNavReviewAskRejectionConfirmation(bundleNavContext.reviewStreaming.operationType,
2517 bundleNavReviewStreamingConfirmRejection);
2518 }
2519}
2520
2521// function used to display the details in modal (from Choice with details or from Customized
2522// security report) it can be either a single page with text or QR code, or a list of touchable bars
2523// leading to single pages
2524static nbgl_layout_t *displayModalDetails(const nbgl_warningDetails_t *details, uint8_t token)
2525{
2526 nbgl_layoutDescription_t layoutDescription = {.modal = true,
2527 .withLeftBorder = true,
2528 .onActionCallback = modalLayoutTouchCallback,
2529 .tapActionText = NULL};
2531 .separationLine = true,
2532 .backAndText.icon = NULL,
2533 .backAndText.tuneId = TUNE_TAP_CASUAL,
2534 .backAndText.token = token};
2535 uint8_t i;
2536 nbgl_layout_t *layout;
2537
2538 layout = nbgl_layoutGet(&layoutDescription);
2539 headerDesc.backAndText.text = details->title;
2540 nbgl_layoutAddHeader(layout, &headerDesc);
2541 if (details->type == BAR_LIST_WARNING) {
2542 // if more than one warning, so use a list of touchable bars
2543 for (i = 0; i < details->barList.nbBars; i++) {
2544 nbgl_layoutBar_t bar = {0};
2545 bar.text = details->barList.texts[i];
2546 bar.subText = details->barList.subTexts[i];
2547 // Only assign a right icon if the bar is clickable (i.e. has details to display)
2548 if ((details->barList.details)
2549 && (details->barList.details[i].type != NO_TYPE_WARNING)) {
2550 bar.iconRight = &PUSH_ICON;
2551 }
2552 // Left icon is optional, only set if provided by the warning details
2553 if (details->barList.icons != NULL) {
2554 bar.iconLeft = details->barList.icons[i];
2555 }
2556 bar.token = FIRST_WARN_BAR_TOKEN + i;
2557 bar.tuneId = TUNE_TAP_CASUAL;
2558 nbgl_layoutAddTouchableBar(layout, &bar);
2560 }
2561 }
2562 else if (details->type == QRCODE_WARNING) {
2563#ifdef NBGL_QRCODE
2564 // display a QR Code
2565 nbgl_layoutAddQRCode(layout, &details->qrCode);
2566#endif // NBGL_QRCODE
2567 headerDesc.backAndText.text = details->title;
2568 }
2569 else if (details->type == CENTERED_INFO_WARNING) {
2570 // display a centered info
2571 nbgl_layoutAddContentCenter(layout, &details->centeredInfo);
2572 headerDesc.separationLine = false;
2573 }
2574 nbgl_layoutDraw(layout);
2575 nbgl_refresh();
2576 return layout;
2577}
2578
2579// function used to display the security level page in modal
2580// it can be either a single page with text or QR code, or a list of touchable bars leading
2581// to single pages
2582static void displaySecurityReport(uint32_t set)
2583{
2584 nbgl_layoutDescription_t layoutDescription = {.modal = true,
2585 .withLeftBorder = true,
2586 .onActionCallback = modalLayoutTouchCallback,
2587 .tapActionText = NULL};
2589 .separationLine = true,
2590 .backAndText.icon = NULL,
2591 .backAndText.tuneId = TUNE_TAP_CASUAL,
2592 .backAndText.token = DISMISS_WARNING_TOKEN};
2593 nbgl_layoutFooter_t footerDesc
2594 = {.type = FOOTER_EMPTY, .separationLine = false, .emptySpace.height = 0};
2595 uint8_t i;
2596 const char *provider;
2597
2598 reviewWithWarnCtx.modalLayout = nbgl_layoutGet(&layoutDescription);
2599
2600 // display a list of touchable bars in some conditions:
2601 // - we must be in the first level of security report
2602 // - and be in the review itself (not from the intro/warning screen)
2603 //
2604 if ((reviewWithWarnCtx.securityReportLevel == 1) && (!reviewWithWarnCtx.isIntro)) {
2605 for (i = 0; i < NB_WARNING_TYPES; i++) {
2606 // Skip GATED_SIGNING_WARN from security report bars
2607 if ((i != GATED_SIGNING_WARN)
2608 && (reviewWithWarnCtx.warning->predefinedSet & (1 << i))) {
2609 nbgl_layoutBar_t bar = {0};
2610 if ((i == BLIND_SIGNING_WARN) || (i == W3C_NO_THREAT_WARN) || (i == W3C_ISSUE_WARN)
2611 || (reviewWithWarnCtx.warning->providerMessage == NULL)) {
2612 bar.subText = securityReportItems[i].subText;
2613 }
2614 else {
2615 bar.subText = reviewWithWarnCtx.warning->providerMessage;
2616 }
2617 bar.text = securityReportItems[i].text;
2618 if (i != W3C_NO_THREAT_WARN) {
2619 bar.iconRight = &PUSH_ICON;
2620 bar.token = FIRST_WARN_BAR_TOKEN + i;
2621 bar.tuneId = TUNE_TAP_CASUAL;
2622 }
2623 else {
2625 }
2626 bar.iconLeft = securityReportItems[i].icon;
2627 nbgl_layoutAddTouchableBar(reviewWithWarnCtx.modalLayout, &bar);
2628 nbgl_layoutAddSeparationLine(reviewWithWarnCtx.modalLayout);
2629 }
2630 }
2631 headerDesc.backAndText.text = "Security report";
2632 nbgl_layoutAddHeader(reviewWithWarnCtx.modalLayout, &headerDesc);
2633 nbgl_layoutDraw(reviewWithWarnCtx.modalLayout);
2634 nbgl_refresh();
2635 return;
2636 }
2637 if (reviewWithWarnCtx.warning && reviewWithWarnCtx.warning->reportProvider) {
2638 provider = reviewWithWarnCtx.warning->reportProvider;
2639 }
2640 else {
2641 provider = "[unknown]";
2642 }
2643 if ((set & (1 << W3C_THREAT_DETECTED_WARN)) || (set & (1 << W3C_RISK_DETECTED_WARN))) {
2644 size_t urlLen = 0;
2645 char *destStr
2646 = tmpString
2647 + W3C_DESCRIPTION_MAX_LEN / 2; // use the second half of tmpString for strings
2648#ifdef NBGL_QRCODE
2649 // display a QR Code
2650 nbgl_layoutQRCode_t qrCode = {.url = destStr,
2651 .text1 = reviewWithWarnCtx.warning->reportUrl,
2652 .text2 = "Scan to view full report",
2653 .centered = true,
2654 .offsetY = 0};
2655 // add "https://"" as prefix of the given URL
2656 snprintf(destStr,
2657 W3C_DESCRIPTION_MAX_LEN / 2,
2658 "https://%s",
2659 reviewWithWarnCtx.warning->reportUrl);
2660 urlLen = strlen(destStr) + 1;
2661 nbgl_layoutAddQRCode(reviewWithWarnCtx.modalLayout, &qrCode);
2662 footerDesc.emptySpace.height = 24;
2663#endif // NBGL_QRCODE
2664 // use the next part of destStr for back bar text
2665 snprintf(destStr + urlLen, W3C_DESCRIPTION_MAX_LEN / 2 - urlLen, "%s report", provider);
2666 headerDesc.backAndText.text = destStr + urlLen;
2667 }
2668 else if (set & (1 << BLIND_SIGNING_WARN)) {
2669 // display a centered if in review
2670 nbgl_contentCenter_t info = {0};
2671 info.icon = &LARGE_WARNING_ICON;
2672 info.title = "This transaction cannot be Clear Signed";
2673 info.description
2674 = "This transaction or message cannot be decoded fully. If you choose to sign, you "
2675 "could be authorizing malicious actions that can drain your wallet.\n\nLearn more: "
2676 "ledger.com/e8";
2677 nbgl_layoutAddContentCenter(reviewWithWarnCtx.modalLayout, &info);
2678 footerDesc.emptySpace.height = SMALL_CENTERING_HEADER;
2679 headerDesc.separationLine = false;
2680 }
2681 else if (set & (1 << W3C_ISSUE_WARN)) {
2682 // if W3 Checks issue, display a centered info
2683 nbgl_contentCenter_t info = {0};
2684 info.icon = &LARGE_WARNING_ICON;
2685 info.title = "Transaction Check unavailable";
2686 info.description
2687 = "If you're not using the Ledger Wallet app, Transaction Check might not work. If "
2688 "you "
2689 "are using Ledger Wallet, reject the transaction and try again.\n\nGet help at "
2690 "ledger.com/e11";
2691 nbgl_layoutAddContentCenter(reviewWithWarnCtx.modalLayout, &info);
2692 footerDesc.emptySpace.height = SMALL_CENTERING_HEADER;
2693 headerDesc.separationLine = false;
2694 }
2695 nbgl_layoutAddHeader(reviewWithWarnCtx.modalLayout, &headerDesc);
2696 if (footerDesc.emptySpace.height > 0) {
2697 nbgl_layoutAddExtendedFooter(reviewWithWarnCtx.modalLayout, &footerDesc);
2698 }
2699 nbgl_layoutDraw(reviewWithWarnCtx.modalLayout);
2700 nbgl_refresh();
2701}
2702
2703// function used to display the security level page in modal
2704// it can be either a single page with text or QR code, or a list of touchable bars leading
2705// to single pages
2706static void displayCustomizedSecurityReport(const nbgl_warningDetails_t *details)
2707{
2708 reviewWithWarnCtx.modalLayout = displayModalDetails(details, DISMISS_WARNING_TOKEN);
2709}
2710
2711// function used to display the initial warning page when starting a "review with warning"
2712static void displayInitialWarning(void)
2713{
2714 // Play notification sound
2715#ifdef HAVE_PIEZO_SOUND
2716 tune_index_e tune = TUNE_RESERVED;
2717#endif // HAVE_PIEZO_SOUND
2718 // clang-format off
2719 nbgl_layoutDescription_t layoutDescription = {0};
2720 nbgl_layoutChoiceButtons_t buttonsInfo = {.bottomText = "Continue anyway",
2721 .token = WARNING_CHOICE_TOKEN,
2722 .topText = "Back to safety",
2723 .style = ROUNDED_AND_FOOTER_STYLE,
2724 .tuneId = TUNE_TAP_CASUAL};
2725 nbgl_layoutHeader_t headerDesc = {.type = HEADER_EMPTY,
2726 .emptySpace.height = MEDIUM_CENTERING_HEADER};
2727 uint32_t set = reviewWithWarnCtx.warning->predefinedSet & ~((1 << W3C_NO_THREAT_WARN) | (1 << W3C_ISSUE_WARN));
2728 // clang-format on
2729
2730 bool isBlindSigningOnly
2731 = (set != 0) && ((set & ~((1 << BLIND_SIGNING_WARN) | (1 << GATED_SIGNING_WARN))) == 0);
2732 reviewWithWarnCtx.isIntro = true;
2733
2734 layoutDescription.withLeftBorder = true;
2735 layoutDescription.onActionCallback = layoutTouchCallback;
2736 reviewWithWarnCtx.layoutCtx = nbgl_layoutGet(&layoutDescription);
2737
2738 nbgl_layoutAddHeader(reviewWithWarnCtx.layoutCtx, &headerDesc);
2739 if (reviewWithWarnCtx.warning->predefinedSet != 0) {
2740 // icon is different in casee of Blind Siging or Gated Signing
2741 nbgl_layoutAddTopRightButton(reviewWithWarnCtx.layoutCtx,
2742 isBlindSigningOnly ? &INFO_I_ICON : &QRCODE_ICON,
2743 WARNING_BUTTON_TOKEN,
2744 TUNE_TAP_CASUAL);
2745 }
2746 else if (reviewWithWarnCtx.warning->introTopRightIcon != NULL) {
2747 nbgl_layoutAddTopRightButton(reviewWithWarnCtx.layoutCtx,
2748 reviewWithWarnCtx.warning->introTopRightIcon,
2749 WARNING_BUTTON_TOKEN,
2750 TUNE_TAP_CASUAL);
2751 }
2752 // add main content
2753 // if predefined content is configured, use it preferably
2754 if (reviewWithWarnCtx.warning->predefinedSet != 0) {
2755 nbgl_contentCenter_t info = {0};
2756
2757 const char *provider;
2758
2759 // default icon
2760 info.icon = &LARGE_WARNING_ICON;
2761
2762 // use small title only if not Blind signing and not Gated signing
2763 if (isBlindSigningOnly == false) {
2764 if (reviewWithWarnCtx.warning->reportProvider) {
2765 provider = reviewWithWarnCtx.warning->reportProvider;
2766 }
2767 else {
2768 provider = "[unknown]";
2769 }
2770 info.smallTitle = tmpString;
2771 snprintf(tmpString, W3C_DESCRIPTION_MAX_LEN, "Detected by %s", provider);
2772 }
2773
2774 // If Blind Signing or Gated Signing
2775 if (isBlindSigningOnly) {
2776 info.title = "Blind signing ahead";
2777 if (set & (1 << GATED_SIGNING_WARN)) {
2778 info.description
2779 = "If you sign this transaction, you could lose your assets. "
2780 "Explore safer alternatives: ledger.com/integrated-apps";
2781 buttonsInfo.bottomText = "Accept risk and continue";
2782 }
2783 else {
2784 info.description
2785 = "This transaction's details are not fully verifiable. If you sign, you could "
2786 "lose all your assets.";
2787 buttonsInfo.bottomText = "Continue anyway";
2788 }
2789#ifdef HAVE_PIEZO_SOUND
2790 tune = TUNE_NEUTRAL;
2791#endif // HAVE_PIEZO_SOUND
2792 }
2793 else if (set & (1 << W3C_RISK_DETECTED_WARN)) {
2794 info.title = "Potential risk";
2795 if (reviewWithWarnCtx.warning->providerMessage == NULL) {
2796 info.description = "Unidentified risk";
2797 }
2798 else {
2799 info.description = reviewWithWarnCtx.warning->providerMessage;
2800 }
2801 buttonsInfo.bottomText = "Accept risk and continue";
2802#ifdef HAVE_PIEZO_SOUND
2803 tune = TUNE_NEUTRAL;
2804#endif // HAVE_PIEZO_SOUND
2805 }
2806 else if (set & (1 << W3C_THREAT_DETECTED_WARN)) {
2807 info.title = "Critical threat";
2808 if (reviewWithWarnCtx.warning->providerMessage == NULL) {
2809 info.description = "Unidentified threat";
2810 }
2811 else {
2812 info.description = reviewWithWarnCtx.warning->providerMessage;
2813 }
2814 buttonsInfo.bottomText = "Accept threat and continue";
2815#ifdef HAVE_PIEZO_SOUND
2816 tune = TUNE_ERROR;
2817#endif // HAVE_PIEZO_SOUND
2818 }
2819 nbgl_layoutAddContentCenter(reviewWithWarnCtx.layoutCtx, &info);
2820 }
2821 else if (reviewWithWarnCtx.warning->info != NULL) {
2822 // if no predefined content, use custom one
2823 nbgl_layoutAddContentCenter(reviewWithWarnCtx.layoutCtx, reviewWithWarnCtx.warning->info);
2824#ifdef HAVE_PIEZO_SOUND
2825 tune = TUNE_LOOK_AT_ME;
2826#endif // HAVE_PIEZO_SOUND
2827 }
2828 // add button and footer on bottom
2829 nbgl_layoutAddChoiceButtons(reviewWithWarnCtx.layoutCtx, &buttonsInfo);
2830
2831#ifdef HAVE_PIEZO_SOUND
2832 if (tune != TUNE_RESERVED) {
2833 os_io_seph_cmd_piezo_play_tune(tune);
2834 }
2835#endif // HAVE_PIEZO_SOUND
2836 nbgl_layoutDraw(reviewWithWarnCtx.layoutCtx);
2837 nbgl_refresh();
2838}
2839
2840// function used to display the prelude when starting a "review with warning"
2841static void displayPrelude(void)
2842{
2843 // clang-format off
2844 nbgl_layoutDescription_t layoutDescription = {0};
2845 nbgl_layoutChoiceButtons_t buttonsInfo
2846 = {.bottomText = reviewWithWarnCtx.warning->prelude->footerText,
2847 .token = PRELUDE_CHOICE_TOKEN,
2848 .topText = reviewWithWarnCtx.warning->prelude->buttonText,
2849 .style = ROUNDED_AND_FOOTER_STYLE,
2850 .tuneId = TUNE_TAP_CASUAL};
2851 nbgl_layoutHeader_t headerDesc = {.type = HEADER_EMPTY,
2852 .emptySpace.height = MEDIUM_CENTERING_HEADER};
2853 // clang-format on
2854
2855 reviewWithWarnCtx.isIntro = true;
2856
2857 layoutDescription.withLeftBorder = true;
2858 layoutDescription.onActionCallback = layoutTouchCallback;
2859 reviewWithWarnCtx.layoutCtx = nbgl_layoutGet(&layoutDescription);
2860
2861 nbgl_layoutAddHeader(reviewWithWarnCtx.layoutCtx, &headerDesc);
2862 // add centered content
2863 nbgl_contentCenter_t info = {0};
2864
2865 // default icon
2866 info.icon = reviewWithWarnCtx.warning->prelude->icon;
2867
2868 info.title = reviewWithWarnCtx.warning->prelude->title;
2869 info.description = reviewWithWarnCtx.warning->prelude->description;
2870 nbgl_layoutAddContentCenter(reviewWithWarnCtx.layoutCtx, &info);
2871
2872 // add button and footer on bottom
2873 nbgl_layoutAddChoiceButtons(reviewWithWarnCtx.layoutCtx, &buttonsInfo);
2874
2875#ifdef HAVE_PIEZO_SOUND
2876 os_io_seph_cmd_piezo_play_tune(TUNE_LOOK_AT_ME);
2877#endif // HAVE_PIEZO_SOUND
2878 nbgl_layoutDraw(reviewWithWarnCtx.layoutCtx);
2879 nbgl_refresh();
2880}
2881
2882// function to factorize code for reviews tipbox
2883static void initWarningTipBox(const nbgl_tipBox_t *tipBox)
2884{
2885 const char *predefinedTipBoxText = NULL;
2886
2887 // if warning is valid and a warning requires a tip-box
2888 if (reviewWithWarnCtx.warning) {
2889 if (reviewWithWarnCtx.warning->predefinedSet & (1 << W3C_ISSUE_WARN)) {
2890 if (reviewWithWarnCtx.warning->predefinedSet & (1 << BLIND_SIGNING_WARN)) {
2891 predefinedTipBoxText = "Transaction Check unavailable.\nBlind signing required.";
2892 }
2893 else {
2894 predefinedTipBoxText = "Transaction Check unavailable";
2895 }
2896 }
2897 else if (reviewWithWarnCtx.warning->predefinedSet & (1 << W3C_THREAT_DETECTED_WARN)) {
2898 if (reviewWithWarnCtx.warning->predefinedSet & (1 << BLIND_SIGNING_WARN)) {
2899 predefinedTipBoxText = "Critical threat detected.\nBlind signing required.";
2900 }
2901 else {
2902 predefinedTipBoxText = "Critical threat detected.";
2903 }
2904 }
2905 else if (reviewWithWarnCtx.warning->predefinedSet & (1 << W3C_RISK_DETECTED_WARN)) {
2906 if (reviewWithWarnCtx.warning->predefinedSet & (1 << BLIND_SIGNING_WARN)) {
2907 predefinedTipBoxText = "Potential risk detected.\nBlind signing required.";
2908 }
2909 else {
2910 predefinedTipBoxText = "Potential risk detected.";
2911 }
2912 }
2913 else if (reviewWithWarnCtx.warning->predefinedSet & (1 << W3C_NO_THREAT_WARN)) {
2914 if (reviewWithWarnCtx.warning->predefinedSet & (1 << BLIND_SIGNING_WARN)) {
2915 predefinedTipBoxText
2916 = "No threat detected by Transaction Check but blind signing required.";
2917 }
2918 else {
2919 predefinedTipBoxText = "No threat detected by Transaction Check.";
2920 }
2921 }
2922 else if (reviewWithWarnCtx.warning->predefinedSet & (1 << BLIND_SIGNING_WARN)) {
2923 predefinedTipBoxText = "Blind signing required.";
2924 }
2925 }
2926
2927 if ((tipBox != NULL) || (predefinedTipBoxText != NULL)) {
2928 // do not display "Swipe to review" if a tip-box is displayed
2929 STARTING_CONTENT.content.extendedCenter.contentCenter.subText = NULL;
2930 if (predefinedTipBoxText != NULL) {
2931 genericContext.validWarningCtx = true;
2932 STARTING_CONTENT.content.extendedCenter.tipBox.icon = NULL;
2933 STARTING_CONTENT.content.extendedCenter.tipBox.text = predefinedTipBoxText;
2934 }
2935 else {
2936 STARTING_CONTENT.content.extendedCenter.tipBox.icon = tipBox->icon;
2937 STARTING_CONTENT.content.extendedCenter.tipBox.text = tipBox->text;
2938 }
2939 STARTING_CONTENT.content.extendedCenter.tipBox.token = TIP_BOX_TOKEN;
2940 STARTING_CONTENT.content.extendedCenter.tipBox.tuneId = TUNE_TAP_CASUAL;
2941 }
2942}
2943
2944// function to factorize code for all simple reviews
2945static void useCaseReview(nbgl_operationType_t operationType,
2946 const nbgl_contentTagValueList_t *tagValueList,
2947 const nbgl_icon_details_t *icon,
2948 const char *reviewTitle,
2949 const char *reviewSubTitle,
2950 const char *finishTitle,
2951 const nbgl_tipBox_t *tipBox,
2952 nbgl_choiceCallback_t choiceCallback,
2953 bool isLight,
2954 bool playNotifSound)
2955{
2956 bundleNavContext.review.operationType = operationType;
2957 bundleNavContext.review.choiceCallback = choiceCallback;
2958
2959 // memorize context
2960 onChoice = bundleNavReviewChoice;
2961 navType = GENERIC_NAV;
2962 pageTitle = NULL;
2963
2964 genericContext.genericContents.contentsList = localContentsList;
2965 genericContext.genericContents.nbContents = 3;
2966 memset(localContentsList, 0, 3 * sizeof(nbgl_content_t));
2967
2968 // First a centered info
2969 STARTING_CONTENT.type = EXTENDED_CENTER;
2970 prepareReviewFirstPage(
2971 &STARTING_CONTENT.content.extendedCenter.contentCenter, icon, reviewTitle, reviewSubTitle);
2972
2973 // Prepare un tipbox if needed
2974 initWarningTipBox(tipBox);
2975
2976 // Then the tag/value pairs
2977 localContentsList[1].type = TAG_VALUE_LIST;
2978 memcpy(&localContentsList[1].content.tagValueList,
2979 tagValueList,
2981 localContentsList[1].contentActionCallback = tagValueList->actionCallback;
2982
2983 // The last page
2984 if (isLight) {
2985 localContentsList[2].type = INFO_BUTTON;
2986 prepareReviewLightLastPage(
2987 operationType, &localContentsList[2].content.infoButton, icon, finishTitle);
2988 }
2989 else {
2990 localContentsList[2].type = INFO_LONG_PRESS;
2991 prepareReviewLastPage(
2992 operationType, &localContentsList[2].content.infoLongPress, icon, finishTitle);
2993 }
2994
2995 // compute number of pages & fill navigation structure
2996 uint8_t nbPages = getNbPagesForGenericContents(
2997 &genericContext.genericContents, 0, (operationType & SKIPPABLE_OPERATION));
2998 prepareNavInfo(true, nbPages, getRejectReviewText(operationType));
2999
3000 // Play notification sound if required
3001 if (playNotifSound) {
3002#ifdef HAVE_PIEZO_SOUND
3003 os_io_seph_cmd_piezo_play_tune(TUNE_LOOK_AT_ME);
3004#endif // HAVE_PIEZO_SOUND
3005 }
3006
3007 displayGenericContextPage(0, true);
3008}
3009
3010// function to factorize code for all streaming reviews
3011static void useCaseReviewStreamingStart(nbgl_operationType_t operationType,
3012 const nbgl_icon_details_t *icon,
3013 const char *reviewTitle,
3014 const char *reviewSubTitle,
3015 nbgl_choiceCallback_t choiceCallback,
3016 bool playNotifSound)
3017{
3018 bundleNavContext.reviewStreaming.operationType = operationType;
3019 bundleNavContext.reviewStreaming.choiceCallback = choiceCallback;
3020 bundleNavContext.reviewStreaming.icon = icon;
3021
3022 // memorize context
3023 onChoice = bundleNavReviewStreamingChoice;
3024 navType = STREAMING_NAV;
3025 pageTitle = NULL;
3026
3027 genericContext.genericContents.contentsList = localContentsList;
3028 genericContext.genericContents.nbContents = 1;
3029 memset(localContentsList, 0, 1 * sizeof(nbgl_content_t));
3030
3031 // First a centered info
3032 STARTING_CONTENT.type = EXTENDED_CENTER;
3033 prepareReviewFirstPage(
3034 &STARTING_CONTENT.content.extendedCenter.contentCenter, icon, reviewTitle, reviewSubTitle);
3035
3036 // Prepare un tipbox if needed
3037 initWarningTipBox(NULL);
3038
3039 // compute number of pages & fill navigation structure
3040 bundleNavContext.reviewStreaming.stepPageNb = getNbPagesForGenericContents(
3041 &genericContext.genericContents, 0, (operationType & SKIPPABLE_OPERATION));
3042 prepareNavInfo(true, NBGL_NO_PROGRESS_INDICATOR, getRejectReviewText(operationType));
3043 // no back button on first page
3044 navInfo.navWithButtons.backButton = false;
3045
3046 // Play notification sound if required
3047 if (playNotifSound) {
3048#ifdef HAVE_PIEZO_SOUND
3049 os_io_seph_cmd_piezo_play_tune(TUNE_LOOK_AT_ME);
3050#endif // HAVE_PIEZO_SOUND
3051 }
3052
3053 displayGenericContextPage(0, true);
3054}
3055
3072static void useCaseHomeExt(const char *appName,
3073 const nbgl_icon_details_t *appIcon,
3074 const char *tagline,
3075 bool withSettings,
3076 nbgl_homeAction_t *homeAction,
3077 nbgl_callback_t topRightCallback,
3078 nbgl_callback_t quitCallback)
3079{
3080 nbgl_pageInfoDescription_t info = {.centeredInfo.icon = appIcon,
3081 .centeredInfo.text1 = appName,
3082 .centeredInfo.text3 = NULL,
3083 .centeredInfo.style = LARGE_CASE_INFO,
3084 .centeredInfo.offsetY = 0,
3085 .footerText = NULL,
3086 .bottomButtonStyle = QUIT_APP_TEXT,
3087 .tapActionText = NULL,
3088 .topRightStyle = withSettings ? SETTINGS_ICON : INFO_ICON,
3089 .topRightToken = CONTINUE_TOKEN,
3090 .tuneId = TUNE_TAP_CASUAL};
3091 reset_callbacks_and_context();
3092
3093 if ((homeAction->text != NULL) || (homeAction->icon != NULL)) {
3094 // trick to use ACTION_BUTTON_TOKEN for action and quit, with index used to distinguish
3095 info.bottomButtonsToken = ACTION_BUTTON_TOKEN;
3096 onAction = homeAction->callback;
3097 info.actionButtonText = homeAction->text;
3098 info.actionButtonIcon = homeAction->icon;
3101 }
3102 else {
3103 info.bottomButtonsToken = QUIT_TOKEN;
3104 onAction = NULL;
3105 info.actionButtonText = NULL;
3106 info.actionButtonIcon = NULL;
3107 }
3108 if (tagline == NULL) {
3109 if (strlen(appName) > MAX_APP_NAME_FOR_SDK_TAGLINE) {
3110 snprintf(tmpString,
3112 "This app enables signing\ntransactions on its network.");
3113 }
3114 else {
3115 snprintf(tmpString,
3117 "%s %s\n%s",
3119 appName,
3121 }
3122
3123 // If there is more than 3 lines, it means the appName was split, so we put it on the next
3124 // line
3125 if (nbgl_getTextNbLinesInWidth(SMALL_REGULAR_FONT, tmpString, AVAILABLE_WIDTH, false) > 3) {
3126 snprintf(tmpString,
3128 "%s\n%s %s",
3130 appName,
3132 }
3133 info.centeredInfo.text2 = tmpString;
3134 }
3135 else {
3136 info.centeredInfo.text2 = tagline;
3137 }
3138
3139 onContinue = topRightCallback;
3140 onQuit = quitCallback;
3141
3142 pageContext = nbgl_pageDrawInfo(&pageCallback, NULL, &info);
3144}
3145
3154static void displayDetails(const char *tag, const char *value, bool wrapping)
3155{
3156 memset(&detailsContext, 0, sizeof(detailsContext));
3157
3158 uint16_t nbLines
3159 = nbgl_getTextNbLinesInWidth(SMALL_REGULAR_FONT, value, AVAILABLE_WIDTH, wrapping);
3160
3161 // initialize context
3162 detailsContext.tag = tag;
3163 detailsContext.value = value;
3164 detailsContext.nbPages = (nbLines + NB_MAX_LINES_IN_DETAILS - 1) / NB_MAX_LINES_IN_DETAILS;
3165 detailsContext.currentPage = 0;
3166 detailsContext.wrapping = wrapping;
3167 // add some spare for room lost with "..." substitution
3168 if (detailsContext.nbPages > 1) {
3169 uint16_t nbLostChars = (detailsContext.nbPages - 1) * 3;
3170 uint16_t nbLostLines = (nbLostChars + ((AVAILABLE_WIDTH) / 16) - 1)
3171 / ((AVAILABLE_WIDTH) / 16); // 16 for average char width
3172 uint8_t nbLinesInLastPage
3173 = nbLines - ((detailsContext.nbPages - 1) * NB_MAX_LINES_IN_DETAILS);
3174
3175 detailsContext.nbPages += nbLostLines / NB_MAX_LINES_IN_DETAILS;
3176 if ((nbLinesInLastPage + (nbLostLines % NB_MAX_LINES_IN_DETAILS))
3178 detailsContext.nbPages++;
3179 }
3180 }
3181
3182 displayDetailsPage(0, true);
3183}
3184
3185// function used to display the modal containing alias tag-value pairs
3186static void displayTagValueListModal(const nbgl_contentTagValueList_t *tagValues)
3187{
3188 uint8_t nbElements = 0;
3189 uint8_t nbElementsInPage;
3190 uint8_t elemIdx = 0;
3191
3192 // initialize context
3193 memset(&detailsContext, 0, sizeof(detailsContext));
3194 nbElements = tagValues->nbPairs;
3195
3196 while (nbElements > 0) {
3197 if (detailsContext.nbPages >= MAX_MODAL_PAGE_NB) {
3199 "displayTagValueListModal(): too many pages, truncating at %d\n",
3200 MAX_MODAL_PAGE_NB);
3201 break;
3202 }
3203 nbElementsInPage = getNbTagValuesInDetailsPage(nbElements, tagValues, elemIdx);
3204
3205 elemIdx += nbElementsInPage;
3206 modalContextSetPageInfo(detailsContext.nbPages, nbElementsInPage);
3207 nbElements -= nbElementsInPage;
3208 detailsContext.nbPages++;
3209 }
3210
3211 displayTagValueListModalPage(0, true);
3212}
3213
3214/**********************
3215 * GLOBAL FUNCTIONS
3216 **********************/
3217
3230uint8_t nbgl_useCaseGetNbTagValuesInPage(uint8_t nbPairs,
3231 const nbgl_contentTagValueList_t *tagValueList,
3232 uint8_t startIndex,
3233 bool *requireSpecificDisplay)
3234{
3235 return getNbTagValuesInPage(
3236 nbPairs, tagValueList, startIndex, false, false, false, requireSpecificDisplay);
3237}
3238
3248static uint8_t getNbInfosInPage(uint8_t nbInfos,
3249 const nbgl_contentInfoList_t *infosList,
3250 uint8_t startIndex,
3251 bool withNav)
3252{
3253 uint8_t nbInfosInPage = 0;
3254 uint16_t currentHeight = 0;
3255 uint16_t previousHeight = 0;
3256 uint16_t navHeight = withNav ? SIMPLE_FOOTER_HEIGHT : 0;
3257 const char *const *infoContents = PIC(infosList->infoContents);
3258
3259 while (nbInfosInPage < nbInfos) {
3260 // The type string must be a 1 liner and its height is LIST_ITEM_MIN_TEXT_HEIGHT
3261 currentHeight
3262 += LIST_ITEM_MIN_TEXT_HEIGHT + 2 * LIST_ITEM_PRE_HEADING + LIST_ITEM_HEADING_SUB_TEXT;
3263
3264 // content height
3265 currentHeight += nbgl_getTextHeightInWidth(SMALL_REGULAR_FONT,
3266 PIC(infoContents[startIndex + nbInfosInPage]),
3268 true);
3269 // if height is over the limit
3270 if (currentHeight >= (INFOS_AREA_HEIGHT - navHeight)) {
3271 // if there was no nav, now there will be, so it can be necessary to remove the last
3272 // item
3273 if (!withNav && (previousHeight >= (INFOS_AREA_HEIGHT - SIMPLE_FOOTER_HEIGHT))) {
3274 nbInfosInPage--;
3275 }
3276 break;
3277 }
3278 previousHeight = currentHeight;
3279 nbInfosInPage++;
3280 }
3281 return nbInfosInPage;
3282}
3283
3293static uint8_t getNbSwitchesInPage(uint8_t nbSwitches,
3294 const nbgl_contentSwitchesList_t *switchesList,
3295 uint8_t startIndex,
3296 bool withNav)
3297{
3298 uint8_t nbSwitchesInPage = 0;
3299 uint16_t currentHeight = 0;
3300 uint16_t previousHeight = 0;
3301 uint16_t navHeight = withNav ? SIMPLE_FOOTER_HEIGHT : 0;
3302 nbgl_contentSwitch_t *switchArray = (nbgl_contentSwitch_t *) PIC(switchesList->switches);
3303
3304 while (nbSwitchesInPage < nbSwitches) {
3305 nbgl_contentSwitch_t *curSwitch = &switchArray[startIndex + nbSwitchesInPage];
3306 // The text string is either a 1 liner and its height is LIST_ITEM_MIN_TEXT_HEIGHT
3307 // or we use its height directly
3308 uint16_t textHeight = MAX(
3309 LIST_ITEM_MIN_TEXT_HEIGHT,
3310 nbgl_getTextHeightInWidth(SMALL_BOLD_FONT, curSwitch->text, AVAILABLE_WIDTH, true));
3311 currentHeight += textHeight + 2 * LIST_ITEM_PRE_HEADING;
3312
3313 if (curSwitch->subText) {
3314 currentHeight += LIST_ITEM_HEADING_SUB_TEXT;
3315
3316 // sub-text height
3317 currentHeight += nbgl_getTextHeightInWidth(
3318 SMALL_REGULAR_FONT, curSwitch->subText, AVAILABLE_WIDTH, true);
3319 }
3320 // if height is over the limit
3321 if (currentHeight >= (INFOS_AREA_HEIGHT - navHeight)) {
3322 break;
3323 }
3324 previousHeight = currentHeight;
3325 nbSwitchesInPage++;
3326 }
3327 // if there was no nav, now there will be, so it can be necessary to remove the last
3328 // item
3329 if (!withNav && (previousHeight >= (INFOS_AREA_HEIGHT - SIMPLE_FOOTER_HEIGHT))) {
3330 nbSwitchesInPage--;
3331 }
3332 return nbSwitchesInPage;
3333}
3334
3344static uint8_t getNbBarsInPage(uint8_t nbBars,
3345 const nbgl_contentBarsList_t *barsList,
3346 uint8_t startIndex,
3347 bool withNav)
3348{
3349 uint8_t nbBarsInPage = 0;
3350 uint16_t currentHeight = 0;
3351 uint16_t previousHeight = 0;
3352 uint16_t navHeight = withNav ? SIMPLE_FOOTER_HEIGHT : 0;
3353
3354 UNUSED(barsList);
3355 UNUSED(startIndex);
3356
3357 while (nbBarsInPage < nbBars) {
3358 currentHeight += LIST_ITEM_MIN_TEXT_HEIGHT + 2 * LIST_ITEM_PRE_HEADING;
3359 // if height is over the limit
3360 if (currentHeight >= (INFOS_AREA_HEIGHT - navHeight)) {
3361 break;
3362 }
3363 previousHeight = currentHeight;
3364 nbBarsInPage++;
3365 }
3366 // if there was no nav, now there may will be, so it can be necessary to remove the last
3367 // item
3368 if (!withNav && (previousHeight >= (INFOS_AREA_HEIGHT - SIMPLE_FOOTER_HEIGHT))) {
3369 nbBarsInPage--;
3370 }
3371 return nbBarsInPage;
3372}
3373
3383uint8_t nbgl_useCaseGetNbChoicesInPage(uint8_t nbChoices,
3384 const nbgl_contentRadioChoice_t *choicesList,
3385 uint8_t startIndex,
3386 bool withNav)
3387{
3388 uint8_t nbChoicesInPage = 0;
3389 uint16_t currentHeight = 0;
3390 uint16_t previousHeight = 0;
3391 uint16_t navHeight = withNav ? SIMPLE_FOOTER_HEIGHT : 0;
3392
3393 UNUSED(choicesList);
3394 UNUSED(startIndex);
3395
3396 while (nbChoicesInPage < nbChoices) {
3397 currentHeight += LIST_ITEM_MIN_TEXT_HEIGHT + 2 * LIST_ITEM_PRE_HEADING;
3398 // if height is over the limit
3399 if (currentHeight >= (INFOS_AREA_HEIGHT - navHeight)) {
3400 // if there was no nav, now there will be, so it can be necessary to remove the last
3401 // item
3402 if (!withNav && (previousHeight >= (INFOS_AREA_HEIGHT - SIMPLE_FOOTER_HEIGHT))) {
3403 nbChoicesInPage--;
3404 }
3405 break;
3406 }
3407 previousHeight = currentHeight;
3408 nbChoicesInPage++;
3409 }
3410 return nbChoicesInPage;
3411}
3412
3417void nbgl_useCaseHome(const char *appName,
3418 const nbgl_icon_details_t *appIcon,
3419 const char *tagline,
3420 bool withSettings,
3421 nbgl_callback_t topRightCallback,
3422 nbgl_callback_t quitCallback)
3423{
3424 nbgl_homeAction_t homeAction = {0};
3425 useCaseHomeExt(
3426 appName, appIcon, tagline, withSettings, &homeAction, topRightCallback, quitCallback);
3427}
3428
3442void nbgl_useCaseNavigableContent(const char *title,
3443 uint8_t initPage,
3444 uint8_t nbPages,
3445 nbgl_callback_t quitCallback,
3446 nbgl_navCallback_t navCallback,
3447 nbgl_layoutTouchCallback_t controlsCallback)
3448{
3449 reset_callbacks_and_context();
3450
3451 // memorize context
3452 onQuit = quitCallback;
3453 onNav = navCallback;
3454 onControls = controlsCallback;
3455 pageTitle = title;
3456 navType = SETTINGS_NAV;
3457
3458 // fill navigation structure
3459 prepareNavInfo(false, nbPages, NULL);
3460
3461 displaySettingsPage(initPage, true);
3462}
3463
3469void nbgl_useCaseSettings(const char *title,
3470 uint8_t initPage,
3471 uint8_t nbPages,
3472 bool touchable,
3473 nbgl_callback_t quitCallback,
3474 nbgl_navCallback_t navCallback,
3475 nbgl_layoutTouchCallback_t controlsCallback)
3476{
3477 UNUSED(touchable);
3479 title, initPage, nbPages, quitCallback, navCallback, controlsCallback);
3480}
3481
3494void nbgl_useCaseGenericSettings(const char *appName,
3495 uint8_t initPage,
3496 const nbgl_genericContents_t *settingContents,
3497 const nbgl_contentInfoList_t *infosList,
3498 nbgl_callback_t quitCallback)
3499{
3500 reset_callbacks_and_context();
3501
3502 // memorize context
3503 onQuit = quitCallback;
3504 pageTitle = appName;
3505 navType = GENERIC_NAV;
3506
3507 if (settingContents != NULL) {
3508 memcpy(&genericContext.genericContents, settingContents, sizeof(nbgl_genericContents_t));
3509 }
3510 if (infosList != NULL) {
3511 genericContext.hasFinishingContent = true;
3512 memset(&FINISHING_CONTENT, 0, sizeof(nbgl_content_t));
3513 FINISHING_CONTENT.type = INFOS_LIST;
3514 memcpy(&FINISHING_CONTENT.content, infosList, sizeof(nbgl_content_u));
3515 }
3516
3517 // fill navigation structure
3518 uint8_t nbPages = getNbPagesForGenericContents(&genericContext.genericContents, 0, false);
3519 if (infosList != NULL) {
3520 nbPages += getNbPagesForContent(&FINISHING_CONTENT, nbPages, true, false);
3521 }
3522
3523 prepareNavInfo(false, nbPages, NULL);
3524
3525 displayGenericContextPage(initPage, true);
3526}
3527
3539void nbgl_useCaseGenericConfiguration(const char *title,
3540 uint8_t initPage,
3541 const nbgl_genericContents_t *contents,
3542 nbgl_callback_t quitCallback)
3543{
3544 nbgl_useCaseGenericSettings(title, initPage, contents, NULL, quitCallback);
3545}
3546
3564 const char *appName,
3565 const nbgl_icon_details_t *appIcon,
3566 const char *tagline,
3567 const uint8_t
3568 initSettingPage, // if not INIT_HOME_PAGE, start directly the corresponding setting page
3569 const nbgl_genericContents_t *settingContents,
3570 const nbgl_contentInfoList_t *infosList,
3571 const nbgl_homeAction_t *action, // Set to NULL if no additional action
3572 nbgl_callback_t quitCallback)
3573{
3574 nbgl_homeAndSettingsContext_t *context = &bundleNavContext.homeAndSettings;
3575
3576 reset_callbacks_and_context();
3577
3578 context->appName = appName;
3579 context->appIcon = appIcon;
3580 context->tagline = tagline;
3581 context->settingContents = settingContents;
3582 context->infosList = infosList;
3583 if (action != NULL) {
3584 memcpy(&context->homeAction, action, sizeof(nbgl_homeAction_t));
3585 }
3586 else {
3587 memset(&context->homeAction, 0, sizeof(nbgl_homeAction_t));
3588 }
3589 context->quitCallback = quitCallback;
3590
3591 if (initSettingPage != INIT_HOME_PAGE) {
3592 bundleNavStartSettingsAtPage(initSettingPage);
3593 }
3594 else {
3595 bundleNavStartHome();
3596 }
3597}
3598
3606void nbgl_useCaseStatus(const char *message, bool isSuccess, nbgl_callback_t quitCallback)
3607{
3608 nbgl_screenTickerConfiguration_t ticker = {.tickerCallback = &tickerCallback,
3609 .tickerIntervale = 0, // not periodic
3610 .tickerValue = STATUS_SCREEN_DURATION};
3611 nbgl_pageInfoDescription_t info = {0};
3612
3613 reset_callbacks_and_context();
3614
3615 onQuit = quitCallback;
3616 if (isSuccess) {
3617#ifdef HAVE_PIEZO_SOUND
3618 os_io_seph_cmd_piezo_play_tune(TUNE_LEDGER_MOMENT);
3619#endif // HAVE_PIEZO_SOUND
3620 }
3621 info.centeredInfo.icon = isSuccess ? &CHECK_CIRCLE_ICON : &DENIED_CIRCLE_ICON;
3623 info.centeredInfo.text1 = message;
3624 info.tapActionText = "";
3625 info.tapActionToken = QUIT_TOKEN;
3626 info.tuneId = TUNE_TAP_CASUAL;
3627 pageContext = nbgl_pageDrawInfo(&pageCallback, &ticker, &info);
3629}
3630
3638 nbgl_callback_t quitCallback)
3639{
3640 const char *msg;
3641 bool isSuccess;
3642 switch (reviewStatusType) {
3644 msg = "Operation signed";
3645 isSuccess = true;
3646 break;
3648 msg = "Operation rejected";
3649 isSuccess = false;
3650 break;
3652 msg = "Transaction signed";
3653 isSuccess = true;
3654 break;
3656 msg = "Transaction rejected";
3657 isSuccess = false;
3658 break;
3660 msg = "Message signed";
3661 isSuccess = true;
3662 break;
3664 msg = "Message rejected";
3665 isSuccess = false;
3666 break;
3668 msg = "Address verified";
3669 isSuccess = true;
3670 break;
3672 msg = "Address verification\ncancelled";
3673 isSuccess = false;
3674 break;
3675 default:
3676 return;
3677 }
3678 nbgl_useCaseStatus(msg, isSuccess, quitCallback);
3679}
3680
3694 const char *message,
3695 const char *subMessage,
3696 const char *confirmText,
3697 const char *cancelText,
3698 nbgl_choiceCallback_t callback)
3699{
3701 // check params
3702 if ((confirmText == NULL) || (cancelText == NULL)) {
3703 LOG_WARN(USE_CASE_LOGGER, "nbgl_useCaseChoice(): confirmText or cancelText is NULL\n");
3704 return;
3705 }
3706 reset_callbacks_and_context();
3707
3708 info.cancelText = cancelText;
3709 info.centeredInfo.text1 = message;
3710 info.centeredInfo.text2 = subMessage;
3712 info.centeredInfo.icon = icon;
3713 info.confirmationText = confirmText;
3714 info.confirmationToken = CHOICE_TOKEN;
3715 info.tuneId = TUNE_TAP_CASUAL;
3716
3717 onChoice = callback;
3718 pageContext = nbgl_pageDrawConfirmation(&pageCallback, &info);
3720}
3721
3737 const char *message,
3738 const char *subMessage,
3739 const char *confirmText,
3740 const char *cancelText,
3741 nbgl_genericDetails_t *details,
3742 nbgl_choiceCallback_t callback)
3743{
3745 (details != NULL) ? &SEARCH_ICON : NULL,
3746 message,
3747 subMessage,
3748 NULL,
3749 confirmText,
3750 cancelText,
3751 details,
3752 callback);
3753}
3754
3778 const nbgl_icon_details_t *headerIcon,
3779 const char *title,
3780 const char *message,
3781 const char *subMessage,
3782 const char *confirmText,
3783 const char *cancelText,
3784 nbgl_warningDetails_t *details,
3785 nbgl_choiceCallback_t callback)
3786{
3787 // clang-format off
3788 nbgl_layoutDescription_t layoutDescription = {0};
3789 nbgl_layoutChoiceButtons_t buttonsInfo = {.bottomText = cancelText,
3790 .token = CHOICE_TOKEN,
3791 .topText = confirmText,
3792 .style = ROUNDED_AND_FOOTER_STYLE,
3793 .tuneId = TUNE_TAP_CASUAL};
3794 nbgl_contentCenter_t centeredInfo = {0};
3795 nbgl_layoutHeader_t headerDesc = {.type = HEADER_EMPTY,
3796 .emptySpace.height = MEDIUM_CENTERING_HEADER};
3797 // clang-format on
3798
3799 if ((confirmText == NULL) || (cancelText == NULL)) {
3801 "nbgl_useCaseAdvancedChoiceWithDetails(): confirmText or cancelText is NULL\n");
3802 return;
3803 }
3804
3805 reset_callbacks_and_context();
3806
3807#ifdef HAVE_PIEZO_SOUND
3808 os_io_seph_cmd_piezo_play_tune(TUNE_LOOK_AT_ME);
3809#endif // HAVE_PIEZO_SOUND
3810
3811 onChoice = callback;
3812 layoutDescription.withLeftBorder = true;
3813 layoutDescription.onActionCallback = layoutTouchCallback;
3814 sharedContext.usage = SHARE_CTX_CHOICE_WITH_DETAILS;
3815 choiceWithDetailsCtx.layoutCtx = nbgl_layoutGet(&layoutDescription);
3816 choiceWithDetailsCtx.details = details;
3817
3818 nbgl_layoutAddHeader(choiceWithDetailsCtx.layoutCtx, &headerDesc);
3819 nbgl_layoutAddChoiceButtons(choiceWithDetailsCtx.layoutCtx, &buttonsInfo);
3820 centeredInfo.icon = centerIcon;
3821 centeredInfo.title = title;
3822 centeredInfo.description = message;
3823 centeredInfo.subText = subMessage;
3824 nbgl_layoutAddContentCenter(choiceWithDetailsCtx.layoutCtx, &centeredInfo);
3825
3826 if ((details != NULL) && (headerIcon != NULL)) {
3828 choiceWithDetailsCtx.layoutCtx, headerIcon, CHOICE_DETAILS_TOKEN, TUNE_TAP_CASUAL);
3829 }
3830
3831 nbgl_layoutDraw(choiceWithDetailsCtx.layoutCtx);
3833}
3834
3848void nbgl_useCaseConfirm(const char *message,
3849 const char *subMessage,
3850 const char *confirmText,
3851 const char *cancelText,
3852 nbgl_callback_t callback)
3853{
3854 // Don't reset callback or nav context as this is just a modal.
3855
3856 nbgl_pageConfirmationDescription_t info = {.cancelText = cancelText,
3857 .centeredInfo.text1 = message,
3858 .centeredInfo.text2 = subMessage,
3859 .centeredInfo.text3 = NULL,
3860 .centeredInfo.style = LARGE_CASE_INFO,
3861 .centeredInfo.icon = &IMPORTANT_CIRCLE_ICON,
3862 .centeredInfo.offsetY = 0,
3863 .confirmationText = confirmText,
3864 .confirmationToken = CHOICE_TOKEN,
3865 .tuneId = TUNE_TAP_CASUAL,
3866 .modal = true};
3867 onModalConfirm = callback;
3868 if (modalPageContext != NULL) {
3869 nbgl_pageRelease(modalPageContext);
3870 }
3871 modalPageContext = nbgl_pageDrawConfirmation(&pageModalCallback, &info);
3873}
3874
3886 const char *message,
3887 const char *actionText,
3888 nbgl_callback_t callback)
3889{
3890 nbgl_pageContent_t content = {0};
3891
3892 reset_callbacks_and_context();
3893 // memorize callback
3894 onAction = callback;
3895
3896 content.tuneId = TUNE_TAP_CASUAL;
3897 content.type = INFO_BUTTON;
3898 content.infoButton.buttonText = actionText;
3899 content.infoButton.text = message;
3900 content.infoButton.icon = icon;
3901 content.infoButton.buttonToken = ACTION_BUTTON_TOKEN;
3902
3903 pageContext = nbgl_pageDrawGenericContent(&pageCallback, NULL, &content);
3905}
3906
3919 const char *reviewTitle,
3920 const char *reviewSubTitle,
3921 const char *rejectText,
3922 nbgl_callback_t continueCallback,
3923 nbgl_callback_t rejectCallback)
3924{
3925 nbgl_pageInfoDescription_t info = {.footerText = rejectText,
3926 .footerToken = QUIT_TOKEN,
3927 .tapActionText = NULL,
3928 .isSwipeable = true,
3929 .tapActionToken = CONTINUE_TOKEN,
3930 .topRightStyle = NO_BUTTON_STYLE,
3931 .actionButtonText = NULL,
3932 .tuneId = TUNE_TAP_CASUAL};
3933
3934 reset_callbacks_and_context();
3935
3936 info.centeredInfo.icon = icon;
3937 info.centeredInfo.text1 = reviewTitle;
3938 info.centeredInfo.text2 = reviewSubTitle;
3939 info.centeredInfo.text3 = "Swipe to review";
3941 info.centeredInfo.offsetY = 0;
3942 onQuit = rejectCallback;
3943 onContinue = continueCallback;
3944
3945#ifdef HAVE_PIEZO_SOUND
3946 // Play notification sound
3947 os_io_seph_cmd_piezo_play_tune(TUNE_LOOK_AT_ME);
3948#endif // HAVE_PIEZO_SOUND
3949
3950 pageContext = nbgl_pageDrawInfo(&pageCallback, NULL, &info);
3951 nbgl_refresh();
3952}
3953
3967 const nbgl_pageInfoLongPress_t *infoLongPress,
3968 const char *rejectText,
3969 nbgl_choiceCallback_t callback)
3970{
3971 uint8_t offset = 0;
3972
3973 reset_callbacks_and_context();
3974
3975 // memorize context
3976 onChoice = callback;
3977 navType = GENERIC_NAV;
3978 pageTitle = NULL;
3979 bundleNavContext.review.operationType = TYPE_OPERATION;
3980
3981 genericContext.genericContents.contentsList = localContentsList;
3982 memset(localContentsList, 0, 2 * sizeof(nbgl_content_t));
3983
3984 if (tagValueList != NULL && tagValueList->nbPairs != 0) {
3985 localContentsList[offset].type = TAG_VALUE_LIST;
3986 memcpy(&localContentsList[offset].content.tagValueList,
3987 tagValueList,
3989 offset++;
3990 }
3991
3992 localContentsList[offset].type = INFO_LONG_PRESS;
3993 memcpy(&localContentsList[offset].content.infoLongPress,
3994 infoLongPress,
3995 sizeof(nbgl_pageInfoLongPress_t));
3996 localContentsList[offset].content.infoLongPress.longPressToken = CONFIRM_TOKEN;
3997 offset++;
3998
3999 genericContext.genericContents.nbContents = offset;
4000
4001 // compute number of pages & fill navigation structure
4002 uint8_t nbPages = getNbPagesForGenericContents(&genericContext.genericContents, 0, false);
4003 UNUSED(rejectText);
4004 prepareNavInfo(true, nbPages, getRejectReviewText(TYPE_OPERATION));
4005
4006 displayGenericContextPage(0, true);
4007}
4008
4023 const nbgl_pageInfoLongPress_t *infoLongPress,
4024 const char *rejectText,
4025 nbgl_choiceCallback_t callback)
4026{
4027 uint8_t offset = 0;
4028
4029 reset_callbacks_and_context();
4030
4031 // memorize context
4032 onChoice = callback;
4033 navType = GENERIC_NAV;
4034 pageTitle = NULL;
4035
4036 genericContext.genericContents.contentsList = localContentsList;
4037 memset(localContentsList, 0, 2 * sizeof(nbgl_content_t));
4038
4039 if (tagValueList != NULL && tagValueList->nbPairs != 0) {
4040 localContentsList[offset].type = TAG_VALUE_LIST;
4041 memcpy(&localContentsList[offset].content.tagValueList,
4042 tagValueList,
4044 offset++;
4045 }
4046
4047 localContentsList[offset].type = INFO_BUTTON;
4048 localContentsList[offset].content.infoButton.text = infoLongPress->text;
4049 localContentsList[offset].content.infoButton.icon = infoLongPress->icon;
4050 localContentsList[offset].content.infoButton.buttonText = infoLongPress->longPressText;
4051 localContentsList[offset].content.infoButton.buttonToken = CONFIRM_TOKEN;
4052 localContentsList[offset].content.infoButton.tuneId = TUNE_TAP_CASUAL;
4053 offset++;
4054
4055 genericContext.genericContents.nbContents = offset;
4056
4057 // compute number of pages & fill navigation structure
4058 uint8_t nbPages = getNbPagesForGenericContents(&genericContext.genericContents, 0, false);
4059 UNUSED(rejectText);
4060 prepareNavInfo(true, nbPages, getRejectReviewText(TYPE_OPERATION));
4061
4062 displayGenericContextPage(0, true);
4063}
4064
4081void nbgl_useCaseReview(nbgl_operationType_t operationType,
4082 const nbgl_contentTagValueList_t *tagValueList,
4083 const nbgl_icon_details_t *icon,
4084 const char *reviewTitle,
4085 const char *reviewSubTitle,
4086 const char *finishTitle,
4087 nbgl_choiceCallback_t choiceCallback)
4088{
4089 reset_callbacks_and_context();
4090
4091 useCaseReview(operationType,
4092 tagValueList,
4093 icon,
4094 reviewTitle,
4095 reviewSubTitle,
4096 finishTitle,
4097 NULL,
4098 choiceCallback,
4099 false,
4100 true);
4101}
4102
4123 const nbgl_contentTagValueList_t *tagValueList,
4124 const nbgl_icon_details_t *icon,
4125 const char *reviewTitle,
4126 const char *reviewSubTitle,
4127 const char *finishTitle,
4128 const nbgl_tipBox_t *tipBox,
4129 nbgl_choiceCallback_t choiceCallback)
4130{
4131 nbgl_useCaseAdvancedReview(operationType,
4132 tagValueList,
4133 icon,
4134 reviewTitle,
4135 reviewSubTitle,
4136 finishTitle,
4137 tipBox,
4138 &blindSigningWarning,
4139 choiceCallback);
4140}
4141
4165 const nbgl_contentTagValueList_t *tagValueList,
4166 const nbgl_icon_details_t *icon,
4167 const char *reviewTitle,
4168 const char *reviewSubTitle,
4169 const char *finishTitle,
4170 const nbgl_tipBox_t *tipBox,
4171 const nbgl_warning_t *warning,
4172 nbgl_choiceCallback_t choiceCallback)
4173{
4174 reset_callbacks_and_context();
4175
4176 // memorize tipBox because it can be in the call stack of the caller
4177 if (tipBox != NULL) {
4178 memcpy(&activeTipBox, tipBox, sizeof(activeTipBox));
4179 }
4180 // if no warning at all, it's a simple review
4181 if ((warning == NULL)
4182 || ((warning->predefinedSet == 0) && (warning->introDetails == NULL)
4183 && (warning->reviewDetails == NULL) && (warning->prelude == NULL))) {
4184 useCaseReview(operationType,
4185 tagValueList,
4186 icon,
4187 reviewTitle,
4188 reviewSubTitle,
4189 finishTitle,
4190 tipBox,
4191 choiceCallback,
4192 false,
4193 true);
4194 return;
4195 }
4196 if (warning->predefinedSet == (1 << W3C_NO_THREAT_WARN)) {
4197 operationType |= NO_THREAT_OPERATION;
4198 }
4199 else if (warning->predefinedSet != 0) {
4200 operationType |= RISKY_OPERATION;
4201 }
4202 sharedContext.usage = SHARE_CTX_REVIEW_WITH_WARNING;
4203 reviewWithWarnCtx.isStreaming = false;
4204 reviewWithWarnCtx.operationType = operationType;
4205 reviewWithWarnCtx.tagValueList = tagValueList;
4206 reviewWithWarnCtx.icon = icon;
4207 reviewWithWarnCtx.reviewTitle = reviewTitle;
4208 reviewWithWarnCtx.reviewSubTitle = reviewSubTitle;
4209 reviewWithWarnCtx.finishTitle = finishTitle;
4210 reviewWithWarnCtx.warning = warning;
4211 reviewWithWarnCtx.choiceCallback = choiceCallback;
4212
4213 // if the warning contains a prelude, display it first
4214 if (reviewWithWarnCtx.warning->prelude) {
4215 displayPrelude();
4216 }
4217 // display the initial warning only of a risk/threat or blind signing or prelude
4218 else if (HAS_INITIAL_WARNING(warning)) {
4219 displayInitialWarning();
4220 }
4221 else {
4222 useCaseReview(operationType,
4223 tagValueList,
4224 icon,
4225 reviewTitle,
4226 reviewSubTitle,
4227 finishTitle,
4228 tipBox,
4229 choiceCallback,
4230 false,
4231 true);
4232 }
4233}
4234
4252 const nbgl_contentTagValueList_t *tagValueList,
4253 const nbgl_icon_details_t *icon,
4254 const char *reviewTitle,
4255 const char *reviewSubTitle,
4256 const char *finishTitle,
4257 nbgl_choiceCallback_t choiceCallback)
4258{
4259 reset_callbacks_and_context();
4260
4261 useCaseReview(operationType,
4262 tagValueList,
4263 icon,
4264 reviewTitle,
4265 reviewSubTitle,
4266 finishTitle,
4267 NULL,
4268 choiceCallback,
4269 true,
4270 true);
4271}
4272
4282 const char *rejectText,
4283 nbgl_callback_t rejectCallback)
4284{
4285 reset_callbacks_and_context();
4286
4287 // memorize context
4288 onQuit = rejectCallback;
4289 navType = GENERIC_NAV;
4290 pageTitle = NULL;
4291 bundleNavContext.review.operationType = TYPE_OPERATION;
4292
4293 memcpy(&genericContext.genericContents, contents, sizeof(nbgl_genericContents_t));
4294
4295 // compute number of pages & fill navigation structure
4296 uint8_t nbPages = getNbPagesForGenericContents(&genericContext.genericContents, 0, false);
4297 prepareNavInfo(true, nbPages, rejectText);
4298 navInfo.quitToken = QUIT_TOKEN;
4299
4300#ifdef HAVE_PIEZO_SOUND
4301 // Play notification sound
4302 os_io_seph_cmd_piezo_play_tune(TUNE_LOOK_AT_ME);
4303#endif // HAVE_PIEZO_SOUND
4304
4305 displayGenericContextPage(0, true);
4306}
4307
4321 const nbgl_icon_details_t *icon,
4322 const char *reviewTitle,
4323 const char *reviewSubTitle,
4324 nbgl_choiceCallback_t choiceCallback)
4325{
4326 reset_callbacks_and_context();
4327
4328 useCaseReviewStreamingStart(
4329 operationType, icon, reviewTitle, reviewSubTitle, choiceCallback, true);
4330}
4331
4346 const nbgl_icon_details_t *icon,
4347 const char *reviewTitle,
4348 const char *reviewSubTitle,
4349 nbgl_choiceCallback_t choiceCallback)
4350{
4352 operationType, icon, reviewTitle, reviewSubTitle, &blindSigningWarning, choiceCallback);
4353}
4354
4371 const nbgl_icon_details_t *icon,
4372 const char *reviewTitle,
4373 const char *reviewSubTitle,
4374 const nbgl_warning_t *warning,
4375 nbgl_choiceCallback_t choiceCallback)
4376{
4377 reset_callbacks_and_context();
4378
4379 // if no warning at all, it's a simple review
4380 if ((warning == NULL)
4381 || ((warning->predefinedSet == 0) && (warning->introDetails == NULL)
4382 && (warning->reviewDetails == NULL) && (warning->prelude == NULL))) {
4383 useCaseReviewStreamingStart(
4384 operationType, icon, reviewTitle, reviewSubTitle, choiceCallback, true);
4385 return;
4386 }
4387 if (warning->predefinedSet == (1 << W3C_NO_THREAT_WARN)) {
4388 operationType |= NO_THREAT_OPERATION;
4389 }
4390 else if (warning->predefinedSet != 0) {
4391 operationType |= RISKY_OPERATION;
4392 }
4393
4394 sharedContext.usage = SHARE_CTX_REVIEW_WITH_WARNING;
4395 reviewWithWarnCtx.isStreaming = true;
4396 reviewWithWarnCtx.operationType = operationType;
4397 reviewWithWarnCtx.icon = icon;
4398 reviewWithWarnCtx.reviewTitle = reviewTitle;
4399 reviewWithWarnCtx.reviewSubTitle = reviewSubTitle;
4400 reviewWithWarnCtx.choiceCallback = choiceCallback;
4401 reviewWithWarnCtx.warning = warning;
4402
4403 // if the warning contains a prelude, display it first
4404 if (reviewWithWarnCtx.warning->prelude) {
4405 displayPrelude();
4406 }
4407 // display the initial warning only of a risk/threat or blind signing
4408 else if (HAS_INITIAL_WARNING(warning)) {
4409 displayInitialWarning();
4410 }
4411 else {
4412 useCaseReviewStreamingStart(
4413 operationType, icon, reviewTitle, reviewSubTitle, choiceCallback, true);
4414 }
4415}
4416
4431 nbgl_choiceCallback_t choiceCallback,
4432 nbgl_callback_t skipCallback)
4433{
4434 // Should follow a call to nbgl_useCaseReviewStreamingStart
4435 // Keep the shared context set up by the streaming start.
4436 memset(&genericContext, 0, offsetof(GenericContext_t, sharedCtx));
4437
4438 bundleNavContext.reviewStreaming.choiceCallback = choiceCallback;
4439 bundleNavContext.reviewStreaming.skipCallback = skipCallback;
4440
4441 // memorize context
4442 onChoice = bundleNavReviewStreamingChoice;
4443 navType = STREAMING_NAV;
4444 pageTitle = NULL;
4445
4446 genericContext.genericContents.contentsList = localContentsList;
4447 genericContext.genericContents.nbContents = 1;
4448 memset(localContentsList, 0, 1 * sizeof(nbgl_content_t));
4449
4450 // Then the tag/value pairs
4451 STARTING_CONTENT.type = TAG_VALUE_LIST;
4452 memcpy(
4453 &STARTING_CONTENT.content.tagValueList, tagValueList, sizeof(nbgl_contentTagValueList_t));
4454
4455 // compute number of pages & fill navigation structure
4456 bundleNavContext.reviewStreaming.stepPageNb = getNbPagesForGenericContents(
4457 &genericContext.genericContents,
4458 0,
4459 (bundleNavContext.reviewStreaming.operationType & SKIPPABLE_OPERATION));
4460 prepareNavInfo(true,
4462 getRejectReviewText(bundleNavContext.reviewStreaming.operationType));
4463 // if the operation is skippable
4464 if (bundleNavContext.reviewStreaming.operationType & SKIPPABLE_OPERATION) {
4465 navInfo.progressIndicator = false;
4466 navInfo.skipText = "Skip";
4467 navInfo.skipToken = SKIP_TOKEN;
4468 }
4469
4470 displayGenericContextPage(0, true);
4471}
4472
4484 nbgl_choiceCallback_t choiceCallback)
4485{
4486 nbgl_useCaseReviewStreamingContinueExt(tagValueList, choiceCallback, NULL);
4487}
4488
4497void nbgl_useCaseReviewStreamingFinish(const char *finishTitle,
4498 nbgl_choiceCallback_t choiceCallback)
4499{
4500 // Should follow a call to nbgl_useCaseReviewStreamingContinue
4501 // Keep the shared context set up by the streaming start.
4502 memset(&genericContext, 0, offsetof(GenericContext_t, sharedCtx));
4503
4504 bundleNavContext.reviewStreaming.choiceCallback = choiceCallback;
4505
4506 // memorize context
4507 onChoice = bundleNavReviewStreamingChoice;
4508 navType = STREAMING_NAV;
4509 pageTitle = NULL;
4510
4511 genericContext.genericContents.contentsList = localContentsList;
4512 genericContext.genericContents.nbContents = 1;
4513 memset(localContentsList, 0, 1 * sizeof(nbgl_content_t));
4514
4515 // Eventually the long press page
4516 STARTING_CONTENT.type = INFO_LONG_PRESS;
4517 prepareReviewLastPage(bundleNavContext.reviewStreaming.operationType,
4518 &STARTING_CONTENT.content.infoLongPress,
4519 bundleNavContext.reviewStreaming.icon,
4520 finishTitle);
4521
4522 // compute number of pages & fill navigation structure
4523 bundleNavContext.reviewStreaming.stepPageNb = getNbPagesForGenericContents(
4524 &genericContext.genericContents,
4525 0,
4526 (bundleNavContext.reviewStreaming.operationType & SKIPPABLE_OPERATION));
4527 prepareNavInfo(true, 1, getRejectReviewText(bundleNavContext.reviewStreaming.operationType));
4528
4529 displayGenericContextPage(0, true);
4530}
4531
4536void nbgl_useCaseAddressConfirmationExt(const char *address,
4537 nbgl_choiceCallback_t callback,
4538 const nbgl_contentTagValueList_t *tagValueList)
4539{
4540 reset_callbacks_and_context();
4541 memset(&addressConfirmationContext, 0, sizeof(addressConfirmationContext));
4542
4543 // save context
4544 onChoice = callback;
4545 navType = GENERIC_NAV;
4546 pageTitle = NULL;
4547
4548 genericContext.genericContents.contentsList = localContentsList;
4549 genericContext.genericContents.nbContents = (tagValueList == NULL) ? 1 : 2;
4550 memset(localContentsList, 0, 2 * sizeof(nbgl_content_t));
4551 prepareAddressConfirmationPages(
4552 address, tagValueList, &STARTING_CONTENT, &localContentsList[1]);
4553
4554 // fill navigation structure, common to all pages
4555 uint8_t nbPages = getNbPagesForGenericContents(&genericContext.genericContents, 0, false);
4556
4557 prepareNavInfo(true, nbPages, "Cancel");
4558
4559#ifdef HAVE_PIEZO_SOUND
4560 // Play notification sound
4561 os_io_seph_cmd_piezo_play_tune(TUNE_LOOK_AT_ME);
4562#endif // HAVE_PIEZO_SOUND
4563
4564 displayGenericContextPage(0, true);
4565}
4566
4583void nbgl_useCaseAddressReview(const char *address,
4584 const nbgl_contentTagValueList_t *additionalTagValueList,
4585 const nbgl_icon_details_t *icon,
4586 const char *reviewTitle,
4587 const char *reviewSubTitle,
4588 nbgl_choiceCallback_t choiceCallback)
4589{
4590 reset_callbacks_and_context();
4591
4592 // release a potential modal
4593 if (addressConfirmationContext.modalLayout) {
4594 nbgl_layoutRelease(addressConfirmationContext.modalLayout);
4595 }
4596 memset(&addressConfirmationContext, 0, sizeof(addressConfirmationContext));
4597
4598 // save context
4599 onChoice = choiceCallback;
4600 navType = GENERIC_NAV;
4601 pageTitle = NULL;
4602 bundleNavContext.review.operationType = TYPE_OPERATION;
4603
4604 genericContext.genericContents.contentsList = localContentsList;
4605 memset(localContentsList, 0, 3 * sizeof(nbgl_content_t));
4606
4607 // First a centered info
4608 STARTING_CONTENT.type = EXTENDED_CENTER;
4609 prepareReviewFirstPage(
4610 &STARTING_CONTENT.content.extendedCenter.contentCenter, icon, reviewTitle, reviewSubTitle);
4611 STARTING_CONTENT.content.extendedCenter.contentCenter.subText = "Swipe to continue";
4612
4613 // Then the address confirmation pages
4614 prepareAddressConfirmationPages(
4615 address, additionalTagValueList, &localContentsList[1], &localContentsList[2]);
4616
4617 // fill navigation structure, common to all pages
4618 genericContext.genericContents.nbContents
4619 = (localContentsList[2].type == TAG_VALUE_CONFIRM) ? 3 : 2;
4620 uint8_t nbPages = getNbPagesForGenericContents(&genericContext.genericContents, 0, false);
4621
4622 prepareNavInfo(true, nbPages, "Cancel");
4623
4624#ifdef HAVE_PIEZO_SOUND
4625 // Play notification sound
4626 os_io_seph_cmd_piezo_play_tune(TUNE_LOOK_AT_ME);
4627#endif // HAVE_PIEZO_SOUND
4628
4629 displayGenericContextPage(0, true);
4630}
4631
4640void nbgl_useCaseSpinner(const char *text)
4641{
4642 // if the previous Use Case was not Spinner, fresh start
4643 if (genericContext.type != USE_CASE_SPINNER) {
4644 memset(&genericContext, 0, sizeof(genericContext));
4645 genericContext.type = USE_CASE_SPINNER;
4646 nbgl_layoutDescription_t layoutDescription = {0};
4647
4648 layoutDescription.withLeftBorder = true;
4649
4650 genericContext.backgroundLayout = nbgl_layoutGet(&layoutDescription);
4651
4653 genericContext.backgroundLayout, text, NULL, genericContext.spinnerPosition);
4654
4655 nbgl_layoutDraw(genericContext.backgroundLayout);
4657 }
4658 else {
4659 // otherwise increment spinner
4660 genericContext.spinnerPosition++;
4661 // there are only NB_SPINNER_POSITIONSpositions
4662 if (genericContext.spinnerPosition == NB_SPINNER_POSITIONS) {
4663 genericContext.spinnerPosition = 0;
4664 }
4665 int ret = nbgl_layoutUpdateSpinner(
4666 genericContext.backgroundLayout, text, NULL, genericContext.spinnerPosition);
4667 if (ret == 1) {
4669 }
4670 else if (ret == 2) {
4672 }
4673 }
4674}
4675
4676#ifdef NBGL_KEYPAD
4695void nbgl_useCaseKeypad(const char *title,
4696 uint8_t minDigits,
4697 uint8_t maxDigits,
4698 bool shuffled,
4699 bool hidden,
4700 nbgl_pinValidCallback_t validatePinCallback,
4701 nbgl_callback_t backCallback)
4702{
4703 nbgl_layoutDescription_t layoutDescription = {0};
4705 .separationLine = true,
4706 .backAndText.token = BACK_TOKEN,
4707 .backAndText.tuneId = TUNE_TAP_CASUAL};
4708 int status = -1;
4709
4710 if ((minDigits > KEYPAD_MAX_DIGITS) || (maxDigits > KEYPAD_MAX_DIGITS)) {
4712 "nbgl_useCaseKeypad(): digits out of range (%d/%d > %d)\n",
4713 minDigits,
4714 maxDigits,
4716 return;
4717 }
4718
4719 reset_callbacks_and_context();
4720 // reset the keypad context
4721 memset(&keypadContext, 0, sizeof(KeypadContext_t));
4722
4723 // memorize context
4724 onQuit = backCallback;
4725
4726 // get a layout
4727 layoutDescription.onActionCallback = keypadGeneric_cb;
4728 keypadContext.layoutCtx = nbgl_layoutGet(&layoutDescription);
4729 keypadContext.hidden = hidden;
4730
4731 // set back key in header
4732 nbgl_layoutAddHeader(keypadContext.layoutCtx, &headerDesc);
4733
4734 // add keypad
4735 status = nbgl_layoutAddKeypad(keypadContext.layoutCtx, keypadCallback, shuffled);
4736 if (status < 0) {
4737 LOG_WARN(
4738 USE_CASE_LOGGER, "nbgl_useCaseKeypad(): layout setup failed (status=%d)\n", status);
4739 return;
4740 }
4741 // add keypad content
4743 keypadContext.layoutCtx, title, keypadContext.hidden, maxDigits, "");
4744 if (status < 0) {
4745 LOG_WARN(
4746 USE_CASE_LOGGER, "nbgl_useCaseKeypad(): layout setup failed (status=%d)\n", status);
4747 return;
4748 }
4749
4750 // validation pin callback
4751 keypadContext.onValidatePin = validatePinCallback;
4752 // pin code acceptable lengths
4753 keypadContext.pinMinDigits = minDigits;
4754 keypadContext.pinMaxDigits = maxDigits;
4755
4756 nbgl_layoutDraw(keypadContext.layoutCtx);
4758}
4759#endif // NBGL_KEYPAD
4760
4761#ifdef NBGL_KEYBOARD
4789void nbgl_useCaseKeyboard(const nbgl_keyboardParams_t *params, nbgl_callback_t backCallback)
4790{
4791 // clang-format off
4792 nbgl_layoutDescription_t layoutDescription = {.onActionCallback = keyboardGeneric_cb};
4794 .backAndText.token = BACK_TOKEN,
4795 .backAndText.tuneId = TUNE_TAP_CASUAL};
4796 nbgl_layoutKbd_t kbdInfo = {.callback = &keyboardCallback,
4797 .lettersOnly = params->lettersOnly,
4798 .mode = params->mode,
4799 .casing = params->casing};
4800 int status = -1;
4801 // clang-format on
4802
4803 reset_callbacks_and_context();
4804 // reset the keyboard context
4805 memset(&keyboardContext, 0, sizeof(KeyboardContext_t));
4806
4807 // memorize context
4808 onQuit = backCallback;
4809
4810 // get a layout
4811 keyboardContext.layoutCtx = nbgl_layoutGet(&layoutDescription);
4812
4813 // set back key in header
4814 nbgl_layoutAddHeader(keyboardContext.layoutCtx, &headerDesc);
4815
4816 // Add keyboard
4817 status = nbgl_layoutAddKeyboard(keyboardContext.layoutCtx, &kbdInfo);
4818 if (status < 0) {
4819 LOG_WARN(
4820 USE_CASE_LOGGER, "nbgl_useCaseKeyboard(): layout setup failed (status=%d)\n", status);
4821 return;
4822 }
4823 keyboardContext.keyboardIndex = status;
4824 keyboardContext.casing = params->casing;
4825 keyboardContext.entryBuffer = PIC(params->entryBuffer);
4826 keyboardContext.entryMaxLen = params->entryMaxLen;
4827 keyboardContext.entryBuffer[0] = '\0';
4828 // add keyboard content
4829 keyboardContext.keyboardContent = (nbgl_layoutKeyboardContent_t){
4830 .type = params->type,
4831 .title = PIC(params->title),
4832 .numbered = params->numbered,
4833 .number = params->number,
4834 .obfuscated = params->obfuscated,
4835 .text = keyboardContext.entryBuffer,
4836 .textToken = KEYBOARD_CROSS_TOKEN,
4837 .tuneId = TUNE_TAP_CASUAL,
4838 };
4839 switch (params->type) {
4841 onAction = PIC(params->confirmationParams.onButtonCallback);
4842 keyboardContext.keyboardContent.confirmationButton = (nbgl_layoutConfirmationButton_t){
4843 .text = PIC(params->confirmationParams.buttonText),
4844 .token = KEYBOARD_BUTTON_TOKEN,
4845 .active = true,
4846 };
4847 break;
4849 onControls = PIC(params->suggestionParams.onButtonCallback);
4850 keyboardContext.getSuggestionButtons
4852 keyboardContext.keyboardContent.suggestionButtons = (nbgl_layoutSuggestionButtons_t){
4853 .buttons = PIC(params->suggestionParams.buttons),
4854 .firstButtonToken = params->suggestionParams.firstButtonToken,
4855 };
4856 break;
4857 default:
4858 return;
4859 }
4860 status = nbgl_layoutAddKeyboardContent(keyboardContext.layoutCtx,
4861 &keyboardContext.keyboardContent);
4862 if (status < 0) {
4863 LOG_WARN(
4864 USE_CASE_LOGGER, "nbgl_useCaseKeyboard(): layout setup failed (status=%d)\n", status);
4865 return;
4866 }
4867
4868 nbgl_layoutDraw(keyboardContext.layoutCtx);
4870}
4871#endif // NBGL_KEYBOARD
4872
4873#endif // HAVE_SE_TOUCH
4874#endif // NBGL_USE_CASE
nbgl_contentTagValue_t *(* nbgl_contentTagValueCallback_t)(uint8_t pairIndex)
prototype of tag/value pair retrieval callback
@ ICON_ILLUSTRATION
simple icon
@ LARGE_CASE_GRAY_INFO
@ LARGE_CASE_INFO
text in BLACK and large case (INTER 32px), subText in black in Inter24px
@ INFO_LONG_PRESS
a centered info and a long press button
@ EXTENDED_CENTER
a centered content and a possible tip-box
@ CHOICES_LIST
list of choices through radio buttons
@ CENTERED_INFO
a centered info
@ SWITCHES_LIST
list of switches with descriptions
@ TAG_VALUE_DETAILS
a tag/value pair and a small button to get details.
@ INFOS_LIST
list of infos with titles
@ TAG_VALUE_CONFIRM
tag/value pairs and a black button/footer to confirm/cancel.
@ TAG_VALUE_LIST
list of tag/value pairs
@ BARS_LIST
list of touchable bars (with > on the right to go to sub-pages)
@ INFO_BUTTON
a centered info and a simple black button
@ INFO_LIST_ALIAS
alias is list of infos
@ TAG_VALUE_LIST_ALIAS
@ ENS_ALIAS
alias comes from ENS
@ ADDRESS_BOOK_ALIAS
alias comes from Address Book
@ QR_CODE_ALIAS
alias is an address to be displayed as a QR Code
void(* nbgl_contentActionCallback_t)(int token, uint8_t index, int page)
prototype of function to be called when an action on a content object occurs
debug traces management
#define LOG_WARN(__logger,...)
Definition nbgl_debug.h:87
#define LOG_DEBUG(__logger,...)
Definition nbgl_debug.h:86
@ USE_CASE_LOGGER
Definition nbgl_debug.h:35
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)
void(* nbgl_layoutTouchCallback_t)(int token, uint8_t index)
prototype of function to be called when an object is touched
int nbgl_layoutAddContentCenter(nbgl_layout_t *layout, const nbgl_contentCenter_t *info)
Creates an area on the center of the main panel, with a possible icon, and possible texts under it.
int nbgl_layoutUpdateKeyboard(nbgl_layout_t *layout, uint8_t index, uint32_t keyMask, bool updateCasing, keyboardCase_t casing)
Updates an existing keyboard on bottom of the screen, with the given configuration.
int nbgl_layoutAddKeyboard(nbgl_layout_t *layout, const nbgl_layoutKbd_t *kbdInfo)
Creates a keyboard on bottom of the screen, with the given configuration.
int nbgl_layoutUpdateKeypad(nbgl_layout_t *layout, uint8_t index, bool enableValidate, bool enableBackspace, bool enableDigits)
Updates an existing keypad on bottom of the screen, with the given configuration.
int nbgl_layoutAddKeyboardContent(nbgl_layout_t *layout, nbgl_layoutKeyboardContent_t *content)
Adds an area containing a potential title, a text entry and either confirmation or suggestion buttons...
int nbgl_layoutAddSeparationLine(nbgl_layout_t *layout)
adds a separation line on bottom of the last added item
int nbgl_layoutAddQRCode(nbgl_layout_t *layout, const nbgl_layoutQRCode_t *info)
Creates an area on the center of the main panel, with a QRCode, a possible text in black (bold) under...
int nbgl_layoutDraw(nbgl_layout_t *layout)
Applies given layout. The screen will be redrawn.
int nbgl_layoutAddTouchableBar(nbgl_layout_t *layout, const nbgl_layoutBar_t *barLayout)
Creates a touchable bar in main panel.
@ WHITE_BACKGROUND
rounded bordered button, with text/icon in black, on white background
@ BLACK_BACKGROUND
rounded bordered button, with text/icon in white, on black background
int nbgl_layoutAddTopRightButton(nbgl_layout_t *layout, const nbgl_icon_details_t *icon, uint8_t token, tune_index_e tuneId)
Creates a Top-right button in the top right corner of the top panel.
#define AVAILABLE_WIDTH
int nbgl_layoutAddTextContent(nbgl_layout_t *layout, nbgl_layoutTextContent_t *content)
Creates in the main container three text areas:
void * nbgl_layout_t
type shared externally
@ HEADER_EMPTY
empty space, to have a better vertical centering of centered info
@ HEADER_BACK_AND_TEXT
back key and optional text
int nbgl_layoutUpdateKeyboardContent(nbgl_layout_t *layout, nbgl_layoutKeyboardContent_t *content)
Updates an area containing a potential title, a text entry and either confirmation or suggestion butt...
int nbgl_layoutAddKeypadContent(nbgl_layout_t *layout, const char *title, bool hidden, uint8_t nbDigits, const char *text)
Adds an area with a title and a placeholder for hidden digits on top of a keypad, to represent the en...
#define NBGL_INVALID_TOKEN
Definition nbgl_layout.h:30
int nbgl_layoutUpdateKeypadContent(nbgl_layout_t *layout, bool hidden, uint8_t nbActiveDigits, const char *text)
Updates an existing set of hidden digits, with the given configuration.
nbgl_layout_t * nbgl_layoutGet(const nbgl_layoutDescription_t *description)
returns a layout of the given type. The layout is reset
int nbgl_layoutAddExtendedFooter(nbgl_layout_t *layout, const nbgl_layoutFooter_t *footerDesc)
Creates a touchable area at the footer of the screen, containing various controls,...
@ ROUNDED_AND_FOOTER_STYLE
A black background button on top of a footer.
int nbgl_layoutAddChoiceButtons(nbgl_layout_t *layout, const nbgl_layoutChoiceButtons_t *info)
Creates two buttons to make a choice. Both buttons are mandatory. Both buttons are full width,...
int nbgl_layoutAddSpinner(nbgl_layout_t *layout, const char *text, const char *subText, uint8_t initPosition)
Creates a centered (vertically & horizontally) spinner with a text under it.
int nbgl_layoutUpdateSpinner(nbgl_layout_t *layout, const char *text, const char *subText, uint8_t position)
Update an existing spinner (must be the only object of the layout)
#define NBGL_NO_PROGRESS_INDICATOR
To be used when a control token shall not be used.
Definition nbgl_layout.h:27
int nbgl_layoutAddHeader(nbgl_layout_t *layout, const nbgl_layoutHeader_t *headerDesc)
Creates a touchable (or not) area at the header of the screen, containing various controls,...
@ KEYBOARD_WITH_BUTTON
text entry area + confirmation button
@ KEYBOARD_WITH_SUGGESTIONS
text entry area + suggestion buttons
#define TAG_VALUE_INTERVALE
@ FOOTER_EMPTY
empty space, to have a better vertical centering of centered info
int nbgl_layoutRelease(nbgl_layout_t *layout)
Release the layout obtained with nbgl_layoutGet()
#define EXIT_PAGE
Definition nbgl_layout.h:35
int nbgl_layoutAddFooter(nbgl_layout_t *layout, const char *text, uint8_t token, tune_index_e tuneId)
Creates a touchable text at the footer of the screen, separated with a thin line from the rest of the...
int nbgl_layoutAddKeypad(nbgl_layout_t *layout, keyboardCallback_t callback, bool shuffled)
Adds a keypad on bottom of the screen, with the associated callback.
#define NB_SPINNER_POSITIONS
Definition nbgl_obj.h:155
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)
draw a generic content page, with the given content, and if nav parameter is not NULL,...
Definition nbgl_page.c:595
nbgl_page_t * nbgl_pageDrawInfo(nbgl_layoutTouchCallback_t onActionCallback, const nbgl_screenTickerConfiguration_t *ticker, const nbgl_pageInfoDescription_t *info)
draw a page with a centered info (icon and/or texts) with a touchable footer, in a potential "tapable...
Definition nbgl_page.c:315
void * nbgl_page_t
type shared externally
Definition nbgl_page.h:81
@ NAV_WITH_BUTTONS
move forward and backward with buttons in bottom nav bar
Definition nbgl_page.h:89
nbgl_page_t * nbgl_pageDrawConfirmation(nbgl_layoutTouchCallback_t onActionCallback, const nbgl_pageConfirmationDescription_t *info)
draw a confirmation page, with a centered info (icon and/or text), a button to confirm and a footer t...
Definition nbgl_page.c:435
int nbgl_pageRelease(nbgl_page_t *)
Release the page obtained with any of the nbgl_pageDrawXXX() functions.
Definition nbgl_page.c:608
nbgl_page_t * nbgl_pageDrawGenericContentExt(nbgl_layoutTouchCallback_t onActionCallback, const nbgl_pageNavigationInfo_t *nav, nbgl_pageContent_t *content, bool modal)
draw a generic content page, with the given content, and if nav parameter is not NULL,...
Definition nbgl_page.c:473
@ 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.
DEPRECATED void nbgl_useCaseHome(const char *appName, const nbgl_icon_details_t *appIcon, const char *tagline, bool withSettings, nbgl_callback_t topRightCallback, nbgl_callback_t quitCallback)
#define NB_MAX_LINES_IN_REVIEW
maximum number of lines for value field in review pages
@ NO_TYPE_WARNING
Invalid type (to use for bars leading to nothing)
@ CENTERED_INFO_WARNING
Centered info.
@ QRCODE_WARNING
QR Code.
@ BAR_LIST_WARNING
list of touchable bars, to display sub-pages
DEPRECATED void nbgl_useCaseSettings(const char *settingsTitle, uint8_t initPage, uint8_t nbPages, bool touchableTitle, nbgl_callback_t quitCallback, nbgl_navCallback_t navCallback, nbgl_layoutTouchCallback_t controlsCallback)
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)
void(* nbgl_pinValidCallback_t)(const uint8_t *pin, uint8_t pinLen)
prototype of pin validation callback function
void nbgl_useCaseReviewStreamingStart(nbgl_operationType_t operationType, const nbgl_icon_details_t *icon, const char *reviewTitle, const char *reviewSubTitle, nbgl_choiceCallback_t choiceCallback)
void nbgl_useCaseHomeAndSettings(const char *appName, const nbgl_icon_details_t *appIcon, const char *tagline, const uint8_t initSettingPage, const nbgl_genericContents_t *settingContents, const nbgl_contentInfoList_t *infosList, const nbgl_homeAction_t *action, nbgl_callback_t quitCallback)
void nbgl_useCaseStaticReviewLight(const nbgl_contentTagValueList_t *tagValueList, const nbgl_pageInfoLongPress_t *infoLongPress, const char *rejectText, nbgl_choiceCallback_t callback)
#define RISKY_OPERATION
This is to use in nbgl_operationType_t when the operation is concerned by an internal warning....
void(* nbgl_choiceCallback_t)(bool confirm)
prototype of choice callback function
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
void nbgl_useCaseReviewStart(const nbgl_icon_details_t *icon, const char *reviewTitle, const char *reviewSubTitle, const char *rejectText, nbgl_callback_t continueCallback, nbgl_callback_t rejectCallback)
#define APP_DESCRIPTION_MAX_LEN
Length of buffer used for the default Home tagline.
void nbgl_useCaseStatus(const char *message, bool isSuccess, nbgl_callback_t quitCallback)
@ W3C_THREAT_DETECTED_WARN
Web3 Checks: Threat detected (see reportRisk field)
@ W3C_ISSUE_WARN
Web3 Checks issue (not available)
@ BLIND_SIGNING_WARN
Blind signing.
@ GATED_SIGNING_WARN
Gated signing.
@ W3C_NO_THREAT_WARN
Web3 Checks: No Threat detected.
@ W3C_RISK_DETECTED_WARN
Web3 Checks: Risk detected (see reportRisk field)
@ NB_WARNING_TYPES
DEPRECATED void nbgl_useCaseAddressConfirmationExt(const char *address, nbgl_choiceCallback_t callback, const nbgl_contentTagValueList_t *tagValueList)
void nbgl_useCaseGenericConfiguration(const char *title, uint8_t initPage, const nbgl_genericContents_t *contents, nbgl_callback_t quitCallback)
#define INIT_HOME_PAGE
Value to pass to nbgl_useCaseHomeAndSettings() initSettingPage parameter to initialize the use case o...
void nbgl_useCaseKeypad(const char *title, uint8_t minDigits, uint8_t maxDigits, bool shuffled, bool hidden, nbgl_pinValidCallback_t validatePinCallback, nbgl_callback_t backCallback)
void nbgl_useCaseReviewStreamingContinueExt(const nbgl_contentTagValueList_t *tagValueList, nbgl_choiceCallback_t choiceCallback, nbgl_callback_t skipCallback)
void nbgl_useCaseReviewBlindSigning(nbgl_operationType_t operationType, const nbgl_contentTagValueList_t *tagValueList, const nbgl_icon_details_t *icon, const char *reviewTitle, const char *reviewSubTitle, const char *finishTitle, const nbgl_tipBox_t *tipBox, nbgl_choiceCallback_t choiceCallback)
void nbgl_useCaseChoice(const nbgl_icon_details_t *icon, const char *message, const char *subMessage, const char *confirmText, const char *rejectString, nbgl_choiceCallback_t callback)
#define FIRST_USER_TOKEN
when using controls in page content (nbgl_pageContent_t), this is the first token value usable for th...
#define TAG_VALUE_AREA_HEIGHT
height available for tag/value pairs display
#define BLIND_OPERATION
This is to use in nbgl_operationType_t when the operation is "blind" This is used to indicate a warni...
#define TAGLINE_PART1
Default strings used in the Home tagline.
void nbgl_useCaseGenericReview(const nbgl_genericContents_t *contents, const char *rejectText, nbgl_callback_t rejectCallback)
#define STATUS_SCREEN_DURATION
void nbgl_useCaseKeyboard(const nbgl_keyboardParams_t *params, nbgl_callback_t backCallback)
#define TAGLINE_PART2
void nbgl_useCaseStaticReview(const nbgl_contentTagValueList_t *tagValueList, const nbgl_pageInfoLongPress_t *infoLongPress, const char *rejectText, nbgl_choiceCallback_t callback)
void nbgl_useCaseReviewStreamingBlindSigningStart(nbgl_operationType_t operationType, const nbgl_icon_details_t *icon, const char *reviewTitle, const char *reviewSubTitle, nbgl_choiceCallback_t choiceCallback)
void nbgl_useCaseReviewStreamingContinue(const nbgl_contentTagValueList_t *tagValueList, nbgl_choiceCallback_t choiceCallback)
void nbgl_useCaseReviewLight(nbgl_operationType_t operationType, const nbgl_contentTagValueList_t *tagValueList, const nbgl_icon_details_t *icon, const char *reviewTitle, const char *reviewSubTitle, const char *finishTitle, nbgl_choiceCallback_t choiceCallback)
uint8_t nbgl_useCaseGetNbChoicesInPage(uint8_t nbChoices, const nbgl_contentRadioChoice_t *choicesList, uint8_t startIndex, bool withNav)
void(* nbgl_keyboardButtonsCallback_t)(nbgl_layoutKeyboardContent_t *content, uint32_t *mask)
prototype of keyboard buttons callback function
#define REAL_TYPE_MASK
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)
This structure contains data to build a BARS_LIST content.
const uint8_t * tokens
array of tokens, one for each bar (nbBars items)
const char *const * barTexts
array of texts for each bar (nbBars items, in black/bold)
tune_index_e tuneId
if not NBGL_NO_TUNE, a tune will be played when a bar is touched
uint8_t nbBars
number of elements in barTexts and tokens array
This structure contains info to build a centered (vertically and horizontally) area,...
uint16_t iconHug
vertical margin to apply on top and bottom of the icon
const nbgl_icon_details_t * icon
the icon (can be null)
const char * title
title in black large (can be null)
const char * description
description in black small regular case (can be null)
const char * subText
sub-text in dark gray regular small case
bool padding
if true, apply a padding of 40px at the bottom
const char * smallTitle
sub-title in black small bold case (can be null)
nbgl_contentIllustrationType_t illustrType
const char * text2
second text (can be null)
const char * text1
first text (can be null)
nbgl_contentCenteredInfoStyle_t style
style to apply to this info
int16_t offsetY
vertical shift to apply to this info (if >0, shift to bottom)
const char * text3
third text (can be null)
const nbgl_icon_details_t * icon
a buffer containing the 1BPP icon
nbgl_contentCenter_t contentCenter
centered content (icon + text(s))
This structure contains data to build a centered info + simple black button content.
const char * buttonText
text of the long press button
const nbgl_icon_details_t * icon
a buffer containing the 1BPP icon
const char * text
centered text in large case
tune_index_e tuneId
if not NBGL_NO_TUNE, a tune will be played when button is touched
This structure contains data to build a INFOS_LIST content.
const char *const * infoContents
array of contents of infos (in black)
const char *const * infoTypes
array of types of infos (in black/bold)
const nbgl_contentValueExt_t * infoExtensions
uint8_t nbInfos
number of elements in infoTypes and infoContents array
This structure contains data to build a centered info + long press button content.
const char * longPressText
text of the long press button
const nbgl_icon_details_t * icon
a buffer containing the 1BPP icon
const char * text
centered text in large case
This structure contains a list of names to build a list of radio buttons (on the right part of screen...
uint8_t initChoice
index of the current choice
const char *const * names
array of strings giving the choices (nbChoices)
uint8_t nbChoices
number of choices
This structure contains info to build a switch (on the right) with a description (on the left),...
const char * text
main text for the switch
const char * subText
description under main text (NULL terminated, single line, may be null)
This structure contains [item,value] pair(s) and info about a potential "details" button,...
tune_index_e tuneId
if not NBGL_NO_TUNE, a tune will be played when details button is touched
const char * confirmationText
text of the confirmation button, if NULL "It matches" is used
uint8_t confirmationToken
the token used as argument of the onActionCallback
nbgl_contentTagValueList_t tagValueList
list of tag/value pairs
const char * detailsButtonText
this text is used for "details" button (if NULL, no button)
const nbgl_icon_details_t * detailsButtonIcon
icon to use in details button
const nbgl_icon_details_t * detailsButtonIcon
icon to use in details button
const char * detailsButtonText
this text is used for "details" button
nbgl_contentTagValueList_t tagValueList
list of tag/value pairs
This structure contains a list of [tag,value] pairs.
const nbgl_contentTagValue_t * pairs
array of [tag,value] pairs (nbPairs items). If NULL, callback is used instead
nbgl_contentTagValueCallback_t callback
function to call to retrieve a given pair
uint8_t nbMaxLinesForValue
if > 0, set the max number of lines for value field.
bool hideEndOfLastLine
if set to true, replace 3 last chars of last line by "..."
bool wrapping
if set to true, value text will be wrapped on ' ' to avoid cutting words
uint8_t startIndex
index of the first pair to get with callback
nbgl_contentActionCallback_t actionCallback
called when a valueIcon is touched on a given pair
This structure contains a [tag,value] pair and possible extensions.
const nbgl_contentValueExt_t * extension
if not NULL, gives additional info on value field
const nbgl_icon_details_t * valueIcon
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_layoutQRCode_t qrCode
QR code, if type == QRCODE_WARNING.
nbgl_genericDetailsType_t type
type of content in the page, determining what to use in the following union
nbgl_contentCenter_t centeredInfo
centered info, if type == CENTERED_INFO_WARNING
Structure describing the action button in Home Screen.
const nbgl_icon_details_t * icon
icon to use in action button in Home page
nbgl_callback_t callback
function to call when action button is touched in Home page
const char * text
text to use in action button in Home page
nbgl_homeActionStyle_t style
style of action button
const char * buttonText
button title
nbgl_callback_t onButtonCallback
callback to call when the button is pressed
nbgl_keyboardButtonsCallback_t updateButtonsCallback
callback to call when a key is pressed to update suggestions
nbgl_layoutTouchCallback_t onButtonCallback
callback to call when one of the buttons is pressed
const char ** buttons
array of strings for buttons (last ones can be NULL)
int firstButtonToken
first token used for buttons, provided in onButtonCallback
Structure containing all parameters for keyboard use case.
keyboardMode_t mode
keyboard mode to start with
nbgl_kbdSuggestParams_t suggestionParams
nbgl_kbdButtonParams_t confirmationParams
used if type is KEYBOARD_WITH_SUGGESTIONS
bool lettersOnly
if true, only display letter keys and Backspace
uint8_t entryMaxLen
maximum length of text that can be entered
nbgl_layoutKeyboardContentType_t type
type of content
bool numbered
if set to true, the text is preceded on the left by 'number.'
keyboardCase_t casing
keyboard casing mode (lower, upper once or upper locked)
const char * title
centered title explaining the screen
bool obfuscated
if true, entered text and suggestions use the obfuscated font
char * entryBuffer
already entered text
uint8_t number
if numbered is true, number used to build 'number.' text
This structure contains info to build a clickable "bar" with a text and an icon.
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 pair of buttons, one on top of the other.
const char * bottomText
bottom-button text (index 1)
This structure contains info to build a confirmation button.
Structure containing all information when creating a layout. This structure must be passed as argumen...
nbgl_layoutTouchCallback_t onActionCallback
the callback to be called on any action on the layout
This structure contains info to build an extended footer.
struct nbgl_layoutFooter_t::@21::@23 emptySpace
if type is FOOTER_EMPTY
nbgl_layoutFooterType_t type
type of footer
This structure contains info to build a header.
nbgl_layoutHeaderType_t type
type of header
bool separationLine
if true, a separation line is added at the bottom of this control
const char * text
can be NULL if no text
struct nbgl_layoutHeader_t::@13::@16 backAndText
if type is HEADER_BACK_ICON_AND_TEXT or HEADER_BACK_AND_TEXT
This structure contains info to build a keyboard with nbgl_layoutAddKeyboard()
keyboardCallback_t callback
function called when an active key is pressed
This structure contains info to build a keyboard content (controls that are linked to keyboard)
This structure contains info to build a centered (vertically and horizontally) area,...
const char * text2
second text (can be null)
const char * url
URL for QR code.
This structure contains info to build suggestion buttons.
This structure contains info for Text content, to be set with nbgl_layoutAddTextContent().
const char * descriptions[NB_MAX_DESCRIPTIONS]
uint8_t nbDescriptions
number of used descriptions in above array
const char * info
description at bottom (in small gray)
const char * title
main text (in large bold font)
Structure containing all specific information when creating a confirmation page.
Definition nbgl_page.h:149
const char * cancelText
the text used for cancel action, if NULL a simple X button is used
Definition nbgl_page.h:152
uint8_t confirmationToken
the token used as argument of the onActionCallback
Definition nbgl_page.h:153
const char * confirmationText
text of the confirmation button
Definition nbgl_page.h:151
nbgl_layoutCenteredInfo_t centeredInfo
description of the centered info to be used
Definition nbgl_page.h:150
tune_index_e tuneId
if not NBGL_NO_TUNE, a tune will be played when button is pressed
Definition nbgl_page.h:157
This structure contains data to build a page in multi-pages mode (nbgl_pageDrawGenericContent)
Definition nbgl_flow.h:58
nbgl_contentTagValueDetails_t tagValueDetails
TAG_VALUE_DETAILS type
Definition nbgl_page.h:68
const char * title
text for the title of the page (if NULL, no title)
Definition nbgl_page.h:53
uint8_t topRightToken
token used when top-right button (if not NULL) is touched
Definition nbgl_page.h:58
nbgl_contentInfoLongPress_t infoLongPress
INFO_LONG_PRESS type
Definition nbgl_page.h:65
nbgl_contentRadioChoice_t choicesList
CHOICES_LIST type
Definition nbgl_flow.h:67
nbgl_contentSwitchesList_t switchesList
SWITCHES_LIST type
Definition nbgl_flow.h:65
tune_index_e tuneId
if not NBGL_NO_TUNE, a tune will be played when title is touched
Definition nbgl_page.h:57
nbgl_contentBarsList_t barsList
BARS_LIST type
Definition nbgl_flow.h:68
nbgl_contentInfoButton_t infoButton
INFO_BUTTON type
Definition nbgl_flow.h:62
nbgl_contentInfoList_t infosList
INFOS_LIST type
Definition nbgl_flow.h:66
nbgl_contentTagValueList_t tagValueList
TAG_VALUE_LIST type
Definition nbgl_flow.h:63
const nbgl_icon_details_t * topRightIcon
Definition nbgl_page.h:59
nbgl_contentType_t type
type of page content in the following union
Definition nbgl_flow.h:59
nbgl_contentCenteredInfo_t centeredInfo
CENTERED_INFO type
Definition nbgl_flow.h:61
nbgl_contentTagValueConfirm_t tagValueConfirm
TAG_VALUE_CONFIRM type
Definition nbgl_flow.h:64
bool isTouchableTitle
if set to true, the title is preceded by <- arrow to go back
Definition nbgl_page.h:54
nbgl_contentExtendedCenter_t extendedCenter
EXTENDED_CENTER type
Definition nbgl_page.h:64
Structure containing all specific information when creating an information page.
Definition nbgl_page.h:185
nbgl_layoutButtonStyle_t actionButtonStyle
style of "action" button
Definition nbgl_page.h:202
const char * actionButtonText
if not NULL an "action" button is set under the centered info
Definition nbgl_page.h:200
tune_index_e tuneId
if not NBGL_NO_TUNE, a tune will be played when button/footer is pressed
Definition nbgl_page.h:204
const nbgl_icon_details_t * actionButtonIcon
potential icon of "action" button
Definition nbgl_page.h:201
nbgl_layoutCenteredInfo_t centeredInfo
description of the centered info to be used
Definition nbgl_page.h:186
Structure containing all specific information when creating a multi-screens page.
Definition nbgl_page.h:127
uint8_t nbPages
the number of pages to display (if <2, no navigation bar)
Definition nbgl_page.h:129
uint8_t quitToken
the token used as argument of the actionCallback when the footer is touched
Definition nbgl_page.h:131
uint8_t skipToken
if skipText is NULL the token used when right part of footer is touched
Definition nbgl_page.h:139
nbgl_pageNavigationType_t navType
Definition nbgl_page.h:132
uint8_t activePage
the index of the page to display at start-up
Definition nbgl_page.h:128
bool progressIndicator
if set to true, display a progress indicator on top of the page
Definition nbgl_page.h:134
nbgl_pageNavWithButtons_t navWithButtons
structure used when navigation with buttons
Definition nbgl_page.h:142
tune_index_e tuneId
if not NBGL_NO_TUNE, a tune will be played when next or back is pressed
Definition nbgl_page.h:136
bool visiblePageIndicator
if set to true, the page indicator will be visible in navigation
Definition nbgl_page.h:116
const char * quitText
the text displayed in footer (on the left), used to quit (only on Flex)
Definition nbgl_page.h:120
bool backButton
if set to true, a back button (<-) is displayed in the nav bar
Definition nbgl_page.h:114
This structure contains data to build a SWITCHES_LIST content.
uint8_t nbSwitches
number of elements in switches and tokens array
const nbgl_contentSwitch_t * switches
array of switches (nbSwitches items)
The necessary parameters to build a tip-box in first review page and the modal if this tip box is tou...
const char * text
text of the tip-box
const nbgl_icon_details_t * icon
icon of the tip-box
The necessary parameters to build a warning page preceding a review. One can either use predefinedSet...
const nbgl_preludeDetails_t * prelude
if not null, means that the review can start by a prelude
const nbgl_warningDetails_t * introDetails
const nbgl_warningDetails_t * reviewDetails
uint32_t predefinedSet
Union of the different type of contents.
nbgl_contentInfoList_t infosList
INFOS_LIST type
nbgl_contentInfoLongPress_t infoLongPress
INFO_LONG_PRESS type
nbgl_contentTagValueConfirm_t tagValueConfirm
TAG_VALUE_CONFIRM type
nbgl_contentTagValueList_t tagValueList
TAG_VALUE_LIST type
nbgl_contentCenteredInfo_t centeredInfo
CENTERED_INFO type
nbgl_contentBarsList_t barsList
BARS_LIST type
nbgl_contentExtendedCenter_t extendedCenter
EXTENDED_CENTER type
nbgl_contentSwitchesList_t switchesList
SWITCHES_LIST type
nbgl_contentInfoButton_t infoButton
INFO_BUTTON type
nbgl_contentRadioChoice_t choicesList
CHOICES_LIST type