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 keypadContext sharedContext.keypad
57#define reviewWithWarnCtx sharedContext.reviewWithWarning
58
59/* max length of the string displaying the description of the Web3 Checks report */
60#define W3C_DESCRIPTION_MAX_LEN 128
61
67#define RISKY_OPERATION (1 << 6)
68
74#define NO_THREAT_OPERATION (1 << 7)
75
76/**********************
77 * TYPEDEFS
78 **********************/
79enum {
80 BACK_TOKEN = 0,
81 NEXT_TOKEN,
82 QUIT_TOKEN,
83 NAV_TOKEN,
84 MODAL_NAV_TOKEN,
85 SKIP_TOKEN,
86 CONTINUE_TOKEN,
87 ADDRESS_QRCODE_BUTTON_TOKEN,
88 ACTION_BUTTON_TOKEN,
89 CHOICE_TOKEN,
90 DETAILS_BUTTON_TOKEN,
91 CONFIRM_TOKEN,
92 REJECT_TOKEN,
93 VALUE_ALIAS_TOKEN,
94 INFO_ALIAS_TOKEN,
95 INFOS_TIP_BOX_TOKEN,
96 BLIND_WARNING_TOKEN,
97 WARNING_BUTTON_TOKEN,
98 TIP_BOX_TOKEN,
99 QUIT_TIPBOX_MODAL_TOKEN,
100 WARNING_CHOICE_TOKEN,
101 DISMISS_QR_TOKEN,
102 DISMISS_WARNING_TOKEN,
103 FIRST_WARN_BAR_TOKEN,
104 LAST_WARN_BAR_TOKEN = (FIRST_WARN_BAR_TOKEN + NB_WARNING_TYPES - 1),
105};
106
107typedef enum {
108 REVIEW_NAV = 0,
109 SETTINGS_NAV,
110 GENERIC_NAV,
111 STREAMING_NAV
112} NavType_t;
113
114typedef struct DetailsContext_s {
115 uint8_t nbPages;
116 uint8_t currentPage;
117 uint8_t currentPairIdx;
118 bool wrapping;
119 const char *tag;
120 const char *value;
121 const char *nextPageStart;
122} DetailsContext_t;
123
124typedef struct AddressConfirmationContext_s {
125 nbgl_layoutTagValue_t tagValuePairs[ADDR_VERIF_NB_PAIRS];
126 nbgl_layout_t *modalLayout;
127 uint8_t nbPairs;
128} AddressConfirmationContext_t;
129
130#ifdef NBGL_KEYPAD
131typedef struct KeypadContext_s {
132 uint8_t pinEntry[KEYPAD_MAX_DIGITS];
133 uint8_t pinLen;
134 uint8_t pinMinDigits;
135 uint8_t pinMaxDigits;
136 nbgl_layout_t *layoutCtx;
137 bool hidden;
138} KeypadContext_t;
139#endif
140
141typedef struct ReviewWithWarningContext_s {
142 bool isStreaming;
143 nbgl_operationType_t operationType;
144 const nbgl_contentTagValueList_t *tagValueList;
145 const nbgl_icon_details_t *icon;
146 const char *reviewTitle;
147 const char *reviewSubTitle;
148 const char *finishTitle;
149 const nbgl_warning_t *warning;
150 nbgl_choiceCallback_t choiceCallback;
151 nbgl_layout_t *layoutCtx;
152 nbgl_layout_t *modalLayout;
153 uint8_t securityReportLevel; // level 1 is the first level of menus
154 bool isIntro; // set to true during intro (before actual review)
155} ReviewWithWarningContext_t;
156
157// this union is intended to save RAM for context storage
158// indeed, these 2 contexts cannot happen simultaneously
159typedef union {
160#ifdef NBGL_KEYPAD
161 KeypadContext_t keypad;
162#endif
163 ReviewWithWarningContext_t reviewWithWarning;
164} SharedContext_t;
165
166typedef enum {
167 USE_CASE_GENERIC = 0,
168 USE_CASE_SPINNER
169} GenericContextType_t;
170
171typedef struct {
172 GenericContextType_t type; // type of Generic context usage
173 uint8_t spinnerPosition;
174 nbgl_genericContents_t genericContents;
175 int8_t currentContentIdx;
176 uint8_t currentContentElementNb;
177 uint8_t currentElementIdx;
178 bool hasStartingContent;
179 bool hasFinishingContent;
180 const char *detailsItem;
181 const char *detailsvalue;
182 bool detailsWrapping;
183 bool validWarningCtx; // set to true if the WarningContext is valid
185 *currentPairs; // to be used to retrieve the pairs with value alias
187 currentCallback; // to be used to retrieve the pairs with value alias
188 nbgl_layout_t modalLayout;
189 nbgl_layout_t backgroundLayout;
190 const nbgl_contentInfoList_t *currentInfos;
191 const nbgl_contentTagValueList_t *currentTagValues;
192} GenericContext_t;
193
194typedef struct {
195 const char *appName;
196 const nbgl_icon_details_t *appIcon;
197 const char *tagline;
198 const nbgl_genericContents_t *settingContents;
199 const nbgl_contentInfoList_t *infosList;
200 nbgl_homeAction_t homeAction;
201 nbgl_callback_t quitCallback;
202} nbgl_homeAndSettingsContext_t;
203
204typedef struct {
205 nbgl_operationType_t operationType;
206 nbgl_choiceCallback_t choiceCallback;
207} nbgl_reviewContext_t;
208
209typedef struct {
210 nbgl_operationType_t operationType;
211 nbgl_choiceCallback_t choiceCallback;
212 nbgl_callback_t skipCallback;
213 const nbgl_icon_details_t *icon;
214 uint8_t stepPageNb;
215} nbgl_reviewStreamingContext_t;
216
217typedef union {
218 nbgl_homeAndSettingsContext_t homeAndSettings;
219 nbgl_reviewContext_t review;
220 nbgl_reviewStreamingContext_t reviewStreaming;
221} nbgl_BundleNavContext_t;
222
223typedef struct {
224 const nbgl_icon_details_t *icon;
225 const char *text;
226 const char *subText;
227} SecurityReportItem_t;
228
229/**********************
230 * STATIC VARIABLES
231 **********************/
232
233// char buffers to build some strings
234static char tmpString[W3C_DESCRIPTION_MAX_LEN];
235
236// multi-purposes callbacks
237static nbgl_callback_t onQuit;
238static nbgl_callback_t onContinue;
239static nbgl_callback_t onAction;
240static nbgl_navCallback_t onNav;
241static nbgl_layoutTouchCallback_t onControls;
242static nbgl_contentActionCallback_t onContentAction;
243static nbgl_choiceCallback_t onChoice;
244static nbgl_callback_t onModalConfirm;
245#ifdef NBGL_KEYPAD
246static nbgl_pinValidCallback_t onValidatePin;
247#endif
248
249// contexts for background and modal pages
250static nbgl_page_t *pageContext;
251static nbgl_page_t *modalPageContext;
252
253// context for pages
254static const char *pageTitle;
255
256// context for tip-box
257static nbgl_tipBox_t activeTipBox;
258
259// context for navigation use case
260static nbgl_pageNavigationInfo_t navInfo;
261static bool forwardNavOnly;
262static NavType_t navType;
263
264static DetailsContext_t detailsContext;
265
266// multi-purpose context shared for non-concurrent usages
267static SharedContext_t sharedContext;
268
269// context for address review
270static AddressConfirmationContext_t addressConfirmationContext;
271
272// contexts for generic navigation
273static GenericContext_t genericContext;
274static nbgl_content_t
275 localContentsList[3]; // 3 needed for nbgl_useCaseReview (starting page / tags / final page)
276static uint8_t genericContextPagesInfo[MAX_PAGE_NB / PAGES_PER_UINT8];
277static uint8_t modalContextPagesInfo[MAX_MODAL_PAGE_NB / PAGES_PER_UINT8];
278
279// contexts for bundle navigation
280static nbgl_BundleNavContext_t bundleNavContext;
281
282// indexed by nbgl_contentType_t
283static const uint8_t nbMaxElementsPerContentType[] = {
284 1, // CENTERED_INFO
285 1, // EXTENDED_CENTER
286 1, // INFO_LONG_PRESS
287 1, // INFO_BUTTON
288 1, // TAG_VALUE_LIST (computed dynamically)
289 1, // TAG_VALUE_DETAILS
290 1, // TAG_VALUE_CONFIRM
291 3, // SWITCHES_LIST (computed dynamically)
292 3, // INFOS_LIST (computed dynamically)
293 5, // CHOICES_LIST (computed dynamically)
294 5, // BARS_LIST (computed dynamically)
295};
296
297// clang-format off
298static const SecurityReportItem_t securityReportItems[NB_WARNING_TYPES] = {
300 .icon = &WARNING_ICON,
301 .text = "Blind signing required",
302 .subText = "This transaction's details are not fully verifiable. If "
303 "you sign, you could lose all your assets."
304 },
305 [W3C_ISSUE_WARN] = {
306 .icon = &WARNING_ICON,
307 .text = "Transaction Check unavailable",
308 .subText = NULL
309 },
311 .icon = &WARNING_ICON,
312 .text = "Risk detected",
313 .subText = "This transaction was scanned as risky by Web3 Checks."
314 },
316 .icon = &WARNING_ICON,
317 .text = "Critical threat",
318 .subText = "This transaction was scanned as malicious by Web3 Checks."
319 },
321 .icon = NULL,
322 .text = "No threat detected",
323 .subText = "Transaction Check didn't find any threat, but always "
324 "review transaction details carefully."
325 }
326};
327// clang-format on
328
329// configuration of warning when using @ref nbgl_useCaseReviewBlindSigning()
330static const nbgl_warning_t blindSigningWarning = {.predefinedSet = (1 << BLIND_SIGNING_WARN)};
331
332#ifdef NBGL_QRCODE
333/* buffer to store reduced address under QR Code */
334static char reducedAddress[QRCODE_REDUCED_ADDR_LEN];
335#endif // NBGL_QRCODE
336
337/**********************
338 * STATIC FUNCTIONS
339 **********************/
340static void displayReviewPage(uint8_t page, bool forceFullRefresh);
341static void displayDetailsPage(uint8_t page, bool forceFullRefresh);
342static void displayTagValueListModalPage(uint8_t pageIdx, bool forceFullRefresh);
343static void displayFullValuePage(const char *backText,
344 const char *aliasText,
345 const nbgl_contentValueExt_t *extension);
346static void displayInfosListModal(const char *modalTitle,
347 const nbgl_contentInfoList_t *infos,
348 bool fromReview);
349static void displayTagValueListModal(const nbgl_contentTagValueList_t *tagValues);
350static void displaySettingsPage(uint8_t page, bool forceFullRefresh);
351static void displayGenericContextPage(uint8_t pageIdx, bool forceFullRefresh);
352static void pageCallback(int token, uint8_t index);
353#ifdef NBGL_QRCODE
354static void displayAddressQRCode(void);
355#endif // NBGL_QRCODE
356static void modalLayoutTouchCallback(int token, uint8_t index);
357static void displaySkipWarning(void);
358
359static void bundleNavStartHome(void);
360static void bundleNavStartSettingsAtPage(uint8_t initSettingPage);
361static void bundleNavStartSettings(void);
362
363static void bundleNavReviewStreamingChoice(bool confirm);
364static void displaySecurityReport(uint32_t set);
365static void displayCustomizedSecurityReport(const nbgl_warningDetails_t *details);
366static void displayInitialWarning(void);
367static void useCaseReview(nbgl_operationType_t operationType,
368 const nbgl_contentTagValueList_t *tagValueList,
369 const nbgl_icon_details_t *icon,
370 const char *reviewTitle,
371 const char *reviewSubTitle,
372 const char *finishTitle,
373 const nbgl_tipBox_t *tipBox,
374 nbgl_choiceCallback_t choiceCallback,
375 bool isLight,
376 bool playNotifSound);
377static void useCaseReviewStreamingStart(nbgl_operationType_t operationType,
378 const nbgl_icon_details_t *icon,
379 const char *reviewTitle,
380 const char *reviewSubTitle,
381 nbgl_choiceCallback_t choiceCallback,
382 bool playNotifSound);
383static void useCaseHomeExt(const char *appName,
384 const nbgl_icon_details_t *appIcon,
385 const char *tagline,
386 bool withSettings,
387 nbgl_homeAction_t *homeAction,
388 nbgl_callback_t topRightCallback,
389 nbgl_callback_t quitCallback);
390static void displayDetails(const char *tag, const char *value, bool wrapping);
391
392static void reset_callbacks(void)
393{
394 onQuit = NULL;
395 onContinue = NULL;
396 onAction = NULL;
397 onNav = NULL;
398 onControls = NULL;
399 onContentAction = NULL;
400 onChoice = NULL;
401 onModalConfirm = NULL;
402#ifdef NBGL_KEYPAD
403 onValidatePin = NULL;
404#endif
405}
406
407// Helper to set genericContext page info
408static void genericContextSetPageInfo(uint8_t pageIdx, uint8_t nbElements, bool flag)
409{
410 uint8_t pageData = SET_PAGE_NB_ELEMENTS(nbElements) + SET_PAGE_FLAG(flag);
411
412 genericContextPagesInfo[pageIdx / PAGES_PER_UINT8]
413 &= ~(0x0F << ((pageIdx % PAGES_PER_UINT8) * PAGE_DATA_BITS));
414 genericContextPagesInfo[pageIdx / PAGES_PER_UINT8]
415 |= pageData << ((pageIdx % PAGES_PER_UINT8) * PAGE_DATA_BITS);
416}
417
418// Helper to get genericContext page info
419static void genericContextGetPageInfo(uint8_t pageIdx, uint8_t *nbElements, bool *flag)
420{
421 uint8_t pageData = genericContextPagesInfo[pageIdx / PAGES_PER_UINT8]
422 >> ((pageIdx % PAGES_PER_UINT8) * PAGE_DATA_BITS);
423 if (nbElements != NULL) {
424 *nbElements = GET_PAGE_NB_ELEMENTS(pageData);
425 }
426 if (flag != NULL) {
427 *flag = GET_PAGE_FLAG(pageData);
428 }
429}
430
431// Helper to set modalContext page info
432static void modalContextSetPageInfo(uint8_t pageIdx, uint8_t nbElements)
433{
434 uint8_t pageData = SET_PAGE_NB_ELEMENTS(nbElements);
435
436 modalContextPagesInfo[pageIdx / PAGES_PER_UINT8]
437 &= ~(0x0F << ((pageIdx % PAGES_PER_UINT8) * PAGE_DATA_BITS));
438 modalContextPagesInfo[pageIdx / PAGES_PER_UINT8]
439 |= pageData << ((pageIdx % PAGES_PER_UINT8) * PAGE_DATA_BITS);
440}
441
442// Helper to get modalContext page info
443static void modalContextGetPageInfo(uint8_t pageIdx, uint8_t *nbElements)
444{
445 uint8_t pageData = modalContextPagesInfo[pageIdx / PAGES_PER_UINT8]
446 >> ((pageIdx % PAGES_PER_UINT8) * PAGE_DATA_BITS);
447 if (nbElements != NULL) {
448 *nbElements = GET_PAGE_NB_ELEMENTS(pageData);
449 }
450}
451
452// Simple helper to get the number of elements inside a nbgl_content_t
453static uint8_t getContentNbElement(const nbgl_content_t *content)
454{
455 switch (content->type) {
456 case TAG_VALUE_LIST:
457 return content->content.tagValueList.nbPairs;
460 case SWITCHES_LIST:
461 return content->content.switchesList.nbSwitches;
462 case INFOS_LIST:
463 return content->content.infosList.nbInfos;
464 case CHOICES_LIST:
465 return content->content.choicesList.nbChoices;
466 case BARS_LIST:
467 return content->content.barsList.nbBars;
468 default:
469 return 1;
470 }
471}
472
473// Helper to retrieve the content inside a nbgl_genericContents_t using
474// either the contentsList or using the contentGetterCallback
475static const nbgl_content_t *getContentAtIdx(const nbgl_genericContents_t *genericContents,
476 int8_t contentIdx,
477 nbgl_content_t *content)
478{
479 if (contentIdx < 0 || contentIdx >= genericContents->nbContents) {
480 LOG_DEBUG(USE_CASE_LOGGER, "No content available at %d\n", contentIdx);
481 return NULL;
482 }
483
484 if (genericContents->callbackCallNeeded) {
485 // Retrieve content through callback, but first memset the content.
486 memset(content, 0, sizeof(nbgl_content_t));
487 genericContents->contentGetterCallback(contentIdx, content);
488 return content;
489 }
490 else {
491 // Retrieve content through list
492 return PIC(&genericContents->contentsList[contentIdx]);
493 }
494}
495
496static void prepareNavInfo(bool isReview, uint8_t nbPages, const char *rejectText)
497{
498 memset(&navInfo, 0, sizeof(navInfo));
499
500 navInfo.nbPages = nbPages;
501 navInfo.tuneId = TUNE_TAP_CASUAL;
502 navInfo.progressIndicator = false;
503 navInfo.navType = NAV_WITH_BUTTONS;
504
505 if (isReview == false) {
506 navInfo.navWithButtons.navToken = NAV_TOKEN;
507 navInfo.navWithButtons.backButton = true;
508 }
509 else {
510 navInfo.quitToken = REJECT_TOKEN;
511 navInfo.navWithButtons.quitText = rejectText;
512 navInfo.navWithButtons.navToken = NAV_TOKEN;
514 = ((navType == STREAMING_NAV) && (nbPages < 2)) ? false : true;
515 navInfo.navWithButtons.visiblePageIndicator = (navType != STREAMING_NAV);
516 }
517}
518
519static void prepareReviewFirstPage(nbgl_contentCenter_t *contentCenter,
520 const nbgl_icon_details_t *icon,
521 const char *reviewTitle,
522 const char *reviewSubTitle)
523{
524 contentCenter->icon = icon;
525 contentCenter->title = reviewTitle;
526 contentCenter->description = reviewSubTitle;
527 contentCenter->subText = "Swipe to review";
528 contentCenter->smallTitle = NULL;
529 contentCenter->iconHug = 0;
530 contentCenter->padding = false;
531 contentCenter->illustrType = ICON_ILLUSTRATION;
532}
533
534static const char *getFinishTitle(nbgl_operationType_t operationType, const char *finishTitle)
535{
536 if (finishTitle != NULL) {
537 return finishTitle;
538 }
539 switch (operationType & REAL_TYPE_MASK) {
540 case TYPE_TRANSACTION:
541 return "Sign transaction";
542 case TYPE_MESSAGE:
543 return "Sign message";
544 default:
545 return "Sign operation";
546 }
547}
548
549static void prepareReviewLastPage(nbgl_operationType_t operationType,
550 nbgl_contentInfoLongPress_t *infoLongPress,
551 const nbgl_icon_details_t *icon,
552 const char *finishTitle)
553{
554 infoLongPress->text = getFinishTitle(operationType, finishTitle);
555 infoLongPress->icon = icon;
556 infoLongPress->longPressText = "Hold to sign";
557 infoLongPress->longPressToken = CONFIRM_TOKEN;
558}
559
560static void prepareReviewLightLastPage(nbgl_operationType_t operationType,
561 nbgl_contentInfoButton_t *infoButton,
562 const nbgl_icon_details_t *icon,
563 const char *finishTitle)
564{
565 infoButton->text = getFinishTitle(operationType, finishTitle);
566 infoButton->icon = icon;
567 infoButton->buttonText = "Approve";
568 infoButton->buttonToken = CONFIRM_TOKEN;
569}
570
571static const char *getRejectReviewText(nbgl_operationType_t operationType)
572{
573 UNUSED(operationType);
574 return "Reject";
575}
576
577// function called when navigating (or exiting) modal details pages
578// or when skip choice is displayed
579static void pageModalCallback(int token, uint8_t index)
580{
581 LOG_DEBUG(USE_CASE_LOGGER, "pageModalCallback, token = %d, index = %d\n", token, index);
582
583 if (token == INFOS_TIP_BOX_TOKEN) {
584 // the icon next to info type alias has been touched
585 displayFullValuePage(activeTipBox.infos.infoTypes[index],
586 activeTipBox.infos.infoContents[index],
587 &activeTipBox.infos.infoExtensions[index]);
588 return;
589 }
590 else if (token == INFO_ALIAS_TOKEN) {
591 // the icon next to info type alias has been touched, but coming from review
592 displayFullValuePage(genericContext.currentInfos->infoTypes[index],
593 genericContext.currentInfos->infoContents[index],
594 &genericContext.currentInfos->infoExtensions[index]);
595 return;
596 }
597 nbgl_pageRelease(modalPageContext);
598 modalPageContext = NULL;
599 if (token == NAV_TOKEN) {
600 if (index == EXIT_PAGE) {
601 // redraw the background layer
603 nbgl_refresh();
604 }
605 else {
606 displayDetailsPage(index, false);
607 }
608 }
609 if (token == MODAL_NAV_TOKEN) {
610 if (index == EXIT_PAGE) {
611 // redraw the background layer
613 nbgl_refresh();
614 }
615 else {
616 displayTagValueListModalPage(index, false);
617 }
618 }
619 else if (token == QUIT_TOKEN) {
620 // redraw the background layer
622 nbgl_refresh();
623 }
624 else if (token == QUIT_TIPBOX_MODAL_TOKEN) {
625 // if in "warning context", go back to security report
626 if (reviewWithWarnCtx.securityReportLevel == 2) {
627 reviewWithWarnCtx.securityReportLevel = 1;
628 displaySecurityReport(reviewWithWarnCtx.warning->predefinedSet);
629 }
630 else {
631 // redraw the background layer
633 nbgl_refresh();
634 }
635 }
636 else if (token == SKIP_TOKEN) {
637 if (index == 0) {
638 // display the last forward only review page, whatever it is
639 displayGenericContextPage(LAST_PAGE_FOR_REVIEW, true);
640 }
641 else {
642 // display background, which should be the page where skip has been touched
644 nbgl_refresh();
645 }
646 }
647 else if (token == CHOICE_TOKEN) {
648 if (index == 0) {
649 if (onModalConfirm != NULL) {
650 onModalConfirm();
651 }
652 }
653 else {
654 // display background, which should be the page where skip has been touched
656 nbgl_refresh();
657 }
658 }
659}
660
661// generic callback for all pages except modal
662static void pageCallback(int token, uint8_t index)
663{
664 LOG_DEBUG(USE_CASE_LOGGER, "pageCallback, token = %d, index = %d\n", token, index);
665 if (token == QUIT_TOKEN) {
666 if (onQuit != NULL) {
667 onQuit();
668 }
669 }
670 else if (token == CONTINUE_TOKEN) {
671 if (onContinue != NULL) {
672 onContinue();
673 }
674 }
675 else if (token == CHOICE_TOKEN) {
676 if (onChoice != NULL) {
677 onChoice((index == 0) ? true : false);
678 }
679 }
680 else if (token == ACTION_BUTTON_TOKEN) {
681 if ((index == 0) && (onAction != NULL)) {
682 onAction();
683 }
684 else if ((index == 1) && (onQuit != NULL)) {
685 onQuit();
686 }
687 }
688#ifdef NBGL_QRCODE
689 else if (token == ADDRESS_QRCODE_BUTTON_TOKEN) {
690 displayAddressQRCode();
691 }
692#endif // NBGL_QRCODE
693 else if (token == CONFIRM_TOKEN) {
694 if (onChoice != NULL) {
695 onChoice(true);
696 }
697 }
698 else if (token == REJECT_TOKEN) {
699 if (onChoice != NULL) {
700 onChoice(false);
701 }
702 }
703 else if (token == DETAILS_BUTTON_TOKEN) {
704 displayDetails(genericContext.detailsItem,
705 genericContext.detailsvalue,
706 genericContext.detailsWrapping);
707 }
708 else if (token == NAV_TOKEN) {
709 if (index == EXIT_PAGE) {
710 if (onQuit != NULL) {
711 onQuit();
712 }
713 }
714 else {
715 if (navType == GENERIC_NAV || navType == STREAMING_NAV) {
716 displayGenericContextPage(index, false);
717 }
718 else if (navType == REVIEW_NAV) {
719 displayReviewPage(index, false);
720 }
721 else {
722 displaySettingsPage(index, false);
723 }
724 }
725 }
726 else if (token == NEXT_TOKEN) {
727 if (onNav != NULL) {
728 displayReviewPage(navInfo.activePage + 1, false);
729 }
730 else {
731 displayGenericContextPage(navInfo.activePage + 1, false);
732 }
733 }
734 else if (token == BACK_TOKEN) {
735 if (onNav != NULL) {
736 displayReviewPage(navInfo.activePage - 1, true);
737 }
738 else {
739 displayGenericContextPage(navInfo.activePage - 1, false);
740 }
741 }
742 else if (token == SKIP_TOKEN) {
743 // display a modal warning to confirm skip
744 displaySkipWarning();
745 }
746 else if (token == VALUE_ALIAS_TOKEN) {
747 // the icon next to value alias has been touched
748 const nbgl_contentTagValue_t *pair;
749 if (genericContext.currentPairs != NULL) {
750 pair = &genericContext.currentPairs[genericContext.currentElementIdx + index];
751 }
752 else {
753 pair = genericContext.currentCallback(genericContext.currentElementIdx + index);
754 }
755 displayFullValuePage(pair->item, pair->value, pair->extension);
756 }
757 else if (token == BLIND_WARNING_TOKEN) {
758 reviewWithWarnCtx.isIntro = false;
759 reviewWithWarnCtx.warning = NULL;
760 displaySecurityReport(1 << BLIND_SIGNING_WARN);
761 }
762 else if (token == WARNING_BUTTON_TOKEN) {
763 // top-right button, display the security modal
764 reviewWithWarnCtx.securityReportLevel = 1;
765 // if preset is used
766 if (reviewWithWarnCtx.warning->predefinedSet) {
767 displaySecurityReport(reviewWithWarnCtx.warning->predefinedSet);
768 }
769 else {
770 // use customized warning
771 if (reviewWithWarnCtx.isIntro) {
772 displayCustomizedSecurityReport(reviewWithWarnCtx.warning->introDetails);
773 }
774 else {
775 displayCustomizedSecurityReport(reviewWithWarnCtx.warning->reviewDetails);
776 }
777 }
778 }
779 else if (token == TIP_BOX_TOKEN) {
780 // if warning context is valid and if W3C directly display same as top-right
781 if (genericContext.validWarningCtx && (reviewWithWarnCtx.warning->predefinedSet != 0)) {
782 reviewWithWarnCtx.securityReportLevel = 1;
783 displaySecurityReport(reviewWithWarnCtx.warning->predefinedSet);
784 }
785 else {
786 displayInfosListModal(activeTipBox.modalTitle, &activeTipBox.infos, false);
787 }
788 }
789 else { // probably a control provided by caller
790 if (onContentAction != NULL) {
791 onContentAction(token, index, navInfo.activePage);
792 }
793 if (onControls != NULL) {
794 onControls(token, index);
795 }
796 }
797}
798
799// callback used for confirmation
800static void tickerCallback(void)
801{
802 nbgl_pageRelease(pageContext);
803 if (onQuit != NULL) {
804 onQuit();
805 }
806}
807
808// function used to display the current page in review
809static void displaySettingsPage(uint8_t page, bool forceFullRefresh)
810{
811 nbgl_pageContent_t content = {0};
812
813 if ((onNav == NULL) || (onNav(page, &content) == false)) {
814 return;
815 }
816
817 // override some fields
818 content.title = pageTitle;
819 content.isTouchableTitle = true;
820 content.titleToken = QUIT_TOKEN;
821 content.tuneId = TUNE_TAP_CASUAL;
822
823 navInfo.activePage = page;
824 pageContext = nbgl_pageDrawGenericContent(&pageCallback, &navInfo, &content);
825
826 if (forceFullRefresh) {
828 }
829 else {
831 }
832}
833
834// function used to display the current page in review
835static void displayReviewPage(uint8_t page, bool forceFullRefresh)
836{
837 nbgl_pageContent_t content = {0};
838
839 // ensure the page is valid
840 if ((navInfo.nbPages != 0) && (page >= (navInfo.nbPages))) {
841 return;
842 }
843 navInfo.activePage = page;
844 if ((onNav == NULL) || (onNav(navInfo.activePage, &content) == false)) {
845 return;
846 }
847
848 // override some fields
849 content.title = NULL;
850 content.isTouchableTitle = false;
851 content.tuneId = TUNE_TAP_CASUAL;
852
853 if (content.type == INFO_LONG_PRESS) { // last page
854 // for forward only review without known length...
855 // if we don't do that we cannot remove the '>' in the navigation bar at the last page
856 navInfo.nbPages = navInfo.activePage + 1;
857 content.infoLongPress.longPressToken = CONFIRM_TOKEN;
858 if (forwardNavOnly) {
859 // remove the "Skip" button
860 navInfo.skipText = NULL;
861 }
862 }
863
864 // override smallCaseForValue for tag/value types to false
865 if (content.type == TAG_VALUE_DETAILS) {
867 // the maximum displayable number of lines for value is NB_MAX_LINES_IN_REVIEW (without More
868 // button)
870 }
871 else if (content.type == TAG_VALUE_LIST) {
872 content.tagValueList.smallCaseForValue = false;
873 }
874 else if (content.type == TAG_VALUE_CONFIRM) {
876 // use confirm token for black button
877 content.tagValueConfirm.confirmationToken = CONFIRM_TOKEN;
878 }
879
880 pageContext = nbgl_pageDrawGenericContent(&pageCallback, &navInfo, &content);
881
882 if (forceFullRefresh) {
884 }
885 else {
887 }
888}
889
890// Helper that does the computing of which nbgl_content_t and which element in the content should be
891// displayed for the next generic context navigation page
892static const nbgl_content_t *genericContextComputeNextPageParams(uint8_t pageIdx,
893 nbgl_content_t *content,
894 uint8_t *p_nbElementsInNextPage,
895 bool *p_flag)
896{
897 int8_t nextContentIdx = genericContext.currentContentIdx;
898 int16_t nextElementIdx = genericContext.currentElementIdx;
899 uint8_t nbElementsInNextPage;
900
901 // Retrieve info on the next page
902 genericContextGetPageInfo(pageIdx, &nbElementsInNextPage, p_flag);
903 *p_nbElementsInNextPage = nbElementsInNextPage;
904
905 // Handle forward navigation:
906 // add to current index the number of pairs of the currently active page
907 if (pageIdx > navInfo.activePage) {
908 uint8_t nbElementsInCurrentPage;
909
910 genericContextGetPageInfo(navInfo.activePage, &nbElementsInCurrentPage, NULL);
911 nextElementIdx += nbElementsInCurrentPage;
912
913 // Handle case where the content to be displayed is in the next content
914 // In such case start at element index 0.
915 // If currentContentElementNb == 0, means not initialized, so skip
916 if ((nextElementIdx >= genericContext.currentContentElementNb)
917 && (genericContext.currentContentElementNb > 0)) {
918 nextContentIdx += 1;
919 nextElementIdx = 0;
920 }
921 }
922
923 // Handle backward navigation:
924 // add to current index the number of pairs of the currently active page
925 if (pageIdx < navInfo.activePage) {
926 // Backward navigation: remove to current index the number of pairs of the current page
927 nextElementIdx -= nbElementsInNextPage;
928
929 // Handle case where the content to be displayed is in the previous content
930 // In such case set a negative number as element index so that it is handled
931 // later once the previous content is accessible so that its elements number
932 // can be retrieved.
933 if (nextElementIdx < 0) {
934 nextContentIdx -= 1;
935 nextElementIdx = -nbElementsInNextPage;
936 }
937 }
938
939 const nbgl_content_t *p_content;
940 // Retrieve next content
941 if ((nextContentIdx == -1) && (genericContext.hasStartingContent)) {
942 p_content = &STARTING_CONTENT;
943 }
944 else if ((nextContentIdx == genericContext.genericContents.nbContents)
945 && (genericContext.hasFinishingContent)) {
946 p_content = &FINISHING_CONTENT;
947 }
948 else {
949 p_content = getContentAtIdx(&genericContext.genericContents, nextContentIdx, content);
950
951 if (p_content == NULL) {
952 LOG_DEBUG(USE_CASE_LOGGER, "Fail to retrieve content\n");
953 return NULL;
954 }
955 }
956
957 // Handle cases where we are going to display data from a new content:
958 // - navigation to a previous or to the next content
959 // - First page display (genericContext.currentContentElementNb == 0)
960 //
961 // In such case we need to:
962 // - Update genericContext.currentContentIdx
963 // - Update genericContext.currentContentElementNb
964 // - Update onContentAction callback
965 // - Update nextElementIdx in case it was previously not calculable
966 if ((nextContentIdx != genericContext.currentContentIdx)
967 || (genericContext.currentContentElementNb == 0)) {
968 genericContext.currentContentIdx = nextContentIdx;
969 genericContext.currentContentElementNb = getContentNbElement(p_content);
970 onContentAction = PIC(p_content->contentActionCallback);
971 if (nextElementIdx < 0) {
972 nextElementIdx = genericContext.currentContentElementNb + nextElementIdx;
973 }
974 }
975
976 // Sanity check
977 if ((nextElementIdx < 0) || (nextElementIdx >= genericContext.currentContentElementNb)) {
979 "Invalid element index %d / %d\n",
980 nextElementIdx,
981 genericContext.currentContentElementNb);
982 return NULL;
983 }
984
985 // Update genericContext elements
986 genericContext.currentElementIdx = nextElementIdx;
987 navInfo.activePage = pageIdx;
988
989 return p_content;
990}
991
992// Helper that generates a nbgl_pageContent_t to be displayed for the next generic context
993// navigation page
994static bool genericContextPreparePageContent(const nbgl_content_t *p_content,
995 uint8_t nbElementsInPage,
996 bool flag,
997 nbgl_pageContent_t *pageContent)
998{
999 uint8_t nextElementIdx = genericContext.currentElementIdx;
1000
1001 pageContent->title = pageTitle;
1002 pageContent->isTouchableTitle = false;
1003 pageContent->titleToken = QUIT_TOKEN;
1004 pageContent->tuneId = TUNE_TAP_CASUAL;
1005
1006 pageContent->type = p_content->type;
1007 switch (pageContent->type) {
1008 case CENTERED_INFO:
1009 memcpy(&pageContent->centeredInfo,
1010 &p_content->content.centeredInfo,
1011 sizeof(pageContent->centeredInfo));
1012 break;
1013 case EXTENDED_CENTER:
1014 memcpy(&pageContent->extendedCenter,
1015 &p_content->content.extendedCenter,
1016 sizeof(pageContent->extendedCenter));
1017 break;
1018 case INFO_LONG_PRESS:
1019 memcpy(&pageContent->infoLongPress,
1020 &p_content->content.infoLongPress,
1021 sizeof(pageContent->infoLongPress));
1022 break;
1023
1024 case INFO_BUTTON:
1025 memcpy(&pageContent->infoButton,
1026 &p_content->content.infoButton,
1027 sizeof(pageContent->infoButton));
1028 break;
1029
1030 case TAG_VALUE_LIST: {
1031 nbgl_contentTagValueList_t *p_tagValueList = &pageContent->tagValueList;
1032
1033 // memorize pairs (or callback) for usage when alias is used
1034 genericContext.currentPairs = p_content->content.tagValueList.pairs;
1035 genericContext.currentCallback = p_content->content.tagValueList.callback;
1036
1037 if (flag) {
1038 // Flag can be set if the pair is too long to fit or because it needs
1039 // to be displayed as centered info.
1040
1041 // First retrieve the pair
1042 const nbgl_layoutTagValue_t *pair;
1043
1044 if (p_content->content.tagValueList.pairs != NULL) {
1045 pair = PIC(&p_content->content.tagValueList.pairs[nextElementIdx]);
1046 }
1047 else {
1048 pair = PIC(p_content->content.tagValueList.callback(nextElementIdx));
1049 }
1050
1051 if (pair->centeredInfo) {
1052 pageContent->type = EXTENDED_CENTER;
1053 prepareReviewFirstPage(&pageContent->extendedCenter.contentCenter,
1054 pair->valueIcon,
1055 pair->item,
1056 pair->value);
1057
1058 // Skip population of nbgl_contentTagValueList_t structure
1059 p_tagValueList = NULL;
1060 }
1061 else {
1062 // if the pair is too long to fit, we use a TAG_VALUE_DETAILS content
1063 pageContent->type = TAG_VALUE_DETAILS;
1064 pageContent->tagValueDetails.detailsButtonText = "More";
1065 pageContent->tagValueDetails.detailsButtonIcon = NULL;
1066 pageContent->tagValueDetails.detailsButtonToken = DETAILS_BUTTON_TOKEN;
1067
1068 p_tagValueList = &pageContent->tagValueDetails.tagValueList;
1069
1070 // Backup pair info for easy data access upon click on button
1071 genericContext.detailsItem = pair->item;
1072 genericContext.detailsvalue = pair->value;
1073 genericContext.detailsWrapping = p_content->content.tagValueList.wrapping;
1074 }
1075 }
1076
1077 if (p_tagValueList != NULL) {
1078 p_tagValueList->nbPairs = nbElementsInPage;
1079 p_tagValueList->token = p_content->content.tagValueList.token;
1080 if (p_content->content.tagValueList.pairs != NULL) {
1081 p_tagValueList->pairs
1082 = PIC(&p_content->content.tagValueList.pairs[nextElementIdx]);
1083 // parse pairs to check if any contains an alias for value
1084 for (uint8_t i = 0; i < nbElementsInPage; i++) {
1085 if (p_tagValueList->pairs[i].aliasValue) {
1086 p_tagValueList->token = VALUE_ALIAS_TOKEN;
1087 break;
1088 }
1089 }
1090 }
1091 else {
1092 p_tagValueList->pairs = NULL;
1093 p_tagValueList->callback = p_content->content.tagValueList.callback;
1094 p_tagValueList->startIndex = nextElementIdx;
1095 // parse pairs to check if any contains an alias for value
1096 for (uint8_t i = 0; i < nbElementsInPage; i++) {
1097 const nbgl_layoutTagValue_t *pair
1098 = PIC(p_content->content.tagValueList.callback(nextElementIdx + i));
1099 if (pair->aliasValue) {
1100 p_tagValueList->token = VALUE_ALIAS_TOKEN;
1101 break;
1102 }
1103 }
1104 }
1105 p_tagValueList->smallCaseForValue = false;
1107 p_tagValueList->wrapping = p_content->content.tagValueList.wrapping;
1108 }
1109
1110 break;
1111 }
1112 case TAG_VALUE_CONFIRM: {
1113 nbgl_contentTagValueList_t *p_tagValueList;
1114 // only display a TAG_VALUE_CONFIRM if we are at the last page
1115 if ((nextElementIdx + nbElementsInPage)
1117 memcpy(&pageContent->tagValueConfirm,
1118 &p_content->content.tagValueConfirm,
1119 sizeof(pageContent->tagValueConfirm));
1120 p_tagValueList = &pageContent->tagValueConfirm.tagValueList;
1121 }
1122 else {
1123 // else display it as a TAG_VALUE_LIST
1124 pageContent->type = TAG_VALUE_LIST;
1125 p_tagValueList = &pageContent->tagValueList;
1126 }
1127 p_tagValueList->nbPairs = nbElementsInPage;
1128 p_tagValueList->pairs
1129 = PIC(&p_content->content.tagValueConfirm.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 // memorize pairs (or callback) for usage when alias is used
1138 genericContext.currentPairs = p_tagValueList->pairs;
1139 genericContext.currentCallback = p_tagValueList->callback;
1140 break;
1141 }
1142 case SWITCHES_LIST:
1143 pageContent->switchesList.nbSwitches = nbElementsInPage;
1144 pageContent->switchesList.switches
1145 = PIC(&p_content->content.switchesList.switches[nextElementIdx]);
1146 break;
1147 case INFOS_LIST:
1148 pageContent->infosList.nbInfos = nbElementsInPage;
1149 pageContent->infosList.infoTypes
1150 = PIC(&p_content->content.infosList.infoTypes[nextElementIdx]);
1151 pageContent->infosList.infoContents
1152 = PIC(&p_content->content.infosList.infoContents[nextElementIdx]);
1153 break;
1154 case CHOICES_LIST:
1155 memcpy(&pageContent->choicesList,
1156 &p_content->content.choicesList,
1157 sizeof(pageContent->choicesList));
1158 pageContent->choicesList.nbChoices = nbElementsInPage;
1159 pageContent->choicesList.names
1160 = PIC(&p_content->content.choicesList.names[nextElementIdx]);
1161 if ((p_content->content.choicesList.initChoice >= nextElementIdx)
1162 && (p_content->content.choicesList.initChoice
1163 < nextElementIdx + nbElementsInPage)) {
1164 pageContent->choicesList.initChoice
1165 = p_content->content.choicesList.initChoice - nextElementIdx;
1166 }
1167 else {
1168 pageContent->choicesList.initChoice = nbElementsInPage;
1169 }
1170 break;
1171 case BARS_LIST:
1172 pageContent->barsList.nbBars = nbElementsInPage;
1173 pageContent->barsList.barTexts
1174 = PIC(&p_content->content.barsList.barTexts[nextElementIdx]);
1175 pageContent->barsList.tokens = PIC(&p_content->content.barsList.tokens[nextElementIdx]);
1176 pageContent->barsList.tuneId = p_content->content.barsList.tuneId;
1177 break;
1178 default:
1179 LOG_DEBUG(USE_CASE_LOGGER, "Unsupported type %d\n", pageContent->type);
1180 return false;
1181 }
1182
1183 bool isFirstOrLastPage
1184 = ((p_content->type == CENTERED_INFO) || (p_content->type == EXTENDED_CENTER))
1185 || (p_content->type == INFO_LONG_PRESS);
1186 nbgl_operationType_t operationType
1187 = (navType == STREAMING_NAV)
1188 ? bundleNavContext.reviewStreaming.operationType
1189 : ((navType == GENERIC_NAV) ? bundleNavContext.review.operationType : 0);
1190
1191 // if first or last page of review and blind/risky operation, add the warning top-right button
1192 if (isFirstOrLastPage
1193 && (operationType & (BLIND_OPERATION | RISKY_OPERATION | NO_THREAT_OPERATION))) {
1194 // if issue is only Web3Checks "no threat", use privacy icon
1195 if ((operationType & NO_THREAT_OPERATION)
1196 && !(reviewWithWarnCtx.warning->predefinedSet & (1 << BLIND_SIGNING_WARN))) {
1197 pageContent->topRightIcon = &PRIVACY_ICON;
1198 }
1199 else {
1200 pageContent->topRightIcon = &WARNING_ICON;
1201 }
1202
1203 pageContent->topRightToken
1204 = (operationType & BLIND_OPERATION) ? BLIND_WARNING_TOKEN : WARNING_BUTTON_TOKEN;
1205 }
1206
1207 return true;
1208}
1209
1210// function used to display the current page in generic context navigation mode
1211static void displayGenericContextPage(uint8_t pageIdx, bool forceFullRefresh)
1212{
1213 // Retrieve next page parameters
1214 nbgl_content_t content;
1215 uint8_t nbElementsInPage;
1216 bool flag;
1217 const nbgl_content_t *p_content = NULL;
1218
1219 if (navType == STREAMING_NAV) {
1220 if (pageIdx == LAST_PAGE_FOR_REVIEW) {
1221 if (bundleNavContext.reviewStreaming.skipCallback != NULL) {
1222 bundleNavContext.reviewStreaming.skipCallback();
1223 }
1224 return;
1225 }
1226 else if (pageIdx >= bundleNavContext.reviewStreaming.stepPageNb) {
1227 bundleNavReviewStreamingChoice(true);
1228 return;
1229 }
1230 }
1231
1232 if (navInfo.activePage == pageIdx) {
1233 p_content
1234 = genericContextComputeNextPageParams(pageIdx, &content, &nbElementsInPage, &flag);
1235 }
1236 else if (navInfo.activePage < pageIdx) {
1237 // Support going more than one step forward.
1238 // It occurs when initializing a navigation on an arbitrary page
1239 for (int i = navInfo.activePage + 1; i <= pageIdx; i++) {
1240 p_content = genericContextComputeNextPageParams(i, &content, &nbElementsInPage, &flag);
1241 }
1242 }
1243 else {
1244 if (pageIdx - navInfo.activePage > 1) {
1245 // We don't support going more than one step backward as it doesn't occurs for now?
1246 LOG_DEBUG(USE_CASE_LOGGER, "Unsupported navigation\n");
1247 return;
1248 }
1249 p_content
1250 = genericContextComputeNextPageParams(pageIdx, &content, &nbElementsInPage, &flag);
1251 }
1252
1253 if (p_content == NULL) {
1254 return;
1255 }
1256
1257 // Create next page content
1258 nbgl_pageContent_t pageContent = {0};
1259 if (!genericContextPreparePageContent(p_content, nbElementsInPage, flag, &pageContent)) {
1260 return;
1261 }
1262
1263 pageContext = nbgl_pageDrawGenericContent(&pageCallback, &navInfo, &pageContent);
1264
1265 if (forceFullRefresh) {
1267 }
1268 else {
1270 }
1271}
1272
1273// from the current details context, return a pointer on the details at the given page
1274static const char *getDetailsPageAt(uint8_t detailsPage)
1275{
1276 uint8_t page = 0;
1277 const char *currentChar = detailsContext.value;
1278 while (page < detailsPage) {
1279 uint16_t nbLines
1280 = nbgl_getTextNbLinesInWidth(SMALL_BOLD_FONT, currentChar, AVAILABLE_WIDTH, false);
1281 if (nbLines > NB_MAX_LINES_IN_DETAILS) {
1282 uint16_t len;
1283 nbgl_getTextMaxLenInNbLines(SMALL_BOLD_FONT,
1284 currentChar,
1287 &len,
1288 detailsContext.wrapping);
1289 len -= 3;
1290 currentChar = currentChar + len;
1291 }
1292 page++;
1293 }
1294 return currentChar;
1295}
1296
1297// function used to display the current page in details review mode
1298static void displayDetailsPage(uint8_t detailsPage, bool forceFullRefresh)
1299{
1300 static nbgl_layoutTagValue_t currentPair;
1301 nbgl_pageNavigationInfo_t info = {.activePage = detailsPage,
1302 .nbPages = detailsContext.nbPages,
1303 .navType = NAV_WITH_BUTTONS,
1304 .quitToken = QUIT_TOKEN,
1305 .navWithButtons.navToken = NAV_TOKEN,
1306 .navWithButtons.quitButton = true,
1307 .navWithButtons.backButton = true,
1308 .navWithButtons.quitText = NULL,
1309 .progressIndicator = false,
1310 .tuneId = TUNE_TAP_CASUAL};
1312 .topRightIcon = NULL,
1313 .tagValueList.nbPairs = 1,
1314 .tagValueList.pairs = &currentPair,
1315 .tagValueList.smallCaseForValue = true,
1316 .tagValueList.wrapping = detailsContext.wrapping};
1317
1318 if (modalPageContext != NULL) {
1319 nbgl_pageRelease(modalPageContext);
1320 }
1321 currentPair.item = detailsContext.tag;
1322 // if move backward or first page
1323 if (detailsPage <= detailsContext.currentPage) {
1324 // recompute current start from beginning
1325 currentPair.value = getDetailsPageAt(detailsPage);
1326 forceFullRefresh = true;
1327 }
1328 // else move forward
1329 else {
1330 currentPair.value = detailsContext.nextPageStart;
1331 }
1332 detailsContext.currentPage = detailsPage;
1333 uint16_t nbLines = nbgl_getTextNbLinesInWidth(
1334 SMALL_BOLD_FONT, currentPair.value, AVAILABLE_WIDTH, detailsContext.wrapping);
1335
1336 if (nbLines > NB_MAX_LINES_IN_DETAILS) {
1337 uint16_t len;
1338 nbgl_getTextMaxLenInNbLines(SMALL_BOLD_FONT,
1339 currentPair.value,
1342 &len,
1343 detailsContext.wrapping);
1344 len -= 3;
1345 // memorize next position to save processing
1346 detailsContext.nextPageStart = currentPair.value + len;
1347 // use special feature to keep only NB_MAX_LINES_IN_DETAILS lines and replace the last 3
1348 // chars by "..."
1350 }
1351 else {
1352 detailsContext.nextPageStart = NULL;
1353 content.tagValueList.nbMaxLinesForValue = 0;
1354 }
1355 if (info.nbPages == 1) {
1356 // if only one page, no navigation bar, and use a footer instead
1357 info.navWithButtons.quitText = "Close";
1358 }
1359 modalPageContext = nbgl_pageDrawGenericContentExt(&pageModalCallback, &info, &content, true);
1360
1361 if (forceFullRefresh) {
1363 }
1364 else {
1366 }
1367}
1368
1369// function used to display the content of a full value, when touching an alias of a tag/value pair
1370static void displayFullValuePage(const char *backText,
1371 const char *aliasText,
1372 const nbgl_contentValueExt_t *extension)
1373{
1374 const char *modalTitle
1375 = (extension->backText != NULL) ? PIC(extension->backText) : PIC(backText);
1376 if (extension->aliasType == INFO_LIST_ALIAS) {
1377 genericContext.currentInfos = extension->infolist;
1378 displayInfosListModal(modalTitle, extension->infolist, true);
1379 }
1380 else if (extension->aliasType == TAG_VALUE_LIST_ALIAS) {
1381 genericContext.currentTagValues = extension->tagValuelist;
1382 displayTagValueListModal(extension->tagValuelist);
1383 }
1384 else {
1385 nbgl_layoutDescription_t layoutDescription = {.modal = true,
1386 .withLeftBorder = true,
1387 .onActionCallback = &modalLayoutTouchCallback,
1388 .tapActionText = NULL};
1390 .separationLine = false,
1391 .backAndText.token = 0,
1392 .backAndText.tuneId = TUNE_TAP_CASUAL,
1393 .backAndText.text = modalTitle};
1394 genericContext.modalLayout = nbgl_layoutGet(&layoutDescription);
1395 // add header with the tag part of the pair, to go back
1396 nbgl_layoutAddHeader(genericContext.modalLayout, &headerDesc);
1397 // add either QR Code or full value text
1398 if (extension->aliasType == QR_CODE_ALIAS) {
1399#ifdef NBGL_QRCODE
1400 nbgl_layoutQRCode_t qrCode
1401 = {.url = extension->fullValue,
1402 .text1 = (extension->title != NULL) ? extension->title : extension->fullValue,
1403 .text2 = extension->explanation,
1404 .centered = true,
1405 .offsetY = 0};
1406
1407 nbgl_layoutAddQRCode(genericContext.modalLayout, &qrCode);
1408#endif // NBGL_QRCODE
1409 }
1410 else {
1411 const char *info;
1412 // add full value text
1413 if (extension->aliasType == ENS_ALIAS) {
1414 info = "ENS names are resolved by Ledger backend.";
1415 }
1416 else if (extension->aliasType == ADDRESS_BOOK_ALIAS) {
1417 info = "This account label comes from your Address Book in Ledger Live.";
1418 }
1419 else {
1420 info = extension->explanation;
1421 }
1423 genericContext.modalLayout, aliasText, extension->fullValue, info);
1424 }
1425 // draw & refresh
1426 nbgl_layoutDraw(genericContext.modalLayout);
1427 nbgl_refresh();
1428 }
1429}
1430
1431// function used to display the modal containing tip-box infos
1432static void displayInfosListModal(const char *modalTitle,
1433 const nbgl_contentInfoList_t *infos,
1434 bool fromReview)
1435{
1437 .nbPages = 1,
1438 .navType = NAV_WITH_BUTTONS,
1439 .quitToken = QUIT_TIPBOX_MODAL_TOKEN,
1440 .navWithButtons.navToken = NAV_TOKEN,
1441 .navWithButtons.quitButton = false,
1442 .navWithButtons.backButton = true,
1443 .navWithButtons.quitText = NULL,
1444 .progressIndicator = false,
1445 .tuneId = TUNE_TAP_CASUAL};
1446 nbgl_pageContent_t content
1447 = {.type = INFOS_LIST,
1448 .topRightIcon = NULL,
1449 .infosList.nbInfos = infos->nbInfos,
1450 .infosList.withExtensions = infos->withExtensions,
1451 .infosList.infoTypes = infos->infoTypes,
1452 .infosList.infoContents = infos->infoContents,
1453 .infosList.infoExtensions = infos->infoExtensions,
1454 .infosList.token = fromReview ? INFO_ALIAS_TOKEN : INFOS_TIP_BOX_TOKEN,
1455 .title = modalTitle,
1456 .titleToken = QUIT_TIPBOX_MODAL_TOKEN,
1457 .tuneId = TUNE_TAP_CASUAL};
1458
1459 if (modalPageContext != NULL) {
1460 nbgl_pageRelease(modalPageContext);
1461 }
1462 modalPageContext = nbgl_pageDrawGenericContentExt(&pageModalCallback, &info, &content, true);
1463
1465}
1466
1467// function used to display the modal containing alias tag-value pairs
1468static void displayTagValueListModalPage(uint8_t pageIdx, bool forceFullRefresh)
1469{
1470 nbgl_pageNavigationInfo_t info = {.activePage = pageIdx,
1471 .nbPages = detailsContext.nbPages,
1472 .navType = NAV_WITH_BUTTONS,
1473 .quitToken = QUIT_TOKEN,
1474 .navWithButtons.navToken = MODAL_NAV_TOKEN,
1475 .navWithButtons.quitButton = true,
1476 .navWithButtons.backButton = true,
1477 .navWithButtons.quitText = NULL,
1478 .progressIndicator = false,
1479 .tuneId = TUNE_TAP_CASUAL};
1481 .topRightIcon = NULL,
1482 .tagValueList.smallCaseForValue = true,
1483 .tagValueList.wrapping = detailsContext.wrapping};
1484 uint8_t nbElementsInPage;
1485
1486 // if first page or forward
1487 if (detailsContext.currentPage <= pageIdx) {
1488 modalContextGetPageInfo(pageIdx, &nbElementsInPage);
1489 }
1490 else {
1491 // backward direction
1492 modalContextGetPageInfo(pageIdx + 1, &nbElementsInPage);
1493 detailsContext.currentPairIdx -= nbElementsInPage;
1494 modalContextGetPageInfo(pageIdx, &nbElementsInPage);
1495 detailsContext.currentPairIdx -= nbElementsInPage;
1496 }
1497 detailsContext.currentPage = pageIdx;
1498
1499 content.tagValueList.pairs
1500 = &genericContext.currentTagValues->pairs[detailsContext.currentPairIdx];
1501 content.tagValueList.nbPairs = nbElementsInPage;
1502 detailsContext.currentPairIdx += nbElementsInPage;
1503 if (info.nbPages == 1) {
1504 // if only one page, no navigation bar, and use a footer instead
1505 info.navWithButtons.quitText = "Close";
1506 }
1507
1508 if (modalPageContext != NULL) {
1509 nbgl_pageRelease(modalPageContext);
1510 }
1511 modalPageContext = nbgl_pageDrawGenericContentExt(&pageModalCallback, &info, &content, true);
1512
1513 if (forceFullRefresh) {
1515 }
1516 else {
1518 }
1519}
1520
1521#ifdef NBGL_QRCODE
1522static void displayAddressQRCode(void)
1523{
1524 // display the address as QR Code
1525 nbgl_layoutDescription_t layoutDescription = {.modal = true,
1526 .withLeftBorder = true,
1527 .onActionCallback = &modalLayoutTouchCallback,
1528 .tapActionText = NULL};
1529 nbgl_layoutHeader_t headerDesc = {
1530 .type = HEADER_EMPTY, .separationLine = false, .emptySpace.height = SMALL_CENTERING_HEADER};
1531 nbgl_layoutQRCode_t qrCode = {.url = addressConfirmationContext.tagValuePairs[0].value,
1532 .text1 = NULL,
1533 .centered = true,
1534 .offsetY = 0};
1535
1536 addressConfirmationContext.modalLayout = nbgl_layoutGet(&layoutDescription);
1537 // add empty header for better look
1538 nbgl_layoutAddHeader(addressConfirmationContext.modalLayout, &headerDesc);
1539 // compute nb lines to check whether it shall be shorten (max is 3 lines)
1540 uint16_t nbLines = nbgl_getTextNbLinesInWidth(SMALL_REGULAR_FONT,
1541 addressConfirmationContext.tagValuePairs[0].value,
1543 false);
1544
1545 if (nbLines <= QRCODE_NB_MAX_LINES) {
1546 qrCode.text2 = addressConfirmationContext.tagValuePairs[0].value; // in gray
1547 }
1548 else {
1549 // only keep beginning and end of text, and add ... in the middle
1550 nbgl_textReduceOnNbLines(SMALL_REGULAR_FONT,
1551 addressConfirmationContext.tagValuePairs[0].value,
1553 QRCODE_NB_MAX_LINES,
1554 reducedAddress,
1555 QRCODE_REDUCED_ADDR_LEN);
1556 qrCode.text2 = reducedAddress; // in gray
1557 }
1558
1559 nbgl_layoutAddQRCode(addressConfirmationContext.modalLayout, &qrCode);
1560
1562 addressConfirmationContext.modalLayout, "Close", DISMISS_QR_TOKEN, TUNE_TAP_CASUAL);
1563 nbgl_layoutDraw(addressConfirmationContext.modalLayout);
1564 nbgl_refresh();
1565}
1566
1567#endif // NBGL_QRCODE
1568
1569// called when header is touched on modal page, to dismiss it
1570static void modalLayoutTouchCallback(int token, uint8_t index)
1571{
1572 UNUSED(index);
1573 if (token == DISMISS_QR_TOKEN) {
1574 // dismiss modal
1575 nbgl_layoutRelease(addressConfirmationContext.modalLayout);
1576 addressConfirmationContext.modalLayout = NULL;
1578 }
1579 else if (token == DISMISS_WARNING_TOKEN) {
1580 // dismiss modal
1581 nbgl_layoutRelease(reviewWithWarnCtx.modalLayout);
1582 // if already at first level, simply redraw the background
1583 if (reviewWithWarnCtx.securityReportLevel <= 1) {
1584 reviewWithWarnCtx.modalLayout = NULL;
1586 }
1587 else {
1588 // We are at level 2 of warning modal, so re-display the level 1
1589 reviewWithWarnCtx.securityReportLevel = 1;
1590 // if preset is used
1591 if (reviewWithWarnCtx.warning->predefinedSet) {
1592 displaySecurityReport(reviewWithWarnCtx.warning->predefinedSet);
1593 }
1594 else {
1595 // use customized warning
1596 const nbgl_warningDetails_t *details
1597 = (reviewWithWarnCtx.isIntro) ? reviewWithWarnCtx.warning->introDetails
1598 : reviewWithWarnCtx.warning->reviewDetails;
1599 displayCustomizedSecurityReport(details);
1600 }
1601 return;
1602 }
1603 }
1604 else if ((token >= FIRST_WARN_BAR_TOKEN) && (token <= LAST_WARN_BAR_TOKEN)) {
1605 // dismiss modal before creating a new one
1606 nbgl_layoutRelease(reviewWithWarnCtx.modalLayout);
1607 reviewWithWarnCtx.securityReportLevel = 2;
1608 // if preset is used
1609 if (reviewWithWarnCtx.warning->predefinedSet) {
1610 displaySecurityReport(1 << (token - FIRST_WARN_BAR_TOKEN));
1611 }
1612 else {
1613 // use customized warning
1614 const nbgl_warningDetails_t *details = (reviewWithWarnCtx.isIntro)
1615 ? reviewWithWarnCtx.warning->introDetails
1616 : reviewWithWarnCtx.warning->reviewDetails;
1617 if (details->type == BAR_LIST_WARNING) {
1618 displayCustomizedSecurityReport(
1619 &details->barList.details[token - FIRST_WARN_BAR_TOKEN]);
1620 }
1621 }
1622 return;
1623 }
1624 else {
1625 // dismiss modal
1626 nbgl_layoutRelease(genericContext.modalLayout);
1627 genericContext.modalLayout = NULL;
1629 }
1630 nbgl_refresh();
1631}
1632
1633// called when item are touched in layout
1634static void layoutTouchCallback(int token, uint8_t index)
1635{
1636 // choice buttons in initial warning page
1637 if (token == WARNING_CHOICE_TOKEN) {
1638 if (index == 0) { // top button to exit
1639 reviewWithWarnCtx.choiceCallback(false);
1640 }
1641 else { // bottom button to continue to review
1642 reviewWithWarnCtx.isIntro = false;
1643 if (reviewWithWarnCtx.isStreaming) {
1644 useCaseReviewStreamingStart(reviewWithWarnCtx.operationType,
1645 reviewWithWarnCtx.icon,
1646 reviewWithWarnCtx.reviewTitle,
1647 reviewWithWarnCtx.reviewSubTitle,
1648 reviewWithWarnCtx.choiceCallback,
1649 false);
1650 }
1651 else {
1652 useCaseReview(reviewWithWarnCtx.operationType,
1653 reviewWithWarnCtx.tagValueList,
1654 reviewWithWarnCtx.icon,
1655 reviewWithWarnCtx.reviewTitle,
1656 reviewWithWarnCtx.reviewSubTitle,
1657 reviewWithWarnCtx.finishTitle,
1658 &activeTipBox,
1659 reviewWithWarnCtx.choiceCallback,
1660 false,
1661 false);
1662 }
1663 }
1664 }
1665 // top-right button in initial warning page
1666 else if (token == WARNING_BUTTON_TOKEN) {
1667 // start directly at first level of security report
1668 reviewWithWarnCtx.securityReportLevel = 1;
1669 // if preset is used
1670 if (reviewWithWarnCtx.warning->predefinedSet) {
1671 displaySecurityReport(reviewWithWarnCtx.warning->predefinedSet);
1672 }
1673 else {
1674 // use customized warning
1675 const nbgl_warningDetails_t *details = (reviewWithWarnCtx.isIntro)
1676 ? reviewWithWarnCtx.warning->introDetails
1677 : reviewWithWarnCtx.warning->reviewDetails;
1678 displayCustomizedSecurityReport(details);
1679 }
1680 }
1681}
1682
1683// called when skip button is touched in footer, during forward only review
1684static void displaySkipWarning(void)
1685{
1687 .cancelText = "Go back to review",
1688 .centeredInfo.text1 = "Skip review?",
1689 .centeredInfo.text2
1690 = "If you're sure you don't need to review all fields, you can skip straight to signing.",
1691 .centeredInfo.text3 = NULL,
1692 .centeredInfo.style = LARGE_CASE_INFO,
1693 .centeredInfo.icon = &IMPORTANT_CIRCLE_ICON,
1694 .centeredInfo.offsetY = 0,
1695 .confirmationText = "Yes, skip",
1696 .confirmationToken = SKIP_TOKEN,
1697 .tuneId = TUNE_TAP_CASUAL,
1698 .modal = true};
1699 if (modalPageContext != NULL) {
1700 nbgl_pageRelease(modalPageContext);
1701 }
1702 modalPageContext = nbgl_pageDrawConfirmation(&pageModalCallback, &info);
1704}
1705
1706#ifdef NBGL_KEYPAD
1707// called to update the keypad and automatically show / hide:
1708// - backspace key if no digits are present
1709// - validation key if the min digit is reached
1710// - keypad if the max number of digit is reached
1711static void updateKeyPad(bool add)
1712{
1713 bool enableValidate, enableBackspace, enableDigits;
1714 bool redrawKeypad = false;
1716
1717 enableDigits = (keypadContext.pinLen < keypadContext.pinMaxDigits);
1718 enableValidate = (keypadContext.pinLen >= keypadContext.pinMinDigits);
1719 enableBackspace = (keypadContext.pinLen > 0);
1720 if (add) {
1721 if ((keypadContext.pinLen == keypadContext.pinMinDigits)
1722 || // activate "validate" button on keypad
1723 (keypadContext.pinLen == keypadContext.pinMaxDigits)
1724 || // deactivate "digits" on keypad
1725 (keypadContext.pinLen == 1)) { // activate "backspace"
1726 redrawKeypad = true;
1727 }
1728 }
1729 else { // remove
1730 if ((keypadContext.pinLen == 0) || // deactivate "backspace" button on keypad
1731 (keypadContext.pinLen == (keypadContext.pinMinDigits - 1))
1732 || // deactivate "validate" button on keypad
1733 (keypadContext.pinLen
1734 == (keypadContext.pinMaxDigits - 1))) { // reactivate "digits" on keypad
1735 redrawKeypad = true;
1736 }
1737 }
1738 if (keypadContext.hidden == true) {
1739 nbgl_layoutUpdateKeypadContent(keypadContext.layoutCtx, true, keypadContext.pinLen, NULL);
1740 }
1741 else {
1743 keypadContext.layoutCtx, false, 0, (const char *) keypadContext.pinEntry);
1744 }
1745 if (redrawKeypad) {
1747 keypadContext.layoutCtx, 0, enableValidate, enableBackspace, enableDigits);
1748 }
1749
1750 if ((!add) && (keypadContext.pinLen == 0)) {
1751 // Full refresh to fully clean the bullets when reaching 0 digits
1752 mode = FULL_COLOR_REFRESH;
1753 }
1755}
1756
1757// called when a key is touched on the keypad
1758static void keypadCallback(char touchedKey)
1759{
1760 switch (touchedKey) {
1761 case BACKSPACE_KEY:
1762 if (keypadContext.pinLen > 0) {
1763 keypadContext.pinLen--;
1764 keypadContext.pinEntry[keypadContext.pinLen] = 0;
1765 }
1766 updateKeyPad(false);
1767 break;
1768
1769 case VALIDATE_KEY:
1770 // Gray out keyboard / buttons as a first user feedback
1771 nbgl_layoutUpdateKeypad(keypadContext.layoutCtx, 0, false, false, true);
1774
1775 onValidatePin(keypadContext.pinEntry, keypadContext.pinLen);
1776 break;
1777
1778 default:
1779 if ((touchedKey >= 0x30) && (touchedKey < 0x40)) {
1780 if (keypadContext.pinLen < keypadContext.pinMaxDigits) {
1781 keypadContext.pinEntry[keypadContext.pinLen] = touchedKey;
1782 keypadContext.pinLen++;
1783 }
1784 updateKeyPad(true);
1785 }
1786 break;
1787 }
1788}
1789
1790// called to create a keypad, with either hidden or visible digits
1791static void keypadGenericUseCase(const char *title,
1792 uint8_t minDigits,
1793 uint8_t maxDigits,
1794 uint8_t backToken,
1795 bool shuffled,
1796 bool hidden,
1797 tune_index_e tuneId,
1798 nbgl_pinValidCallback_t validatePinCallback,
1799 nbgl_layoutTouchCallback_t actionCallback)
1800{
1801 nbgl_layoutDescription_t layoutDescription = {0};
1803 .separationLine = true,
1804 .backAndText.token = backToken,
1805 .backAndText.tuneId = tuneId,
1806 .backAndText.text = NULL};
1807 int status = -1;
1808
1809 if ((minDigits > KEYPAD_MAX_DIGITS) || (maxDigits > KEYPAD_MAX_DIGITS)) {
1810 return;
1811 }
1812
1813 reset_callbacks();
1814 // reset the keypad context
1815 memset(&keypadContext, 0, sizeof(KeypadContext_t));
1816
1817 // get a layout
1818 layoutDescription.onActionCallback = actionCallback;
1819 layoutDescription.modal = false;
1820 layoutDescription.withLeftBorder = false;
1821 keypadContext.layoutCtx = nbgl_layoutGet(&layoutDescription);
1822 keypadContext.hidden = hidden;
1823
1824 // set back key in header
1825 nbgl_layoutAddHeader(keypadContext.layoutCtx, &headerDesc);
1826
1827 // add keypad
1828 status = nbgl_layoutAddKeypad(keypadContext.layoutCtx, keypadCallback, shuffled);
1829 if (status < 0) {
1830 return;
1831 }
1832 // add keypad content
1834 keypadContext.layoutCtx, title, keypadContext.hidden, maxDigits, "");
1835
1836 if (status < 0) {
1837 return;
1838 }
1839
1840 // validation pin callback
1841 onValidatePin = validatePinCallback;
1842 // pin code acceptable lengths
1843 keypadContext.pinMinDigits = minDigits;
1844 keypadContext.pinMaxDigits = maxDigits;
1845
1846 nbgl_layoutDraw(keypadContext.layoutCtx);
1848}
1849#endif
1850
1865static uint8_t getNbTagValuesInPage(uint8_t nbPairs,
1866 const nbgl_contentTagValueList_t *tagValueList,
1867 uint8_t startIndex,
1868 bool isSkippable,
1869 bool hasConfirmationButton,
1870 bool hasDetailsButton,
1871 bool *requireSpecificDisplay)
1872{
1873 uint8_t nbPairsInPage = 0;
1874 uint16_t currentHeight = PRE_TAG_VALUE_MARGIN; // upper margin
1875 uint16_t maxUsableHeight = TAG_VALUE_AREA_HEIGHT;
1876
1877 // if the review is skippable, it means that there is less height for tag/value pairs
1878 // the small centering header becomes a touchable header
1879 if (isSkippable) {
1880 maxUsableHeight -= TOUCHABLE_HEADER_BAR_HEIGHT - SMALL_CENTERING_HEADER;
1881 }
1882
1883 *requireSpecificDisplay = false;
1884 while (nbPairsInPage < nbPairs) {
1885 const nbgl_layoutTagValue_t *pair;
1886 nbgl_font_id_e value_font;
1887 uint16_t nbLines;
1888
1889 // margin between pairs
1890 // 12 or 24 px between each tag/value pair
1891 if (nbPairsInPage > 0) {
1892 currentHeight += INTER_TAG_VALUE_MARGIN;
1893 }
1894 // fetch tag/value pair strings.
1895 if (tagValueList->pairs != NULL) {
1896 pair = PIC(&tagValueList->pairs[startIndex + nbPairsInPage]);
1897 }
1898 else {
1899 pair = PIC(tagValueList->callback(startIndex + nbPairsInPage));
1900 }
1901
1902 if (pair->forcePageStart && nbPairsInPage > 0) {
1903 // This pair must be at the top of a page
1904 break;
1905 }
1906
1907 if (pair->centeredInfo) {
1908 if (nbPairsInPage > 0) {
1909 // This pair must be at the top of a page
1910 break;
1911 }
1912 else {
1913 // This pair is the only one of the page and has a specific display behavior
1914 nbPairsInPage = 1;
1915 *requireSpecificDisplay = true;
1916 break;
1917 }
1918 }
1919
1920 // tag height
1921 currentHeight += nbgl_getTextHeightInWidth(
1922 SMALL_REGULAR_FONT, pair->item, AVAILABLE_WIDTH, tagValueList->wrapping);
1923 // space between tag and value
1924 currentHeight += 4;
1925 // set value font
1926 if (tagValueList->smallCaseForValue) {
1927 value_font = SMALL_REGULAR_FONT;
1928 }
1929 else {
1930 value_font = LARGE_MEDIUM_FONT;
1931 }
1932 // value height
1933 currentHeight += nbgl_getTextHeightInWidth(
1934 value_font, pair->value, AVAILABLE_WIDTH, tagValueList->wrapping);
1935 // nb lines for value
1937 value_font, pair->value, AVAILABLE_WIDTH, tagValueList->wrapping);
1938 if ((currentHeight >= maxUsableHeight) || (nbLines > NB_MAX_LINES_IN_REVIEW)) {
1939 if (nbPairsInPage == 0) {
1940 // Pair too long to fit in a single screen
1941 // It will be the only one of the page and has a specific display behavior
1942 nbPairsInPage = 1;
1943 *requireSpecificDisplay = true;
1944 }
1945 break;
1946 }
1947 nbPairsInPage++;
1948 }
1949 // if this is a TAG_VALUE_CONFIRM and we have reached the last pairs,
1950 // let's check if it still fits with a CONFIRMATION button, and if not,
1951 // remove the last pair
1952 if (hasConfirmationButton && (nbPairsInPage == nbPairs)) {
1953 maxUsableHeight -= UP_FOOTER_BUTTON_HEIGHT;
1954 if (currentHeight > maxUsableHeight) {
1955 nbPairsInPage--;
1956 }
1957 }
1958 // do the same with just a details button
1959 else if (hasDetailsButton) {
1960 maxUsableHeight -= (SMALL_BUTTON_RADIUS * 2);
1961 if (currentHeight > maxUsableHeight) {
1962 nbPairsInPage--;
1963 }
1964 }
1965 return nbPairsInPage;
1966}
1967
1977static uint8_t getNbTagValuesInDetailsPage(uint8_t nbPairs,
1978 const nbgl_contentTagValueList_t *tagValueList,
1979 uint8_t startIndex)
1980{
1981 uint8_t nbPairsInPage = 0;
1982 uint16_t currentHeight = PRE_TAG_VALUE_MARGIN; // upper margin
1983 uint16_t maxUsableHeight = TAG_VALUE_AREA_HEIGHT;
1984
1985 while (nbPairsInPage < nbPairs) {
1986 const nbgl_layoutTagValue_t *pair;
1987
1988 // margin between pairs
1989 // 12 or 24 px between each tag/value pair
1990 if (nbPairsInPage > 0) {
1991 currentHeight += INTER_TAG_VALUE_MARGIN;
1992 }
1993 // fetch tag/value pair strings.
1994 if (tagValueList->pairs != NULL) {
1995 pair = PIC(&tagValueList->pairs[startIndex + nbPairsInPage]);
1996 }
1997 else {
1998 pair = PIC(tagValueList->callback(startIndex + nbPairsInPage));
1999 }
2000
2001 // tag height
2002 currentHeight += nbgl_getTextHeightInWidth(
2003 SMALL_REGULAR_FONT, pair->item, AVAILABLE_WIDTH, tagValueList->wrapping);
2004 // space between tag and value
2005 currentHeight += 4;
2006
2007 // value height
2008 currentHeight += nbgl_getTextHeightInWidth(
2009 SMALL_REGULAR_FONT, pair->value, AVAILABLE_WIDTH, tagValueList->wrapping);
2010
2011 // we have reached the maximum height, it means than there are to many pairs
2012 if (currentHeight >= maxUsableHeight) {
2013 break;
2014 }
2015 nbPairsInPage++;
2016 }
2017 return nbPairsInPage;
2018}
2019
2020static uint8_t getNbPagesForContent(const nbgl_content_t *content,
2021 uint8_t pageIdxStart,
2022 bool isLast,
2023 bool isSkippable)
2024{
2025 uint8_t nbElements = 0;
2026 uint8_t nbPages = 0;
2027 uint8_t nbElementsInPage;
2028 uint8_t elemIdx = 0;
2029 bool flag;
2030
2031 nbElements = getContentNbElement(content);
2032
2033 while (nbElements > 0) {
2034 flag = 0;
2035 // if the current page is not the first one (or last), a navigation bar exists
2036 bool hasNav = !isLast || (pageIdxStart > 0) || (elemIdx > 0);
2037 if (content->type == TAG_VALUE_LIST) {
2038 nbElementsInPage = getNbTagValuesInPage(nbElements,
2039 &content->content.tagValueList,
2040 elemIdx,
2041 isSkippable,
2042 false,
2043 false,
2044 &flag);
2045 }
2046 else if (content->type == TAG_VALUE_CONFIRM) {
2047 nbElementsInPage = getNbTagValuesInPage(nbElements,
2049 elemIdx,
2050 isSkippable,
2051 isLast,
2052 !isLast,
2053 &flag);
2054 }
2055 else if (content->type == INFOS_LIST) {
2056 nbElementsInPage = nbgl_useCaseGetNbInfosInPage(
2057 nbElements, &content->content.infosList, elemIdx, hasNav);
2058 }
2059 else if (content->type == SWITCHES_LIST) {
2060 nbElementsInPage = nbgl_useCaseGetNbSwitchesInPage(
2061 nbElements, &content->content.switchesList, elemIdx, hasNav);
2062 }
2063 else if (content->type == BARS_LIST) {
2064 nbElementsInPage = nbgl_useCaseGetNbBarsInPage(
2065 nbElements, &content->content.barsList, elemIdx, hasNav);
2066 }
2067 else if (content->type == CHOICES_LIST) {
2068 nbElementsInPage = nbgl_useCaseGetNbChoicesInPage(
2069 nbElements, &content->content.choicesList, elemIdx, hasNav);
2070 }
2071 else {
2072 nbElementsInPage = MIN(nbMaxElementsPerContentType[content->type], nbElements);
2073 }
2074
2075 elemIdx += nbElementsInPage;
2076 genericContextSetPageInfo(pageIdxStart + nbPages, nbElementsInPage, flag);
2077 nbElements -= nbElementsInPage;
2078 nbPages++;
2079 }
2080
2081 return nbPages;
2082}
2083
2084static uint8_t getNbPagesForGenericContents(const nbgl_genericContents_t *genericContents,
2085 uint8_t pageIdxStart,
2086 bool isSkippable)
2087{
2088 uint8_t nbPages = 0;
2089 nbgl_content_t content;
2090 const nbgl_content_t *p_content;
2091
2092 for (int i = 0; i < genericContents->nbContents; i++) {
2093 p_content = getContentAtIdx(genericContents, i, &content);
2094 if (p_content == NULL) {
2095 return 0;
2096 }
2097 nbPages += getNbPagesForContent(p_content,
2098 pageIdxStart + nbPages,
2099 (i == (genericContents->nbContents - 1)),
2100 isSkippable);
2101 }
2102
2103 return nbPages;
2104}
2105
2106static void prepareAddressConfirmationPages(const char *address,
2107 const nbgl_contentTagValueList_t *tagValueList,
2108 nbgl_content_t *firstPageContent,
2109 nbgl_content_t *secondPageContent)
2110{
2111 nbgl_contentTagValueConfirm_t *tagValueConfirm;
2112
2113 addressConfirmationContext.tagValuePairs[0].item = "Address";
2114 addressConfirmationContext.tagValuePairs[0].value = address;
2115 addressConfirmationContext.nbPairs = 1;
2116
2117 // First page
2118 firstPageContent->type = TAG_VALUE_CONFIRM;
2119 tagValueConfirm = &firstPageContent->content.tagValueConfirm;
2120
2121#ifdef NBGL_QRCODE
2122 tagValueConfirm->detailsButtonIcon = &QRCODE_ICON;
2123 // only use "Show as QR" when address & pairs are not fitting in a single page
2124 if ((tagValueList != NULL) && (tagValueList->nbPairs < ADDR_VERIF_NB_PAIRS)) {
2126 bool flag;
2127 // copy in intermediate structure
2128 for (uint8_t i = 0; i < tagValueList->nbPairs; i++) {
2129 memcpy(&addressConfirmationContext.tagValuePairs[1 + i],
2130 &tagValueList->pairs[i],
2131 sizeof(nbgl_contentTagValue_t));
2132 addressConfirmationContext.nbPairs++;
2133 }
2134 // check how many can fit in a page
2135 memcpy(&tmpList, tagValueList, sizeof(nbgl_contentTagValueList_t));
2136 tmpList.nbPairs = addressConfirmationContext.nbPairs;
2137 tmpList.pairs = addressConfirmationContext.tagValuePairs;
2138 addressConfirmationContext.nbPairs = getNbTagValuesInPage(
2139 addressConfirmationContext.nbPairs, &tmpList, 0, false, true, true, &flag);
2140 // if they don't all fit, keep only the address
2141 if (tmpList.nbPairs > addressConfirmationContext.nbPairs) {
2142 addressConfirmationContext.nbPairs = 1;
2143 }
2144 }
2145 else {
2146 tagValueConfirm->detailsButtonText = NULL;
2147 }
2148 tagValueConfirm->detailsButtonToken = ADDRESS_QRCODE_BUTTON_TOKEN;
2149#else // NBGL_QRCODE
2150 tagValueConfirm->detailsButtonText = NULL;
2151 tagValueConfirm->detailsButtonIcon = NULL;
2152#endif // NBGL_QRCODE
2153 tagValueConfirm->tuneId = TUNE_TAP_CASUAL;
2154 tagValueConfirm->tagValueList.nbPairs = addressConfirmationContext.nbPairs;
2155 tagValueConfirm->tagValueList.pairs = addressConfirmationContext.tagValuePairs;
2156 tagValueConfirm->tagValueList.smallCaseForValue = false;
2157 tagValueConfirm->tagValueList.nbMaxLinesForValue = 0;
2158 tagValueConfirm->tagValueList.wrapping = false;
2159 // if it's an extended address verif, it takes 2 pages, so display a "Tap to continue", and
2160 // no confirmation button
2161 if ((tagValueList != NULL)
2162 && (tagValueList->nbPairs > (addressConfirmationContext.nbPairs - 1))) {
2163 tagValueConfirm->detailsButtonText = "Show as QR";
2164 tagValueConfirm->confirmationText = NULL;
2165 // the second page is dedicated to the extended tag/value pairs
2166 secondPageContent->type = TAG_VALUE_CONFIRM;
2167 tagValueConfirm = &secondPageContent->content.tagValueConfirm;
2168 tagValueConfirm->confirmationText = "Confirm";
2169 tagValueConfirm->confirmationToken = CONFIRM_TOKEN;
2170 tagValueConfirm->detailsButtonText = NULL;
2171 tagValueConfirm->detailsButtonIcon = NULL;
2172 tagValueConfirm->tuneId = TUNE_TAP_CASUAL;
2173 memcpy(&tagValueConfirm->tagValueList, tagValueList, sizeof(nbgl_contentTagValueList_t));
2174 tagValueConfirm->tagValueList.nbPairs
2175 = tagValueList->nbPairs - (addressConfirmationContext.nbPairs - 1);
2176 tagValueConfirm->tagValueList.pairs
2177 = &tagValueList->pairs[addressConfirmationContext.nbPairs - 1];
2178 }
2179 else {
2180 // otherwise no tap to continue but a confirmation button
2181 tagValueConfirm->confirmationText = "Confirm";
2182 tagValueConfirm->confirmationToken = CONFIRM_TOKEN;
2183 }
2184}
2185
2186static void bundleNavStartHome(void)
2187{
2188 nbgl_homeAndSettingsContext_t *context = &bundleNavContext.homeAndSettings;
2189
2190 useCaseHomeExt(context->appName,
2191 context->appIcon,
2192 context->tagline,
2193 context->settingContents != NULL ? true : false,
2194 &context->homeAction,
2195 bundleNavStartSettings,
2196 context->quitCallback);
2197}
2198
2199static void bundleNavStartSettingsAtPage(uint8_t initSettingPage)
2200{
2201 nbgl_homeAndSettingsContext_t *context = &bundleNavContext.homeAndSettings;
2202
2203 nbgl_useCaseGenericSettings(context->appName,
2204 initSettingPage,
2205 context->settingContents,
2206 context->infosList,
2207 bundleNavStartHome);
2208}
2209
2210static void bundleNavStartSettings(void)
2211{
2212 bundleNavStartSettingsAtPage(0);
2213}
2214
2215static void bundleNavReviewConfirmRejection(void)
2216{
2217 bundleNavContext.review.choiceCallback(false);
2218}
2219
2220static void bundleNavReviewAskRejectionConfirmation(nbgl_operationType_t operationType,
2221 nbgl_callback_t callback)
2222{
2223 const char *title;
2224 const char *confirmText;
2225 // clear skip and blind bits
2226 operationType
2227 &= ~(SKIPPABLE_OPERATION | BLIND_OPERATION | RISKY_OPERATION | NO_THREAT_OPERATION);
2228 if (operationType == TYPE_TRANSACTION) {
2229 title = "Reject transaction?";
2230 confirmText = "Go back to transaction";
2231 }
2232 else if (operationType == TYPE_MESSAGE) {
2233 title = "Reject message?";
2234 confirmText = "Go back to message";
2235 }
2236 else {
2237 title = "Reject operation?";
2238 confirmText = "Go back to operation";
2239 }
2240
2241 // display a choice to confirm/cancel rejection
2242 nbgl_useCaseConfirm(title, NULL, "Yes, reject", confirmText, callback);
2243}
2244
2245static void bundleNavReviewChoice(bool confirm)
2246{
2247 if (confirm) {
2248 bundleNavContext.review.choiceCallback(true);
2249 }
2250 else {
2251 bundleNavReviewAskRejectionConfirmation(bundleNavContext.review.operationType,
2252 bundleNavReviewConfirmRejection);
2253 }
2254}
2255
2256static void bundleNavReviewStreamingConfirmRejection(void)
2257{
2258 bundleNavContext.reviewStreaming.choiceCallback(false);
2259}
2260
2261static void bundleNavReviewStreamingChoice(bool confirm)
2262{
2263 if (confirm) {
2264 // Display a spinner if it wasn't the finish step
2265 if (STARTING_CONTENT.type != INFO_LONG_PRESS) {
2266 nbgl_useCaseSpinner("Processing");
2267 }
2268 bundleNavContext.reviewStreaming.choiceCallback(true);
2269 }
2270 else {
2271 bundleNavReviewAskRejectionConfirmation(bundleNavContext.reviewStreaming.operationType,
2272 bundleNavReviewStreamingConfirmRejection);
2273 }
2274}
2275
2276// function used to display the security level page in modal
2277// it can be either a single page with text or QR code, or a list of touchable bars leading
2278// to single pages
2279static void displaySecurityReport(uint32_t set)
2280{
2281 nbgl_layoutDescription_t layoutDescription = {.modal = true,
2282 .withLeftBorder = true,
2283 .onActionCallback = modalLayoutTouchCallback,
2284 .tapActionText = NULL};
2286 .separationLine = true,
2287 .backAndText.icon = NULL,
2288 .backAndText.tuneId = TUNE_TAP_CASUAL,
2289 .backAndText.token = DISMISS_WARNING_TOKEN};
2290 nbgl_layoutFooter_t footerDesc
2291 = {.type = FOOTER_EMPTY, .separationLine = false, .emptySpace.height = 0};
2292 uint8_t i;
2293 uint8_t nbWarnings = 0;
2294 const char *provider;
2295
2296 reviewWithWarnCtx.modalLayout = nbgl_layoutGet(&layoutDescription);
2297
2298 // count the number of warnings
2299 for (i = 0; i < NB_WARNING_TYPES; i++) {
2300 if ((set & (1 << i)) && (i != W3C_NO_THREAT_WARN)) {
2301 nbWarnings++;
2302 }
2303 }
2304
2305 // display a list of touchable bars in some conditions:
2306 // - we must be in the first level of security report
2307 // - and be in the review itself (not from the intro/warning screen)
2308 //
2309 if ((reviewWithWarnCtx.securityReportLevel == 1) && (!reviewWithWarnCtx.isIntro)) {
2310 for (i = 0; i < NB_WARNING_TYPES; i++) {
2311 if (reviewWithWarnCtx.warning->predefinedSet & (1 << i)) {
2312 nbgl_layoutBar_t bar = {0};
2313 if ((i == BLIND_SIGNING_WARN) || (i == W3C_NO_THREAT_WARN) || (i == W3C_ISSUE_WARN)
2314 || (reviewWithWarnCtx.warning->providerMessage == NULL)) {
2315 bar.subText = securityReportItems[i].subText;
2316 }
2317 else {
2318 bar.subText = reviewWithWarnCtx.warning->providerMessage;
2319 }
2320 bar.text = securityReportItems[i].text;
2321 if (i != W3C_NO_THREAT_WARN) {
2322 bar.iconRight = &PUSH_ICON;
2323 bar.token = FIRST_WARN_BAR_TOKEN + i;
2324 bar.tuneId = TUNE_TAP_CASUAL;
2325 }
2326 else {
2328 }
2329 bar.iconLeft = securityReportItems[i].icon;
2330 nbgl_layoutAddTouchableBar(reviewWithWarnCtx.modalLayout, &bar);
2331 nbgl_layoutAddSeparationLine(reviewWithWarnCtx.modalLayout);
2332 }
2333 }
2334 headerDesc.backAndText.text = "Security report";
2335 nbgl_layoutAddHeader(reviewWithWarnCtx.modalLayout, &headerDesc);
2336 nbgl_layoutDraw(reviewWithWarnCtx.modalLayout);
2337 nbgl_refresh();
2338 return;
2339 }
2340 if (reviewWithWarnCtx.warning && reviewWithWarnCtx.warning->reportProvider) {
2341 provider = reviewWithWarnCtx.warning->reportProvider;
2342 }
2343 else {
2344 provider = "[unknown]";
2345 }
2346 if ((set & (1 << W3C_THREAT_DETECTED_WARN)) || (set & (1 << W3C_RISK_DETECTED_WARN))) {
2347 size_t urlLen = 0;
2348 char *destStr
2349 = tmpString
2350 + W3C_DESCRIPTION_MAX_LEN / 2; // use the second half of tmpString for strings
2351#ifdef NBGL_QRCODE
2352 // display a QR Code
2353 nbgl_layoutQRCode_t qrCode = {.url = destStr,
2354 .text1 = reviewWithWarnCtx.warning->reportUrl,
2355 .text2 = "Scan to view full report",
2356 .centered = true,
2357 .offsetY = 0};
2358 // add "https://"" as prefix of the given URL
2359 snprintf(destStr,
2360 W3C_DESCRIPTION_MAX_LEN / 2,
2361 "https://%s",
2362 reviewWithWarnCtx.warning->reportUrl);
2363 urlLen = strlen(destStr) + 1;
2364 nbgl_layoutAddQRCode(reviewWithWarnCtx.modalLayout, &qrCode);
2365 footerDesc.emptySpace.height = 24;
2366#endif // NBGL_QRCODE
2367 // use the next part of destStr for back bar text
2368 snprintf(destStr + urlLen, W3C_DESCRIPTION_MAX_LEN / 2 - urlLen, "%s report", provider);
2369 headerDesc.backAndText.text = destStr + urlLen;
2370 }
2371 else if (set & (1 << BLIND_SIGNING_WARN)) {
2372 // display a centered if in review
2373 nbgl_contentCenter_t info = {0};
2374 info.icon = &LARGE_WARNING_ICON;
2375 info.title = "This transaction cannot be Clear Signed";
2376 info.description
2377 = "This transaction or message cannot be decoded fully. If you choose to sign, you "
2378 "could be authorizing malicious actions that can drain your wallet.\n\nLearn more: "
2379 "ledger.com/e8";
2380 nbgl_layoutAddContentCenter(reviewWithWarnCtx.modalLayout, &info);
2381 footerDesc.emptySpace.height = SMALL_CENTERING_HEADER;
2382 headerDesc.separationLine = false;
2383 }
2384 else if (set & (1 << W3C_ISSUE_WARN)) {
2385 // if W3 Checks issue, display a centered info
2386 nbgl_contentCenter_t info = {0};
2387 info.icon = &LARGE_WARNING_ICON;
2388 info.title = "Transaction Check unavailable";
2389 info.description
2390 = "If you're not using the\nLedger Live app, Transaction Check might not work. If your "
2391 "are using Ledger Live, reject the transaction and try again.\n\nGet help at "
2392 "ledger.com/e11";
2393 nbgl_layoutAddContentCenter(reviewWithWarnCtx.modalLayout, &info);
2394 footerDesc.emptySpace.height = SMALL_CENTERING_HEADER;
2395 headerDesc.separationLine = false;
2396 }
2397 nbgl_layoutAddHeader(reviewWithWarnCtx.modalLayout, &headerDesc);
2398 if (footerDesc.emptySpace.height > 0) {
2399 nbgl_layoutAddExtendedFooter(reviewWithWarnCtx.modalLayout, &footerDesc);
2400 }
2401 nbgl_layoutDraw(reviewWithWarnCtx.modalLayout);
2402 nbgl_refresh();
2403}
2404
2405// function used to display the security level page in modal
2406// it can be either a single page with text or QR code, or a list of touchable bars leading
2407// to single pages
2408static void displayCustomizedSecurityReport(const nbgl_warningDetails_t *details)
2409{
2410 nbgl_layoutDescription_t layoutDescription = {.modal = true,
2411 .withLeftBorder = true,
2412 .onActionCallback = modalLayoutTouchCallback,
2413 .tapActionText = NULL};
2415 .separationLine = true,
2416 .backAndText.icon = NULL,
2417 .backAndText.tuneId = TUNE_TAP_CASUAL,
2418 .backAndText.token = DISMISS_WARNING_TOKEN};
2419 uint8_t i;
2420
2421 reviewWithWarnCtx.modalLayout = nbgl_layoutGet(&layoutDescription);
2422 headerDesc.backAndText.text = details->title;
2423 nbgl_layoutAddHeader(reviewWithWarnCtx.modalLayout, &headerDesc);
2424 if (details->type == BAR_LIST_WARNING) {
2425 // if more than one warning warning, so use a list of touchable bars
2426 for (i = 0; i < details->barList.nbBars; i++) {
2427 nbgl_layoutBar_t bar;
2428 bar.text = details->barList.texts[i];
2429 bar.subText = details->barList.subTexts[i];
2430 bar.iconRight = &PUSH_ICON;
2431 bar.iconLeft = details->barList.icons[i];
2432 bar.token = FIRST_WARN_BAR_TOKEN + i;
2433 bar.tuneId = TUNE_TAP_CASUAL;
2434 bar.large = false;
2435 bar.inactive = false;
2436 nbgl_layoutAddTouchableBar(reviewWithWarnCtx.modalLayout, &bar);
2437 nbgl_layoutAddSeparationLine(reviewWithWarnCtx.modalLayout);
2438 }
2439 }
2440 else if (details->type == QRCODE_WARNING) {
2441#ifdef NBGL_QRCODE
2442 // display a QR Code
2443 nbgl_layoutAddQRCode(reviewWithWarnCtx.modalLayout, &details->qrCode);
2444#endif // NBGL_QRCODE
2445 headerDesc.backAndText.text = details->title;
2446 }
2447 else if (details->type == CENTERED_INFO_WARNING) {
2448 // display a centered info
2449 nbgl_layoutAddContentCenter(reviewWithWarnCtx.modalLayout, &details->centeredInfo);
2450 headerDesc.separationLine = false;
2451 }
2452 nbgl_layoutDraw(reviewWithWarnCtx.modalLayout);
2453 nbgl_refresh();
2454}
2455
2456// function used to display the initial warning page when starting a "review with warning"
2457static void displayInitialWarning(void)
2458{
2459 // Play notification sound
2460#ifdef HAVE_PIEZO_SOUND
2461 tune_index_e tune = TUNE_RESERVED;
2462#endif // HAVE_PIEZO_SOUND
2463 nbgl_layoutDescription_t layoutDescription;
2464 nbgl_layoutChoiceButtons_t buttonsInfo = {.bottomText = "Continue anyway",
2465 .token = WARNING_CHOICE_TOKEN,
2466 .topText = "Back to safety",
2467 .style = ROUNDED_AND_FOOTER_STYLE,
2468 .tuneId = TUNE_TAP_CASUAL};
2469 nbgl_layoutHeader_t headerDesc = {.type = HEADER_EMPTY,
2470 .separationLine = false,
2471 .emptySpace.height = MEDIUM_CENTERING_HEADER};
2472 uint32_t set = reviewWithWarnCtx.warning->predefinedSet
2473 & ~((1 << W3C_NO_THREAT_WARN) | (1 << W3C_ISSUE_WARN));
2474
2475 reviewWithWarnCtx.isIntro = true;
2476
2477 layoutDescription.modal = false;
2478 layoutDescription.withLeftBorder = true;
2479
2480 layoutDescription.onActionCallback = layoutTouchCallback;
2481 layoutDescription.tapActionText = NULL;
2482
2483 layoutDescription.ticker.tickerCallback = NULL;
2484 reviewWithWarnCtx.layoutCtx = nbgl_layoutGet(&layoutDescription);
2485
2486 nbgl_layoutAddHeader(reviewWithWarnCtx.layoutCtx, &headerDesc);
2487 if (reviewWithWarnCtx.warning->predefinedSet != 0) {
2489 reviewWithWarnCtx.layoutCtx,
2490 (set == (1 << BLIND_SIGNING_WARN)) ? &INFO_I_ICON : &QRCODE_ICON,
2491 WARNING_BUTTON_TOKEN,
2492 TUNE_TAP_CASUAL);
2493 }
2494 else if (reviewWithWarnCtx.warning->introTopRightIcon != NULL) {
2495 nbgl_layoutAddTopRightButton(reviewWithWarnCtx.layoutCtx,
2496 reviewWithWarnCtx.warning->introTopRightIcon,
2497 WARNING_BUTTON_TOKEN,
2498 TUNE_TAP_CASUAL);
2499 }
2500 // add main content
2501 // if predefined content is configured, use it preferably
2502 if (reviewWithWarnCtx.warning->predefinedSet != 0) {
2503 nbgl_contentCenter_t info = {0};
2504
2505 const char *provider;
2506
2507 // default icon
2508 info.icon = &LARGE_WARNING_ICON;
2509
2510 // use small title only if not Blind signing only
2511 if (set != (1 << BLIND_SIGNING_WARN)) {
2512 if (reviewWithWarnCtx.warning->reportProvider) {
2513 provider = reviewWithWarnCtx.warning->reportProvider;
2514 }
2515 else {
2516 provider = "[unknown]";
2517 }
2518 info.smallTitle = tmpString;
2519 snprintf(tmpString, W3C_DESCRIPTION_MAX_LEN, "Detected by %s", provider);
2520 }
2521
2522 if (set == (1 << BLIND_SIGNING_WARN)) {
2523 info.title = "Blind signing ahead";
2524 info.description
2525 = "This transaction's details are not fully verifiable. If you sign, you could "
2526 "lose all your assets.";
2527 buttonsInfo.bottomText = "Continue anyway";
2528#ifdef HAVE_PIEZO_SOUND
2529 tune = TUNE_NEUTRAL;
2530#endif // HAVE_PIEZO_SOUND
2531 }
2532 else if (set & (1 << W3C_RISK_DETECTED_WARN)) {
2533 info.title = "Potential risk";
2534 if (reviewWithWarnCtx.warning->providerMessage == NULL) {
2535 info.description = "Unidentified risk";
2536 }
2537 else {
2538 info.description = reviewWithWarnCtx.warning->providerMessage;
2539 }
2540 buttonsInfo.bottomText = "Accept risk and continue";
2541#ifdef HAVE_PIEZO_SOUND
2542 tune = TUNE_NEUTRAL;
2543#endif // HAVE_PIEZO_SOUND
2544 }
2545 else if (set & (1 << W3C_THREAT_DETECTED_WARN)) {
2546 info.title = "Critical threat";
2547 if (reviewWithWarnCtx.warning->providerMessage == NULL) {
2548 info.description = "Unidentified threat";
2549 }
2550 else {
2551 info.description = reviewWithWarnCtx.warning->providerMessage;
2552 }
2553 buttonsInfo.bottomText = "Accept threat and continue";
2554#ifdef HAVE_PIEZO_SOUND
2555 tune = TUNE_ERROR;
2556#endif // HAVE_PIEZO_SOUND
2557 }
2558 nbgl_layoutAddContentCenter(reviewWithWarnCtx.layoutCtx, &info);
2559 }
2560 else if (reviewWithWarnCtx.warning->info != NULL) {
2561 // if no predefined content, use custom one
2562 nbgl_layoutAddContentCenter(reviewWithWarnCtx.layoutCtx, reviewWithWarnCtx.warning->info);
2563#ifdef HAVE_PIEZO_SOUND
2564 tune = TUNE_LOOK_AT_ME;
2565#endif // HAVE_PIEZO_SOUND
2566 }
2567 // add button and footer on bottom
2568 nbgl_layoutAddChoiceButtons(reviewWithWarnCtx.layoutCtx, &buttonsInfo);
2569
2570#ifdef HAVE_PIEZO_SOUND
2571 if (tune != TUNE_RESERVED) {
2572 os_io_seph_cmd_piezo_play_tune(tune);
2573 }
2574#endif // HAVE_PIEZO_SOUND
2575 nbgl_layoutDraw(reviewWithWarnCtx.layoutCtx);
2576 nbgl_refresh();
2577}
2578
2579// function to factorize code for reviews tipbox
2580static void initWarningTipBox(const nbgl_tipBox_t *tipBox)
2581{
2582 const char *predefinedTipBoxText = NULL;
2583
2584 // if warning is valid and a warning requires a tip-box
2585 if (reviewWithWarnCtx.warning) {
2586 if (reviewWithWarnCtx.warning->predefinedSet & (1 << W3C_ISSUE_WARN)) {
2587 if (reviewWithWarnCtx.warning->predefinedSet & (1 << BLIND_SIGNING_WARN)) {
2588 predefinedTipBoxText = "Transaction Check unavailable.\nBlind signing required.";
2589 }
2590 else {
2591 predefinedTipBoxText = "Transaction Check unavailable";
2592 }
2593 }
2594 else if (reviewWithWarnCtx.warning->predefinedSet & (1 << W3C_THREAT_DETECTED_WARN)) {
2595 if (reviewWithWarnCtx.warning->predefinedSet & (1 << BLIND_SIGNING_WARN)) {
2596 predefinedTipBoxText = "Critical threat detected.\nBlind signing required.";
2597 }
2598 else {
2599 predefinedTipBoxText = "Critical threat detected.";
2600 }
2601 }
2602 else if (reviewWithWarnCtx.warning->predefinedSet & (1 << W3C_RISK_DETECTED_WARN)) {
2603 if (reviewWithWarnCtx.warning->predefinedSet & (1 << BLIND_SIGNING_WARN)) {
2604 predefinedTipBoxText = "Potential risk detected.\nBlind signing required.";
2605 }
2606 else {
2607 predefinedTipBoxText = "Potential risk detected.";
2608 }
2609 }
2610 else if (reviewWithWarnCtx.warning->predefinedSet & (1 << W3C_NO_THREAT_WARN)) {
2611 if (reviewWithWarnCtx.warning->predefinedSet & (1 << BLIND_SIGNING_WARN)) {
2612 predefinedTipBoxText
2613 = "No threat detected by Transaction Check but blind signing required.";
2614 }
2615 else {
2616 predefinedTipBoxText = "No threat detected by Transaction Check.";
2617 }
2618 }
2619 else if (reviewWithWarnCtx.warning->predefinedSet & (1 << BLIND_SIGNING_WARN)) {
2620 predefinedTipBoxText = "Blind signing required.";
2621 }
2622 }
2623
2624 if ((tipBox != NULL) || (predefinedTipBoxText != NULL)) {
2625 // do not display "Swipe to review" if a tip-box is displayed
2626 STARTING_CONTENT.content.extendedCenter.contentCenter.subText = NULL;
2627 if (predefinedTipBoxText != NULL) {
2628 genericContext.validWarningCtx = true;
2629 STARTING_CONTENT.content.extendedCenter.tipBox.icon = NULL;
2630 STARTING_CONTENT.content.extendedCenter.tipBox.text = predefinedTipBoxText;
2631 }
2632 else {
2633 STARTING_CONTENT.content.extendedCenter.tipBox.icon = tipBox->icon;
2634 STARTING_CONTENT.content.extendedCenter.tipBox.text = tipBox->text;
2635 }
2636 STARTING_CONTENT.content.extendedCenter.tipBox.token = TIP_BOX_TOKEN;
2637 STARTING_CONTENT.content.extendedCenter.tipBox.tuneId = TUNE_TAP_CASUAL;
2638 }
2639}
2640
2641// function to factorize code for all simple reviews
2642static void useCaseReview(nbgl_operationType_t operationType,
2643 const nbgl_contentTagValueList_t *tagValueList,
2644 const nbgl_icon_details_t *icon,
2645 const char *reviewTitle,
2646 const char *reviewSubTitle,
2647 const char *finishTitle,
2648 const nbgl_tipBox_t *tipBox,
2649 nbgl_choiceCallback_t choiceCallback,
2650 bool isLight,
2651 bool playNotifSound)
2652{
2653 reset_callbacks();
2654 memset(&genericContext, 0, sizeof(genericContext));
2655
2656 bundleNavContext.review.operationType = operationType;
2657 bundleNavContext.review.choiceCallback = choiceCallback;
2658
2659 // memorize context
2660 onChoice = bundleNavReviewChoice;
2661 navType = GENERIC_NAV;
2662 pageTitle = NULL;
2663
2664 genericContext.genericContents.contentsList = localContentsList;
2665 genericContext.genericContents.nbContents = 3;
2666 memset(localContentsList, 0, 3 * sizeof(nbgl_content_t));
2667
2668 // First a centered info
2669 STARTING_CONTENT.type = EXTENDED_CENTER;
2670 prepareReviewFirstPage(
2671 &STARTING_CONTENT.content.extendedCenter.contentCenter, icon, reviewTitle, reviewSubTitle);
2672
2673 // Prepare un tipbox if needed
2674 initWarningTipBox(tipBox);
2675
2676 // Then the tag/value pairs
2677 localContentsList[1].type = TAG_VALUE_LIST;
2678 memcpy(&localContentsList[1].content.tagValueList,
2679 tagValueList,
2681 localContentsList[1].contentActionCallback = tagValueList->actionCallback;
2682
2683 // The last page
2684 if (isLight) {
2685 localContentsList[2].type = INFO_BUTTON;
2686 prepareReviewLightLastPage(
2687 operationType, &localContentsList[2].content.infoButton, icon, finishTitle);
2688 }
2689 else {
2690 localContentsList[2].type = INFO_LONG_PRESS;
2691 prepareReviewLastPage(
2692 operationType, &localContentsList[2].content.infoLongPress, icon, finishTitle);
2693 }
2694
2695 // compute number of pages & fill navigation structure
2696 uint8_t nbPages = getNbPagesForGenericContents(
2697 &genericContext.genericContents, 0, (operationType & SKIPPABLE_OPERATION));
2698 prepareNavInfo(true, nbPages, getRejectReviewText(operationType));
2699
2700 // Play notification sound if required
2701 if (playNotifSound) {
2702#ifdef HAVE_PIEZO_SOUND
2703 os_io_seph_cmd_piezo_play_tune(TUNE_LOOK_AT_ME);
2704#endif // HAVE_PIEZO_SOUND
2705 }
2706
2707 displayGenericContextPage(0, true);
2708}
2709
2710// function to factorize code for all streaming reviews
2711static void useCaseReviewStreamingStart(nbgl_operationType_t operationType,
2712 const nbgl_icon_details_t *icon,
2713 const char *reviewTitle,
2714 const char *reviewSubTitle,
2715 nbgl_choiceCallback_t choiceCallback,
2716 bool playNotifSound)
2717{
2718 reset_callbacks();
2719 memset(&genericContext, 0, sizeof(genericContext));
2720
2721 bundleNavContext.reviewStreaming.operationType = operationType;
2722 bundleNavContext.reviewStreaming.choiceCallback = choiceCallback;
2723 bundleNavContext.reviewStreaming.icon = icon;
2724
2725 // memorize context
2726 onChoice = bundleNavReviewStreamingChoice;
2727 navType = STREAMING_NAV;
2728 pageTitle = NULL;
2729
2730 genericContext.genericContents.contentsList = localContentsList;
2731 genericContext.genericContents.nbContents = 1;
2732 memset(localContentsList, 0, 1 * sizeof(nbgl_content_t));
2733
2734 // First a centered info
2735 STARTING_CONTENT.type = EXTENDED_CENTER;
2736 prepareReviewFirstPage(
2737 &STARTING_CONTENT.content.extendedCenter.contentCenter, icon, reviewTitle, reviewSubTitle);
2738
2739 // Prepare un tipbox if needed
2740 initWarningTipBox(NULL);
2741
2742 // compute number of pages & fill navigation structure
2743 bundleNavContext.reviewStreaming.stepPageNb = getNbPagesForGenericContents(
2744 &genericContext.genericContents, 0, (operationType & SKIPPABLE_OPERATION));
2745 prepareNavInfo(true, NBGL_NO_PROGRESS_INDICATOR, getRejectReviewText(operationType));
2746 // no back button on first page
2747 navInfo.navWithButtons.backButton = false;
2748
2749 // Play notification sound if required
2750 if (playNotifSound) {
2751#ifdef HAVE_PIEZO_SOUND
2752 os_io_seph_cmd_piezo_play_tune(TUNE_LOOK_AT_ME);
2753#endif // HAVE_PIEZO_SOUND
2754 }
2755
2756 displayGenericContextPage(0, true);
2757}
2758
2775static void useCaseHomeExt(const char *appName,
2776 const nbgl_icon_details_t *appIcon,
2777 const char *tagline,
2778 bool withSettings,
2779 nbgl_homeAction_t *homeAction,
2780 nbgl_callback_t topRightCallback,
2781 nbgl_callback_t quitCallback)
2782{
2783 reset_callbacks();
2784
2785 nbgl_pageInfoDescription_t info = {.centeredInfo.icon = appIcon,
2786 .centeredInfo.text1 = appName,
2787 .centeredInfo.text3 = NULL,
2788 .centeredInfo.style = LARGE_CASE_INFO,
2789 .centeredInfo.offsetY = 0,
2790 .footerText = NULL,
2791 .bottomButtonStyle = QUIT_APP_TEXT,
2792 .tapActionText = NULL,
2793 .topRightStyle = withSettings ? SETTINGS_ICON : INFO_ICON,
2794 .topRightToken = CONTINUE_TOKEN,
2795 .tuneId = TUNE_TAP_CASUAL};
2796 if ((homeAction->text != NULL) || (homeAction->icon != NULL)) {
2797 // trick to use ACTION_BUTTON_TOKEN for action and quit, with index used to distinguish
2798 info.bottomButtonsToken = ACTION_BUTTON_TOKEN;
2799 onAction = homeAction->callback;
2800 info.actionButtonText = homeAction->text;
2801 info.actionButtonIcon = homeAction->icon;
2804 }
2805 else {
2806 info.bottomButtonsToken = QUIT_TOKEN;
2807 onAction = NULL;
2808 info.actionButtonText = NULL;
2809 info.actionButtonIcon = NULL;
2810 }
2811 if (tagline == NULL) {
2812 if (strlen(appName) > MAX_APP_NAME_FOR_SDK_TAGLINE) {
2813 snprintf(tmpString,
2815 "This app enables signing\ntransactions on its network.");
2816 }
2817 else {
2818 snprintf(tmpString,
2820 "%s %s\n%s",
2822 appName,
2824 }
2825
2826 // If there is more than 3 lines, it means the appName was split, so we put it on the next
2827 // line
2828 if (nbgl_getTextNbLinesInWidth(SMALL_REGULAR_FONT, tmpString, AVAILABLE_WIDTH, false) > 3) {
2829 snprintf(tmpString,
2831 "%s\n%s %s",
2833 appName,
2835 }
2836 info.centeredInfo.text2 = tmpString;
2837 }
2838 else {
2839 info.centeredInfo.text2 = tagline;
2840 }
2841
2842 onContinue = topRightCallback;
2843 onQuit = quitCallback;
2844
2845 pageContext = nbgl_pageDrawInfo(&pageCallback, NULL, &info);
2847}
2848
2857static void displayDetails(const char *tag, const char *value, bool wrapping)
2858{
2859 memset(&detailsContext, 0, sizeof(detailsContext));
2860
2861 uint16_t nbLines
2862 = nbgl_getTextNbLinesInWidth(SMALL_REGULAR_FONT, value, AVAILABLE_WIDTH, wrapping);
2863
2864 // initialize context
2865 detailsContext.tag = tag;
2866 detailsContext.value = value;
2867 detailsContext.nbPages = (nbLines + NB_MAX_LINES_IN_DETAILS - 1) / NB_MAX_LINES_IN_DETAILS;
2868 detailsContext.currentPage = 0;
2869 detailsContext.wrapping = wrapping;
2870 // add some spare for room lost with "..." substitution
2871 if (detailsContext.nbPages > 1) {
2872 uint16_t nbLostChars = (detailsContext.nbPages - 1) * 3;
2873 uint16_t nbLostLines = (nbLostChars + ((AVAILABLE_WIDTH) / 16) - 1)
2874 / ((AVAILABLE_WIDTH) / 16); // 16 for average char width
2875 uint8_t nbLinesInLastPage
2876 = nbLines - ((detailsContext.nbPages - 1) * NB_MAX_LINES_IN_DETAILS);
2877
2878 detailsContext.nbPages += nbLostLines / NB_MAX_LINES_IN_DETAILS;
2879 if ((nbLinesInLastPage + (nbLostLines % NB_MAX_LINES_IN_DETAILS))
2881 detailsContext.nbPages++;
2882 }
2883 }
2884
2885 displayDetailsPage(0, true);
2886}
2887
2888// function used to display the modal containing alias tag-value pairs
2889static void displayTagValueListModal(const nbgl_contentTagValueList_t *tagValues)
2890{
2891 uint8_t nbElements = 0;
2892 uint8_t nbElementsInPage;
2893 uint8_t elemIdx = 0;
2894
2895 // initialize context
2896 memset(&detailsContext, 0, sizeof(detailsContext));
2897 nbElements = tagValues->nbPairs;
2898
2899 while (nbElements > 0) {
2900 nbElementsInPage = getNbTagValuesInDetailsPage(nbElements, tagValues, elemIdx);
2901
2902 elemIdx += nbElementsInPage;
2903 modalContextSetPageInfo(detailsContext.nbPages, nbElementsInPage);
2904 nbElements -= nbElementsInPage;
2905 detailsContext.nbPages++;
2906 }
2907
2908 displayTagValueListModalPage(0, true);
2909}
2910
2911/**********************
2912 * GLOBAL FUNCTIONS
2913 **********************/
2914
2927uint8_t nbgl_useCaseGetNbTagValuesInPage(uint8_t nbPairs,
2928 const nbgl_contentTagValueList_t *tagValueList,
2929 uint8_t startIndex,
2930 bool *requireSpecificDisplay)
2931{
2932 return getNbTagValuesInPage(
2933 nbPairs, tagValueList, startIndex, false, false, false, requireSpecificDisplay);
2934}
2935
2949uint8_t nbgl_useCaseGetNbTagValuesInPageExt(uint8_t nbPairs,
2950 const nbgl_contentTagValueList_t *tagValueList,
2951 uint8_t startIndex,
2952 bool isSkippable,
2953 bool *requireSpecificDisplay)
2954{
2955 return getNbTagValuesInPage(
2956 nbPairs, tagValueList, startIndex, isSkippable, false, false, requireSpecificDisplay);
2957}
2958
2968uint8_t nbgl_useCaseGetNbInfosInPage(uint8_t nbInfos,
2969 const nbgl_contentInfoList_t *infosList,
2970 uint8_t startIndex,
2971 bool withNav)
2972{
2973 uint8_t nbInfosInPage = 0;
2974 uint16_t currentHeight = 0;
2975 uint16_t previousHeight;
2976 uint16_t navHeight = withNav ? SIMPLE_FOOTER_HEIGHT : 0;
2977 const char *const *infoContents = PIC(infosList->infoContents);
2978
2979 while (nbInfosInPage < nbInfos) {
2980 // The type string must be a 1 liner and its height is LIST_ITEM_MIN_TEXT_HEIGHT
2981 currentHeight
2982 += LIST_ITEM_MIN_TEXT_HEIGHT + 2 * LIST_ITEM_PRE_HEADING + LIST_ITEM_HEADING_SUB_TEXT;
2983
2984 // content height
2985 currentHeight += nbgl_getTextHeightInWidth(SMALL_REGULAR_FONT,
2986 PIC(infoContents[startIndex + nbInfosInPage]),
2988 true);
2989 // if height is over the limit
2990 if (currentHeight >= (INFOS_AREA_HEIGHT - navHeight)) {
2991 // if there was no nav, now there will be, so it can be necessary to remove the last
2992 // item
2993 if (!withNav && (previousHeight >= (INFOS_AREA_HEIGHT - SIMPLE_FOOTER_HEIGHT))) {
2994 nbInfosInPage--;
2995 }
2996 break;
2997 }
2998 previousHeight = currentHeight;
2999 nbInfosInPage++;
3000 }
3001 return nbInfosInPage;
3002}
3003
3013uint8_t nbgl_useCaseGetNbSwitchesInPage(uint8_t nbSwitches,
3014 const nbgl_contentSwitchesList_t *switchesList,
3015 uint8_t startIndex,
3016 bool withNav)
3017{
3018 uint8_t nbSwitchesInPage = 0;
3019 uint16_t currentHeight = 0;
3020 uint16_t previousHeight;
3021 uint16_t navHeight = withNav ? SIMPLE_FOOTER_HEIGHT : 0;
3022 nbgl_contentSwitch_t *switchArray = (nbgl_contentSwitch_t *) PIC(switchesList->switches);
3023
3024 while (nbSwitchesInPage < nbSwitches) {
3025 // The text string must be a 1 liner and its height is LIST_ITEM_MIN_TEXT_HEIGHT
3026 currentHeight
3027 += LIST_ITEM_MIN_TEXT_HEIGHT + 2 * LIST_ITEM_PRE_HEADING + LIST_ITEM_HEADING_SUB_TEXT;
3028
3029 // sub-text height
3030 currentHeight
3031 += nbgl_getTextHeightInWidth(SMALL_REGULAR_FONT,
3032 switchArray[startIndex + nbSwitchesInPage].subText,
3034 true);
3035 // if height is over the limit
3036 if (currentHeight >= (INFOS_AREA_HEIGHT - navHeight)) {
3037 break;
3038 }
3039 previousHeight = currentHeight;
3040 nbSwitchesInPage++;
3041 }
3042 // if there was no nav, now there will be, so it can be necessary to remove the last
3043 // item
3044 if (!withNav && (previousHeight >= (INFOS_AREA_HEIGHT - SIMPLE_FOOTER_HEIGHT))) {
3045 nbSwitchesInPage--;
3046 }
3047 return nbSwitchesInPage;
3048}
3049
3059uint8_t nbgl_useCaseGetNbBarsInPage(uint8_t nbBars,
3060 const nbgl_contentBarsList_t *barsList,
3061 uint8_t startIndex,
3062 bool withNav)
3063{
3064 uint8_t nbBarsInPage = 0;
3065 uint16_t currentHeight = 0;
3066 uint16_t previousHeight;
3067 uint16_t navHeight = withNav ? SIMPLE_FOOTER_HEIGHT : 0;
3068
3069 UNUSED(barsList);
3070 UNUSED(startIndex);
3071
3072 while (nbBarsInPage < nbBars) {
3073 currentHeight += LIST_ITEM_MIN_TEXT_HEIGHT + 2 * LIST_ITEM_PRE_HEADING;
3074 // if height is over the limit
3075 if (currentHeight >= (INFOS_AREA_HEIGHT - navHeight)) {
3076 break;
3077 }
3078 previousHeight = currentHeight;
3079 nbBarsInPage++;
3080 }
3081 // if there was no nav, now there may will be, so it can be necessary to remove the last
3082 // item
3083 if (!withNav && (previousHeight >= (INFOS_AREA_HEIGHT - SIMPLE_FOOTER_HEIGHT))) {
3084 nbBarsInPage--;
3085 }
3086 return nbBarsInPage;
3087}
3088
3098uint8_t nbgl_useCaseGetNbChoicesInPage(uint8_t nbChoices,
3099 const nbgl_contentRadioChoice_t *choicesList,
3100 uint8_t startIndex,
3101 bool withNav)
3102{
3103 uint8_t nbChoicesInPage = 0;
3104 uint16_t currentHeight = 0;
3105 uint16_t previousHeight;
3106 uint16_t navHeight = withNav ? SIMPLE_FOOTER_HEIGHT : 0;
3107
3108 UNUSED(choicesList);
3109 UNUSED(startIndex);
3110
3111 while (nbChoicesInPage < nbChoices) {
3112 currentHeight += LIST_ITEM_MIN_TEXT_HEIGHT + 2 * LIST_ITEM_PRE_HEADING;
3113 // if height is over the limit
3114 if (currentHeight >= (INFOS_AREA_HEIGHT - navHeight)) {
3115 // if there was no nav, now there will be, so it can be necessary to remove the last
3116 // item
3117 if (!withNav && (previousHeight >= (INFOS_AREA_HEIGHT - SIMPLE_FOOTER_HEIGHT))) {
3118 nbChoicesInPage--;
3119 }
3120 break;
3121 }
3122 previousHeight = currentHeight;
3123 nbChoicesInPage++;
3124 }
3125 return nbChoicesInPage;
3126}
3127
3135{
3136 uint8_t nbPages = 0;
3137 uint8_t nbPairs = tagValueList->nbPairs;
3138 uint8_t nbPairsInPage;
3139 uint8_t i = 0;
3140 bool flag;
3141
3142 while (i < tagValueList->nbPairs) {
3143 // upper margin
3144 nbPairsInPage = nbgl_useCaseGetNbTagValuesInPageExt(nbPairs, tagValueList, i, false, &flag);
3145 i += nbPairsInPage;
3146 nbPairs -= nbPairsInPage;
3147 nbPages++;
3148 }
3149 return nbPages;
3150}
3151
3156void nbgl_useCaseHome(const char *appName,
3157 const nbgl_icon_details_t *appIcon,
3158 const char *tagline,
3159 bool withSettings,
3160 nbgl_callback_t topRightCallback,
3161 nbgl_callback_t quitCallback)
3162{
3163 nbgl_homeAction_t homeAction = {0};
3164 useCaseHomeExt(
3165 appName, appIcon, tagline, withSettings, &homeAction, topRightCallback, quitCallback);
3166}
3167
3172void nbgl_useCaseHomeExt(const char *appName,
3173 const nbgl_icon_details_t *appIcon,
3174 const char *tagline,
3175 bool withSettings,
3176 const char *actionButtonText,
3177 nbgl_callback_t actionCallback,
3178 nbgl_callback_t topRightCallback,
3179 nbgl_callback_t quitCallback)
3180{
3181 nbgl_homeAction_t homeAction = {.callback = actionCallback,
3182 .icon = NULL,
3183 .style = STRONG_HOME_ACTION,
3184 .text = actionButtonText};
3185
3186 useCaseHomeExt(
3187 appName, appIcon, tagline, withSettings, &homeAction, topRightCallback, quitCallback);
3188}
3189
3203void nbgl_useCaseNavigableContent(const char *title,
3204 uint8_t initPage,
3205 uint8_t nbPages,
3206 nbgl_callback_t quitCallback,
3207 nbgl_navCallback_t navCallback,
3208 nbgl_layoutTouchCallback_t controlsCallback)
3209{
3210 reset_callbacks();
3211
3212 // memorize context
3213 onQuit = quitCallback;
3214 onNav = navCallback;
3215 onControls = controlsCallback;
3216 pageTitle = title;
3217 navType = SETTINGS_NAV;
3218
3219 // fill navigation structure
3220 prepareNavInfo(false, nbPages, NULL);
3221
3222 displaySettingsPage(initPage, true);
3223}
3224
3230void nbgl_useCaseSettings(const char *title,
3231 uint8_t initPage,
3232 uint8_t nbPages,
3233 bool touchable,
3234 nbgl_callback_t quitCallback,
3235 nbgl_navCallback_t navCallback,
3236 nbgl_layoutTouchCallback_t controlsCallback)
3237{
3238 UNUSED(touchable);
3240 title, initPage, nbPages, quitCallback, navCallback, controlsCallback);
3241}
3242
3255void nbgl_useCaseGenericSettings(const char *appName,
3256 uint8_t initPage,
3257 const nbgl_genericContents_t *settingContents,
3258 const nbgl_contentInfoList_t *infosList,
3259 nbgl_callback_t quitCallback)
3260{
3261 reset_callbacks();
3262 memset(&genericContext, 0, sizeof(genericContext));
3263
3264 // memorize context
3265 onQuit = quitCallback;
3266 pageTitle = appName;
3267 navType = GENERIC_NAV;
3268
3269 if (settingContents != NULL) {
3270 memcpy(&genericContext.genericContents, settingContents, sizeof(nbgl_genericContents_t));
3271 }
3272 if (infosList != NULL) {
3273 genericContext.hasFinishingContent = true;
3274 memset(&FINISHING_CONTENT, 0, sizeof(nbgl_content_t));
3275 FINISHING_CONTENT.type = INFOS_LIST;
3276 memcpy(&FINISHING_CONTENT.content, infosList, sizeof(nbgl_content_u));
3277 }
3278
3279 // fill navigation structure
3280 uint8_t nbPages = getNbPagesForGenericContents(&genericContext.genericContents, 0, false);
3281 if (infosList != NULL) {
3282 nbPages += getNbPagesForContent(&FINISHING_CONTENT, nbPages, true, false);
3283 }
3284
3285 prepareNavInfo(false, nbPages, NULL);
3286
3287 displayGenericContextPage(initPage, true);
3288}
3289
3301void nbgl_useCaseGenericConfiguration(const char *title,
3302 uint8_t initPage,
3303 const nbgl_genericContents_t *contents,
3304 nbgl_callback_t quitCallback)
3305{
3306 nbgl_useCaseGenericSettings(title, initPage, contents, NULL, quitCallback);
3307}
3308
3326 const char *appName,
3327 const nbgl_icon_details_t *appIcon,
3328 const char *tagline,
3329 const uint8_t
3330 initSettingPage, // if not INIT_HOME_PAGE, start directly the corresponding setting page
3331 const nbgl_genericContents_t *settingContents,
3332 const nbgl_contentInfoList_t *infosList,
3333 const nbgl_homeAction_t *action, // Set to NULL if no additional action
3334 nbgl_callback_t quitCallback)
3335{
3336 nbgl_homeAndSettingsContext_t *context = &bundleNavContext.homeAndSettings;
3337
3338 context->appName = appName;
3339 context->appIcon = appIcon;
3340 context->tagline = tagline;
3341 context->settingContents = settingContents;
3342 context->infosList = infosList;
3343 if (action != NULL) {
3344 memcpy(&context->homeAction, action, sizeof(nbgl_homeAction_t));
3345 }
3346 else {
3347 memset(&context->homeAction, 0, sizeof(nbgl_homeAction_t));
3348 }
3349 context->quitCallback = quitCallback;
3350
3351 if (initSettingPage != INIT_HOME_PAGE) {
3352 bundleNavStartSettingsAtPage(initSettingPage);
3353 }
3354 else {
3355 bundleNavStartHome();
3356 }
3357}
3358
3366void nbgl_useCaseStatus(const char *message, bool isSuccess, nbgl_callback_t quitCallback)
3367{
3368 reset_callbacks();
3369
3370 nbgl_screenTickerConfiguration_t ticker = {.tickerCallback = &tickerCallback,
3371 .tickerIntervale = 0, // not periodic
3372 .tickerValue = STATUS_SCREEN_DURATION};
3373
3374 onQuit = quitCallback;
3375 if (isSuccess) {
3376#ifdef HAVE_PIEZO_SOUND
3377 os_io_seph_cmd_piezo_play_tune(TUNE_LEDGER_MOMENT);
3378#endif // HAVE_PIEZO_SOUND
3379
3380 pageContext = nbgl_pageDrawLedgerInfo(&pageCallback, &ticker, message, QUIT_TOKEN);
3381 }
3382 else {
3384 .footerText = NULL,
3385 .centeredInfo.icon = &DENIED_CIRCLE_ICON,
3386 .centeredInfo.offsetY = SMALL_FOOTER_HEIGHT / 2,
3387 .centeredInfo.onTop = false,
3388 .centeredInfo.style = LARGE_CASE_INFO,
3389 .centeredInfo.text1 = message,
3390 .centeredInfo.text2 = NULL,
3391 .centeredInfo.text3 = NULL,
3392 .tapActionText = "",
3393 .isSwipeable = false,
3394 .tapActionToken = QUIT_TOKEN,
3395 .topRightStyle = NO_BUTTON_STYLE,
3396 .actionButtonText = NULL,
3397 .tuneId = TUNE_TAP_CASUAL};
3398 pageContext = nbgl_pageDrawInfo(&pageCallback, &ticker, &info);
3399 }
3401}
3402
3410 nbgl_callback_t quitCallback)
3411{
3412 const char *msg;
3413 bool isSuccess;
3414 switch (reviewStatusType) {
3416 msg = "Operation signed";
3417 isSuccess = true;
3418 break;
3420 msg = "Operation rejected";
3421 isSuccess = false;
3422 break;
3424 msg = "Transaction signed";
3425 isSuccess = true;
3426 break;
3428 msg = "Transaction rejected";
3429 isSuccess = false;
3430 break;
3432 msg = "Message signed";
3433 isSuccess = true;
3434 break;
3436 msg = "Message rejected";
3437 isSuccess = false;
3438 break;
3440 msg = "Address verified";
3441 isSuccess = true;
3442 break;
3444 msg = "Address verification\ncancelled";
3445 isSuccess = false;
3446 break;
3447 default:
3448 return;
3449 }
3450 nbgl_useCaseStatus(msg, isSuccess, quitCallback);
3451}
3452
3466 const char *message,
3467 const char *subMessage,
3468 const char *confirmText,
3469 const char *cancelText,
3470 nbgl_choiceCallback_t callback)
3471{
3472 reset_callbacks();
3473
3474 nbgl_pageConfirmationDescription_t info = {.cancelText = cancelText,
3475 .centeredInfo.text1 = message,
3476 .centeredInfo.text2 = subMessage,
3477 .centeredInfo.text3 = NULL,
3478 .centeredInfo.style = LARGE_CASE_INFO,
3479 .centeredInfo.icon = icon,
3480 .centeredInfo.offsetY = 0,
3481 .confirmationText = confirmText,
3482 .confirmationToken = CHOICE_TOKEN,
3483 .tuneId = TUNE_TAP_CASUAL,
3484 .modal = false};
3485 // check params
3486 if ((confirmText == NULL) || (cancelText == NULL)) {
3487 return;
3488 }
3489 onChoice = callback;
3490 pageContext = nbgl_pageDrawConfirmation(&pageCallback, &info);
3492}
3493
3507void nbgl_useCaseConfirm(const char *message,
3508 const char *subMessage,
3509 const char *confirmText,
3510 const char *cancelText,
3511 nbgl_callback_t callback)
3512{
3513 // Don't reset callback or nav context as this is just a modal.
3514
3515 nbgl_pageConfirmationDescription_t info = {.cancelText = cancelText,
3516 .centeredInfo.text1 = message,
3517 .centeredInfo.text2 = subMessage,
3518 .centeredInfo.text3 = NULL,
3519 .centeredInfo.style = LARGE_CASE_INFO,
3520 .centeredInfo.icon = &IMPORTANT_CIRCLE_ICON,
3521 .centeredInfo.offsetY = 0,
3522 .confirmationText = confirmText,
3523 .confirmationToken = CHOICE_TOKEN,
3524 .tuneId = TUNE_TAP_CASUAL,
3525 .modal = true};
3526 onModalConfirm = callback;
3527 if (modalPageContext != NULL) {
3528 nbgl_pageRelease(modalPageContext);
3529 }
3530 modalPageContext = nbgl_pageDrawConfirmation(&pageModalCallback, &info);
3532}
3533
3545 const char *message,
3546 const char *actionText,
3547 nbgl_callback_t callback)
3548{
3549 nbgl_pageContent_t content = {0};
3550
3551 // memorize callback
3552 onAction = callback;
3553
3554 content.tuneId = TUNE_TAP_CASUAL;
3555 content.type = INFO_BUTTON;
3556 content.infoButton.buttonText = actionText;
3557 content.infoButton.text = message;
3558 content.infoButton.icon = icon;
3559 content.infoButton.buttonToken = ACTION_BUTTON_TOKEN;
3560
3561 pageContext = nbgl_pageDrawGenericContent(&pageCallback, NULL, &content);
3563}
3564
3577 const char *reviewTitle,
3578 const char *reviewSubTitle,
3579 const char *rejectText,
3580 nbgl_callback_t continueCallback,
3581 nbgl_callback_t rejectCallback)
3582{
3583 reset_callbacks();
3584
3585 nbgl_pageInfoDescription_t info = {.footerText = rejectText,
3586 .footerToken = QUIT_TOKEN,
3587 .tapActionText = NULL,
3588 .isSwipeable = true,
3589 .tapActionToken = CONTINUE_TOKEN,
3590 .topRightStyle = NO_BUTTON_STYLE,
3591 .actionButtonText = NULL,
3592 .tuneId = TUNE_TAP_CASUAL};
3593 info.centeredInfo.icon = icon;
3594 info.centeredInfo.text1 = reviewTitle;
3595 info.centeredInfo.text2 = reviewSubTitle;
3596 info.centeredInfo.text3 = "Swipe to review";
3598 info.centeredInfo.offsetY = 0;
3599 onQuit = rejectCallback;
3600 onContinue = continueCallback;
3601
3602#ifdef HAVE_PIEZO_SOUND
3603 // Play notification sound
3604 os_io_seph_cmd_piezo_play_tune(TUNE_LOOK_AT_ME);
3605#endif // HAVE_PIEZO_SOUND
3606
3607 pageContext = nbgl_pageDrawInfo(&pageCallback, NULL, &info);
3608 nbgl_refresh();
3609}
3610
3615void nbgl_useCaseRegularReview(uint8_t initPage,
3616 uint8_t nbPages,
3617 const char *rejectText,
3618 nbgl_layoutTouchCallback_t buttonCallback,
3619 nbgl_navCallback_t navCallback,
3620 nbgl_choiceCallback_t choiceCallback)
3621{
3622 reset_callbacks();
3623
3624 // memorize context
3625 onChoice = choiceCallback;
3626 onNav = navCallback;
3627 onControls = buttonCallback;
3628 forwardNavOnly = false;
3629 navType = REVIEW_NAV;
3630
3631 // fill navigation structure
3632 UNUSED(rejectText);
3633 prepareNavInfo(true, nbPages, getRejectReviewText(TYPE_OPERATION));
3634
3635 displayReviewPage(initPage, true);
3636}
3637
3651 const nbgl_pageInfoLongPress_t *infoLongPress,
3652 const char *rejectText,
3653 nbgl_choiceCallback_t callback)
3654{
3655 uint8_t offset = 0;
3656
3657 reset_callbacks();
3658 memset(&genericContext, 0, sizeof(genericContext));
3659
3660 // memorize context
3661 onChoice = callback;
3662 navType = GENERIC_NAV;
3663 pageTitle = NULL;
3664 bundleNavContext.review.operationType = TYPE_OPERATION;
3665
3666 genericContext.genericContents.contentsList = localContentsList;
3667 memset(localContentsList, 0, 2 * sizeof(nbgl_content_t));
3668
3669 if (tagValueList != NULL && tagValueList->nbPairs != 0) {
3670 localContentsList[offset].type = TAG_VALUE_LIST;
3671 memcpy(&localContentsList[offset].content.tagValueList,
3672 tagValueList,
3674 offset++;
3675 }
3676
3677 localContentsList[offset].type = INFO_LONG_PRESS;
3678 memcpy(&localContentsList[offset].content.infoLongPress,
3679 infoLongPress,
3680 sizeof(nbgl_pageInfoLongPress_t));
3681 localContentsList[offset].content.infoLongPress.longPressToken = CONFIRM_TOKEN;
3682 offset++;
3683
3684 genericContext.genericContents.nbContents = offset;
3685
3686 // compute number of pages & fill navigation structure
3687 uint8_t nbPages = getNbPagesForGenericContents(&genericContext.genericContents, 0, false);
3688 UNUSED(rejectText);
3689 prepareNavInfo(true, nbPages, getRejectReviewText(TYPE_OPERATION));
3690
3691 displayGenericContextPage(0, true);
3692}
3693
3708 const nbgl_pageInfoLongPress_t *infoLongPress,
3709 const char *rejectText,
3710 nbgl_choiceCallback_t callback)
3711{
3712 uint8_t offset = 0;
3713
3714 reset_callbacks();
3715 memset(&genericContext, 0, sizeof(genericContext));
3716
3717 // memorize context
3718 onChoice = callback;
3719 navType = GENERIC_NAV;
3720 pageTitle = NULL;
3721
3722 genericContext.genericContents.contentsList = localContentsList;
3723 memset(localContentsList, 0, 2 * sizeof(nbgl_content_t));
3724
3725 if (tagValueList != NULL && tagValueList->nbPairs != 0) {
3726 localContentsList[offset].type = TAG_VALUE_LIST;
3727 memcpy(&localContentsList[offset].content.tagValueList,
3728 tagValueList,
3730 offset++;
3731 }
3732
3733 localContentsList[offset].type = INFO_BUTTON;
3734 localContentsList[offset].content.infoButton.text = infoLongPress->text;
3735 localContentsList[offset].content.infoButton.icon = infoLongPress->icon;
3736 localContentsList[offset].content.infoButton.buttonText = infoLongPress->longPressText;
3737 localContentsList[offset].content.infoButton.buttonToken = CONFIRM_TOKEN;
3738 localContentsList[offset].content.infoButton.tuneId = TUNE_TAP_CASUAL;
3739 offset++;
3740
3741 genericContext.genericContents.nbContents = offset;
3742
3743 // compute number of pages & fill navigation structure
3744 uint8_t nbPages = getNbPagesForGenericContents(&genericContext.genericContents, 0, false);
3745 UNUSED(rejectText);
3746 prepareNavInfo(true, nbPages, getRejectReviewText(TYPE_OPERATION));
3747
3748 displayGenericContextPage(0, true);
3749}
3750
3767void nbgl_useCaseReview(nbgl_operationType_t operationType,
3768 const nbgl_contentTagValueList_t *tagValueList,
3769 const nbgl_icon_details_t *icon,
3770 const char *reviewTitle,
3771 const char *reviewSubTitle,
3772 const char *finishTitle,
3773 nbgl_choiceCallback_t choiceCallback)
3774{
3775 useCaseReview(operationType,
3776 tagValueList,
3777 icon,
3778 reviewTitle,
3779 reviewSubTitle,
3780 finishTitle,
3781 NULL,
3782 choiceCallback,
3783 false,
3784 true);
3785}
3786
3807 const nbgl_contentTagValueList_t *tagValueList,
3808 const nbgl_icon_details_t *icon,
3809 const char *reviewTitle,
3810 const char *reviewSubTitle,
3811 const char *finishTitle,
3812 const nbgl_tipBox_t *tipBox,
3813 nbgl_choiceCallback_t choiceCallback)
3814{
3815 nbgl_useCaseAdvancedReview(operationType,
3816 tagValueList,
3817 icon,
3818 reviewTitle,
3819 reviewSubTitle,
3820 finishTitle,
3821 tipBox,
3822 &blindSigningWarning,
3823 choiceCallback);
3824}
3825
3849 const nbgl_contentTagValueList_t *tagValueList,
3850 const nbgl_icon_details_t *icon,
3851 const char *reviewTitle,
3852 const char *reviewSubTitle,
3853 const char *finishTitle,
3854 const nbgl_tipBox_t *tipBox,
3855 const nbgl_warning_t *warning,
3856 nbgl_choiceCallback_t choiceCallback)
3857{
3858 memset(&reviewWithWarnCtx, 0, sizeof(reviewWithWarnCtx));
3859 // memorize tipBox because it can be in the call stack of the caller
3860 if (tipBox != NULL) {
3861 memcpy(&activeTipBox, tipBox, sizeof(activeTipBox));
3862 }
3863 else {
3864 memset(&activeTipBox, 0, sizeof(activeTipBox));
3865 }
3866 // if no warning at all, it's a simple review
3867 if ((warning == NULL)
3868 || ((warning->predefinedSet == 0) && (warning->introDetails == NULL)
3869 && (warning->reviewDetails == NULL))) {
3870 useCaseReview(operationType,
3871 tagValueList,
3872 icon,
3873 reviewTitle,
3874 reviewSubTitle,
3875 finishTitle,
3876 tipBox,
3877 choiceCallback,
3878 false,
3879 true);
3880 return;
3881 }
3882 if (warning->predefinedSet == (1 << W3C_NO_THREAT_WARN)) {
3883 operationType |= NO_THREAT_OPERATION;
3884 }
3885 else {
3886 operationType |= RISKY_OPERATION;
3887 }
3888
3889 reviewWithWarnCtx.isStreaming = false;
3890 reviewWithWarnCtx.operationType = operationType;
3891 reviewWithWarnCtx.tagValueList = tagValueList;
3892 reviewWithWarnCtx.icon = icon;
3893 reviewWithWarnCtx.reviewTitle = reviewTitle;
3894 reviewWithWarnCtx.reviewSubTitle = reviewSubTitle;
3895 reviewWithWarnCtx.finishTitle = finishTitle;
3896 reviewWithWarnCtx.warning = warning;
3897 reviewWithWarnCtx.choiceCallback = choiceCallback;
3898
3899 // display the initial warning only of a risk/threat or blind signing
3900 if (!(reviewWithWarnCtx.warning->predefinedSet & (1 << W3C_THREAT_DETECTED_WARN))
3901 && !(reviewWithWarnCtx.warning->predefinedSet & (1 << W3C_RISK_DETECTED_WARN))
3902 && !(reviewWithWarnCtx.warning->predefinedSet & (1 << BLIND_SIGNING_WARN))) {
3903 useCaseReview(operationType,
3904 tagValueList,
3905 icon,
3906 reviewTitle,
3907 reviewSubTitle,
3908 finishTitle,
3909 tipBox,
3910 choiceCallback,
3911 false,
3912 true);
3913 return;
3914 }
3915
3916 displayInitialWarning();
3917}
3918
3936 const nbgl_contentTagValueList_t *tagValueList,
3937 const nbgl_icon_details_t *icon,
3938 const char *reviewTitle,
3939 const char *reviewSubTitle,
3940 const char *finishTitle,
3941 nbgl_choiceCallback_t choiceCallback)
3942{
3943 useCaseReview(operationType,
3944 tagValueList,
3945 icon,
3946 reviewTitle,
3947 reviewSubTitle,
3948 finishTitle,
3949 NULL,
3950 choiceCallback,
3951 true,
3952 true);
3953}
3954
3964 const char *rejectText,
3965 nbgl_callback_t rejectCallback)
3966{
3967 reset_callbacks();
3968 memset(&genericContext, 0, sizeof(genericContext));
3969
3970 // memorize context
3971 onQuit = rejectCallback;
3972 navType = GENERIC_NAV;
3973 pageTitle = NULL;
3974 bundleNavContext.review.operationType = TYPE_OPERATION;
3975
3976 memcpy(&genericContext.genericContents, contents, sizeof(nbgl_genericContents_t));
3977
3978 // compute number of pages & fill navigation structure
3979 uint8_t nbPages = getNbPagesForGenericContents(&genericContext.genericContents, 0, false);
3980 prepareNavInfo(true, nbPages, rejectText);
3981 navInfo.quitToken = QUIT_TOKEN;
3982
3983#ifdef HAVE_PIEZO_SOUND
3984 // Play notification sound
3985 os_io_seph_cmd_piezo_play_tune(TUNE_LOOK_AT_ME);
3986#endif // HAVE_PIEZO_SOUND
3987
3988 displayGenericContextPage(0, true);
3989}
3990
4004 const nbgl_icon_details_t *icon,
4005 const char *reviewTitle,
4006 const char *reviewSubTitle,
4007 nbgl_choiceCallback_t choiceCallback)
4008{
4009 useCaseReviewStreamingStart(
4010 operationType, icon, reviewTitle, reviewSubTitle, choiceCallback, true);
4011}
4012
4027 const nbgl_icon_details_t *icon,
4028 const char *reviewTitle,
4029 const char *reviewSubTitle,
4030 nbgl_choiceCallback_t choiceCallback)
4031{
4033 operationType, icon, reviewTitle, reviewSubTitle, &blindSigningWarning, choiceCallback);
4034}
4035
4052 const nbgl_icon_details_t *icon,
4053 const char *reviewTitle,
4054 const char *reviewSubTitle,
4055 const nbgl_warning_t *warning,
4056 nbgl_choiceCallback_t choiceCallback)
4057{
4058 memset(&reviewWithWarnCtx, 0, sizeof(reviewWithWarnCtx));
4059 // if no warning at all, it's a simple review
4060 if ((warning == NULL)
4061 || ((warning->predefinedSet == 0) && (warning->introDetails == NULL)
4062 && (warning->reviewDetails == NULL))) {
4063 useCaseReviewStreamingStart(
4064 operationType, icon, reviewTitle, reviewSubTitle, choiceCallback, true);
4065 return;
4066 }
4067 if (warning->predefinedSet == (1 << W3C_NO_THREAT_WARN)) {
4068 operationType |= NO_THREAT_OPERATION;
4069 }
4070 else {
4071 operationType |= RISKY_OPERATION;
4072 }
4073
4074 reviewWithWarnCtx.isStreaming = true;
4075 reviewWithWarnCtx.operationType = operationType;
4076 reviewWithWarnCtx.icon = icon;
4077 reviewWithWarnCtx.reviewTitle = reviewTitle;
4078 reviewWithWarnCtx.reviewSubTitle = reviewSubTitle;
4079 reviewWithWarnCtx.choiceCallback = choiceCallback;
4080 reviewWithWarnCtx.warning = warning;
4081
4082 // display the initial warning only of a risk/threat or blind signing
4083 if (!(reviewWithWarnCtx.warning->predefinedSet & (1 << W3C_THREAT_DETECTED_WARN))
4084 && !(reviewWithWarnCtx.warning->predefinedSet & (1 << W3C_RISK_DETECTED_WARN))
4085 && !(reviewWithWarnCtx.warning->predefinedSet & (1 << BLIND_SIGNING_WARN))) {
4086 useCaseReviewStreamingStart(
4087 operationType, icon, reviewTitle, reviewSubTitle, choiceCallback, true);
4088 return;
4089 }
4090 displayInitialWarning();
4091}
4092
4107 nbgl_choiceCallback_t choiceCallback,
4108 nbgl_callback_t skipCallback)
4109{
4110 // Should follow a call to nbgl_useCaseReviewStreamingStart
4111 memset(&genericContext, 0, sizeof(genericContext));
4112
4113 bundleNavContext.reviewStreaming.choiceCallback = choiceCallback;
4114 bundleNavContext.reviewStreaming.skipCallback = skipCallback;
4115
4116 // memorize context
4117 onChoice = bundleNavReviewStreamingChoice;
4118 navType = STREAMING_NAV;
4119 pageTitle = NULL;
4120
4121 genericContext.genericContents.contentsList = localContentsList;
4122 genericContext.genericContents.nbContents = 1;
4123 memset(localContentsList, 0, 1 * sizeof(nbgl_content_t));
4124
4125 // Then the tag/value pairs
4126 STARTING_CONTENT.type = TAG_VALUE_LIST;
4127 memcpy(
4128 &STARTING_CONTENT.content.tagValueList, tagValueList, sizeof(nbgl_contentTagValueList_t));
4129
4130 // compute number of pages & fill navigation structure
4131 bundleNavContext.reviewStreaming.stepPageNb = getNbPagesForGenericContents(
4132 &genericContext.genericContents,
4133 0,
4134 (bundleNavContext.reviewStreaming.operationType & SKIPPABLE_OPERATION));
4135 prepareNavInfo(true,
4137 getRejectReviewText(bundleNavContext.reviewStreaming.operationType));
4138 // if the operation is skippable
4139 if (bundleNavContext.reviewStreaming.operationType & SKIPPABLE_OPERATION) {
4140 navInfo.progressIndicator = false;
4141 navInfo.skipText = "Skip";
4142 navInfo.skipToken = SKIP_TOKEN;
4143 }
4144
4145 displayGenericContextPage(0, true);
4146}
4147
4159 nbgl_choiceCallback_t choiceCallback)
4160{
4161 nbgl_useCaseReviewStreamingContinueExt(tagValueList, choiceCallback, NULL);
4162}
4163
4172void nbgl_useCaseReviewStreamingFinish(const char *finishTitle,
4173 nbgl_choiceCallback_t choiceCallback)
4174{
4175 // Should follow a call to nbgl_useCaseReviewStreamingContinue
4176 memset(&genericContext, 0, sizeof(genericContext));
4177
4178 bundleNavContext.reviewStreaming.choiceCallback = choiceCallback;
4179
4180 // memorize context
4181 onChoice = bundleNavReviewStreamingChoice;
4182 navType = STREAMING_NAV;
4183 pageTitle = NULL;
4184
4185 genericContext.genericContents.contentsList = localContentsList;
4186 genericContext.genericContents.nbContents = 1;
4187 memset(localContentsList, 0, 1 * sizeof(nbgl_content_t));
4188
4189 // Eventually the long press page
4190 STARTING_CONTENT.type = INFO_LONG_PRESS;
4191 prepareReviewLastPage(bundleNavContext.reviewStreaming.operationType,
4192 &STARTING_CONTENT.content.infoLongPress,
4193 bundleNavContext.reviewStreaming.icon,
4194 finishTitle);
4195
4196 // compute number of pages & fill navigation structure
4197 bundleNavContext.reviewStreaming.stepPageNb = getNbPagesForGenericContents(
4198 &genericContext.genericContents,
4199 0,
4200 (bundleNavContext.reviewStreaming.operationType & SKIPPABLE_OPERATION));
4201 prepareNavInfo(true, 1, getRejectReviewText(bundleNavContext.reviewStreaming.operationType));
4202
4203 displayGenericContextPage(0, true);
4204}
4205
4210void nbgl_useCaseAddressConfirmationExt(const char *address,
4211 nbgl_choiceCallback_t callback,
4212 const nbgl_contentTagValueList_t *tagValueList)
4213{
4214 reset_callbacks();
4215 memset(&genericContext, 0, sizeof(genericContext));
4216 memset(&addressConfirmationContext, 0, sizeof(addressConfirmationContext));
4217
4218 // save context
4219 onChoice = callback;
4220 navType = GENERIC_NAV;
4221 pageTitle = NULL;
4222
4223 genericContext.genericContents.contentsList = localContentsList;
4224 genericContext.genericContents.nbContents = (tagValueList == NULL) ? 1 : 2;
4225 memset(localContentsList, 0, 2 * sizeof(nbgl_content_t));
4226 prepareAddressConfirmationPages(
4227 address, tagValueList, &STARTING_CONTENT, &localContentsList[1]);
4228
4229 // fill navigation structure, common to all pages
4230 uint8_t nbPages = getNbPagesForGenericContents(&genericContext.genericContents, 0, false);
4231
4232 prepareNavInfo(true, nbPages, "Cancel");
4233
4234#ifdef HAVE_PIEZO_SOUND
4235 // Play notification sound
4236 os_io_seph_cmd_piezo_play_tune(TUNE_LOOK_AT_ME);
4237#endif // HAVE_PIEZO_SOUND
4238
4239 displayGenericContextPage(0, true);
4240}
4241
4258void nbgl_useCaseAddressReview(const char *address,
4259 const nbgl_contentTagValueList_t *additionalTagValueList,
4260 const nbgl_icon_details_t *icon,
4261 const char *reviewTitle,
4262 const char *reviewSubTitle,
4263 nbgl_choiceCallback_t choiceCallback)
4264{
4265 reset_callbacks();
4266 memset(&genericContext, 0, sizeof(genericContext));
4267 // release a potential modal
4268 if (addressConfirmationContext.modalLayout) {
4269 nbgl_layoutRelease(addressConfirmationContext.modalLayout);
4270 }
4271 memset(&addressConfirmationContext, 0, sizeof(addressConfirmationContext));
4272
4273 // save context
4274 onChoice = choiceCallback;
4275 navType = GENERIC_NAV;
4276 pageTitle = NULL;
4277 bundleNavContext.review.operationType = TYPE_OPERATION;
4278
4279 genericContext.genericContents.contentsList = localContentsList;
4280 memset(localContentsList, 0, 3 * sizeof(nbgl_content_t));
4281
4282 // First a centered info
4283 STARTING_CONTENT.type = EXTENDED_CENTER;
4284 prepareReviewFirstPage(
4285 &STARTING_CONTENT.content.extendedCenter.contentCenter, icon, reviewTitle, reviewSubTitle);
4286 STARTING_CONTENT.content.extendedCenter.contentCenter.subText = "Swipe to continue";
4287
4288 // Then the address confirmation pages
4289 prepareAddressConfirmationPages(
4290 address, additionalTagValueList, &localContentsList[1], &localContentsList[2]);
4291
4292 // fill navigation structure, common to all pages
4293 genericContext.genericContents.nbContents
4294 = (localContentsList[2].type == TAG_VALUE_CONFIRM) ? 3 : 2;
4295 uint8_t nbPages = getNbPagesForGenericContents(&genericContext.genericContents, 0, false);
4296
4297 prepareNavInfo(true, nbPages, "Cancel");
4298
4299#ifdef HAVE_PIEZO_SOUND
4300 // Play notification sound
4301 os_io_seph_cmd_piezo_play_tune(TUNE_LOOK_AT_ME);
4302#endif // HAVE_PIEZO_SOUND
4303
4304 displayGenericContextPage(0, true);
4305}
4306
4315void nbgl_useCaseSpinner(const char *text)
4316{
4317 // if the previous Use Case was not Spinner, fresh start
4318 if (genericContext.type != USE_CASE_SPINNER) {
4319 memset(&genericContext, 0, sizeof(genericContext));
4320 genericContext.type = USE_CASE_SPINNER;
4321 nbgl_layoutDescription_t layoutDescription = {0};
4322
4323 layoutDescription.withLeftBorder = true;
4324
4325 genericContext.backgroundLayout = nbgl_layoutGet(&layoutDescription);
4326
4328 genericContext.backgroundLayout, text, NULL, genericContext.spinnerPosition);
4329
4330 nbgl_layoutDraw(genericContext.backgroundLayout);
4332 }
4333 else {
4334 // otherwise increment spinner
4335 genericContext.spinnerPosition++;
4336 // there are only NB_SPINNER_POSITIONSpositions
4337 if (genericContext.spinnerPosition == NB_SPINNER_POSITIONS) {
4338 genericContext.spinnerPosition = 0;
4339 }
4340 int ret = nbgl_layoutUpdateSpinner(
4341 genericContext.backgroundLayout, text, NULL, genericContext.spinnerPosition);
4342 if (ret == 1) {
4344 }
4345 else if (ret == 2) {
4347 }
4348 }
4349}
4350
4351#ifdef NBGL_KEYPAD
4371void nbgl_useCaseKeypadDigits(const char *title,
4372 uint8_t minDigits,
4373 uint8_t maxDigits,
4374 uint8_t backToken,
4375 bool shuffled,
4376 tune_index_e tuneId,
4377 nbgl_pinValidCallback_t validatePinCallback,
4378 nbgl_layoutTouchCallback_t actionCallback)
4379{
4380 keypadGenericUseCase(title,
4381 minDigits,
4382 maxDigits,
4383 backToken,
4384 shuffled,
4385 false,
4386 tuneId,
4387 validatePinCallback,
4388 actionCallback);
4389}
4409void nbgl_useCaseKeypadPIN(const char *title,
4410 uint8_t minDigits,
4411 uint8_t maxDigits,
4412 uint8_t backToken,
4413 bool shuffled,
4414 tune_index_e tuneId,
4415 nbgl_pinValidCallback_t validatePinCallback,
4416 nbgl_layoutTouchCallback_t actionCallback)
4417{
4418 keypadGenericUseCase(title,
4419 minDigits,
4420 maxDigits,
4421 backToken,
4422 shuffled,
4423 true,
4424 tuneId,
4425 validatePinCallback,
4426 actionCallback);
4427}
4428#endif // NBGL_KEYPAD
4429
4430#endif // HAVE_SE_TOUCH
4431#endif // NBGL_USE_CASE
nbgl_contentTagValue_t *(* nbgl_contentTagValueCallback_t)(uint8_t pairIndex)
prototype of tag/value pair retrieval callback
@ ICON_ILLUSTRATION
simple icon
@ LARGE_CASE_GRAY_INFO
@ LARGE_CASE_INFO
text in BLACK and large case (INTER 32px), subText in black in Inter24px
@ INFO_LONG_PRESS
a centered info and a long press button
@ EXTENDED_CENTER
a centered content and a possible tip-box
@ CHOICES_LIST
list of choices through radio buttons
@ CENTERED_INFO
a centered info
@ SWITCHES_LIST
list of switches with descriptions
@ TAG_VALUE_DETAILS
a tag/value pair and a small button to get details.
@ INFOS_LIST
list of infos with titles
@ TAG_VALUE_CONFIRM
tag/value pairs and a black button/footer to confirm/cancel.
@ TAG_VALUE_LIST
list of tag/value pairs
@ BARS_LIST
list of touchable bars (with > on the right to go to sub-pages)
@ INFO_BUTTON
a centered info and a simple black button
@ INFO_LIST_ALIAS
alias is list of infos
@ TAG_VALUE_LIST_ALIAS
@ ENS_ALIAS
alias comes from ENS
@ ADDRESS_BOOK_ALIAS
alias comes from Address Book
@ QR_CODE_ALIAS
alias is an address to be displayed as a QR Code
void(* nbgl_contentActionCallback_t)(int token, uint8_t index, int page)
prototype of function to be called when an action on a content object occurs
debug traces management
#define LOG_DEBUG(__logger,...)
Definition nbgl_debug.h:86
@ USE_CASE_LOGGER
Definition nbgl_debug.h:35
bool nbgl_getTextMaxLenInNbLines(nbgl_font_id_e fontId, const char *text, uint16_t maxWidth, uint16_t maxNbLines, uint16_t *len, bool wrapping)
compute the len of the given text (in bytes) fitting in the given maximum nb lines,...
Definition nbgl_fonts.c:566
nbgl_font_id_e
Definition nbgl_fonts.h:143
void nbgl_textReduceOnNbLines(nbgl_font_id_e fontId, const char *origText, uint16_t maxWidth, uint8_t nbLines, char *reducedText, uint16_t reducedTextLen)
Create a reduced version of given ASCII text to wrap it on the given max width (in pixels),...
uint16_t nbgl_getTextHeightInWidth(nbgl_font_id_e fontId, const char *text, uint16_t maxWidth, bool wrapping)
return the height of the given multiline text, with the given font.
uint16_t nbgl_getTextNbLinesInWidth(nbgl_font_id_e fontId, const char *text, uint16_t maxWidth, bool wrapping)
compute the number of lines of the given text fitting in the given maxWidth
Definition nbgl_fonts.c:725
void(* nbgl_layoutTouchCallback_t)(int token, uint8_t index)
prototype of function to be called when an object is touched
int nbgl_layoutAddContentCenter(nbgl_layout_t *layout, const nbgl_contentCenter_t *info)
Creates an area on the center of the main panel, with a possible icon, and possible texts under it.
int nbgl_layoutAddTextContent(nbgl_layout_t *layout, const char *title, const char *description, const char *info)
Creates in the main container three text areas:
int nbgl_layoutUpdateKeypad(nbgl_layout_t *layout, uint8_t index, bool enableValidate, bool enableBackspace, bool enableDigits)
Updates an existing keypad on bottom of the screen, with the given configuration.
int nbgl_layoutAddSeparationLine(nbgl_layout_t *layout)
adds a separation line on bottom of the last added item
int nbgl_layoutAddQRCode(nbgl_layout_t *layout, const nbgl_layoutQRCode_t *info)
Creates an area on the center of the main panel, with a QRCode, a possible text in black (bold) under...
int nbgl_layoutDraw(nbgl_layout_t *layout)
Applies given layout. The screen will be redrawn.
int nbgl_layoutAddTouchableBar(nbgl_layout_t *layout, const nbgl_layoutBar_t *barLayout)
Creates a touchable bar in main panel.
@ WHITE_BACKGROUND
rounded bordered button, with text/icon in black, on white background
@ BLACK_BACKGROUND
rounded bordered button, with text/icon in white, on black background
int nbgl_layoutAddTopRightButton(nbgl_layout_t *layout, const nbgl_icon_details_t *icon, uint8_t token, tune_index_e tuneId)
Creates a Top-right button in the top right corner of the top panel.
#define AVAILABLE_WIDTH
void * nbgl_layout_t
type shared externally
@ HEADER_EMPTY
empty space, to have a better vertical centering of centered info
@ HEADER_BACK_AND_TEXT
back key and optional text
int nbgl_layoutAddKeypadContent(nbgl_layout_t *layout, const char *title, bool hidden, uint8_t nbDigits, const char *text)
Adds an area with a title and a placeholder for hidden digits on top of a keypad, to represent the en...
#define NBGL_INVALID_TOKEN
Definition nbgl_layout.h:30
int nbgl_layoutUpdateKeypadContent(nbgl_layout_t *layout, bool hidden, uint8_t nbActiveDigits, const char *text)
Updates an existing set of hidden digits, with the given configuration.
nbgl_layout_t * nbgl_layoutGet(const nbgl_layoutDescription_t *description)
returns a layout of the given type. The layout is reset
int nbgl_layoutAddExtendedFooter(nbgl_layout_t *layout, const nbgl_layoutFooter_t *footerDesc)
Creates a touchable area at the footer of the screen, containing various controls,...
@ ROUNDED_AND_FOOTER_STYLE
A black background button on top of a footer.
int nbgl_layoutAddChoiceButtons(nbgl_layout_t *layout, const nbgl_layoutChoiceButtons_t *info)
Creates two buttons to make a choice. Both buttons are mandatory. Both buttons are full width,...
int nbgl_layoutAddSpinner(nbgl_layout_t *layout, const char *text, const char *subText, uint8_t initPosition)
Creates a centered (vertically & horizontally) spinner with a text under it.
int nbgl_layoutUpdateSpinner(nbgl_layout_t *layout, const char *text, const char *subText, uint8_t position)
Update an existing spinner (must be the only object of the layout)
#define NBGL_NO_PROGRESS_INDICATOR
To be used when a control token shall not be used.
Definition nbgl_layout.h:27
int nbgl_layoutAddHeader(nbgl_layout_t *layout, const nbgl_layoutHeader_t *headerDesc)
Creates a touchable (or not) area at the header of the screen, containing various controls,...
@ FOOTER_EMPTY
empty space, to have a better vertical centering of centered info
int nbgl_layoutRelease(nbgl_layout_t *layout)
Release the layout obtained with nbgl_layoutGet()
#define EXIT_PAGE
Definition nbgl_layout.h:35
int nbgl_layoutAddFooter(nbgl_layout_t *layout, const char *text, uint8_t token, tune_index_e tuneId)
Creates a touchable text at the footer of the screen, separated with a thin line from the rest of the...
int nbgl_layoutAddKeypad(nbgl_layout_t *layout, keyboardCallback_t callback, bool shuffled)
Adds a keypad on bottom of the screen, with the associated callback.
#define NB_SPINNER_POSITIONS
Definition nbgl_obj.h:258
void nbgl_refresh(void)
This functions refreshes the actual screen on display with what has changed since the last refresh.
Definition nbgl_obj.c:1665
#define KEYPAD_MAX_DIGITS
Definition nbgl_obj.h:67
void nbgl_refreshSpecial(nbgl_refresh_mode_t mode)
This functions refreshes the actual screen on display with what has changed since the last refresh,...
Definition nbgl_obj.c:1675
#define BACKSPACE_KEY
Definition nbgl_obj.h:26
void nbgl_refreshSpecialWithPostRefresh(nbgl_refresh_mode_t mode, nbgl_post_refresh_t post_refresh)
Definition nbgl_obj.c:1691
#define VALIDATE_KEY
Definition nbgl_obj.h:27
nbgl_page_t * nbgl_pageDrawGenericContent(nbgl_layoutTouchCallback_t onActionCallback, const nbgl_pageNavigationInfo_t *nav, nbgl_pageContent_t *content)
draw a generic content page, with the given content, and if nav parameter is not NULL,...
Definition nbgl_page.c:612
nbgl_page_t * nbgl_pageDrawInfo(nbgl_layoutTouchCallback_t onActionCallback, const nbgl_screenTickerConfiguration_t *ticker, const nbgl_pageInfoDescription_t *info)
draw a page with a centered info (icon and/or texts) with a touchable footer, in a potential "tapable...
Definition nbgl_page.c:325
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_pageDrawLedgerInfo(nbgl_layoutTouchCallback_t onActionCallback, const nbgl_screenTickerConfiguration_t *ticker, const char *text, int tapActionToken)
draw a page with a centered text in large case, with a round check icon
Definition nbgl_page.c:256
nbgl_page_t * nbgl_pageDrawConfirmation(nbgl_layoutTouchCallback_t onActionCallback, const nbgl_pageConfirmationDescription_t *info)
draw a confirmation page, with a centered info (icon and/or text), a button to confirm and a footer t...
Definition nbgl_page.c:442
int nbgl_pageRelease(nbgl_page_t *)
Release the page obtained with any of the nbgl_pageDrawXXX() functions.
Definition nbgl_page.c:625
nbgl_page_t * nbgl_pageDrawGenericContentExt(nbgl_layoutTouchCallback_t onActionCallback, const nbgl_pageNavigationInfo_t *nav, nbgl_pageContent_t *content, bool modal)
draw a generic content page, with the given content, and if nav parameter is not NULL,...
Definition nbgl_page.c:483
@ QUIT_APP_TEXT
A full width button with "Quit app" text (only for bottom button)
Definition nbgl_page.h:40
@ INFO_ICON
info (i) icon in the button.
Definition nbgl_page.h:39
@ NO_BUTTON_STYLE
no button.
Definition nbgl_page.h:36
@ SETTINGS_ICON
settings (wheel) icon in the button.
Definition nbgl_page.h:37
struct PACKED__ nbgl_screenTickerConfiguration_s nbgl_screenTickerConfiguration_t
struct to configure a screen layer
void nbgl_screenRedraw(void)
This function redraws the whole screen on top of stack and its children.
Definition nbgl_screen.c:66
@ POST_REFRESH_FORCE_POWER_ON
Force screen power on after refresh.
Definition nbgl_types.h:354
#define MIN(x, y)
Definition nbgl_types.h:118
struct PACKED__ nbgl_icon_details_s nbgl_icon_details_t
Represents all information about an icon.
nbgl_refresh_mode_t
different modes of refresh for nbgl_refreshSpecial()
Definition nbgl_types.h:326
@ FULL_COLOR_CLEAN_REFRESH
to be used for lock screen display (cleaner but longer refresh)
Definition nbgl_types.h:329
@ BLACK_AND_WHITE_FAST_REFRESH
to be used for pure B&W area, when contrast is not priority
Definition nbgl_types.h:331
@ FULL_COLOR_PARTIAL_REFRESH
to be used for small partial refresh (radio buttons, switches)
Definition nbgl_types.h:328
@ FULL_COLOR_REFRESH
to be used for normal refresh
Definition nbgl_types.h:327
API of the Advanced BOLOS Graphical Library, for typical application use-cases.
DEPRECATED void nbgl_useCaseHome(const char *appName, const nbgl_icon_details_t *appIcon, const char *tagline, bool withSettings, nbgl_callback_t topRightCallback, nbgl_callback_t quitCallback)
#define NB_MAX_LINES_IN_REVIEW
maximum number of lines for value field in review pages
DEPRECATED void nbgl_useCaseSettings(const char *settingsTitle, uint8_t initPage, uint8_t nbPages, bool touchableTitle, nbgl_callback_t quitCallback, nbgl_navCallback_t navCallback, nbgl_layoutTouchCallback_t controlsCallback)
uint8_t nbgl_useCaseGetNbTagValuesInPageExt(uint8_t nbPairs, const nbgl_contentTagValueList_t *tagValueList, uint8_t startIndex, bool isSkippable, bool *requireSpecificDisplay)
void(* nbgl_callback_t)(void)
prototype of generic callback function
#define SKIPPABLE_OPERATION
This is to use in nbgl_operationType_t when the operation is skippable. This is used.
void nbgl_useCaseGenericSettings(const char *appName, uint8_t initPage, const nbgl_genericContents_t *settingContents, const nbgl_contentInfoList_t *infosList, nbgl_callback_t quitCallback)
void nbgl_useCaseKeypadPIN(const char *title, uint8_t minDigits, uint8_t maxDigits, uint8_t backToken, bool shuffled, tune_index_e tuneId, nbgl_pinValidCallback_t validatePinCallback, nbgl_layoutTouchCallback_t actionCallback)
uint32_t nbgl_operationType_t
This mask is used to describe the type of operation to review with additional options It is a mask of...
void nbgl_useCaseReview(nbgl_operationType_t operationType, const nbgl_contentTagValueList_t *tagValueList, const nbgl_icon_details_t *icon, const char *reviewTitle, const char *reviewSubTitle, const char *finishTitle, nbgl_choiceCallback_t choiceCallback)
void nbgl_useCaseAction(const nbgl_icon_details_t *icon, const char *message, const char *actionText, nbgl_callback_t callback)
uint8_t nbgl_useCaseGetNbTagValuesInPage(uint8_t nbPairs, const nbgl_contentTagValueList_t *tagValueList, uint8_t startIndex, bool *requireSpecificDisplay)
uint8_t nbgl_useCaseGetNbPagesForTagValueList(const nbgl_contentTagValueList_t *tagValueList)
DEPRECATED void nbgl_useCaseRegularReview(uint8_t initPage, uint8_t nbPages, const char *rejectText, nbgl_layoutTouchCallback_t buttonCallback, nbgl_navCallback_t navCallback, nbgl_choiceCallback_t choiceCallback)
void(* nbgl_pinValidCallback_t)(const uint8_t *pin, uint8_t pinLen)
prototype of pin validation callback function
void nbgl_useCaseReviewStreamingStart(nbgl_operationType_t operationType, const nbgl_icon_details_t *icon, const char *reviewTitle, const char *reviewSubTitle, nbgl_choiceCallback_t choiceCallback)
void nbgl_useCaseHomeAndSettings(const char *appName, const nbgl_icon_details_t *appIcon, const char *tagline, const uint8_t initSettingPage, const nbgl_genericContents_t *settingContents, const nbgl_contentInfoList_t *infosList, const nbgl_homeAction_t *action, nbgl_callback_t quitCallback)
void nbgl_useCaseStaticReviewLight(const nbgl_contentTagValueList_t *tagValueList, const nbgl_pageInfoLongPress_t *infoLongPress, const char *rejectText, nbgl_choiceCallback_t callback)
uint8_t nbgl_useCaseGetNbInfosInPage(uint8_t nbInfos, const nbgl_contentInfoList_t *infosList, uint8_t startIndex, bool withNav)
void(* nbgl_choiceCallback_t)(bool confirm)
prototype of choice callback function
uint8_t nbgl_useCaseGetNbBarsInPage(uint8_t nbBars, const nbgl_contentBarsList_t *barsList, uint8_t startIndex, bool withNav)
void nbgl_useCaseNavigableContent(const char *title, uint8_t initPage, uint8_t nbPages, nbgl_callback_t quitCallback, nbgl_navCallback_t navCallback, nbgl_layoutTouchCallback_t controlsCallback)
#define INFOS_AREA_HEIGHT
height available for infos pairs display
@ STRONG_HOME_ACTION
Black button, implicating the main action of the App.
void nbgl_useCaseReviewStreamingFinish(const char *finishTitle, nbgl_choiceCallback_t choiceCallback)
void nbgl_useCaseSpinner(const char *text)
void nbgl_useCaseConfirm(const char *message, const char *subMessage, const char *confirmText, const char *rejectText, nbgl_callback_t callback)
#define NB_MAX_LINES_IN_DETAILS
maximum number of lines for value field in details pages
void nbgl_useCaseReviewStart(const nbgl_icon_details_t *icon, const char *reviewTitle, const char *reviewSubTitle, const char *rejectText, nbgl_callback_t continueCallback, nbgl_callback_t rejectCallback)
DEPRECATED void nbgl_useCaseHomeExt(const char *appName, const nbgl_icon_details_t *appIcon, const char *tagline, bool withSettings, const char *actionButtonText, nbgl_callback_t actionCallback, nbgl_callback_t topRightCallback, nbgl_callback_t quitCallback)
void nbgl_useCaseKeypadDigits(const char *title, uint8_t minDigits, uint8_t maxDigits, uint8_t backToken, bool shuffled, tune_index_e tuneId, nbgl_pinValidCallback_t validatePinCallback, nbgl_layoutTouchCallback_t actionCallback)
#define APP_DESCRIPTION_MAX_LEN
Length of buffer used for the default Home tagline.
void nbgl_useCaseStatus(const char *message, bool isSuccess, nbgl_callback_t quitCallback)
@ W3C_THREAT_DETECTED_WARN
Web3 Checks: Threat detected (see reportRisk field)
@ W3C_ISSUE_WARN
Web3 Checks issue (not available)
@ BLIND_SIGNING_WARN
Blind signing.
@ W3C_NO_THREAT_WARN
Web3 Checks: No Threat detected.
@ W3C_RISK_DETECTED_WARN
Web3 Checks: Risk detected (see reportRisk field)
@ NB_WARNING_TYPES
DEPRECATED void nbgl_useCaseAddressConfirmationExt(const char *address, nbgl_choiceCallback_t callback, const nbgl_contentTagValueList_t *tagValueList)
void nbgl_useCaseGenericConfiguration(const char *title, uint8_t initPage, const nbgl_genericContents_t *contents, nbgl_callback_t quitCallback)
#define INIT_HOME_PAGE
Value to pass to nbgl_useCaseHomeAndSettings() initSettingPage parameter to initialize the use case o...
void nbgl_useCaseReviewStreamingContinueExt(const nbgl_contentTagValueList_t *tagValueList, nbgl_choiceCallback_t choiceCallback, nbgl_callback_t skipCallback)
void nbgl_useCaseReviewBlindSigning(nbgl_operationType_t operationType, const nbgl_contentTagValueList_t *tagValueList, const nbgl_icon_details_t *icon, const char *reviewTitle, const char *reviewSubTitle, const char *finishTitle, const nbgl_tipBox_t *tipBox, nbgl_choiceCallback_t choiceCallback)
void nbgl_useCaseChoice(const nbgl_icon_details_t *icon, const char *message, const char *subMessage, const char *confirmText, const char *rejectString, nbgl_choiceCallback_t callback)
#define TAG_VALUE_AREA_HEIGHT
height available for tag/value pairs display
#define BLIND_OPERATION
This is to use in nbgl_operationType_t when the operation is "blind" This is used to indicate a warni...
#define TAGLINE_PART1
Default strings used in the Home tagline.
void nbgl_useCaseGenericReview(const nbgl_genericContents_t *contents, const char *rejectText, nbgl_callback_t rejectCallback)
#define STATUS_SCREEN_DURATION
#define TAGLINE_PART2
void nbgl_useCaseStaticReview(const nbgl_contentTagValueList_t *tagValueList, const nbgl_pageInfoLongPress_t *infoLongPress, const char *rejectText, nbgl_choiceCallback_t callback)
void nbgl_useCaseReviewStreamingBlindSigningStart(nbgl_operationType_t operationType, const nbgl_icon_details_t *icon, const char *reviewTitle, const char *reviewSubTitle, nbgl_choiceCallback_t choiceCallback)
void nbgl_useCaseReviewStreamingContinue(const nbgl_contentTagValueList_t *tagValueList, nbgl_choiceCallback_t choiceCallback)
void nbgl_useCaseReviewLight(nbgl_operationType_t operationType, const nbgl_contentTagValueList_t *tagValueList, const nbgl_icon_details_t *icon, const char *reviewTitle, const char *reviewSubTitle, const char *finishTitle, nbgl_choiceCallback_t choiceCallback)
uint8_t nbgl_useCaseGetNbChoicesInPage(uint8_t nbChoices, const nbgl_contentRadioChoice_t *choicesList, uint8_t startIndex, bool withNav)
#define REAL_TYPE_MASK
This is the mask to apply on nbgl_operationType_t to get the real type provided by app.
void nbgl_useCaseReviewStatus(nbgl_reviewStatusType_t reviewStatusType, nbgl_callback_t quitCallback)
#define LAST_PAGE_FOR_REVIEW
value of page parameter used with navigation callback when "skip" button is touched,...
@ CENTERED_INFO_WARNING
Centered info.
@ QRCODE_WARNING
QR Code.
@ BAR_LIST_WARNING
list of touchable bars, to display sub-pages
nbgl_reviewStatusType_t
The different types of review status.
@ STATUS_TYPE_TRANSACTION_REJECTED
@ STATUS_TYPE_ADDRESS_REJECTED
@ STATUS_TYPE_TRANSACTION_SIGNED
@ STATUS_TYPE_OPERATION_REJECTED
@ STATUS_TYPE_OPERATION_SIGNED
@ STATUS_TYPE_ADDRESS_VERIFIED
@ STATUS_TYPE_MESSAGE_SIGNED
@ STATUS_TYPE_MESSAGE_REJECTED
bool(* nbgl_navCallback_t)(uint8_t page, nbgl_pageContent_t *content)
prototype of navigation callback function
#define MAX_APP_NAME_FOR_SDK_TAGLINE
Max supported length of appName used for the default Home tagline.
void nbgl_useCaseAddressReview(const char *address, const nbgl_contentTagValueList_t *additionalTagValueList, const nbgl_icon_details_t *icon, const char *reviewTitle, const char *reviewSubTitle, nbgl_choiceCallback_t choiceCallback)
@ TYPE_MESSAGE
@ TYPE_TRANSACTION
For operations transferring a coin or taken from an account to another.
@ TYPE_OPERATION
For other types of operation (generic type)
void nbgl_useCaseAdvancedReviewStreamingStart(nbgl_operationType_t operationType, const nbgl_icon_details_t *icon, const char *reviewTitle, const char *reviewSubTitle, const nbgl_warning_t *warning, nbgl_choiceCallback_t choiceCallback)
void nbgl_useCaseAdvancedReview(nbgl_operationType_t operationType, const nbgl_contentTagValueList_t *tagValueList, const nbgl_icon_details_t *icon, const char *reviewTitle, const char *reviewSubTitle, const char *finishTitle, const nbgl_tipBox_t *tipBox, const nbgl_warning_t *warning, nbgl_choiceCallback_t choiceCallback)
uint8_t nbgl_useCaseGetNbSwitchesInPage(uint8_t nbSwitches, const nbgl_contentSwitchesList_t *switchesList, uint8_t startIndex, bool withNav)
This structure contains data to build a BARS_LIST content.
const uint8_t * tokens
array of tokens, one for each bar (nbBars items)
const char *const * barTexts
array of texts for each bar (nbBars items, in black/bold)
tune_index_e tuneId
if not NBGL_NO_TUNE, a tune will be played when a bar is touched
uint8_t nbBars
number of elements in barTexts and tokens array
This structure contains info to build a centered (vertically and horizontally) area,...
uint16_t iconHug
vertical margin to apply on top and bottom of the icon
const nbgl_icon_details_t * icon
the icon (can be null)
const char * title
title in black large (can be null)
const char * description
description in black small regular case (can be null)
const char * subText
sub-text in dark gray regular small case
bool padding
if true, apply a padding of 40px at the bottom
const char * smallTitle
sub-title in black small bold case (can be null)
nbgl_contentIllustrationType_t illustrType
const char * text2
second text (can be null)
const char * text1
first text (can be null)
nbgl_contentCenteredInfoStyle_t style
style to apply to this info
int16_t offsetY
vertical shift to apply to this info (if >0, shift to bottom)
const char * text3
third text (can be null)
const nbgl_icon_details_t * icon
a buffer containing the 1BPP icon
nbgl_contentCenter_t contentCenter
centered content (icon + text(s))
This structure contains data to build a centered info + simple black button content.
const char * buttonText
text of the long press button
const nbgl_icon_details_t * icon
a buffer containing the 1BPP icon
const char * text
centered text in large case
tune_index_e tuneId
if not NBGL_NO_TUNE, a tune will be played when button is touched
This structure contains data to build a INFOS_LIST content.
const char *const * infoContents
array of contents of infos (in black)
const char *const * infoTypes
array of types of infos (in black/bold)
const nbgl_contentValueExt_t * infoExtensions
uint8_t nbInfos
number of elements in infoTypes and infoContents array
This structure contains data to build a centered info + long press button content.
const char * longPressText
text of the long press button
const nbgl_icon_details_t * icon
a buffer containing the 1BPP icon
const char * text
centered text in large case
This structure contains a list of names to build a list of radio buttons (on the right part of screen...
uint8_t initChoice
index of the current choice
const char *const * names
array of strings giving the choices (nbChoices)
uint8_t nbChoices
number of choices
This structure contains info to build a switch (on the right) with a description (on the left),...
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
bool wrapping
if set to true, value text will be wrapped on ' ' to avoid cutting words
uint8_t startIndex
index of the first pair to get with callback
nbgl_contentActionCallback_t actionCallback
called when a valueIcon is touched on a given pair
This structure contains a [tag,value] pair and possible extensions.
const nbgl_contentValueExt_t * extension
if not NULL, gives additional info on value field
const nbgl_icon_details_t * valueIcon
int8_t centeredInfo
if set to 1, the tag will be displayed as a centered info
const char * value
string giving the value name
const char * item
string giving the tag name
This structure contains additions to a tag/value pair, to be able to build a screen to display these ...
const char * fullValue
full string of the value when used as an alias
nbgl_contentValueAliasType_t aliasType
type of alias
const struct nbgl_contentTagValueList_s * tagValuelist
if aliasType is TAG_VALUE_LIST_ALIAS
const char * backText
used as title of the popping page, if not NULL, otherwise "item" is used
const struct nbgl_contentInfoList_s * infolist
if aliasType is INFO_LIST_ALIAS
This structure contains data to build a content.
nbgl_content_u content
nbgl_contentActionCallback_t contentActionCallback
callback to be called when an action on an object occurs
nbgl_contentType_t type
type of page content in the content union
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
Structure describing the action button in Home Screen.
const nbgl_icon_details_t * icon
icon to use in action button in Home page
nbgl_callback_t callback
function to call when action button is touched in Home page
const char * text
text to use in action button in Home page
nbgl_homeActionStyle_t style
style of action button
This structure contains info to build a clickable "bar" with a text and an icon.
bool inactive
if set to true, the bar is grayed-out and cannot be touched
const char * text
text (can be NULL)
uint8_t token
the token that will be used as argument of the callback
bool large
set to true only for the main level of OS settings
const char * subText
sub text (can be NULL)
tune_index_e tuneId
if not NBGL_NO_TUNE, a tune will be played
const nbgl_icon_details_t * iconLeft
a buffer containing the 1BPP icon for icon on left (can be NULL)
const nbgl_icon_details_t * iconRight
This structure contains info to build a pair of buttons, one on top of the other.
const char * bottomText
bottom-button text (index 1)
Structure containing all information when creating a layout. This structure must be passed as argumen...
nbgl_screenTickerConfiguration_t ticker
const char * tapActionText
Light gray text used when main container is "tapable".
nbgl_layoutTouchCallback_t onActionCallback
the callback to be called on any action on the layout
This structure contains info to build an extended footer.
struct nbgl_layoutFooter_t::@19::@21 emptySpace
if type is FOOTER_EMPTY
nbgl_layoutFooterType_t type
type of footer
This structure contains info to build a header.
nbgl_layoutHeaderType_t type
type of header
bool separationLine
if true, a separation line is added at the bottom of this control
const char * text
can be NULL if no text
struct nbgl_layoutHeader_t::@11::@14 backAndText
if type is HEADER_BACK_ICON_AND_TEXT or HEADER_BACK_AND_TEXT
This structure contains info to build a centered (vertically and horizontally) area,...
const char * text2
second text (can be null)
const char * url
URL for QR code.
Structure containing all specific information when creating a confirmation page.
Definition nbgl_page.h:149
const char * cancelText
the text used for cancel action, if NULL a simple X button is used
Definition nbgl_page.h:152
tune_index_e tuneId
if not NBGL_NO_TUNE, a tune will be played when button is pressed
Definition nbgl_page.h:157
This structure contains data to build a page in multi-pages mode (nbgl_pageDrawGenericContent)
Definition nbgl_flow.h:58
nbgl_contentTagValueDetails_t tagValueDetails
TAG_VALUE_DETAILS type
Definition nbgl_page.h:68
const char * title
text for the title of the page (if NULL, no title)
Definition nbgl_page.h:53
uint8_t topRightToken
token used when top-right button (if not NULL) is touched
Definition nbgl_page.h:58
nbgl_contentInfoLongPress_t infoLongPress
INFO_LONG_PRESS type
Definition nbgl_page.h:65
nbgl_contentRadioChoice_t choicesList
CHOICES_LIST type
Definition nbgl_flow.h:66
nbgl_contentSwitchesList_t switchesList
SWITCHES_LIST type
Definition nbgl_flow.h:64
tune_index_e tuneId
if not NBGL_NO_TUNE, a tune will be played when title is touched
Definition nbgl_page.h:57
nbgl_contentBarsList_t barsList
BARS_LIST type
Definition nbgl_flow.h:67
nbgl_contentInfoButton_t infoButton
INFO_BUTTON type
Definition nbgl_flow.h:62
nbgl_contentInfoList_t infosList
INFOS_LIST type
Definition nbgl_flow.h:65
nbgl_contentTagValueList_t tagValueList
TAG_VALUE_LIST type
Definition nbgl_flow.h:63
const nbgl_icon_details_t * topRightIcon
Definition nbgl_page.h:59
nbgl_contentType_t type
type of page content in the following union
Definition nbgl_flow.h:59
nbgl_contentCenteredInfo_t centeredInfo
CENTERED_INFO type
Definition nbgl_flow.h:61
nbgl_contentTagValueConfirm_t tagValueConfirm
TAG_VALUE_CONFIRM type
Definition nbgl_page.h:69
bool isTouchableTitle
if set to true, the title is preceded by <- arrow to go back
Definition nbgl_page.h:54
nbgl_contentExtendedCenter_t extendedCenter
EXTENDED_CENTER type
Definition nbgl_page.h:64
Structure containing all specific information when creating an information page.
Definition nbgl_page.h:185
nbgl_layoutButtonStyle_t actionButtonStyle
style of "action" button
Definition nbgl_page.h:202
const char * actionButtonText
if not NULL an "action" button is set under the centered info
Definition nbgl_page.h:200
const nbgl_icon_details_t * actionButtonIcon
potential icon of "action" button
Definition nbgl_page.h:201
nbgl_pageButtonStyle_t bottomButtonStyle
style to apply to the Bottom button
Definition nbgl_page.h:188
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 * modalTitle
title given to modal window displayed when tip-box is touched
nbgl_contentInfoList_t infos
infos pairs displayed in modal, if type is INFOS_LIST.
const char * text
text of the tip-box
const nbgl_icon_details_t * icon
icon of the tip-box
const char *const * texts
array of texts for each bar (nbBars items, in black/bold)
const struct nbgl_warningDetails_s * details
array of nbBars structures giving what to display when each bar is touched.
const char *const * subTexts
array of texts for each bar (nbBars items, in black)
uint8_t nbBars
number of touchable bars
const nbgl_icon_details_t ** icons
array of icons for each bar (nbBars items)
The necessary parameters to build the page(s) displayed when the top-right button is touched in intro...
nbgl_warningDetailsType_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
const char * title
text of the page (used to go back)
nbgl_layoutQRCode_t qrCode
QR code, if type == QRCODE_WARNING.
nbgl_warningBarList_t barList
touchable bars list, if type == BAR_LIST_WARNING
The necessary parameters to build a warning page preceding a review. One can either use predefinedSet...
const nbgl_warningDetails_t * introDetails
const nbgl_warningDetails_t * reviewDetails
uint32_t predefinedSet
Union of the different type of contents.
nbgl_contentInfoList_t infosList
INFOS_LIST type
nbgl_contentInfoLongPress_t infoLongPress
INFO_LONG_PRESS type
nbgl_contentTagValueConfirm_t tagValueConfirm
TAG_VALUE_CONFIRM type
nbgl_contentTagValueList_t tagValueList
TAG_VALUE_LIST type
nbgl_contentCenteredInfo_t centeredInfo
CENTERED_INFO type
nbgl_contentBarsList_t barsList
BARS_LIST type
nbgl_contentExtendedCenter_t extendedCenter
EXTENDED_CENTER type
nbgl_contentSwitchesList_t switchesList
SWITCHES_LIST type
nbgl_contentInfoButton_t infoButton
INFO_BUTTON type
nbgl_contentRadioChoice_t choicesList
CHOICES_LIST type