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
42/* Alias to clarify usage of genericContext hasStartingContent and hasFinishingContent feature */
43#define STARTING_CONTENT localContentsList[0]
44#define FINISHING_CONTENT localContentsList[1]
45
46/* max number of lines for address under QR Code */
47#define QRCODE_NB_MAX_LINES 3
48/* max number of char for reduced QR Code address */
49#define QRCODE_REDUCED_ADDR_LEN 128
50
51// macros to ease access to shared contexts
52#define keypadContext sharedContext.keypad
53#define reviewWithWarnCtx sharedContext.reviewWithWarning
54
55/* max length of the string displaying the description of the Web3 Checks report */
56#define W3C_DESCRIPTION_MAX_LEN 128
57
63#define RISKY_OPERATION (1 << 6)
64
70#define NO_THREAT_OPERATION (1 << 7)
71
72/**********************
73 * TYPEDEFS
74 **********************/
75enum {
76 BACK_TOKEN = 0,
77 NEXT_TOKEN,
78 QUIT_TOKEN,
79 NAV_TOKEN,
80 SKIP_TOKEN,
81 CONTINUE_TOKEN,
82 ADDRESS_QRCODE_BUTTON_TOKEN,
83 ACTION_BUTTON_TOKEN,
84 CHOICE_TOKEN,
85 DETAILS_BUTTON_TOKEN,
86 CONFIRM_TOKEN,
87 REJECT_TOKEN,
88 VALUE_ALIAS_TOKEN,
89 INFO_ALIAS_TOKEN,
90 INFOS_TIP_BOX_TOKEN,
91 BLIND_WARNING_TOKEN,
92 WARNING_BUTTON_TOKEN,
93 TIP_BOX_TOKEN,
94 QUIT_TIPBOX_MODAL_TOKEN,
95 WARNING_CHOICE_TOKEN,
96 DISMISS_QR_TOKEN,
97 DISMISS_WARNING_TOKEN,
98 FIRST_WARN_BAR_TOKEN,
99 LAST_WARN_BAR_TOKEN = (FIRST_WARN_BAR_TOKEN + NB_WARNING_TYPES - 1),
100};
101
102typedef enum {
103 REVIEW_NAV = 0,
104 SETTINGS_NAV,
105 GENERIC_NAV,
106 STREAMING_NAV
107} NavType_t;
108
109typedef struct DetailsContext_s {
110 uint8_t nbPages;
111 uint8_t currentPage;
112 bool wrapping;
113 const char *tag;
114 const char *value;
115 const char *nextPageStart;
116} DetailsContext_t;
117
118typedef struct AddressConfirmationContext_s {
119 nbgl_layoutTagValue_t tagValuePair;
120 nbgl_layout_t *modalLayout;
121} AddressConfirmationContext_t;
122
123#ifdef NBGL_KEYPAD
124typedef struct KeypadContext_s {
125 uint8_t pinEntry[KEYPAD_MAX_DIGITS];
126 uint8_t pinLen;
127 uint8_t pinMinDigits;
128 uint8_t pinMaxDigits;
129 nbgl_layout_t *layoutCtx;
130 bool hidden;
131} KeypadContext_t;
132#endif
133
134typedef struct ReviewWithWarningContext_s {
135 bool isStreaming;
136 nbgl_operationType_t operationType;
137 const nbgl_contentTagValueList_t *tagValueList;
138 const nbgl_icon_details_t *icon;
139 const char *reviewTitle;
140 const char *reviewSubTitle;
141 const char *finishTitle;
142 const nbgl_warning_t *warning;
143 nbgl_choiceCallback_t choiceCallback;
144 nbgl_layout_t *layoutCtx;
145 nbgl_layout_t *modalLayout;
146 uint8_t securityReportLevel; // level 1 is the first level of menus
147 bool isIntro; // set to true during intro (before actual review)
148} ReviewWithWarningContext_t;
149
150// this union is intended to save RAM for context storage
151// indeed, these 2 contexts cannot happen simultaneously
152typedef union {
153#ifdef NBGL_KEYPAD
154 KeypadContext_t keypad;
155#endif
156 ReviewWithWarningContext_t reviewWithWarning;
157} SharedContext_t;
158
159typedef enum {
160 USE_CASE_GENERIC = 0,
161 USE_CASE_SPINNER
162} GenericContextType_t;
163
164typedef struct {
165 GenericContextType_t type; // type of Generic context usage
166 uint8_t spinnerPosition;
167 nbgl_genericContents_t genericContents;
168 int8_t currentContentIdx;
169 uint8_t currentContentElementNb;
170 uint8_t currentElementIdx;
171 bool hasStartingContent;
172 bool hasFinishingContent;
173 const char *detailsItem;
174 const char *detailsvalue;
175 bool detailsWrapping;
176 bool validWarningCtx; // set to true if the WarningContext is valid
178 *currentPairs; // to be used to retrieve the pairs with value alias
180 currentCallback; // to be used to retrieve the pairs with value alias
181 nbgl_layout_t modalLayout;
182 nbgl_layout_t backgroundLayout;
183 const nbgl_contentInfoList_t *currentInfos;
184} GenericContext_t;
185
186typedef struct {
187 const char *appName;
188 const nbgl_icon_details_t *appIcon;
189 const char *tagline;
190 const nbgl_genericContents_t *settingContents;
191 const nbgl_contentInfoList_t *infosList;
192 nbgl_homeAction_t homeAction;
193 nbgl_callback_t quitCallback;
194} nbgl_homeAndSettingsContext_t;
195
196typedef struct {
197 nbgl_operationType_t operationType;
198 nbgl_choiceCallback_t choiceCallback;
199} nbgl_reviewContext_t;
200
201typedef struct {
202 nbgl_operationType_t operationType;
203 nbgl_choiceCallback_t choiceCallback;
204 nbgl_callback_t skipCallback;
205 const nbgl_icon_details_t *icon;
206 uint8_t stepPageNb;
207} nbgl_reviewStreamingContext_t;
208
209typedef union {
210 nbgl_homeAndSettingsContext_t homeAndSettings;
211 nbgl_reviewContext_t review;
212 nbgl_reviewStreamingContext_t reviewStreaming;
213} nbgl_BundleNavContext_t;
214
215typedef struct {
216 const nbgl_icon_details_t *icon;
217 const char *text;
218 const char *subText;
219} SecurityReportItem_t;
220
221/**********************
222 * STATIC VARIABLES
223 **********************/
224
225// char buffers to build some strings
226static char tmpString[W3C_DESCRIPTION_MAX_LEN];
227
228// multi-purposes callbacks
229static nbgl_callback_t onQuit;
230static nbgl_callback_t onContinue;
231static nbgl_callback_t onAction;
232static nbgl_navCallback_t onNav;
233static nbgl_layoutTouchCallback_t onControls;
234static nbgl_contentActionCallback_t onContentAction;
235static nbgl_choiceCallback_t onChoice;
236static nbgl_callback_t onModalConfirm;
237#ifdef NBGL_KEYPAD
238static nbgl_pinValidCallback_t onValidatePin;
239#endif
240
241// contexts for background and modal pages
242static nbgl_page_t *pageContext;
243static nbgl_page_t *modalPageContext;
244
245// context for pages
246static const char *pageTitle;
247
248// context for tip-box
249static nbgl_tipBox_t activeTipBox;
250
251// context for navigation use case
252static nbgl_pageNavigationInfo_t navInfo;
253static bool forwardNavOnly;
254static NavType_t navType;
255
256static DetailsContext_t detailsContext;
257
258// multi-purpose context shared for non-concurrent usages
259static SharedContext_t sharedContext;
260
261// context for address review
262static AddressConfirmationContext_t addressConfirmationContext;
263
264// contexts for generic navigation
265static GenericContext_t genericContext;
266static nbgl_content_t
267 localContentsList[3]; // 3 needed for nbgl_useCaseReview (starting page / tags / final page)
268static uint8_t genericContextPagesInfo[MAX_PAGE_NB / PAGES_PER_UINT8];
269
270// contexts for bundle navigation
271static nbgl_BundleNavContext_t bundleNavContext;
272
273// indexed by nbgl_contentType_t
274static const uint8_t nbMaxElementsPerContentType[] = {
275 1, // CENTERED_INFO
276 1, // EXTENDED_CENTER
277 1, // INFO_LONG_PRESS
278 1, // INFO_BUTTON
279 1, // TAG_VALUE_LIST (computed dynamically)
280 1, // TAG_VALUE_DETAILS
281 1, // TAG_VALUE_CONFIRM
282 3, // SWITCHES_LIST (computed dynamically)
283 3, // INFOS_LIST (computed dynamically)
284 5, // CHOICES_LIST (computed dynamically)
285 5, // BARS_LIST (computed dynamically)
286};
287
288// clang-format off
289static const SecurityReportItem_t securityReportItems[NB_WARNING_TYPES] = {
291 .icon = &WARNING_ICON,
292 .text = "Blind signing required",
293 .subText = "This transaction's details are not fully verifiable. If "
294 "you sign, you could lose all your assets."
295 },
296 [W3C_ISSUE_WARN] = {
297 .icon = &WARNING_ICON,
298 .text = "Transaction Check unavailable",
299 .subText = NULL
300 },
302 .icon = &WARNING_ICON,
303 .text = "Risk detected",
304 .subText = "This transaction was scanned as risky by Web3 Checks."
305 },
307 .icon = &WARNING_ICON,
308 .text = "Critical threat",
309 .subText = "This transaction was scanned as malicious by Web3 Checks."
310 },
312 .icon = NULL,
313 .text = "No threat detected",
314 .subText = "Transaction Check didn't find any threat, but always "
315 "review transaction details carefully."
316 }
317};
318// clang-format on
319
320// configuration of warning when using @ref nbgl_useCaseReviewBlindSigning()
321static const nbgl_warning_t blindSigningWarning = {.predefinedSet = (1 << BLIND_SIGNING_WARN)};
322
323#ifdef NBGL_QRCODE
324/* buffer to store reduced address under QR Code */
325static char reducedAddress[QRCODE_REDUCED_ADDR_LEN];
326#endif // NBGL_QRCODE
327
328/**********************
329 * STATIC FUNCTIONS
330 **********************/
331static void displayReviewPage(uint8_t page, bool forceFullRefresh);
332static void displayDetailsPage(uint8_t page, bool forceFullRefresh);
333static void displayFullValuePage(const char *backText,
334 const char *aliasText,
335 const nbgl_contentValueExt_t *extension);
336static void displayInfosListModal(const char *modalTitle,
337 const nbgl_contentInfoList_t *infos,
338 bool fromReview);
339static void displaySettingsPage(uint8_t page, bool forceFullRefresh);
340static void displayGenericContextPage(uint8_t pageIdx, bool forceFullRefresh);
341static void pageCallback(int token, uint8_t index);
342#ifdef NBGL_QRCODE
343static void displayAddressQRCode(void);
344#endif // NBGL_QRCODE
345static void modalLayoutTouchCallback(int token, uint8_t index);
346static void displaySkipWarning(void);
347
348static void bundleNavStartHome(void);
349static void bundleNavStartSettingsAtPage(uint8_t initSettingPage);
350static void bundleNavStartSettings(void);
351
352static void bundleNavReviewStreamingChoice(bool confirm);
353static void displaySecurityReport(uint32_t set);
354static void displayCustomizedSecurityReport(const nbgl_warningDetails_t *details);
355static void displayInitialWarning(void);
356static void useCaseReview(nbgl_operationType_t operationType,
357 const nbgl_contentTagValueList_t *tagValueList,
358 const nbgl_icon_details_t *icon,
359 const char *reviewTitle,
360 const char *reviewSubTitle,
361 const char *finishTitle,
362 const nbgl_tipBox_t *tipBox,
363 nbgl_choiceCallback_t choiceCallback,
364 bool isLight,
365 bool playNotifSound);
366static void useCaseReviewStreamingStart(nbgl_operationType_t operationType,
367 const nbgl_icon_details_t *icon,
368 const char *reviewTitle,
369 const char *reviewSubTitle,
370 nbgl_choiceCallback_t choiceCallback,
371 bool playNotifSound);
372static void useCaseHomeExt(const char *appName,
373 const nbgl_icon_details_t *appIcon,
374 const char *tagline,
375 bool withSettings,
376 nbgl_homeAction_t *homeAction,
377 nbgl_callback_t topRightCallback,
378 nbgl_callback_t quitCallback);
379static void displayDetails(const char *tag, const char *value, bool wrapping);
380
381static void reset_callbacks(void)
382{
383 onQuit = NULL;
384 onContinue = NULL;
385 onAction = NULL;
386 onNav = NULL;
387 onControls = NULL;
388 onContentAction = NULL;
389 onChoice = NULL;
390 onModalConfirm = NULL;
391#ifdef NBGL_KEYPAD
392 onValidatePin = NULL;
393#endif
394}
395
396// Helper to set genericContext page info
397static void genericContextSetPageInfo(uint8_t pageIdx, uint8_t nbElements, bool flag)
398{
399 uint8_t pageData = SET_PAGE_NB_ELEMENTS(nbElements) + SET_PAGE_FLAG(flag);
400
401 genericContextPagesInfo[pageIdx / PAGES_PER_UINT8]
402 &= ~(0x0F << ((pageIdx % PAGES_PER_UINT8) * PAGE_DATA_BITS));
403 genericContextPagesInfo[pageIdx / PAGES_PER_UINT8]
404 |= pageData << ((pageIdx % PAGES_PER_UINT8) * PAGE_DATA_BITS);
405}
406
407// Helper to get genericContext page info
408static void genericContextGetPageInfo(uint8_t pageIdx, uint8_t *nbElements, bool *flag)
409{
410 uint8_t pageData = genericContextPagesInfo[pageIdx / PAGES_PER_UINT8]
411 >> ((pageIdx % PAGES_PER_UINT8) * PAGE_DATA_BITS);
412 if (nbElements != NULL) {
413 *nbElements = GET_PAGE_NB_ELEMENTS(pageData);
414 }
415 if (flag != NULL) {
416 *flag = GET_PAGE_FLAG(pageData);
417 }
418}
419
420// Simple helper to get the number of elements inside a nbgl_content_t
421static uint8_t getContentNbElement(const nbgl_content_t *content)
422{
423 switch (content->type) {
424 case TAG_VALUE_LIST:
425 return content->content.tagValueList.nbPairs;
428 case SWITCHES_LIST:
429 return content->content.switchesList.nbSwitches;
430 case INFOS_LIST:
431 return content->content.infosList.nbInfos;
432 case CHOICES_LIST:
433 return content->content.choicesList.nbChoices;
434 case BARS_LIST:
435 return content->content.barsList.nbBars;
436 default:
437 return 1;
438 }
439}
440
441// Helper to retrieve the content inside a nbgl_genericContents_t using
442// either the contentsList or using the contentGetterCallback
443static const nbgl_content_t *getContentAtIdx(const nbgl_genericContents_t *genericContents,
444 int8_t contentIdx,
445 nbgl_content_t *content)
446{
447 if (contentIdx < 0 || contentIdx >= genericContents->nbContents) {
448 LOG_DEBUG(USE_CASE_LOGGER, "No content available at %d\n", contentIdx);
449 return NULL;
450 }
451
452 if (genericContents->callbackCallNeeded) {
453 // Retrieve content through callback, but first memset the content.
454 memset(content, 0, sizeof(nbgl_content_t));
455 genericContents->contentGetterCallback(contentIdx, content);
456 return content;
457 }
458 else {
459 // Retrieve content through list
460 return PIC(&genericContents->contentsList[contentIdx]);
461 }
462}
463
464static void prepareNavInfo(bool isReview, uint8_t nbPages, const char *rejectText)
465{
466 memset(&navInfo, 0, sizeof(navInfo));
467
468 navInfo.nbPages = nbPages;
469 navInfo.tuneId = TUNE_TAP_CASUAL;
470 navInfo.progressIndicator = false;
471 navInfo.navType = NAV_WITH_BUTTONS;
472
473 if (isReview == false) {
474 navInfo.navWithButtons.navToken = NAV_TOKEN;
475 navInfo.navWithButtons.backButton = true;
476 }
477 else {
478 navInfo.quitToken = REJECT_TOKEN;
479 navInfo.navWithButtons.quitText = rejectText;
480 navInfo.navWithButtons.navToken = NAV_TOKEN;
482 = ((navType == STREAMING_NAV) && (nbPages < 2)) ? false : true;
483 navInfo.navWithButtons.visiblePageIndicator = (navType != STREAMING_NAV);
484 }
485}
486
487static void prepareReviewFirstPage(nbgl_contentCenter_t *contentCenter,
488 const nbgl_icon_details_t *icon,
489 const char *reviewTitle,
490 const char *reviewSubTitle)
491{
492 contentCenter->icon = icon;
493 contentCenter->title = reviewTitle;
494 contentCenter->description = reviewSubTitle;
495 contentCenter->subText = "Swipe to review";
496 contentCenter->smallTitle = NULL;
497 contentCenter->iconHug = 0;
498 contentCenter->padding = false;
499 contentCenter->illustrType = ICON_ILLUSTRATION;
500}
501
502static const char *getFinishTitle(nbgl_operationType_t operationType, const char *finishTitle)
503{
504 if (finishTitle != NULL) {
505 return finishTitle;
506 }
507 switch (operationType & REAL_TYPE_MASK) {
508 case TYPE_TRANSACTION:
509 return "Sign transaction";
510 case TYPE_MESSAGE:
511 return "Sign message";
512 default:
513 return "Sign operation";
514 }
515}
516
517static void prepareReviewLastPage(nbgl_operationType_t operationType,
518 nbgl_contentInfoLongPress_t *infoLongPress,
519 const nbgl_icon_details_t *icon,
520 const char *finishTitle)
521{
522 infoLongPress->text = getFinishTitle(operationType, finishTitle);
523 infoLongPress->icon = icon;
524 infoLongPress->longPressText = "Hold to sign";
525 infoLongPress->longPressToken = CONFIRM_TOKEN;
526}
527
528static void prepareReviewLightLastPage(nbgl_operationType_t operationType,
529 nbgl_contentInfoButton_t *infoButton,
530 const nbgl_icon_details_t *icon,
531 const char *finishTitle)
532{
533 infoButton->text = getFinishTitle(operationType, finishTitle);
534 infoButton->icon = icon;
535 infoButton->buttonText = "Approve";
536 infoButton->buttonToken = CONFIRM_TOKEN;
537}
538
539static const char *getRejectReviewText(nbgl_operationType_t operationType)
540{
541 UNUSED(operationType);
542 return "Reject";
543}
544
545// function called when navigating (or exiting) modal details pages
546// or when skip choice is displayed
547static void pageModalCallback(int token, uint8_t index)
548{
549 LOG_DEBUG(USE_CASE_LOGGER, "pageModalCallback, token = %d, index = %d\n", token, index);
550
551 if (token == INFOS_TIP_BOX_TOKEN) {
552 // the icon next to info type alias has been touched
553 displayFullValuePage(activeTipBox.infos.infoTypes[index],
554 activeTipBox.infos.infoContents[index],
555 &activeTipBox.infos.infoExtensions[index]);
556 return;
557 }
558 else if (token == INFO_ALIAS_TOKEN) {
559 // the icon next to info type alias has been touched, but coming from review
560 displayFullValuePage(genericContext.currentInfos->infoTypes[index],
561 genericContext.currentInfos->infoContents[index],
562 &genericContext.currentInfos->infoExtensions[index]);
563 return;
564 }
565 nbgl_pageRelease(modalPageContext);
566 modalPageContext = NULL;
567 if (token == NAV_TOKEN) {
568 if (index == EXIT_PAGE) {
569 // redraw the background layer
571 nbgl_refresh();
572 }
573 else {
574 displayDetailsPage(index, false);
575 }
576 }
577 else if (token == QUIT_TOKEN) {
578 // redraw the background layer
580 nbgl_refresh();
581 }
582 else if (token == QUIT_TIPBOX_MODAL_TOKEN) {
583 // if in "warning context", go back to security report
584 if (reviewWithWarnCtx.securityReportLevel == 2) {
585 reviewWithWarnCtx.securityReportLevel = 1;
586 displaySecurityReport(reviewWithWarnCtx.warning->predefinedSet);
587 }
588 else {
589 // redraw the background layer
591 nbgl_refresh();
592 }
593 }
594 else if (token == SKIP_TOKEN) {
595 if (index == 0) {
596 // display the last forward only review page, whatever it is
597 displayGenericContextPage(LAST_PAGE_FOR_REVIEW, true);
598 }
599 else {
600 // display background, which should be the page where skip has been touched
602 nbgl_refresh();
603 }
604 }
605 else if (token == CHOICE_TOKEN) {
606 if (index == 0) {
607 if (onModalConfirm != NULL) {
608 onModalConfirm();
609 }
610 }
611 else {
612 // display background, which should be the page where skip has been touched
614 nbgl_refresh();
615 }
616 }
617}
618
619// generic callback for all pages except modal
620static void pageCallback(int token, uint8_t index)
621{
622 LOG_DEBUG(USE_CASE_LOGGER, "pageCallback, token = %d, index = %d\n", token, index);
623 if (token == QUIT_TOKEN) {
624 if (onQuit != NULL) {
625 onQuit();
626 }
627 }
628 else if (token == CONTINUE_TOKEN) {
629 if (onContinue != NULL) {
630 onContinue();
631 }
632 }
633 else if (token == CHOICE_TOKEN) {
634 if (onChoice != NULL) {
635 onChoice((index == 0) ? true : false);
636 }
637 }
638 else if (token == ACTION_BUTTON_TOKEN) {
639 if ((index == 0) && (onAction != NULL)) {
640 onAction();
641 }
642 else if ((index == 1) && (onQuit != NULL)) {
643 onQuit();
644 }
645 }
646#ifdef NBGL_QRCODE
647 else if (token == ADDRESS_QRCODE_BUTTON_TOKEN) {
648 displayAddressQRCode();
649 }
650#endif // NBGL_QRCODE
651 else if (token == CONFIRM_TOKEN) {
652 if (onChoice != NULL) {
653 onChoice(true);
654 }
655 }
656 else if (token == REJECT_TOKEN) {
657 if (onChoice != NULL) {
658 onChoice(false);
659 }
660 }
661 else if (token == DETAILS_BUTTON_TOKEN) {
662 displayDetails(genericContext.detailsItem,
663 genericContext.detailsvalue,
664 genericContext.detailsWrapping);
665 }
666 else if (token == NAV_TOKEN) {
667 if (index == EXIT_PAGE) {
668 if (onQuit != NULL) {
669 onQuit();
670 }
671 }
672 else {
673 if (navType == GENERIC_NAV || navType == STREAMING_NAV) {
674 displayGenericContextPage(index, false);
675 }
676 else if (navType == REVIEW_NAV) {
677 displayReviewPage(index, false);
678 }
679 else {
680 displaySettingsPage(index, false);
681 }
682 }
683 }
684 else if (token == NEXT_TOKEN) {
685 if (onNav != NULL) {
686 displayReviewPage(navInfo.activePage + 1, false);
687 }
688 else {
689 displayGenericContextPage(navInfo.activePage + 1, false);
690 }
691 }
692 else if (token == BACK_TOKEN) {
693 if (onNav != NULL) {
694 displayReviewPage(navInfo.activePage - 1, true);
695 }
696 else {
697 displayGenericContextPage(navInfo.activePage - 1, false);
698 }
699 }
700 else if (token == SKIP_TOKEN) {
701 // display a modal warning to confirm skip
702 displaySkipWarning();
703 }
704 else if (token == VALUE_ALIAS_TOKEN) {
705 // the icon next to value alias has been touched
706 const nbgl_contentTagValue_t *pair;
707 if (genericContext.currentPairs != NULL) {
708 pair = &genericContext.currentPairs[genericContext.currentElementIdx + index];
709 }
710 else {
711 pair = genericContext.currentCallback(genericContext.currentElementIdx + index);
712 }
713 displayFullValuePage(pair->item, pair->value, pair->extension);
714 }
715 else if (token == BLIND_WARNING_TOKEN) {
716 reviewWithWarnCtx.isIntro = false;
717 reviewWithWarnCtx.warning = NULL;
718 displaySecurityReport(1 << BLIND_SIGNING_WARN);
719 }
720 else if (token == WARNING_BUTTON_TOKEN) {
721 // top-right button, display the security modal
722 reviewWithWarnCtx.securityReportLevel = 1;
723 // if preset is used
724 if (reviewWithWarnCtx.warning->predefinedSet) {
725 displaySecurityReport(reviewWithWarnCtx.warning->predefinedSet);
726 }
727 else {
728 // use customized warning
729 if (reviewWithWarnCtx.isIntro) {
730 displayCustomizedSecurityReport(reviewWithWarnCtx.warning->introDetails);
731 }
732 else {
733 displayCustomizedSecurityReport(reviewWithWarnCtx.warning->reviewDetails);
734 }
735 }
736 }
737 else if (token == TIP_BOX_TOKEN) {
738 // if warning context is valid and if W3C directly display same as top-right
739 if (genericContext.validWarningCtx && (reviewWithWarnCtx.warning->predefinedSet != 0)) {
740 reviewWithWarnCtx.securityReportLevel = 1;
741 displaySecurityReport(reviewWithWarnCtx.warning->predefinedSet);
742 }
743 else {
744 displayInfosListModal(activeTipBox.modalTitle, &activeTipBox.infos, false);
745 }
746 }
747 else { // probably a control provided by caller
748 if (onContentAction != NULL) {
749 onContentAction(token, index, navInfo.activePage);
750 }
751 if (onControls != NULL) {
752 onControls(token, index);
753 }
754 }
755}
756
757// callback used for confirmation
758static void tickerCallback(void)
759{
760 nbgl_pageRelease(pageContext);
761 if (onQuit != NULL) {
762 onQuit();
763 }
764}
765
766// function used to display the current page in review
767static void displaySettingsPage(uint8_t page, bool forceFullRefresh)
768{
769 nbgl_pageContent_t content = {0};
770
771 if ((onNav == NULL) || (onNav(page, &content) == false)) {
772 return;
773 }
774
775 // override some fields
776 content.title = pageTitle;
777 content.isTouchableTitle = true;
778 content.titleToken = QUIT_TOKEN;
779 content.tuneId = TUNE_TAP_CASUAL;
780
781 navInfo.activePage = page;
782 pageContext = nbgl_pageDrawGenericContent(&pageCallback, &navInfo, &content);
783
784 if (forceFullRefresh) {
786 }
787 else {
789 }
790}
791
792// function used to display the current page in review
793static void displayReviewPage(uint8_t page, bool forceFullRefresh)
794{
795 nbgl_pageContent_t content = {0};
796
797 // ensure the page is valid
798 if ((navInfo.nbPages != 0) && (page >= (navInfo.nbPages))) {
799 return;
800 }
801 navInfo.activePage = page;
802 if ((onNav == NULL) || (onNav(navInfo.activePage, &content) == false)) {
803 return;
804 }
805
806 // override some fields
807 content.title = NULL;
808 content.isTouchableTitle = false;
809 content.tuneId = TUNE_TAP_CASUAL;
810
811 if (content.type == INFO_LONG_PRESS) { // last page
812 // for forward only review without known length...
813 // if we don't do that we cannot remove the '>' in the navigation bar at the last page
814 navInfo.nbPages = navInfo.activePage + 1;
815 content.infoLongPress.longPressToken = CONFIRM_TOKEN;
816 if (forwardNavOnly) {
817 // remove the "Skip" button
818 navInfo.skipText = NULL;
819 }
820 }
821
822 // override smallCaseForValue for tag/value types to false
823 if (content.type == TAG_VALUE_DETAILS) {
825 // the maximum displayable number of lines for value is NB_MAX_LINES_IN_REVIEW (without More
826 // button)
828 }
829 else if (content.type == TAG_VALUE_LIST) {
830 content.tagValueList.smallCaseForValue = false;
831 }
832 else if (content.type == TAG_VALUE_CONFIRM) {
834 // use confirm token for black button
835 content.tagValueConfirm.confirmationToken = CONFIRM_TOKEN;
836 }
837
838 pageContext = nbgl_pageDrawGenericContent(&pageCallback, &navInfo, &content);
839
840 if (forceFullRefresh) {
842 }
843 else {
845 }
846}
847
848// Helper that does the computing of which nbgl_content_t and which element in the content should be
849// displayed for the next generic context navigation page
850static const nbgl_content_t *genericContextComputeNextPageParams(uint8_t pageIdx,
851 nbgl_content_t *content,
852 uint8_t *p_nbElementsInNextPage,
853 bool *p_flag)
854{
855 int8_t nextContentIdx = genericContext.currentContentIdx;
856 int16_t nextElementIdx = genericContext.currentElementIdx;
857 uint8_t nbElementsInNextPage;
858
859 // Retrieve info on the next page
860 genericContextGetPageInfo(pageIdx, &nbElementsInNextPage, p_flag);
861 *p_nbElementsInNextPage = nbElementsInNextPage;
862
863 // Handle forward navigation:
864 // add to current index the number of pairs of the currently active page
865 if (pageIdx > navInfo.activePage) {
866 uint8_t nbElementsInCurrentPage;
867
868 genericContextGetPageInfo(navInfo.activePage, &nbElementsInCurrentPage, NULL);
869 nextElementIdx += nbElementsInCurrentPage;
870
871 // Handle case where the content to be displayed is in the next content
872 // In such case start at element index 0.
873 // If currentContentElementNb == 0, means not initialized, so skip
874 if ((nextElementIdx >= genericContext.currentContentElementNb)
875 && (genericContext.currentContentElementNb > 0)) {
876 nextContentIdx += 1;
877 nextElementIdx = 0;
878 }
879 }
880
881 // Handle backward navigation:
882 // add to current index the number of pairs of the currently active page
883 if (pageIdx < navInfo.activePage) {
884 // Backward navigation: remove to current index the number of pairs of the current page
885 nextElementIdx -= nbElementsInNextPage;
886
887 // Handle case where the content to be displayed is in the previous content
888 // In such case set a negative number as element index so that it is handled
889 // later once the previous content is accessible so that its elements number
890 // can be retrieved.
891 if (nextElementIdx < 0) {
892 nextContentIdx -= 1;
893 nextElementIdx = -nbElementsInNextPage;
894 }
895 }
896
897 const nbgl_content_t *p_content;
898 // Retrieve next content
899 if ((nextContentIdx == -1) && (genericContext.hasStartingContent)) {
900 p_content = &STARTING_CONTENT;
901 }
902 else if ((nextContentIdx == genericContext.genericContents.nbContents)
903 && (genericContext.hasFinishingContent)) {
904 p_content = &FINISHING_CONTENT;
905 }
906 else {
907 p_content = getContentAtIdx(&genericContext.genericContents, nextContentIdx, content);
908
909 if (p_content == NULL) {
910 LOG_DEBUG(USE_CASE_LOGGER, "Fail to retrieve content\n");
911 return NULL;
912 }
913 }
914
915 // Handle cases where we are going to display data from a new content:
916 // - navigation to a previous or to the next content
917 // - First page display (genericContext.currentContentElementNb == 0)
918 //
919 // In such case we need to:
920 // - Update genericContext.currentContentIdx
921 // - Update genericContext.currentContentElementNb
922 // - Update onContentAction callback
923 // - Update nextElementIdx in case it was previously not calculable
924 if ((nextContentIdx != genericContext.currentContentIdx)
925 || (genericContext.currentContentElementNb == 0)) {
926 genericContext.currentContentIdx = nextContentIdx;
927 genericContext.currentContentElementNb = getContentNbElement(p_content);
928 onContentAction = PIC(p_content->contentActionCallback);
929 if (nextElementIdx < 0) {
930 nextElementIdx = genericContext.currentContentElementNb + nextElementIdx;
931 }
932 }
933
934 // Sanity check
935 if ((nextElementIdx < 0) || (nextElementIdx >= genericContext.currentContentElementNb)) {
937 "Invalid element index %d / %d\n",
938 nextElementIdx,
939 genericContext.currentContentElementNb);
940 return NULL;
941 }
942
943 // Update genericContext elements
944 genericContext.currentElementIdx = nextElementIdx;
945 navInfo.activePage = pageIdx;
946
947 return p_content;
948}
949
950// Helper that generates a nbgl_pageContent_t to be displayed for the next generic context
951// navigation page
952static bool genericContextPreparePageContent(const nbgl_content_t *p_content,
953 uint8_t nbElementsInPage,
954 bool flag,
955 nbgl_pageContent_t *pageContent)
956{
957 uint8_t nextElementIdx = genericContext.currentElementIdx;
958
959 pageContent->title = pageTitle;
960 pageContent->isTouchableTitle = false;
961 pageContent->titleToken = QUIT_TOKEN;
962 pageContent->tuneId = TUNE_TAP_CASUAL;
963
964 pageContent->type = p_content->type;
965 switch (pageContent->type) {
966 case CENTERED_INFO:
967 memcpy(&pageContent->centeredInfo,
968 &p_content->content.centeredInfo,
969 sizeof(pageContent->centeredInfo));
970 break;
971 case EXTENDED_CENTER:
972 memcpy(&pageContent->extendedCenter,
973 &p_content->content.extendedCenter,
974 sizeof(pageContent->extendedCenter));
975 break;
976 case INFO_LONG_PRESS:
977 memcpy(&pageContent->infoLongPress,
978 &p_content->content.infoLongPress,
979 sizeof(pageContent->infoLongPress));
980 break;
981
982 case INFO_BUTTON:
983 memcpy(&pageContent->infoButton,
984 &p_content->content.infoButton,
985 sizeof(pageContent->infoButton));
986 break;
987
988 case TAG_VALUE_LIST: {
989 nbgl_contentTagValueList_t *p_tagValueList = &pageContent->tagValueList;
990
991 // memorize pairs (or callback) for usage when alias is used
992 genericContext.currentPairs = p_content->content.tagValueList.pairs;
993 genericContext.currentCallback = p_content->content.tagValueList.callback;
994
995 if (flag) {
996 // Flag can be set if the pair is too long to fit or because it needs
997 // to be displayed as centered info.
998
999 // First retrieve the pair
1000 const nbgl_layoutTagValue_t *pair;
1001
1002 if (p_content->content.tagValueList.pairs != NULL) {
1003 pair = PIC(&p_content->content.tagValueList.pairs[nextElementIdx]);
1004 }
1005 else {
1006 pair = PIC(p_content->content.tagValueList.callback(nextElementIdx));
1007 }
1008
1009 if (pair->centeredInfo) {
1010 pageContent->type = EXTENDED_CENTER;
1011 prepareReviewFirstPage(&pageContent->extendedCenter.contentCenter,
1012 pair->valueIcon,
1013 pair->item,
1014 pair->value);
1015
1016 // Skip population of nbgl_contentTagValueList_t structure
1017 p_tagValueList = NULL;
1018 }
1019 else {
1020 // if the pair is too long to fit, we use a TAG_VALUE_DETAILS content
1021 pageContent->type = TAG_VALUE_DETAILS;
1022 pageContent->tagValueDetails.detailsButtonText = "More";
1023 pageContent->tagValueDetails.detailsButtonIcon = NULL;
1024 pageContent->tagValueDetails.detailsButtonToken = DETAILS_BUTTON_TOKEN;
1025
1026 p_tagValueList = &pageContent->tagValueDetails.tagValueList;
1027
1028 // Backup pair info for easy data access upon click on button
1029 genericContext.detailsItem = pair->item;
1030 genericContext.detailsvalue = pair->value;
1031 genericContext.detailsWrapping = p_content->content.tagValueList.wrapping;
1032 }
1033 }
1034
1035 if (p_tagValueList != NULL) {
1036 p_tagValueList->nbPairs = nbElementsInPage;
1037 p_tagValueList->token = p_content->content.tagValueList.token;
1038 if (p_content->content.tagValueList.pairs != NULL) {
1039 p_tagValueList->pairs
1040 = PIC(&p_content->content.tagValueList.pairs[nextElementIdx]);
1041 // parse pairs to check if any contains an alias for value
1042 for (uint8_t i = 0; i < nbElementsInPage; i++) {
1043 if (p_tagValueList->pairs[i].aliasValue) {
1044 p_tagValueList->token = VALUE_ALIAS_TOKEN;
1045 break;
1046 }
1047 }
1048 }
1049 else {
1050 p_tagValueList->pairs = NULL;
1051 p_tagValueList->callback = p_content->content.tagValueList.callback;
1052 p_tagValueList->startIndex = nextElementIdx;
1053 // parse pairs to check if any contains an alias for value
1054 for (uint8_t i = 0; i < nbElementsInPage; i++) {
1055 const nbgl_layoutTagValue_t *pair
1056 = PIC(p_content->content.tagValueList.callback(nextElementIdx + i));
1057 if (pair->aliasValue) {
1058 p_tagValueList->token = VALUE_ALIAS_TOKEN;
1059 break;
1060 }
1061 }
1062 }
1063 p_tagValueList->smallCaseForValue = false;
1065 p_tagValueList->wrapping = p_content->content.tagValueList.wrapping;
1066 }
1067
1068 break;
1069 }
1070 case TAG_VALUE_CONFIRM:
1071 // only display a TAG_VALUE_CONFIRM if we are at the last page
1072 if ((nextElementIdx + nbElementsInPage)
1074 memcpy(&pageContent->tagValueConfirm,
1075 &p_content->content.tagValueConfirm,
1076 sizeof(pageContent->tagValueConfirm));
1077 nbgl_contentTagValueList_t *p_tagValueList
1078 = &pageContent->tagValueConfirm.tagValueList;
1079 p_tagValueList->nbPairs = nbElementsInPage;
1080 p_tagValueList->pairs
1081 = PIC(&p_content->content.tagValueConfirm.tagValueList.pairs[nextElementIdx]);
1082 }
1083 else {
1084 // else display it as a TAG_VALUE_LIST
1085 pageContent->type = TAG_VALUE_LIST;
1086 nbgl_contentTagValueList_t *p_tagValueList = &pageContent->tagValueList;
1087 p_tagValueList->nbPairs = nbElementsInPage;
1088 p_tagValueList->pairs
1089 = PIC(&p_content->content.tagValueConfirm.tagValueList.pairs[nextElementIdx]);
1090 }
1091 break;
1092 case SWITCHES_LIST:
1093 pageContent->switchesList.nbSwitches = nbElementsInPage;
1094 pageContent->switchesList.switches
1095 = PIC(&p_content->content.switchesList.switches[nextElementIdx]);
1096 break;
1097 case INFOS_LIST:
1098 pageContent->infosList.nbInfos = nbElementsInPage;
1099 pageContent->infosList.infoTypes
1100 = PIC(&p_content->content.infosList.infoTypes[nextElementIdx]);
1101 pageContent->infosList.infoContents
1102 = PIC(&p_content->content.infosList.infoContents[nextElementIdx]);
1103 break;
1104 case CHOICES_LIST:
1105 memcpy(&pageContent->choicesList,
1106 &p_content->content.choicesList,
1107 sizeof(pageContent->choicesList));
1108 pageContent->choicesList.nbChoices = nbElementsInPage;
1109 pageContent->choicesList.names
1110 = PIC(&p_content->content.choicesList.names[nextElementIdx]);
1111 if ((p_content->content.choicesList.initChoice >= nextElementIdx)
1112 && (p_content->content.choicesList.initChoice
1113 < nextElementIdx + nbElementsInPage)) {
1114 pageContent->choicesList.initChoice
1115 = p_content->content.choicesList.initChoice - nextElementIdx;
1116 }
1117 else {
1118 pageContent->choicesList.initChoice = nbElementsInPage;
1119 }
1120 break;
1121 case BARS_LIST:
1122 pageContent->barsList.nbBars = nbElementsInPage;
1123 pageContent->barsList.barTexts
1124 = PIC(&p_content->content.barsList.barTexts[nextElementIdx]);
1125 pageContent->barsList.tokens = PIC(&p_content->content.barsList.tokens[nextElementIdx]);
1126 pageContent->barsList.tuneId = p_content->content.barsList.tuneId;
1127 break;
1128 default:
1129 LOG_DEBUG(USE_CASE_LOGGER, "Unsupported type %d\n", pageContent->type);
1130 return false;
1131 }
1132
1133 bool isFirstOrLastPage
1134 = ((p_content->type == CENTERED_INFO) || (p_content->type == EXTENDED_CENTER))
1135 || (p_content->type == INFO_LONG_PRESS);
1136 nbgl_operationType_t operationType
1137 = (navType == STREAMING_NAV)
1138 ? bundleNavContext.reviewStreaming.operationType
1139 : ((navType == GENERIC_NAV) ? bundleNavContext.review.operationType : 0);
1140
1141 // if first or last page of review and blind/risky operation, add the warning top-right button
1142 if (isFirstOrLastPage
1143 && (operationType & (BLIND_OPERATION | RISKY_OPERATION | NO_THREAT_OPERATION))) {
1144 // if issue is only Web3Checks "no threat", use privacy icon
1145 if ((operationType & NO_THREAT_OPERATION)
1146 && !(reviewWithWarnCtx.warning->predefinedSet & (1 << BLIND_SIGNING_WARN))) {
1147 pageContent->topRightIcon = &PRIVACY_ICON;
1148 }
1149 else {
1150 pageContent->topRightIcon = &WARNING_ICON;
1151 }
1152
1153 pageContent->topRightToken
1154 = (operationType & BLIND_OPERATION) ? BLIND_WARNING_TOKEN : WARNING_BUTTON_TOKEN;
1155 }
1156
1157 return true;
1158}
1159
1160// function used to display the current page in generic context navigation mode
1161static void displayGenericContextPage(uint8_t pageIdx, bool forceFullRefresh)
1162{
1163 // Retrieve next page parameters
1164 nbgl_content_t content;
1165 uint8_t nbElementsInPage;
1166 bool flag;
1167 const nbgl_content_t *p_content = NULL;
1168
1169 if (navType == STREAMING_NAV) {
1170 if (pageIdx == LAST_PAGE_FOR_REVIEW) {
1171 if (bundleNavContext.reviewStreaming.skipCallback != NULL) {
1172 bundleNavContext.reviewStreaming.skipCallback();
1173 }
1174 return;
1175 }
1176 else if (pageIdx >= bundleNavContext.reviewStreaming.stepPageNb) {
1177 bundleNavReviewStreamingChoice(true);
1178 return;
1179 }
1180 }
1181
1182 if (navInfo.activePage == pageIdx) {
1183 p_content
1184 = genericContextComputeNextPageParams(pageIdx, &content, &nbElementsInPage, &flag);
1185 }
1186 else if (navInfo.activePage < pageIdx) {
1187 // Support going more than one step forward.
1188 // It occurs when initializing a navigation on an arbitrary page
1189 for (int i = navInfo.activePage + 1; i <= pageIdx; i++) {
1190 p_content = genericContextComputeNextPageParams(i, &content, &nbElementsInPage, &flag);
1191 }
1192 }
1193 else {
1194 if (pageIdx - navInfo.activePage > 1) {
1195 // We don't support going more than one step backward as it doesn't occurs for now?
1196 LOG_DEBUG(USE_CASE_LOGGER, "Unsupported navigation\n");
1197 return;
1198 }
1199 p_content
1200 = genericContextComputeNextPageParams(pageIdx, &content, &nbElementsInPage, &flag);
1201 }
1202
1203 if (p_content == NULL) {
1204 return;
1205 }
1206
1207 // Create next page content
1208 nbgl_pageContent_t pageContent = {0};
1209 if (!genericContextPreparePageContent(p_content, nbElementsInPage, flag, &pageContent)) {
1210 return;
1211 }
1212
1213 pageContext = nbgl_pageDrawGenericContent(&pageCallback, &navInfo, &pageContent);
1214
1215 if (forceFullRefresh) {
1217 }
1218 else {
1220 }
1221}
1222
1223// from the current details context, return a pointer on the details at the given page
1224static const char *getDetailsPageAt(uint8_t detailsPage)
1225{
1226 uint8_t page = 0;
1227 const char *currentChar = detailsContext.value;
1228 while (page < detailsPage) {
1229 uint16_t nbLines
1230 = nbgl_getTextNbLinesInWidth(SMALL_BOLD_FONT, currentChar, AVAILABLE_WIDTH, false);
1231 if (nbLines > NB_MAX_LINES_IN_DETAILS) {
1232 uint16_t len;
1233 nbgl_getTextMaxLenInNbLines(SMALL_BOLD_FONT,
1234 currentChar,
1237 &len,
1238 detailsContext.wrapping);
1239 len -= 3;
1240 currentChar = currentChar + len;
1241 }
1242 page++;
1243 }
1244 return currentChar;
1245}
1246
1247// function used to display the current page in details review mode
1248static void displayDetailsPage(uint8_t detailsPage, bool forceFullRefresh)
1249{
1250 static nbgl_layoutTagValue_t currentPair;
1251 nbgl_pageNavigationInfo_t info = {.activePage = detailsPage,
1252 .nbPages = detailsContext.nbPages,
1253 .navType = NAV_WITH_BUTTONS,
1254 .quitToken = QUIT_TOKEN,
1255 .navWithButtons.navToken = NAV_TOKEN,
1256 .navWithButtons.quitButton = true,
1257 .navWithButtons.backButton = true,
1258 .navWithButtons.quitText = NULL,
1259 .progressIndicator = false,
1260 .tuneId = TUNE_TAP_CASUAL};
1262 .topRightIcon = NULL,
1263 .tagValueList.nbPairs = 1,
1264 .tagValueList.pairs = &currentPair,
1265 .tagValueList.smallCaseForValue = true,
1266 .tagValueList.wrapping = detailsContext.wrapping};
1267
1268 if (modalPageContext != NULL) {
1269 nbgl_pageRelease(modalPageContext);
1270 }
1271 currentPair.item = detailsContext.tag;
1272 // if move backward or first page
1273 if (detailsPage <= detailsContext.currentPage) {
1274 // recompute current start from beginning
1275 currentPair.value = getDetailsPageAt(detailsPage);
1276 forceFullRefresh = true;
1277 }
1278 // else move forward
1279 else {
1280 currentPair.value = detailsContext.nextPageStart;
1281 }
1282 detailsContext.currentPage = detailsPage;
1283 uint16_t nbLines = nbgl_getTextNbLinesInWidth(
1284 SMALL_BOLD_FONT, currentPair.value, AVAILABLE_WIDTH, detailsContext.wrapping);
1285
1286 if (nbLines > NB_MAX_LINES_IN_DETAILS) {
1287 uint16_t len;
1288 nbgl_getTextMaxLenInNbLines(SMALL_BOLD_FONT,
1289 currentPair.value,
1292 &len,
1293 detailsContext.wrapping);
1294 len -= 3;
1295 // memorize next position to save processing
1296 detailsContext.nextPageStart = currentPair.value + len;
1297 // use special feature to keep only NB_MAX_LINES_IN_DETAILS lines and replace the last 3
1298 // chars by "..."
1300 }
1301 else {
1302 detailsContext.nextPageStart = NULL;
1303 content.tagValueList.nbMaxLinesForValue = 0;
1304 }
1305 if (info.nbPages == 1) {
1306 // if only one page, no navigation bar, and use a footer instead
1307 info.navWithButtons.quitText = "Close";
1308 }
1309 modalPageContext = nbgl_pageDrawGenericContentExt(&pageModalCallback, &info, &content, true);
1310
1311 if (forceFullRefresh) {
1313 }
1314 else {
1316 }
1317}
1318
1319// function used to display the content of a full value, when touching an alias of a tag/value pair
1320static void displayFullValuePage(const char *backText,
1321 const char *aliasText,
1322 const nbgl_contentValueExt_t *extension)
1323{
1324 const char *modalTitle
1325 = (extension->backText != NULL) ? PIC(extension->backText) : PIC(backText);
1326 if (extension->aliasType == INFO_LIST_ALIAS) {
1327 genericContext.currentInfos = extension->infolist;
1328 displayInfosListModal(modalTitle, extension->infolist, true);
1329 }
1330 else {
1331 nbgl_layoutDescription_t layoutDescription = {.modal = true,
1332 .withLeftBorder = true,
1333 .onActionCallback = &modalLayoutTouchCallback,
1334 .tapActionText = NULL};
1336 .separationLine = false,
1337 .backAndText.token = 0,
1338 .backAndText.tuneId = TUNE_TAP_CASUAL,
1339 .backAndText.text = modalTitle};
1340 genericContext.modalLayout = nbgl_layoutGet(&layoutDescription);
1341 // add header with the tag part of the pair, to go back
1342 nbgl_layoutAddHeader(genericContext.modalLayout, &headerDesc);
1343 // add either QR Code or full value text
1344 if (extension->aliasType == QR_CODE_ALIAS) {
1345#ifdef NBGL_QRCODE
1346 nbgl_layoutQRCode_t qrCode
1347 = {.url = extension->fullValue,
1348 .text1 = (extension->title != NULL) ? extension->title : extension->fullValue,
1349 .text2 = extension->explanation,
1350 .centered = true,
1351 .offsetY = 0};
1352
1353 nbgl_layoutAddQRCode(genericContext.modalLayout, &qrCode);
1354#endif // NBGL_QRCODE
1355 }
1356 else {
1357 const char *info;
1358 // add full value text
1359 if (extension->aliasType == ENS_ALIAS) {
1360 info = "ENS names are resolved by Ledger backend.";
1361 }
1362 else if (extension->aliasType == ADDRESS_BOOK_ALIAS) {
1363 info = "This account label comes from your Address Book in Ledger Live.";
1364 }
1365 else {
1366 info = extension->explanation;
1367 }
1369 genericContext.modalLayout, aliasText, extension->fullValue, info);
1370 }
1371 // draw & refresh
1372 nbgl_layoutDraw(genericContext.modalLayout);
1373 nbgl_refresh();
1374 }
1375}
1376
1377// function used to display the modal containing tip-box infos
1378static void displayInfosListModal(const char *modalTitle,
1379 const nbgl_contentInfoList_t *infos,
1380 bool fromReview)
1381{
1383 .nbPages = 1,
1384 .navType = NAV_WITH_BUTTONS,
1385 .quitToken = QUIT_TIPBOX_MODAL_TOKEN,
1386 .navWithButtons.navToken = NAV_TOKEN,
1387 .navWithButtons.quitButton = false,
1388 .navWithButtons.backButton = true,
1389 .navWithButtons.quitText = NULL,
1390 .progressIndicator = false,
1391 .tuneId = TUNE_TAP_CASUAL};
1392 nbgl_pageContent_t content
1393 = {.type = INFOS_LIST,
1394 .topRightIcon = NULL,
1395 .infosList.nbInfos = infos->nbInfos,
1396 .infosList.withExtensions = infos->withExtensions,
1397 .infosList.infoTypes = infos->infoTypes,
1398 .infosList.infoContents = infos->infoContents,
1399 .infosList.infoExtensions = infos->infoExtensions,
1400 .infosList.token = fromReview ? INFO_ALIAS_TOKEN : INFOS_TIP_BOX_TOKEN,
1401 .title = modalTitle,
1402 .titleToken = QUIT_TIPBOX_MODAL_TOKEN,
1403 .tuneId = TUNE_TAP_CASUAL};
1404
1405 if (modalPageContext != NULL) {
1406 nbgl_pageRelease(modalPageContext);
1407 }
1408 modalPageContext = nbgl_pageDrawGenericContentExt(&pageModalCallback, &info, &content, true);
1409
1411}
1412
1413#ifdef NBGL_QRCODE
1414static void displayAddressQRCode(void)
1415{
1416 // display the address as QR Code
1417 nbgl_layoutDescription_t layoutDescription = {.modal = true,
1418 .withLeftBorder = true,
1419 .onActionCallback = &modalLayoutTouchCallback,
1420 .tapActionText = NULL};
1421 nbgl_layoutHeader_t headerDesc = {
1422 .type = HEADER_EMPTY, .separationLine = false, .emptySpace.height = SMALL_CENTERING_HEADER};
1423 nbgl_layoutQRCode_t qrCode = {.url = addressConfirmationContext.tagValuePair.value,
1424 .text1 = NULL,
1425 .centered = true,
1426 .offsetY = 0};
1427
1428 addressConfirmationContext.modalLayout = nbgl_layoutGet(&layoutDescription);
1429 // add empty header for better look
1430 nbgl_layoutAddHeader(addressConfirmationContext.modalLayout, &headerDesc);
1431 // compute nb lines to check whether it shall be shorten (max is 3 lines)
1432 uint16_t nbLines = nbgl_getTextNbLinesInWidth(
1433 SMALL_REGULAR_FONT, addressConfirmationContext.tagValuePair.value, AVAILABLE_WIDTH, false);
1434
1435 if (nbLines <= QRCODE_NB_MAX_LINES) {
1436 qrCode.text2 = addressConfirmationContext.tagValuePair.value; // in gray
1437 }
1438 else {
1439 // only keep beginning and end of text, and add ... in the middle
1440 nbgl_textReduceOnNbLines(SMALL_REGULAR_FONT,
1441 addressConfirmationContext.tagValuePair.value,
1443 QRCODE_NB_MAX_LINES,
1444 reducedAddress,
1445 QRCODE_REDUCED_ADDR_LEN);
1446 qrCode.text2 = reducedAddress; // in gray
1447 }
1448
1449 nbgl_layoutAddQRCode(addressConfirmationContext.modalLayout, &qrCode);
1450
1452 addressConfirmationContext.modalLayout, "Close", DISMISS_QR_TOKEN, TUNE_TAP_CASUAL);
1453 nbgl_layoutDraw(addressConfirmationContext.modalLayout);
1454 nbgl_refresh();
1455}
1456
1457#endif // NBGL_QRCODE
1458
1459// called when header is touched on modal page, to dismiss it
1460static void modalLayoutTouchCallback(int token, uint8_t index)
1461{
1462 UNUSED(index);
1463 if (token == DISMISS_QR_TOKEN) {
1464 // dismiss modal
1465 nbgl_layoutRelease(addressConfirmationContext.modalLayout);
1466 addressConfirmationContext.modalLayout = NULL;
1468 }
1469 else if (token == DISMISS_WARNING_TOKEN) {
1470 // dismiss modal
1471 nbgl_layoutRelease(reviewWithWarnCtx.modalLayout);
1472 // if already at first level, simply redraw the background
1473 if (reviewWithWarnCtx.securityReportLevel <= 1) {
1474 reviewWithWarnCtx.modalLayout = NULL;
1476 }
1477 else {
1478 // We are at level 2 of warning modal, so re-display the level 1
1479 reviewWithWarnCtx.securityReportLevel = 1;
1480 // if preset is used
1481 if (reviewWithWarnCtx.warning->predefinedSet) {
1482 displaySecurityReport(reviewWithWarnCtx.warning->predefinedSet);
1483 }
1484 else {
1485 // use customized warning
1486 const nbgl_warningDetails_t *details
1487 = (reviewWithWarnCtx.isIntro) ? reviewWithWarnCtx.warning->introDetails
1488 : reviewWithWarnCtx.warning->reviewDetails;
1489 displayCustomizedSecurityReport(details);
1490 }
1491 return;
1492 }
1493 }
1494 else if ((token >= FIRST_WARN_BAR_TOKEN) && (token <= LAST_WARN_BAR_TOKEN)) {
1495 // dismiss modal before creating a new one
1496 nbgl_layoutRelease(reviewWithWarnCtx.modalLayout);
1497 reviewWithWarnCtx.securityReportLevel = 2;
1498 // if preset is used
1499 if (reviewWithWarnCtx.warning->predefinedSet) {
1500 displaySecurityReport(1 << (token - FIRST_WARN_BAR_TOKEN));
1501 }
1502 else {
1503 // use customized warning
1504 const nbgl_warningDetails_t *details = (reviewWithWarnCtx.isIntro)
1505 ? reviewWithWarnCtx.warning->introDetails
1506 : reviewWithWarnCtx.warning->reviewDetails;
1507 if (details->type == BAR_LIST_WARNING) {
1508 displayCustomizedSecurityReport(
1509 &details->barList.details[token - FIRST_WARN_BAR_TOKEN]);
1510 }
1511 }
1512 return;
1513 }
1514 else {
1515 // dismiss modal
1516 nbgl_layoutRelease(genericContext.modalLayout);
1517 genericContext.modalLayout = NULL;
1519 }
1520 nbgl_refresh();
1521}
1522
1523// called when item are touched in layout
1524static void layoutTouchCallback(int token, uint8_t index)
1525{
1526 // choice buttons in initial warning page
1527 if (token == WARNING_CHOICE_TOKEN) {
1528 if (index == 0) { // top button to exit
1529 reviewWithWarnCtx.choiceCallback(false);
1530 }
1531 else { // bottom button to continue to review
1532 reviewWithWarnCtx.isIntro = false;
1533 if (reviewWithWarnCtx.isStreaming) {
1534 useCaseReviewStreamingStart(reviewWithWarnCtx.operationType,
1535 reviewWithWarnCtx.icon,
1536 reviewWithWarnCtx.reviewTitle,
1537 reviewWithWarnCtx.reviewSubTitle,
1538 reviewWithWarnCtx.choiceCallback,
1539 false);
1540 }
1541 else {
1542 useCaseReview(reviewWithWarnCtx.operationType,
1543 reviewWithWarnCtx.tagValueList,
1544 reviewWithWarnCtx.icon,
1545 reviewWithWarnCtx.reviewTitle,
1546 reviewWithWarnCtx.reviewSubTitle,
1547 reviewWithWarnCtx.finishTitle,
1548 &activeTipBox,
1549 reviewWithWarnCtx.choiceCallback,
1550 false,
1551 false);
1552 }
1553 }
1554 }
1555 // top-right button in initial warning page
1556 else if (token == WARNING_BUTTON_TOKEN) {
1557 // start directly at first level of security report
1558 reviewWithWarnCtx.securityReportLevel = 1;
1559 // if preset is used
1560 if (reviewWithWarnCtx.warning->predefinedSet) {
1561 displaySecurityReport(reviewWithWarnCtx.warning->predefinedSet);
1562 }
1563 else {
1564 // use customized warning
1565 const nbgl_warningDetails_t *details = (reviewWithWarnCtx.isIntro)
1566 ? reviewWithWarnCtx.warning->introDetails
1567 : reviewWithWarnCtx.warning->reviewDetails;
1568 displayCustomizedSecurityReport(details);
1569 }
1570 }
1571}
1572
1573// called when skip button is touched in footer, during forward only review
1574static void displaySkipWarning(void)
1575{
1577 .cancelText = "Go back to review",
1578 .centeredInfo.text1 = "Skip review?",
1579 .centeredInfo.text2
1580 = "If you're sure you don't need to review all fields, you can skip straight to signing.",
1581 .centeredInfo.text3 = NULL,
1582 .centeredInfo.style = LARGE_CASE_INFO,
1583 .centeredInfo.icon = &IMPORTANT_CIRCLE_ICON,
1584 .centeredInfo.offsetY = 0,
1585 .confirmationText = "Yes, skip",
1586 .confirmationToken = SKIP_TOKEN,
1587 .tuneId = TUNE_TAP_CASUAL,
1588 .modal = true};
1589 if (modalPageContext != NULL) {
1590 nbgl_pageRelease(modalPageContext);
1591 }
1592 modalPageContext = nbgl_pageDrawConfirmation(&pageModalCallback, &info);
1594}
1595
1596#ifdef NBGL_KEYPAD
1597// called to update the keypad and automatically show / hide:
1598// - backspace key if no digits are present
1599// - validation key if the min digit is reached
1600// - keypad if the max number of digit is reached
1601static void updateKeyPad(bool add)
1602{
1603 bool enableValidate, enableBackspace, enableDigits;
1604 bool redrawKeypad = false;
1606
1607 enableDigits = (keypadContext.pinLen < keypadContext.pinMaxDigits);
1608 enableValidate = (keypadContext.pinLen >= keypadContext.pinMinDigits);
1609 enableBackspace = (keypadContext.pinLen > 0);
1610 if (add) {
1611 if ((keypadContext.pinLen == keypadContext.pinMinDigits)
1612 || // activate "validate" button on keypad
1613 (keypadContext.pinLen == keypadContext.pinMaxDigits)
1614 || // deactivate "digits" on keypad
1615 (keypadContext.pinLen == 1)) { // activate "backspace"
1616 redrawKeypad = true;
1617 }
1618 }
1619 else { // remove
1620 if ((keypadContext.pinLen == 0) || // deactivate "backspace" button on keypad
1621 (keypadContext.pinLen == (keypadContext.pinMinDigits - 1))
1622 || // deactivate "validate" button on keypad
1623 (keypadContext.pinLen
1624 == (keypadContext.pinMaxDigits - 1))) { // reactivate "digits" on keypad
1625 redrawKeypad = true;
1626 }
1627 }
1628 if (keypadContext.hidden == true) {
1629 nbgl_layoutUpdateKeypadContent(keypadContext.layoutCtx, true, keypadContext.pinLen, NULL);
1630 }
1631 else {
1633 keypadContext.layoutCtx, false, 0, (const char *) keypadContext.pinEntry);
1634 }
1635 if (redrawKeypad) {
1637 keypadContext.layoutCtx, 0, enableValidate, enableBackspace, enableDigits);
1638 }
1639
1640 if ((!add) && (keypadContext.pinLen == 0)) {
1641 // Full refresh to fully clean the bullets when reaching 0 digits
1642 mode = FULL_COLOR_REFRESH;
1643 }
1645}
1646
1647// called when a key is touched on the keypad
1648static void keypadCallback(char touchedKey)
1649{
1650 switch (touchedKey) {
1651 case BACKSPACE_KEY:
1652 if (keypadContext.pinLen > 0) {
1653 keypadContext.pinLen--;
1654 keypadContext.pinEntry[keypadContext.pinLen] = 0;
1655 }
1656 updateKeyPad(false);
1657 break;
1658
1659 case VALIDATE_KEY:
1660 // Gray out keyboard / buttons as a first user feedback
1661 nbgl_layoutUpdateKeypad(keypadContext.layoutCtx, 0, false, false, true);
1664
1665 onValidatePin(keypadContext.pinEntry, keypadContext.pinLen);
1666 break;
1667
1668 default:
1669 if ((touchedKey >= 0x30) && (touchedKey < 0x40)) {
1670 if (keypadContext.pinLen < keypadContext.pinMaxDigits) {
1671 keypadContext.pinEntry[keypadContext.pinLen] = touchedKey;
1672 keypadContext.pinLen++;
1673 }
1674 updateKeyPad(true);
1675 }
1676 break;
1677 }
1678}
1679
1680// called to create a keypad, with either hidden or visible digits
1681static void keypadGenericUseCase(const char *title,
1682 uint8_t minDigits,
1683 uint8_t maxDigits,
1684 uint8_t backToken,
1685 bool shuffled,
1686 bool hidden,
1687 tune_index_e tuneId,
1688 nbgl_pinValidCallback_t validatePinCallback,
1689 nbgl_layoutTouchCallback_t actionCallback)
1690{
1691 nbgl_layoutDescription_t layoutDescription = {0};
1693 .separationLine = true,
1694 .backAndText.token = backToken,
1695 .backAndText.tuneId = tuneId,
1696 .backAndText.text = NULL};
1697 int status = -1;
1698
1699 if ((minDigits > KEYPAD_MAX_DIGITS) || (maxDigits > KEYPAD_MAX_DIGITS)) {
1700 return;
1701 }
1702
1703 reset_callbacks();
1704 // reset the keypad context
1705 memset(&keypadContext, 0, sizeof(KeypadContext_t));
1706
1707 // get a layout
1708 layoutDescription.onActionCallback = actionCallback;
1709 layoutDescription.modal = false;
1710 layoutDescription.withLeftBorder = false;
1711 keypadContext.layoutCtx = nbgl_layoutGet(&layoutDescription);
1712 keypadContext.hidden = hidden;
1713
1714 // set back key in header
1715 nbgl_layoutAddHeader(keypadContext.layoutCtx, &headerDesc);
1716
1717 // add keypad
1718 status = nbgl_layoutAddKeypad(keypadContext.layoutCtx, keypadCallback, shuffled);
1719 if (status < 0) {
1720 return;
1721 }
1722 // add keypad content
1724 keypadContext.layoutCtx, title, keypadContext.hidden, maxDigits, "");
1725
1726 if (status < 0) {
1727 return;
1728 }
1729
1730 // validation pin callback
1731 onValidatePin = validatePinCallback;
1732 // pin code acceptable lengths
1733 keypadContext.pinMinDigits = minDigits;
1734 keypadContext.pinMaxDigits = maxDigits;
1735
1736 nbgl_layoutDraw(keypadContext.layoutCtx);
1738}
1739#endif
1740
1755static uint8_t getNbTagValuesInPage(uint8_t nbPairs,
1756 const nbgl_contentTagValueList_t *tagValueList,
1757 uint8_t startIndex,
1758 bool isSkippable,
1759 bool hasConfirmationButton,
1760 bool *requireSpecificDisplay)
1761{
1762 uint8_t nbPairsInPage = 0;
1763 uint16_t currentHeight = PRE_TAG_VALUE_MARGIN; // upper margin
1764 uint16_t maxUsableHeight = TAG_VALUE_AREA_HEIGHT;
1765
1766 // if the review is skippable, it means that there is less height for tag/value pairs
1767 // the small centering header becomes a touchable header
1768 if (isSkippable) {
1769 maxUsableHeight -= TOUCHABLE_HEADER_BAR_HEIGHT - SMALL_CENTERING_HEADER;
1770 }
1771
1772 *requireSpecificDisplay = false;
1773 while (nbPairsInPage < nbPairs) {
1774 const nbgl_layoutTagValue_t *pair;
1775 nbgl_font_id_e value_font;
1776 uint16_t nbLines;
1777
1778 // margin between pairs
1779 // 12 or 24 px between each tag/value pair
1780 if (nbPairsInPage > 0) {
1781 currentHeight += INTER_TAG_VALUE_MARGIN;
1782 }
1783 // fetch tag/value pair strings.
1784 if (tagValueList->pairs != NULL) {
1785 pair = PIC(&tagValueList->pairs[startIndex + nbPairsInPage]);
1786 }
1787 else {
1788 pair = PIC(tagValueList->callback(startIndex + nbPairsInPage));
1789 }
1790
1791 if (pair->forcePageStart && nbPairsInPage > 0) {
1792 // This pair must be at the top of a page
1793 break;
1794 }
1795
1796 if (pair->centeredInfo) {
1797 if (nbPairsInPage > 0) {
1798 // This pair must be at the top of a page
1799 break;
1800 }
1801 else {
1802 // This pair is the only one of the page and has a specific display behavior
1803 nbPairsInPage = 1;
1804 *requireSpecificDisplay = true;
1805 break;
1806 }
1807 }
1808
1809 // tag height
1810 currentHeight += nbgl_getTextHeightInWidth(
1811 SMALL_REGULAR_FONT, pair->item, AVAILABLE_WIDTH, tagValueList->wrapping);
1812 // space between tag and value
1813 currentHeight += 4;
1814 // set value font
1815 if (tagValueList->smallCaseForValue) {
1816 value_font = SMALL_REGULAR_FONT;
1817 }
1818 else {
1819 value_font = LARGE_MEDIUM_FONT;
1820 }
1821 // value height
1822 currentHeight += nbgl_getTextHeightInWidth(
1823 value_font, pair->value, AVAILABLE_WIDTH, tagValueList->wrapping);
1824 // nb lines for value
1826 value_font, pair->value, AVAILABLE_WIDTH, tagValueList->wrapping);
1827 if ((currentHeight >= maxUsableHeight) || (nbLines > NB_MAX_LINES_IN_REVIEW)) {
1828 if (nbPairsInPage == 0) {
1829 // Pair too long to fit in a single screen
1830 // It will be the only one of the page and has a specific display behavior
1831 nbPairsInPage = 1;
1832 *requireSpecificDisplay = true;
1833 }
1834 break;
1835 }
1836 nbPairsInPage++;
1837 }
1838 // if this is a TAG_VALUE_CONFIRM and we have reached the last pairs,
1839 // let's check if it still fits with a CONFIRMATION button, and if not,
1840 // remove the last pair
1841 if (hasConfirmationButton && (nbPairsInPage == nbPairs)) {
1842 maxUsableHeight -= UP_FOOTER_BUTTON_HEIGHT;
1843 if (currentHeight > maxUsableHeight) {
1844 nbPairsInPage--;
1845 }
1846 }
1847 return nbPairsInPage;
1848}
1849
1850static uint8_t getNbPagesForContent(const nbgl_content_t *content,
1851 uint8_t pageIdxStart,
1852 bool isLast,
1853 bool isSkippable)
1854{
1855 uint8_t nbElements = 0;
1856 uint8_t nbPages = 0;
1857 uint8_t nbElementsInPage;
1858 uint8_t elemIdx = 0;
1859 bool flag;
1860
1861 nbElements = getContentNbElement(content);
1862
1863 while (nbElements > 0) {
1864 flag = 0;
1865 // if the current page is not the first one (or last), a navigation bar exists
1866 bool hasNav = !isLast || (pageIdxStart > 0) || (elemIdx > 0);
1867 if (content->type == TAG_VALUE_LIST) {
1868 nbElementsInPage = getNbTagValuesInPage(
1869 nbElements, &content->content.tagValueList, elemIdx, isSkippable, false, &flag);
1870 }
1871 else if (content->type == TAG_VALUE_CONFIRM) {
1872 nbElementsInPage = getNbTagValuesInPage(nbElements,
1874 elemIdx,
1875 isSkippable,
1876 true,
1877 &flag);
1878 }
1879 else if (content->type == INFOS_LIST) {
1880 nbElementsInPage = nbgl_useCaseGetNbInfosInPage(
1881 nbElements, &content->content.infosList, elemIdx, hasNav);
1882 }
1883 else if (content->type == SWITCHES_LIST) {
1884 nbElementsInPage = nbgl_useCaseGetNbSwitchesInPage(
1885 nbElements, &content->content.switchesList, elemIdx, hasNav);
1886 }
1887 else if (content->type == BARS_LIST) {
1888 nbElementsInPage = nbgl_useCaseGetNbBarsInPage(
1889 nbElements, &content->content.barsList, elemIdx, hasNav);
1890 }
1891 else if (content->type == CHOICES_LIST) {
1892 nbElementsInPage = nbgl_useCaseGetNbChoicesInPage(
1893 nbElements, &content->content.choicesList, elemIdx, hasNav);
1894 }
1895 else {
1896 nbElementsInPage = MIN(nbMaxElementsPerContentType[content->type], nbElements);
1897 }
1898
1899 elemIdx += nbElementsInPage;
1900 genericContextSetPageInfo(pageIdxStart + nbPages, nbElementsInPage, flag);
1901 nbElements -= nbElementsInPage;
1902 nbPages++;
1903 }
1904
1905 return nbPages;
1906}
1907
1908static uint8_t getNbPagesForGenericContents(const nbgl_genericContents_t *genericContents,
1909 uint8_t pageIdxStart,
1910 bool isSkippable)
1911{
1912 uint8_t nbPages = 0;
1913 nbgl_content_t content;
1914 const nbgl_content_t *p_content;
1915
1916 for (int i = 0; i < genericContents->nbContents; i++) {
1917 p_content = getContentAtIdx(genericContents, i, &content);
1918 if (p_content == NULL) {
1919 return 0;
1920 }
1921 nbPages += getNbPagesForContent(p_content,
1922 pageIdxStart + nbPages,
1923 (i == (genericContents->nbContents - 1)),
1924 isSkippable);
1925 }
1926
1927 return nbPages;
1928}
1929
1930static void prepareAddressConfirmationPages(const char *address,
1931 const nbgl_contentTagValueList_t *tagValueList,
1932 nbgl_content_t *firstPageContent,
1933 nbgl_content_t *secondPageContent)
1934{
1935 nbgl_contentTagValueConfirm_t *tagValueConfirm;
1936
1937 addressConfirmationContext.tagValuePair.item = "Address";
1938 addressConfirmationContext.tagValuePair.value = address;
1939
1940 // First page
1941 firstPageContent->type = TAG_VALUE_CONFIRM;
1942 tagValueConfirm = &firstPageContent->content.tagValueConfirm;
1943
1944#ifdef NBGL_QRCODE
1945 tagValueConfirm->detailsButtonIcon = &QRCODE_ICON;
1946 // only use "Show as QR" when it's not the last page
1947 if (tagValueList != NULL) {
1948 tagValueConfirm->detailsButtonText = "Show as QR";
1949 }
1950 else {
1951 tagValueConfirm->detailsButtonText = NULL;
1952 }
1953 tagValueConfirm->detailsButtonToken = ADDRESS_QRCODE_BUTTON_TOKEN;
1954#else // NBGL_QRCODE
1955 tagValueConfirm->detailsButtonText = NULL;
1956 tagValueConfirm->detailsButtonIcon = NULL;
1957#endif // NBGL_QRCODE
1958 tagValueConfirm->tuneId = TUNE_TAP_CASUAL;
1959 tagValueConfirm->tagValueList.nbPairs = 1;
1960 tagValueConfirm->tagValueList.pairs = &addressConfirmationContext.tagValuePair;
1961 tagValueConfirm->tagValueList.smallCaseForValue = false;
1962 tagValueConfirm->tagValueList.nbMaxLinesForValue = 0;
1963 tagValueConfirm->tagValueList.wrapping = false;
1964 // if it's an extended address verif, it takes 2 pages, so display a "Tap to continue", and
1965 // no confirmation button
1966 if (tagValueList != NULL) {
1967 tagValueConfirm->confirmationText = NULL;
1968 }
1969 else {
1970 // otherwise no tap to continue but a confirmation button
1971 tagValueConfirm->confirmationText = "Confirm";
1972 tagValueConfirm->confirmationToken = CONFIRM_TOKEN;
1973 }
1974
1975 // Second page if any:
1976 if (tagValueList != NULL) {
1977 // the second page is dedicated to the extended tag/value pairs
1978 secondPageContent->type = TAG_VALUE_CONFIRM;
1979 tagValueConfirm = &secondPageContent->content.tagValueConfirm;
1980 tagValueConfirm->confirmationText = "Confirm";
1981 tagValueConfirm->confirmationToken = CONFIRM_TOKEN;
1982 tagValueConfirm->detailsButtonText = NULL;
1983 tagValueConfirm->detailsButtonIcon = NULL;
1984 tagValueConfirm->tuneId = TUNE_TAP_CASUAL;
1985 memcpy(&tagValueConfirm->tagValueList, tagValueList, sizeof(nbgl_contentTagValueList_t));
1986 }
1987}
1988
1989static void bundleNavStartHome(void)
1990{
1991 nbgl_homeAndSettingsContext_t *context = &bundleNavContext.homeAndSettings;
1992
1993 useCaseHomeExt(context->appName,
1994 context->appIcon,
1995 context->tagline,
1996 context->settingContents != NULL ? true : false,
1997 &context->homeAction,
1998 bundleNavStartSettings,
1999 context->quitCallback);
2000}
2001
2002static void bundleNavStartSettingsAtPage(uint8_t initSettingPage)
2003{
2004 nbgl_homeAndSettingsContext_t *context = &bundleNavContext.homeAndSettings;
2005
2006 nbgl_useCaseGenericSettings(context->appName,
2007 initSettingPage,
2008 context->settingContents,
2009 context->infosList,
2010 bundleNavStartHome);
2011}
2012
2013static void bundleNavStartSettings(void)
2014{
2015 bundleNavStartSettingsAtPage(0);
2016}
2017
2018static void bundleNavReviewConfirmRejection(void)
2019{
2020 bundleNavContext.review.choiceCallback(false);
2021}
2022
2023static void bundleNavReviewAskRejectionConfirmation(nbgl_operationType_t operationType,
2024 nbgl_callback_t callback)
2025{
2026 const char *title;
2027 const char *confirmText;
2028 // clear skip and blind bits
2029 operationType
2030 &= ~(SKIPPABLE_OPERATION | BLIND_OPERATION | RISKY_OPERATION | NO_THREAT_OPERATION);
2031 if (operationType == TYPE_TRANSACTION) {
2032 title = "Reject transaction?";
2033 confirmText = "Go back to transaction";
2034 }
2035 else if (operationType == TYPE_MESSAGE) {
2036 title = "Reject message?";
2037 confirmText = "Go back to message";
2038 }
2039 else {
2040 title = "Reject operation?";
2041 confirmText = "Go back to operation";
2042 }
2043
2044 // display a choice to confirm/cancel rejection
2045 nbgl_useCaseConfirm(title, NULL, "Yes, reject", confirmText, callback);
2046}
2047
2048static void bundleNavReviewChoice(bool confirm)
2049{
2050 if (confirm) {
2051 bundleNavContext.review.choiceCallback(true);
2052 }
2053 else {
2054 bundleNavReviewAskRejectionConfirmation(bundleNavContext.review.operationType,
2055 bundleNavReviewConfirmRejection);
2056 }
2057}
2058
2059static void bundleNavReviewStreamingConfirmRejection(void)
2060{
2061 bundleNavContext.reviewStreaming.choiceCallback(false);
2062}
2063
2064static void bundleNavReviewStreamingChoice(bool confirm)
2065{
2066 if (confirm) {
2067 // Display a spinner if it wasn't the finish step
2068 if (STARTING_CONTENT.type != INFO_LONG_PRESS) {
2069 nbgl_useCaseSpinner("Processing");
2070 }
2071 bundleNavContext.reviewStreaming.choiceCallback(true);
2072 }
2073 else {
2074 bundleNavReviewAskRejectionConfirmation(bundleNavContext.reviewStreaming.operationType,
2075 bundleNavReviewStreamingConfirmRejection);
2076 }
2077}
2078
2079// function used to display the security level page in modal
2080// it can be either a single page with text or QR code, or a list of touchable bars leading
2081// to single pages
2082static void displaySecurityReport(uint32_t set)
2083{
2084 nbgl_layoutDescription_t layoutDescription = {.modal = true,
2085 .withLeftBorder = true,
2086 .onActionCallback = modalLayoutTouchCallback,
2087 .tapActionText = NULL};
2089 .separationLine = true,
2090 .backAndText.icon = NULL,
2091 .backAndText.tuneId = TUNE_TAP_CASUAL,
2092 .backAndText.token = DISMISS_WARNING_TOKEN};
2093 nbgl_layoutFooter_t footerDesc
2094 = {.type = FOOTER_EMPTY, .separationLine = false, .emptySpace.height = 0};
2095 uint8_t i;
2096 uint8_t nbWarnings = 0;
2097 const char *provider;
2098
2099 reviewWithWarnCtx.modalLayout = nbgl_layoutGet(&layoutDescription);
2100
2101 // count the number of warnings
2102 for (i = 0; i < NB_WARNING_TYPES; i++) {
2103 if ((set & (1 << i)) && (i != W3C_NO_THREAT_WARN)) {
2104 nbWarnings++;
2105 }
2106 }
2107
2108 // display a list of touchable bars in some conditions:
2109 // - we must be in the first level of security report
2110 // - and be in the review itself (not from the intro/warning screen)
2111 //
2112 if ((reviewWithWarnCtx.securityReportLevel == 1) && (!reviewWithWarnCtx.isIntro)) {
2113 for (i = 0; i < NB_WARNING_TYPES; i++) {
2114 if (reviewWithWarnCtx.warning->predefinedSet & (1 << i)) {
2115 nbgl_layoutBar_t bar = {0};
2116 if ((i == BLIND_SIGNING_WARN) || (i == W3C_NO_THREAT_WARN) || (i == W3C_ISSUE_WARN)
2117 || (reviewWithWarnCtx.warning->providerMessage == NULL)) {
2118 bar.subText = securityReportItems[i].subText;
2119 }
2120 else {
2121 bar.subText = reviewWithWarnCtx.warning->providerMessage;
2122 }
2123 bar.text = securityReportItems[i].text;
2124 if (i != W3C_NO_THREAT_WARN) {
2125 bar.iconRight = &PUSH_ICON;
2126 bar.token = FIRST_WARN_BAR_TOKEN + i;
2127 bar.tuneId = TUNE_TAP_CASUAL;
2128 }
2129 else {
2131 }
2132 bar.iconLeft = securityReportItems[i].icon;
2133 nbgl_layoutAddTouchableBar(reviewWithWarnCtx.modalLayout, &bar);
2134 nbgl_layoutAddSeparationLine(reviewWithWarnCtx.modalLayout);
2135 }
2136 }
2137 headerDesc.backAndText.text = "Security report";
2138 nbgl_layoutAddHeader(reviewWithWarnCtx.modalLayout, &headerDesc);
2139 nbgl_layoutDraw(reviewWithWarnCtx.modalLayout);
2140 nbgl_refresh();
2141 return;
2142 }
2143 if (reviewWithWarnCtx.warning && reviewWithWarnCtx.warning->reportProvider) {
2144 provider = reviewWithWarnCtx.warning->reportProvider;
2145 }
2146 else {
2147 provider = "[unknown]";
2148 }
2149 if ((set & (1 << W3C_THREAT_DETECTED_WARN)) || (set & (1 << W3C_RISK_DETECTED_WARN))) {
2150 size_t urlLen = 0;
2151 char *destStr
2152 = tmpString
2153 + W3C_DESCRIPTION_MAX_LEN / 2; // use the second half of tmpString for strings
2154#ifdef NBGL_QRCODE
2155 // display a QR Code
2156 nbgl_layoutQRCode_t qrCode = {.url = destStr,
2157 .text1 = reviewWithWarnCtx.warning->reportUrl,
2158 .text2 = "Scan to view full report",
2159 .centered = true,
2160 .offsetY = 0};
2161 // add "https://"" as prefix of the given URL
2162 snprintf(destStr,
2163 W3C_DESCRIPTION_MAX_LEN / 2,
2164 "https://%s",
2165 reviewWithWarnCtx.warning->reportUrl);
2166 urlLen = strlen(destStr) + 1;
2167 nbgl_layoutAddQRCode(reviewWithWarnCtx.modalLayout, &qrCode);
2168 footerDesc.emptySpace.height = 24;
2169#endif // NBGL_QRCODE
2170 // use the next part of destStr for back bar text
2171 snprintf(destStr + urlLen, W3C_DESCRIPTION_MAX_LEN / 2 - urlLen, "%s report", provider);
2172 headerDesc.backAndText.text = destStr + urlLen;
2173 }
2174 else if (set & (1 << BLIND_SIGNING_WARN)) {
2175 // display a centered if in review
2176 nbgl_contentCenter_t info = {0};
2177 info.icon = &LARGE_WARNING_ICON;
2178 info.title = "This transaction cannot be Clear Signed";
2179 info.description
2180 = "This transaction or message cannot be decoded fully. If you choose to sign, you "
2181 "could be authorizing malicious actions that can drain your wallet.\n\nLearn more: "
2182 "ledger.com/e8";
2183 nbgl_layoutAddContentCenter(reviewWithWarnCtx.modalLayout, &info);
2184 footerDesc.emptySpace.height = MEDIUM_CENTERING_HEADER;
2185 headerDesc.separationLine = false;
2186 }
2187 else if (set & (1 << W3C_ISSUE_WARN)) {
2188 // if W3 Checks issue, display a centered info
2189 nbgl_contentCenter_t info = {0};
2190 info.icon = &LARGE_WARNING_ICON;
2191 info.title = "Transaction Check unavailable";
2192 info.description
2193 = "If you're not using the\nLedger Live app, Transaction Check might not work. If your "
2194 "are using Ledger Live, reject the transaction and try again.\n\nGet help at "
2195 "ledger.com/e11";
2196 nbgl_layoutAddContentCenter(reviewWithWarnCtx.modalLayout, &info);
2197 footerDesc.emptySpace.height = MEDIUM_CENTERING_HEADER;
2198 headerDesc.separationLine = false;
2199 }
2200 nbgl_layoutAddHeader(reviewWithWarnCtx.modalLayout, &headerDesc);
2201 if (footerDesc.emptySpace.height > 0) {
2202 nbgl_layoutAddExtendedFooter(reviewWithWarnCtx.modalLayout, &footerDesc);
2203 }
2204 nbgl_layoutDraw(reviewWithWarnCtx.modalLayout);
2205 nbgl_refresh();
2206}
2207
2208// function used to display the security level page in modal
2209// it can be either a single page with text or QR code, or a list of touchable bars leading
2210// to single pages
2211static void displayCustomizedSecurityReport(const nbgl_warningDetails_t *details)
2212{
2213 nbgl_layoutDescription_t layoutDescription = {.modal = true,
2214 .withLeftBorder = true,
2215 .onActionCallback = modalLayoutTouchCallback,
2216 .tapActionText = NULL};
2218 .separationLine = true,
2219 .backAndText.icon = NULL,
2220 .backAndText.tuneId = TUNE_TAP_CASUAL,
2221 .backAndText.token = DISMISS_WARNING_TOKEN};
2222 uint8_t i;
2223
2224 reviewWithWarnCtx.modalLayout = nbgl_layoutGet(&layoutDescription);
2225 headerDesc.backAndText.text = details->title;
2226 nbgl_layoutAddHeader(reviewWithWarnCtx.modalLayout, &headerDesc);
2227 if (details->type == BAR_LIST_WARNING) {
2228 // if more than one warning warning, so use a list of touchable bars
2229 for (i = 0; i < details->barList.nbBars; i++) {
2230 nbgl_layoutBar_t bar;
2231 bar.text = details->barList.texts[i];
2232 bar.subText = details->barList.subTexts[i];
2233 bar.iconRight = &PUSH_ICON;
2234 bar.iconLeft = details->barList.icons[i];
2235 bar.token = FIRST_WARN_BAR_TOKEN + i;
2236 bar.tuneId = TUNE_TAP_CASUAL;
2237 bar.large = false;
2238 bar.inactive = false;
2239 nbgl_layoutAddTouchableBar(reviewWithWarnCtx.modalLayout, &bar);
2240 nbgl_layoutAddSeparationLine(reviewWithWarnCtx.modalLayout);
2241 }
2242 }
2243 else if (details->type == QRCODE_WARNING) {
2244#ifdef NBGL_QRCODE
2245 // display a QR Code
2246 nbgl_layoutAddQRCode(reviewWithWarnCtx.modalLayout, &details->qrCode);
2247#endif // NBGL_QRCODE
2248 headerDesc.backAndText.text = details->title;
2249 }
2250 else if (details->type == CENTERED_INFO_WARNING) {
2251 // display a centered info
2252 nbgl_layoutAddContentCenter(reviewWithWarnCtx.modalLayout, &details->centeredInfo);
2253 headerDesc.separationLine = false;
2254 }
2255 nbgl_layoutDraw(reviewWithWarnCtx.modalLayout);
2256 nbgl_refresh();
2257}
2258
2259// function used to display the initial warning page when starting a "review with warning"
2260static void displayInitialWarning(void)
2261{
2262 // Play notification sound
2263#ifdef HAVE_PIEZO_SOUND
2264 tune_index_e tune = TUNE_RESERVED;
2265#endif // HAVE_PIEZO_SOUND
2266 nbgl_layoutDescription_t layoutDescription;
2267 nbgl_layoutChoiceButtons_t buttonsInfo = {.bottomText = "Continue anyway",
2268 .token = WARNING_CHOICE_TOKEN,
2269 .topText = "Back to safety",
2270 .style = ROUNDED_AND_FOOTER_STYLE,
2271 .tuneId = TUNE_TAP_CASUAL};
2272 nbgl_layoutHeader_t headerDesc = {.type = HEADER_EMPTY,
2273 .separationLine = false,
2274 .emptySpace.height = MEDIUM_CENTERING_HEADER};
2275 uint32_t set = reviewWithWarnCtx.warning->predefinedSet
2276 & ~((1 << W3C_NO_THREAT_WARN) | (1 << W3C_ISSUE_WARN));
2277
2278 reviewWithWarnCtx.isIntro = true;
2279
2280 layoutDescription.modal = false;
2281 layoutDescription.withLeftBorder = true;
2282
2283 layoutDescription.onActionCallback = layoutTouchCallback;
2284 layoutDescription.tapActionText = NULL;
2285
2286 layoutDescription.ticker.tickerCallback = NULL;
2287 reviewWithWarnCtx.layoutCtx = nbgl_layoutGet(&layoutDescription);
2288
2289 nbgl_layoutAddHeader(reviewWithWarnCtx.layoutCtx, &headerDesc);
2290 if (reviewWithWarnCtx.warning->predefinedSet != 0) {
2292 reviewWithWarnCtx.layoutCtx,
2293 (set == (1 << BLIND_SIGNING_WARN)) ? &INFO_I_ICON : &QRCODE_ICON,
2294 WARNING_BUTTON_TOKEN,
2295 TUNE_TAP_CASUAL);
2296 }
2297 else if (reviewWithWarnCtx.warning->introTopRightIcon != NULL) {
2298 nbgl_layoutAddTopRightButton(reviewWithWarnCtx.layoutCtx,
2299 reviewWithWarnCtx.warning->introTopRightIcon,
2300 WARNING_BUTTON_TOKEN,
2301 TUNE_TAP_CASUAL);
2302 }
2303 // add main content
2304 // if predefined content is configured, use it preferably
2305 if (reviewWithWarnCtx.warning->predefinedSet != 0) {
2306 nbgl_contentCenter_t info = {0};
2307
2308 const char *provider;
2309
2310 // default icon
2311 info.icon = &LARGE_WARNING_ICON;
2312
2313 // use small title only if not Blind signing only
2314 if (set != (1 << BLIND_SIGNING_WARN)) {
2315 if (reviewWithWarnCtx.warning->reportProvider) {
2316 provider = reviewWithWarnCtx.warning->reportProvider;
2317 }
2318 else {
2319 provider = "[unknown]";
2320 }
2321 info.smallTitle = tmpString;
2322 snprintf(tmpString, W3C_DESCRIPTION_MAX_LEN, "Detected by %s", provider);
2323 }
2324
2325 if (set == (1 << BLIND_SIGNING_WARN)) {
2326 info.title = "Blind signing ahead";
2327 info.description
2328 = "This transaction's details are not fully verifiable. If you sign, you could "
2329 "lose all your assets.";
2330 buttonsInfo.bottomText = "Continue anyway";
2331#ifdef HAVE_PIEZO_SOUND
2332 tune = TUNE_NEUTRAL;
2333#endif // HAVE_PIEZO_SOUND
2334 }
2335 else if (set & (1 << W3C_RISK_DETECTED_WARN)) {
2336 info.title = "Potential risk";
2337 if (reviewWithWarnCtx.warning->providerMessage == NULL) {
2338 info.description = "Unidentified risk";
2339 }
2340 else {
2341 info.description = reviewWithWarnCtx.warning->providerMessage;
2342 }
2343 buttonsInfo.bottomText = "Accept risk and continue";
2344#ifdef HAVE_PIEZO_SOUND
2345 tune = TUNE_NEUTRAL;
2346#endif // HAVE_PIEZO_SOUND
2347 }
2348 else if (set & (1 << W3C_THREAT_DETECTED_WARN)) {
2349 info.title = "Critical threat";
2350 if (reviewWithWarnCtx.warning->providerMessage == NULL) {
2351 info.description = "Unidentified threat";
2352 }
2353 else {
2354 info.description = reviewWithWarnCtx.warning->providerMessage;
2355 }
2356 buttonsInfo.bottomText = "Accept threat and continue";
2357#ifdef HAVE_PIEZO_SOUND
2358 tune = TUNE_ERROR;
2359#endif // HAVE_PIEZO_SOUND
2360 }
2361 nbgl_layoutAddContentCenter(reviewWithWarnCtx.layoutCtx, &info);
2362 }
2363 else if (reviewWithWarnCtx.warning->info != NULL) {
2364 // if no predefined content, use custom one
2365 nbgl_layoutAddContentCenter(reviewWithWarnCtx.layoutCtx, reviewWithWarnCtx.warning->info);
2366#ifdef HAVE_PIEZO_SOUND
2367 tune = TUNE_LOOK_AT_ME;
2368#endif // HAVE_PIEZO_SOUND
2369 }
2370 // add button and footer on bottom
2371 nbgl_layoutAddChoiceButtons(reviewWithWarnCtx.layoutCtx, &buttonsInfo);
2372
2373#ifdef HAVE_PIEZO_SOUND
2374 if (tune != TUNE_RESERVED) {
2375 os_io_seph_cmd_piezo_play_tune(tune);
2376 }
2377#endif // HAVE_PIEZO_SOUND
2378 nbgl_layoutDraw(reviewWithWarnCtx.layoutCtx);
2379 nbgl_refresh();
2380}
2381
2382// function to factorize code for reviews tipbox
2383static void initWarningTipBox(const nbgl_tipBox_t *tipBox)
2384{
2385 const char *predefinedTipBoxText = NULL;
2386
2387 // if warning is valid and a warning requires a tip-box
2388 if (reviewWithWarnCtx.warning) {
2389 if (reviewWithWarnCtx.warning->predefinedSet & (1 << W3C_ISSUE_WARN)) {
2390 if (reviewWithWarnCtx.warning->predefinedSet & (1 << BLIND_SIGNING_WARN)) {
2391 predefinedTipBoxText = "Transaction Check unavailable.\nBlind signing required.";
2392 }
2393 else {
2394 predefinedTipBoxText = "Transaction Check unavailable";
2395 }
2396 }
2397 else if (reviewWithWarnCtx.warning->predefinedSet & (1 << W3C_THREAT_DETECTED_WARN)) {
2398 if (reviewWithWarnCtx.warning->predefinedSet & (1 << BLIND_SIGNING_WARN)) {
2399 predefinedTipBoxText = "Critical threat detected.\nBlind signing required.";
2400 }
2401 else {
2402 predefinedTipBoxText = "Critical threat detected.";
2403 }
2404 }
2405 else if (reviewWithWarnCtx.warning->predefinedSet & (1 << W3C_RISK_DETECTED_WARN)) {
2406 if (reviewWithWarnCtx.warning->predefinedSet & (1 << BLIND_SIGNING_WARN)) {
2407 predefinedTipBoxText = "Potential risk detected.\nBlind signing required.";
2408 }
2409 else {
2410 predefinedTipBoxText = "Potential risk detected.";
2411 }
2412 }
2413 else if (reviewWithWarnCtx.warning->predefinedSet & (1 << W3C_NO_THREAT_WARN)) {
2414 if (reviewWithWarnCtx.warning->predefinedSet & (1 << BLIND_SIGNING_WARN)) {
2415 predefinedTipBoxText
2416 = "No threat detected by Transaction Check but blind signing required.";
2417 }
2418 else {
2419 predefinedTipBoxText = "No threat detected by Transaction Check.";
2420 }
2421 }
2422 else if (reviewWithWarnCtx.warning->predefinedSet & (1 << BLIND_SIGNING_WARN)) {
2423 predefinedTipBoxText = "Blind signing required.";
2424 }
2425 }
2426
2427 if ((tipBox != NULL) || (predefinedTipBoxText != NULL)) {
2428 // do not display "Swipe to review" if a tip-box is displayed
2429 STARTING_CONTENT.content.extendedCenter.contentCenter.subText = NULL;
2430 if (predefinedTipBoxText != NULL) {
2431 genericContext.validWarningCtx = true;
2432 STARTING_CONTENT.content.extendedCenter.tipBox.icon = NULL;
2433 STARTING_CONTENT.content.extendedCenter.tipBox.text = predefinedTipBoxText;
2434 }
2435 else {
2436 STARTING_CONTENT.content.extendedCenter.tipBox.icon = tipBox->icon;
2437 STARTING_CONTENT.content.extendedCenter.tipBox.text = tipBox->text;
2438 }
2439 STARTING_CONTENT.content.extendedCenter.tipBox.token = TIP_BOX_TOKEN;
2440 STARTING_CONTENT.content.extendedCenter.tipBox.tuneId = TUNE_TAP_CASUAL;
2441 }
2442}
2443
2444// function to factorize code for all simple reviews
2445static void useCaseReview(nbgl_operationType_t operationType,
2446 const nbgl_contentTagValueList_t *tagValueList,
2447 const nbgl_icon_details_t *icon,
2448 const char *reviewTitle,
2449 const char *reviewSubTitle,
2450 const char *finishTitle,
2451 const nbgl_tipBox_t *tipBox,
2452 nbgl_choiceCallback_t choiceCallback,
2453 bool isLight,
2454 bool playNotifSound)
2455{
2456 reset_callbacks();
2457 memset(&genericContext, 0, sizeof(genericContext));
2458
2459 bundleNavContext.review.operationType = operationType;
2460 bundleNavContext.review.choiceCallback = choiceCallback;
2461
2462 // memorize context
2463 onChoice = bundleNavReviewChoice;
2464 navType = GENERIC_NAV;
2465 pageTitle = NULL;
2466
2467 genericContext.genericContents.contentsList = localContentsList;
2468 genericContext.genericContents.nbContents = 3;
2469 memset(localContentsList, 0, 3 * sizeof(nbgl_content_t));
2470
2471 // First a centered info
2472 STARTING_CONTENT.type = EXTENDED_CENTER;
2473 prepareReviewFirstPage(
2474 &STARTING_CONTENT.content.extendedCenter.contentCenter, icon, reviewTitle, reviewSubTitle);
2475
2476 // Prepare un tipbox if needed
2477 initWarningTipBox(tipBox);
2478
2479 // Then the tag/value pairs
2480 localContentsList[1].type = TAG_VALUE_LIST;
2481 memcpy(&localContentsList[1].content.tagValueList,
2482 tagValueList,
2484 localContentsList[1].contentActionCallback = tagValueList->actionCallback;
2485
2486 // The last page
2487 if (isLight) {
2488 localContentsList[2].type = INFO_BUTTON;
2489 prepareReviewLightLastPage(
2490 operationType, &localContentsList[2].content.infoButton, icon, finishTitle);
2491 }
2492 else {
2493 localContentsList[2].type = INFO_LONG_PRESS;
2494 prepareReviewLastPage(
2495 operationType, &localContentsList[2].content.infoLongPress, icon, finishTitle);
2496 }
2497
2498 // compute number of pages & fill navigation structure
2499 uint8_t nbPages = getNbPagesForGenericContents(
2500 &genericContext.genericContents, 0, (operationType & SKIPPABLE_OPERATION));
2501 prepareNavInfo(true, nbPages, getRejectReviewText(operationType));
2502
2503 // Play notification sound if required
2504 if (playNotifSound) {
2505#ifdef HAVE_PIEZO_SOUND
2506 os_io_seph_cmd_piezo_play_tune(TUNE_LOOK_AT_ME);
2507#endif // HAVE_PIEZO_SOUND
2508 }
2509
2510 displayGenericContextPage(0, true);
2511}
2512
2513// function to factorize code for all streaming reviews
2514static void useCaseReviewStreamingStart(nbgl_operationType_t operationType,
2515 const nbgl_icon_details_t *icon,
2516 const char *reviewTitle,
2517 const char *reviewSubTitle,
2518 nbgl_choiceCallback_t choiceCallback,
2519 bool playNotifSound)
2520{
2521 reset_callbacks();
2522 memset(&genericContext, 0, sizeof(genericContext));
2523
2524 bundleNavContext.reviewStreaming.operationType = operationType;
2525 bundleNavContext.reviewStreaming.choiceCallback = choiceCallback;
2526 bundleNavContext.reviewStreaming.icon = icon;
2527
2528 // memorize context
2529 onChoice = bundleNavReviewStreamingChoice;
2530 navType = STREAMING_NAV;
2531 pageTitle = NULL;
2532
2533 genericContext.genericContents.contentsList = localContentsList;
2534 genericContext.genericContents.nbContents = 1;
2535 memset(localContentsList, 0, 1 * sizeof(nbgl_content_t));
2536
2537 // First a centered info
2538 STARTING_CONTENT.type = EXTENDED_CENTER;
2539 prepareReviewFirstPage(
2540 &STARTING_CONTENT.content.extendedCenter.contentCenter, icon, reviewTitle, reviewSubTitle);
2541
2542 // Prepare un tipbox if needed
2543 initWarningTipBox(NULL);
2544
2545 // compute number of pages & fill navigation structure
2546 bundleNavContext.reviewStreaming.stepPageNb = getNbPagesForGenericContents(
2547 &genericContext.genericContents, 0, (operationType & SKIPPABLE_OPERATION));
2548 prepareNavInfo(true, NBGL_NO_PROGRESS_INDICATOR, getRejectReviewText(operationType));
2549 // no back button on first page
2550 navInfo.navWithButtons.backButton = false;
2551
2552 // Play notification sound if required
2553 if (playNotifSound) {
2554#ifdef HAVE_PIEZO_SOUND
2555 os_io_seph_cmd_piezo_play_tune(TUNE_LOOK_AT_ME);
2556#endif // HAVE_PIEZO_SOUND
2557 }
2558
2559 displayGenericContextPage(0, true);
2560}
2561
2578static void useCaseHomeExt(const char *appName,
2579 const nbgl_icon_details_t *appIcon,
2580 const char *tagline,
2581 bool withSettings,
2582 nbgl_homeAction_t *homeAction,
2583 nbgl_callback_t topRightCallback,
2584 nbgl_callback_t quitCallback)
2585{
2586 reset_callbacks();
2587
2588 nbgl_pageInfoDescription_t info = {.centeredInfo.icon = appIcon,
2589 .centeredInfo.text1 = appName,
2590 .centeredInfo.text3 = NULL,
2591 .centeredInfo.style = LARGE_CASE_INFO,
2592 .centeredInfo.offsetY = 0,
2593 .footerText = NULL,
2594 .bottomButtonStyle = QUIT_APP_TEXT,
2595 .tapActionText = NULL,
2596 .topRightStyle = withSettings ? SETTINGS_ICON : INFO_ICON,
2597 .topRightToken = CONTINUE_TOKEN,
2598 .tuneId = TUNE_TAP_CASUAL};
2599 if ((homeAction->text != NULL) || (homeAction->icon != NULL)) {
2600 // trick to use ACTION_BUTTON_TOKEN for action and quit, with index used to distinguish
2601 info.bottomButtonsToken = ACTION_BUTTON_TOKEN;
2602 onAction = homeAction->callback;
2603 info.actionButtonText = homeAction->text;
2604 info.actionButtonIcon = homeAction->icon;
2607 }
2608 else {
2609 info.bottomButtonsToken = QUIT_TOKEN;
2610 onAction = NULL;
2611 info.actionButtonText = NULL;
2612 info.actionButtonIcon = NULL;
2613 }
2614 if (tagline == NULL) {
2615 if (strlen(appName) > MAX_APP_NAME_FOR_SDK_TAGLINE) {
2616 snprintf(tmpString,
2618 "This app enables signing\ntransactions on its network.");
2619 }
2620 else {
2621 snprintf(tmpString,
2623 "%s %s\n%s",
2625 appName,
2627 }
2628
2629 // If there is more than 3 lines, it means the appName was split, so we put it on the next
2630 // line
2631 if (nbgl_getTextNbLinesInWidth(SMALL_REGULAR_FONT, tmpString, AVAILABLE_WIDTH, false) > 3) {
2632 snprintf(tmpString,
2634 "%s\n%s %s",
2636 appName,
2638 }
2639 info.centeredInfo.text2 = tmpString;
2640 }
2641 else {
2642 info.centeredInfo.text2 = tagline;
2643 }
2644
2645 onContinue = topRightCallback;
2646 onQuit = quitCallback;
2647
2648 pageContext = nbgl_pageDrawInfo(&pageCallback, NULL, &info);
2650}
2651
2660static void displayDetails(const char *tag, const char *value, bool wrapping)
2661{
2662 memset(&detailsContext, 0, sizeof(detailsContext));
2663
2664 uint16_t nbLines
2665 = nbgl_getTextNbLinesInWidth(SMALL_REGULAR_FONT, value, AVAILABLE_WIDTH, wrapping);
2666
2667 // initialize context
2668 detailsContext.tag = tag;
2669 detailsContext.value = value;
2670 detailsContext.nbPages = (nbLines + NB_MAX_LINES_IN_DETAILS - 1) / NB_MAX_LINES_IN_DETAILS;
2671 detailsContext.currentPage = 0;
2672 detailsContext.wrapping = wrapping;
2673 // add some spare for room lost with "..." substitution
2674 if (detailsContext.nbPages > 1) {
2675 uint16_t nbLostChars = (detailsContext.nbPages - 1) * 3;
2676 uint16_t nbLostLines = (nbLostChars + ((AVAILABLE_WIDTH) / 16) - 1)
2677 / ((AVAILABLE_WIDTH) / 16); // 16 for average char width
2678 uint8_t nbLinesInLastPage
2679 = nbLines - ((detailsContext.nbPages - 1) * NB_MAX_LINES_IN_DETAILS);
2680
2681 detailsContext.nbPages += nbLostLines / NB_MAX_LINES_IN_DETAILS;
2682 if ((nbLinesInLastPage + (nbLostLines % NB_MAX_LINES_IN_DETAILS))
2684 detailsContext.nbPages++;
2685 }
2686 }
2687
2688 displayDetailsPage(0, true);
2689}
2690
2691/**********************
2692 * GLOBAL FUNCTIONS
2693 **********************/
2694
2707uint8_t nbgl_useCaseGetNbTagValuesInPage(uint8_t nbPairs,
2708 const nbgl_contentTagValueList_t *tagValueList,
2709 uint8_t startIndex,
2710 bool *requireSpecificDisplay)
2711{
2712 return getNbTagValuesInPage(
2713 nbPairs, tagValueList, startIndex, false, false, requireSpecificDisplay);
2714}
2715
2729uint8_t nbgl_useCaseGetNbTagValuesInPageExt(uint8_t nbPairs,
2730 const nbgl_contentTagValueList_t *tagValueList,
2731 uint8_t startIndex,
2732 bool isSkippable,
2733 bool *requireSpecificDisplay)
2734{
2735 return getNbTagValuesInPage(
2736 nbPairs, tagValueList, startIndex, isSkippable, false, requireSpecificDisplay);
2737}
2738
2748uint8_t nbgl_useCaseGetNbInfosInPage(uint8_t nbInfos,
2749 const nbgl_contentInfoList_t *infosList,
2750 uint8_t startIndex,
2751 bool withNav)
2752{
2753 uint8_t nbInfosInPage = 0;
2754 uint16_t currentHeight = 0;
2755 uint16_t previousHeight;
2756 uint16_t navHeight = withNav ? SIMPLE_FOOTER_HEIGHT : 0;
2757 const char *const *infoTypes = PIC(infosList->infoTypes);
2758 const char *const *infoContents = PIC(infosList->infoContents);
2759
2760 while (nbInfosInPage < nbInfos) {
2761 // margin between infos
2762 currentHeight += PRE_TEXT_MARGIN;
2763
2764 // type height
2765 currentHeight += nbgl_getTextHeightInWidth(
2766 SMALL_BOLD_FONT, PIC(infoTypes[startIndex + nbInfosInPage]), AVAILABLE_WIDTH, true);
2767 // space between type and content
2768 currentHeight += TEXT_SUBTEXT_MARGIN;
2769
2770 // content height
2771 currentHeight += nbgl_getTextHeightInWidth(SMALL_REGULAR_FONT,
2772 PIC(infoContents[startIndex + nbInfosInPage]),
2774 true);
2775 currentHeight += POST_SUBTEXT_MARGIN; // under the content
2776 // if height is over the limit
2777 if (currentHeight >= (INFOS_AREA_HEIGHT - navHeight)) {
2778 // if there was no nav, now there will be, so it can be necessary to remove the last
2779 // item
2780 if (!withNav && (previousHeight >= (INFOS_AREA_HEIGHT - SIMPLE_FOOTER_HEIGHT))) {
2781 nbInfosInPage--;
2782 }
2783 break;
2784 }
2785 previousHeight = currentHeight;
2786 nbInfosInPage++;
2787 }
2788 return nbInfosInPage;
2789}
2790
2800uint8_t nbgl_useCaseGetNbSwitchesInPage(uint8_t nbSwitches,
2801 const nbgl_contentSwitchesList_t *switchesList,
2802 uint8_t startIndex,
2803 bool withNav)
2804{
2805 uint8_t nbSwitchesInPage = 0;
2806 uint16_t currentHeight = 0;
2807 uint16_t previousHeight;
2808 uint16_t navHeight = withNav ? SIMPLE_FOOTER_HEIGHT : 0;
2809 nbgl_contentSwitch_t *switchArray = (nbgl_contentSwitch_t *) PIC(switchesList->switches);
2810
2811 while (nbSwitchesInPage < nbSwitches) {
2812 // margin between switches
2813 currentHeight += PRE_TEXT_MARGIN;
2814
2815 // text height
2816 currentHeight += nbgl_getTextHeightInWidth(SMALL_BOLD_FONT,
2817 switchArray[startIndex + nbSwitchesInPage].text,
2819 true);
2820 // space between text and sub-text
2821 currentHeight += TEXT_SUBTEXT_MARGIN;
2822
2823 // sub-text height
2824 currentHeight
2825 += nbgl_getTextHeightInWidth(SMALL_REGULAR_FONT,
2826 switchArray[startIndex + nbSwitchesInPage].subText,
2828 true);
2829 currentHeight += POST_SUBTEXT_MARGIN; // under the sub-text
2830 // if height is over the limit
2831 if (currentHeight >= (INFOS_AREA_HEIGHT - navHeight)) {
2832 break;
2833 }
2834 previousHeight = currentHeight;
2835 nbSwitchesInPage++;
2836 }
2837 // if there was no nav, now there will be, so it can be necessary to remove the last
2838 // item
2839 if (!withNav && (previousHeight >= (INFOS_AREA_HEIGHT - SIMPLE_FOOTER_HEIGHT))) {
2840 nbSwitchesInPage--;
2841 }
2842 return nbSwitchesInPage;
2843}
2844
2854uint8_t nbgl_useCaseGetNbBarsInPage(uint8_t nbBars,
2855 const nbgl_contentBarsList_t *barsList,
2856 uint8_t startIndex,
2857 bool withNav)
2858{
2859 uint8_t nbBarsInPage = 0;
2860 uint16_t currentHeight = 0;
2861 uint16_t previousHeight;
2862 uint16_t navHeight = withNav ? SIMPLE_FOOTER_HEIGHT : 0;
2863
2864 UNUSED(barsList);
2865 UNUSED(startIndex);
2866
2867 while (nbBarsInPage < nbBars) {
2868 currentHeight += TOUCHABLE_BAR_HEIGHT;
2869 // if height is over the limit
2870 if (currentHeight >= (INFOS_AREA_HEIGHT - navHeight)) {
2871 break;
2872 }
2873 previousHeight = currentHeight;
2874 nbBarsInPage++;
2875 }
2876 // if there was no nav, now there may will be, so it can be necessary to remove the last
2877 // item
2878 if (!withNav && (previousHeight >= (INFOS_AREA_HEIGHT - SIMPLE_FOOTER_HEIGHT))) {
2879 nbBarsInPage--;
2880 }
2881 return nbBarsInPage;
2882}
2883
2893uint8_t nbgl_useCaseGetNbChoicesInPage(uint8_t nbChoices,
2894 const nbgl_contentRadioChoice_t *choicesList,
2895 uint8_t startIndex,
2896 bool withNav)
2897{
2898 uint8_t nbChoicesInPage = 0;
2899 uint16_t currentHeight = 0;
2900 uint16_t previousHeight;
2901 uint16_t navHeight = withNav ? SIMPLE_FOOTER_HEIGHT : 0;
2902
2903 UNUSED(choicesList);
2904 UNUSED(startIndex);
2905
2906 while (nbChoicesInPage < nbChoices) {
2907 currentHeight += TOUCHABLE_BAR_HEIGHT;
2908 // if height is over the limit
2909 if (currentHeight >= (INFOS_AREA_HEIGHT - navHeight)) {
2910 // if there was no nav, now there will be, so it can be necessary to remove the last
2911 // item
2912 if (!withNav && (previousHeight >= (INFOS_AREA_HEIGHT - SIMPLE_FOOTER_HEIGHT))) {
2913 nbChoicesInPage--;
2914 }
2915 break;
2916 }
2917 previousHeight = currentHeight;
2918 nbChoicesInPage++;
2919 }
2920 return nbChoicesInPage;
2921}
2922
2930{
2931 uint8_t nbPages = 0;
2932 uint8_t nbPairs = tagValueList->nbPairs;
2933 uint8_t nbPairsInPage;
2934 uint8_t i = 0;
2935 bool flag;
2936
2937 while (i < tagValueList->nbPairs) {
2938 // upper margin
2939 nbPairsInPage = nbgl_useCaseGetNbTagValuesInPageExt(nbPairs, tagValueList, i, false, &flag);
2940 i += nbPairsInPage;
2941 nbPairs -= nbPairsInPage;
2942 nbPages++;
2943 }
2944 return nbPages;
2945}
2946
2951void nbgl_useCaseHome(const char *appName,
2952 const nbgl_icon_details_t *appIcon,
2953 const char *tagline,
2954 bool withSettings,
2955 nbgl_callback_t topRightCallback,
2956 nbgl_callback_t quitCallback)
2957{
2958 nbgl_homeAction_t homeAction = {0};
2959 useCaseHomeExt(
2960 appName, appIcon, tagline, withSettings, &homeAction, topRightCallback, quitCallback);
2961}
2962
2967void nbgl_useCaseHomeExt(const char *appName,
2968 const nbgl_icon_details_t *appIcon,
2969 const char *tagline,
2970 bool withSettings,
2971 const char *actionButtonText,
2972 nbgl_callback_t actionCallback,
2973 nbgl_callback_t topRightCallback,
2974 nbgl_callback_t quitCallback)
2975{
2976 nbgl_homeAction_t homeAction = {.callback = actionCallback,
2977 .icon = NULL,
2978 .style = STRONG_HOME_ACTION,
2979 .text = actionButtonText};
2980
2981 useCaseHomeExt(
2982 appName, appIcon, tagline, withSettings, &homeAction, topRightCallback, quitCallback);
2983}
2984
2998void nbgl_useCaseNavigableContent(const char *title,
2999 uint8_t initPage,
3000 uint8_t nbPages,
3001 nbgl_callback_t quitCallback,
3002 nbgl_navCallback_t navCallback,
3003 nbgl_layoutTouchCallback_t controlsCallback)
3004{
3005 reset_callbacks();
3006
3007 // memorize context
3008 onQuit = quitCallback;
3009 onNav = navCallback;
3010 onControls = controlsCallback;
3011 pageTitle = title;
3012 navType = SETTINGS_NAV;
3013
3014 // fill navigation structure
3015 prepareNavInfo(false, nbPages, NULL);
3016
3017 displaySettingsPage(initPage, true);
3018}
3019
3025void nbgl_useCaseSettings(const char *title,
3026 uint8_t initPage,
3027 uint8_t nbPages,
3028 bool touchable,
3029 nbgl_callback_t quitCallback,
3030 nbgl_navCallback_t navCallback,
3031 nbgl_layoutTouchCallback_t controlsCallback)
3032{
3033 UNUSED(touchable);
3035 title, initPage, nbPages, quitCallback, navCallback, controlsCallback);
3036}
3037
3050void nbgl_useCaseGenericSettings(const char *appName,
3051 uint8_t initPage,
3052 const nbgl_genericContents_t *settingContents,
3053 const nbgl_contentInfoList_t *infosList,
3054 nbgl_callback_t quitCallback)
3055{
3056 reset_callbacks();
3057 memset(&genericContext, 0, sizeof(genericContext));
3058
3059 // memorize context
3060 onQuit = quitCallback;
3061 pageTitle = appName;
3062 navType = GENERIC_NAV;
3063
3064 if (settingContents != NULL) {
3065 memcpy(&genericContext.genericContents, settingContents, sizeof(nbgl_genericContents_t));
3066 }
3067 if (infosList != NULL) {
3068 genericContext.hasFinishingContent = true;
3069 memset(&FINISHING_CONTENT, 0, sizeof(nbgl_content_t));
3070 FINISHING_CONTENT.type = INFOS_LIST;
3071 memcpy(&FINISHING_CONTENT.content, infosList, sizeof(nbgl_content_u));
3072 }
3073
3074 // fill navigation structure
3075 uint8_t nbPages = getNbPagesForGenericContents(&genericContext.genericContents, 0, false);
3076 if (infosList != NULL) {
3077 nbPages += getNbPagesForContent(&FINISHING_CONTENT, nbPages, true, false);
3078 }
3079
3080 prepareNavInfo(false, nbPages, NULL);
3081
3082 displayGenericContextPage(initPage, true);
3083}
3084
3096void nbgl_useCaseGenericConfiguration(const char *title,
3097 uint8_t initPage,
3098 const nbgl_genericContents_t *contents,
3099 nbgl_callback_t quitCallback)
3100{
3101 nbgl_useCaseGenericSettings(title, initPage, contents, NULL, quitCallback);
3102}
3103
3121 const char *appName,
3122 const nbgl_icon_details_t *appIcon,
3123 const char *tagline,
3124 const uint8_t
3125 initSettingPage, // if not INIT_HOME_PAGE, start directly the corresponding setting page
3126 const nbgl_genericContents_t *settingContents,
3127 const nbgl_contentInfoList_t *infosList,
3128 const nbgl_homeAction_t *action, // Set to NULL if no additional action
3129 nbgl_callback_t quitCallback)
3130{
3131 nbgl_homeAndSettingsContext_t *context = &bundleNavContext.homeAndSettings;
3132
3133 context->appName = appName;
3134 context->appIcon = appIcon;
3135 context->tagline = tagline;
3136 context->settingContents = settingContents;
3137 context->infosList = infosList;
3138 if (action != NULL) {
3139 memcpy(&context->homeAction, action, sizeof(nbgl_homeAction_t));
3140 }
3141 else {
3142 memset(&context->homeAction, 0, sizeof(nbgl_homeAction_t));
3143 }
3144 context->quitCallback = quitCallback;
3145
3146 if (initSettingPage != INIT_HOME_PAGE) {
3147 bundleNavStartSettingsAtPage(initSettingPage);
3148 }
3149 else {
3150 bundleNavStartHome();
3151 }
3152}
3153
3161void nbgl_useCaseStatus(const char *message, bool isSuccess, nbgl_callback_t quitCallback)
3162{
3163 reset_callbacks();
3164
3165 nbgl_screenTickerConfiguration_t ticker = {.tickerCallback = &tickerCallback,
3166 .tickerIntervale = 0, // not periodic
3167 .tickerValue = STATUS_SCREEN_DURATION};
3168
3169 onQuit = quitCallback;
3170 if (isSuccess) {
3171#ifdef HAVE_PIEZO_SOUND
3172 os_io_seph_cmd_piezo_play_tune(TUNE_LEDGER_MOMENT);
3173#endif // HAVE_PIEZO_SOUND
3174
3175 pageContext = nbgl_pageDrawLedgerInfo(&pageCallback, &ticker, message, QUIT_TOKEN);
3176 }
3177 else {
3179 .footerText = NULL,
3180 .centeredInfo.icon = &DENIED_CIRCLE_ICON,
3181 .centeredInfo.offsetY = SMALL_FOOTER_HEIGHT / 2,
3182 .centeredInfo.onTop = false,
3183 .centeredInfo.style = LARGE_CASE_INFO,
3184 .centeredInfo.text1 = message,
3185 .centeredInfo.text2 = NULL,
3186 .centeredInfo.text3 = NULL,
3187 .tapActionText = "",
3188 .isSwipeable = false,
3189 .tapActionToken = QUIT_TOKEN,
3190 .topRightStyle = NO_BUTTON_STYLE,
3191 .actionButtonText = NULL,
3192 .tuneId = TUNE_TAP_CASUAL};
3193 pageContext = nbgl_pageDrawInfo(&pageCallback, &ticker, &info);
3194 }
3196}
3197
3205 nbgl_callback_t quitCallback)
3206{
3207 const char *msg;
3208 bool isSuccess;
3209 switch (reviewStatusType) {
3211 msg = "Operation signed";
3212 isSuccess = true;
3213 break;
3215 msg = "Operation rejected";
3216 isSuccess = false;
3217 break;
3219 msg = "Transaction signed";
3220 isSuccess = true;
3221 break;
3223 msg = "Transaction rejected";
3224 isSuccess = false;
3225 break;
3227 msg = "Message signed";
3228 isSuccess = true;
3229 break;
3231 msg = "Message rejected";
3232 isSuccess = false;
3233 break;
3235 msg = "Address verified";
3236 isSuccess = true;
3237 break;
3239 msg = "Address verification\ncancelled";
3240 isSuccess = false;
3241 break;
3242 default:
3243 return;
3244 }
3245 nbgl_useCaseStatus(msg, isSuccess, quitCallback);
3246}
3247
3261 const char *message,
3262 const char *subMessage,
3263 const char *confirmText,
3264 const char *cancelText,
3265 nbgl_choiceCallback_t callback)
3266{
3267 reset_callbacks();
3268
3269 nbgl_pageConfirmationDescription_t info = {.cancelText = cancelText,
3270 .centeredInfo.text1 = message,
3271 .centeredInfo.text2 = subMessage,
3272 .centeredInfo.text3 = NULL,
3273 .centeredInfo.style = LARGE_CASE_INFO,
3274 .centeredInfo.icon = icon,
3275 .centeredInfo.offsetY = 0,
3276 .confirmationText = confirmText,
3277 .confirmationToken = CHOICE_TOKEN,
3278 .tuneId = TUNE_TAP_CASUAL,
3279 .modal = false};
3280 // check params
3281 if ((confirmText == NULL) || (cancelText == NULL)) {
3282 return;
3283 }
3284 onChoice = callback;
3285 pageContext = nbgl_pageDrawConfirmation(&pageCallback, &info);
3287}
3288
3302void nbgl_useCaseConfirm(const char *message,
3303 const char *subMessage,
3304 const char *confirmText,
3305 const char *cancelText,
3306 nbgl_callback_t callback)
3307{
3308 // Don't reset callback or nav context as this is just a modal.
3309
3310 nbgl_pageConfirmationDescription_t info = {.cancelText = cancelText,
3311 .centeredInfo.text1 = message,
3312 .centeredInfo.text2 = subMessage,
3313 .centeredInfo.text3 = NULL,
3314 .centeredInfo.style = LARGE_CASE_INFO,
3315 .centeredInfo.icon = &IMPORTANT_CIRCLE_ICON,
3316 .centeredInfo.offsetY = 0,
3317 .confirmationText = confirmText,
3318 .confirmationToken = CHOICE_TOKEN,
3319 .tuneId = TUNE_TAP_CASUAL,
3320 .modal = true};
3321 onModalConfirm = callback;
3322 if (modalPageContext != NULL) {
3323 nbgl_pageRelease(modalPageContext);
3324 }
3325 modalPageContext = nbgl_pageDrawConfirmation(&pageModalCallback, &info);
3327}
3328
3340 const char *message,
3341 const char *actionText,
3342 nbgl_callback_t callback)
3343{
3344 nbgl_pageContent_t content = {0};
3345
3346 // memorize callback
3347 onAction = callback;
3348
3349 content.tuneId = TUNE_TAP_CASUAL;
3350 content.type = INFO_BUTTON;
3351 content.infoButton.buttonText = actionText;
3352 content.infoButton.text = message;
3353 content.infoButton.icon = icon;
3354 content.infoButton.buttonToken = ACTION_BUTTON_TOKEN;
3355
3356 pageContext = nbgl_pageDrawGenericContent(&pageCallback, NULL, &content);
3358}
3359
3372 const char *reviewTitle,
3373 const char *reviewSubTitle,
3374 const char *rejectText,
3375 nbgl_callback_t continueCallback,
3376 nbgl_callback_t rejectCallback)
3377{
3378 reset_callbacks();
3379
3380 nbgl_pageInfoDescription_t info = {.footerText = rejectText,
3381 .footerToken = QUIT_TOKEN,
3382 .tapActionText = NULL,
3383 .isSwipeable = true,
3384 .tapActionToken = CONTINUE_TOKEN,
3385 .topRightStyle = NO_BUTTON_STYLE,
3386 .actionButtonText = NULL,
3387 .tuneId = TUNE_TAP_CASUAL};
3388 info.centeredInfo.icon = icon;
3389 info.centeredInfo.text1 = reviewTitle;
3390 info.centeredInfo.text2 = reviewSubTitle;
3391 info.centeredInfo.text3 = "Swipe to review";
3393 info.centeredInfo.offsetY = 0;
3394 onQuit = rejectCallback;
3395 onContinue = continueCallback;
3396
3397#ifdef HAVE_PIEZO_SOUND
3398 // Play notification sound
3399 os_io_seph_cmd_piezo_play_tune(TUNE_LOOK_AT_ME);
3400#endif // HAVE_PIEZO_SOUND
3401
3402 pageContext = nbgl_pageDrawInfo(&pageCallback, NULL, &info);
3403 nbgl_refresh();
3404}
3405
3410void nbgl_useCaseRegularReview(uint8_t initPage,
3411 uint8_t nbPages,
3412 const char *rejectText,
3413 nbgl_layoutTouchCallback_t buttonCallback,
3414 nbgl_navCallback_t navCallback,
3415 nbgl_choiceCallback_t choiceCallback)
3416{
3417 reset_callbacks();
3418
3419 // memorize context
3420 onChoice = choiceCallback;
3421 onNav = navCallback;
3422 onControls = buttonCallback;
3423 forwardNavOnly = false;
3424 navType = REVIEW_NAV;
3425
3426 // fill navigation structure
3427 UNUSED(rejectText);
3428 prepareNavInfo(true, nbPages, getRejectReviewText(TYPE_OPERATION));
3429
3430 displayReviewPage(initPage, true);
3431}
3432
3446 const nbgl_pageInfoLongPress_t *infoLongPress,
3447 const char *rejectText,
3448 nbgl_choiceCallback_t callback)
3449{
3450 uint8_t offset = 0;
3451
3452 reset_callbacks();
3453 memset(&genericContext, 0, sizeof(genericContext));
3454
3455 // memorize context
3456 onChoice = callback;
3457 navType = GENERIC_NAV;
3458 pageTitle = NULL;
3459 bundleNavContext.review.operationType = TYPE_OPERATION;
3460
3461 genericContext.genericContents.contentsList = localContentsList;
3462 memset(localContentsList, 0, 2 * sizeof(nbgl_content_t));
3463
3464 if (tagValueList != NULL && tagValueList->nbPairs != 0) {
3465 localContentsList[offset].type = TAG_VALUE_LIST;
3466 memcpy(&localContentsList[offset].content.tagValueList,
3467 tagValueList,
3469 offset++;
3470 }
3471
3472 localContentsList[offset].type = INFO_LONG_PRESS;
3473 memcpy(&localContentsList[offset].content.infoLongPress,
3474 infoLongPress,
3475 sizeof(nbgl_pageInfoLongPress_t));
3476 localContentsList[offset].content.infoLongPress.longPressToken = CONFIRM_TOKEN;
3477 offset++;
3478
3479 genericContext.genericContents.nbContents = offset;
3480
3481 // compute number of pages & fill navigation structure
3482 uint8_t nbPages = getNbPagesForGenericContents(&genericContext.genericContents, 0, false);
3483 UNUSED(rejectText);
3484 prepareNavInfo(true, nbPages, getRejectReviewText(TYPE_OPERATION));
3485
3486 displayGenericContextPage(0, true);
3487}
3488
3503 const nbgl_pageInfoLongPress_t *infoLongPress,
3504 const char *rejectText,
3505 nbgl_choiceCallback_t callback)
3506{
3507 uint8_t offset = 0;
3508
3509 reset_callbacks();
3510 memset(&genericContext, 0, sizeof(genericContext));
3511
3512 // memorize context
3513 onChoice = callback;
3514 navType = GENERIC_NAV;
3515 pageTitle = NULL;
3516
3517 genericContext.genericContents.contentsList = localContentsList;
3518 memset(localContentsList, 0, 2 * sizeof(nbgl_content_t));
3519
3520 if (tagValueList != NULL && tagValueList->nbPairs != 0) {
3521 localContentsList[offset].type = TAG_VALUE_LIST;
3522 memcpy(&localContentsList[offset].content.tagValueList,
3523 tagValueList,
3525 offset++;
3526 }
3527
3528 localContentsList[offset].type = INFO_BUTTON;
3529 localContentsList[offset].content.infoButton.text = infoLongPress->text;
3530 localContentsList[offset].content.infoButton.icon = infoLongPress->icon;
3531 localContentsList[offset].content.infoButton.buttonText = infoLongPress->longPressText;
3532 localContentsList[offset].content.infoButton.buttonToken = CONFIRM_TOKEN;
3533 localContentsList[offset].content.infoButton.tuneId = TUNE_TAP_CASUAL;
3534 offset++;
3535
3536 genericContext.genericContents.nbContents = offset;
3537
3538 // compute number of pages & fill navigation structure
3539 uint8_t nbPages = getNbPagesForGenericContents(&genericContext.genericContents, 0, false);
3540 UNUSED(rejectText);
3541 prepareNavInfo(true, nbPages, getRejectReviewText(TYPE_OPERATION));
3542
3543 displayGenericContextPage(0, true);
3544}
3545
3562void nbgl_useCaseReview(nbgl_operationType_t operationType,
3563 const nbgl_contentTagValueList_t *tagValueList,
3564 const nbgl_icon_details_t *icon,
3565 const char *reviewTitle,
3566 const char *reviewSubTitle,
3567 const char *finishTitle,
3568 nbgl_choiceCallback_t choiceCallback)
3569{
3570 useCaseReview(operationType,
3571 tagValueList,
3572 icon,
3573 reviewTitle,
3574 reviewSubTitle,
3575 finishTitle,
3576 NULL,
3577 choiceCallback,
3578 false,
3579 true);
3580}
3581
3602 const nbgl_contentTagValueList_t *tagValueList,
3603 const nbgl_icon_details_t *icon,
3604 const char *reviewTitle,
3605 const char *reviewSubTitle,
3606 const char *finishTitle,
3607 const nbgl_tipBox_t *tipBox,
3608 nbgl_choiceCallback_t choiceCallback)
3609{
3610 nbgl_useCaseAdvancedReview(operationType,
3611 tagValueList,
3612 icon,
3613 reviewTitle,
3614 reviewSubTitle,
3615 finishTitle,
3616 tipBox,
3617 &blindSigningWarning,
3618 choiceCallback);
3619}
3620
3644 const nbgl_contentTagValueList_t *tagValueList,
3645 const nbgl_icon_details_t *icon,
3646 const char *reviewTitle,
3647 const char *reviewSubTitle,
3648 const char *finishTitle,
3649 const nbgl_tipBox_t *tipBox,
3650 const nbgl_warning_t *warning,
3651 nbgl_choiceCallback_t choiceCallback)
3652{
3653 memset(&reviewWithWarnCtx, 0, sizeof(reviewWithWarnCtx));
3654 // memorize tipBox because it can be in the call stack of the caller
3655 if (tipBox != NULL) {
3656 memcpy(&activeTipBox, tipBox, sizeof(activeTipBox));
3657 }
3658 else {
3659 memset(&activeTipBox, 0, sizeof(activeTipBox));
3660 }
3661 // if no warning at all, it's a simple review
3662 if ((warning == NULL)
3663 || ((warning->predefinedSet == 0) && (warning->introDetails == NULL)
3664 && (warning->reviewDetails == NULL))) {
3665 useCaseReview(operationType,
3666 tagValueList,
3667 icon,
3668 reviewTitle,
3669 reviewSubTitle,
3670 finishTitle,
3671 tipBox,
3672 choiceCallback,
3673 false,
3674 true);
3675 return;
3676 }
3677 if (warning->predefinedSet == (1 << W3C_NO_THREAT_WARN)) {
3678 operationType |= NO_THREAT_OPERATION;
3679 }
3680 else {
3681 operationType |= RISKY_OPERATION;
3682 }
3683
3684 reviewWithWarnCtx.isStreaming = false;
3685 reviewWithWarnCtx.operationType = operationType;
3686 reviewWithWarnCtx.tagValueList = tagValueList;
3687 reviewWithWarnCtx.icon = icon;
3688 reviewWithWarnCtx.reviewTitle = reviewTitle;
3689 reviewWithWarnCtx.reviewSubTitle = reviewSubTitle;
3690 reviewWithWarnCtx.finishTitle = finishTitle;
3691 reviewWithWarnCtx.warning = warning;
3692 reviewWithWarnCtx.choiceCallback = choiceCallback;
3693
3694 // display the initial warning only of a risk/threat or blind signing
3695 if (!(reviewWithWarnCtx.warning->predefinedSet & (1 << W3C_THREAT_DETECTED_WARN))
3696 && !(reviewWithWarnCtx.warning->predefinedSet & (1 << W3C_RISK_DETECTED_WARN))
3697 && !(reviewWithWarnCtx.warning->predefinedSet & (1 << BLIND_SIGNING_WARN))) {
3698 useCaseReview(operationType,
3699 tagValueList,
3700 icon,
3701 reviewTitle,
3702 reviewSubTitle,
3703 finishTitle,
3704 tipBox,
3705 choiceCallback,
3706 false,
3707 true);
3708 return;
3709 }
3710
3711 displayInitialWarning();
3712}
3713
3731 const nbgl_contentTagValueList_t *tagValueList,
3732 const nbgl_icon_details_t *icon,
3733 const char *reviewTitle,
3734 const char *reviewSubTitle,
3735 const char *finishTitle,
3736 nbgl_choiceCallback_t choiceCallback)
3737{
3738 useCaseReview(operationType,
3739 tagValueList,
3740 icon,
3741 reviewTitle,
3742 reviewSubTitle,
3743 finishTitle,
3744 NULL,
3745 choiceCallback,
3746 true,
3747 true);
3748}
3749
3759 const char *rejectText,
3760 nbgl_callback_t rejectCallback)
3761{
3762 reset_callbacks();
3763 memset(&genericContext, 0, sizeof(genericContext));
3764
3765 // memorize context
3766 onQuit = rejectCallback;
3767 navType = GENERIC_NAV;
3768 pageTitle = NULL;
3769 bundleNavContext.review.operationType = TYPE_OPERATION;
3770
3771 memcpy(&genericContext.genericContents, contents, sizeof(nbgl_genericContents_t));
3772
3773 // compute number of pages & fill navigation structure
3774 uint8_t nbPages = getNbPagesForGenericContents(&genericContext.genericContents, 0, false);
3775 prepareNavInfo(true, nbPages, rejectText);
3776 navInfo.quitToken = QUIT_TOKEN;
3777
3778#ifdef HAVE_PIEZO_SOUND
3779 // Play notification sound
3780 os_io_seph_cmd_piezo_play_tune(TUNE_LOOK_AT_ME);
3781#endif // HAVE_PIEZO_SOUND
3782
3783 displayGenericContextPage(0, true);
3784}
3785
3799 const nbgl_icon_details_t *icon,
3800 const char *reviewTitle,
3801 const char *reviewSubTitle,
3802 nbgl_choiceCallback_t choiceCallback)
3803{
3804 useCaseReviewStreamingStart(
3805 operationType, icon, reviewTitle, reviewSubTitle, choiceCallback, true);
3806}
3807
3822 const nbgl_icon_details_t *icon,
3823 const char *reviewTitle,
3824 const char *reviewSubTitle,
3825 nbgl_choiceCallback_t choiceCallback)
3826{
3828 operationType, icon, reviewTitle, reviewSubTitle, &blindSigningWarning, choiceCallback);
3829}
3830
3847 const nbgl_icon_details_t *icon,
3848 const char *reviewTitle,
3849 const char *reviewSubTitle,
3850 const nbgl_warning_t *warning,
3851 nbgl_choiceCallback_t choiceCallback)
3852{
3853 memset(&reviewWithWarnCtx, 0, sizeof(reviewWithWarnCtx));
3854 // if no warning at all, it's a simple review
3855 if ((warning == NULL)
3856 || ((warning->predefinedSet == 0) && (warning->introDetails == NULL)
3857 && (warning->reviewDetails == NULL))) {
3858 useCaseReviewStreamingStart(
3859 operationType, icon, reviewTitle, reviewSubTitle, choiceCallback, true);
3860 return;
3861 }
3862 if (warning->predefinedSet == (1 << W3C_NO_THREAT_WARN)) {
3863 operationType |= NO_THREAT_OPERATION;
3864 }
3865 else {
3866 operationType |= RISKY_OPERATION;
3867 }
3868
3869 reviewWithWarnCtx.isStreaming = true;
3870 reviewWithWarnCtx.operationType = operationType;
3871 reviewWithWarnCtx.icon = icon;
3872 reviewWithWarnCtx.reviewTitle = reviewTitle;
3873 reviewWithWarnCtx.reviewSubTitle = reviewSubTitle;
3874 reviewWithWarnCtx.choiceCallback = choiceCallback;
3875 reviewWithWarnCtx.warning = warning;
3876
3877 // display the initial warning only of a risk/threat or blind signing
3878 if (!(reviewWithWarnCtx.warning->predefinedSet & (1 << W3C_THREAT_DETECTED_WARN))
3879 && !(reviewWithWarnCtx.warning->predefinedSet & (1 << W3C_RISK_DETECTED_WARN))
3880 && !(reviewWithWarnCtx.warning->predefinedSet & (1 << BLIND_SIGNING_WARN))) {
3881 useCaseReviewStreamingStart(
3882 operationType, icon, reviewTitle, reviewSubTitle, choiceCallback, true);
3883 return;
3884 }
3885 displayInitialWarning();
3886}
3887
3902 nbgl_choiceCallback_t choiceCallback,
3903 nbgl_callback_t skipCallback)
3904{
3905 // Should follow a call to nbgl_useCaseReviewStreamingStart
3906 memset(&genericContext, 0, sizeof(genericContext));
3907
3908 bundleNavContext.reviewStreaming.choiceCallback = choiceCallback;
3909 bundleNavContext.reviewStreaming.skipCallback = skipCallback;
3910
3911 // memorize context
3912 onChoice = bundleNavReviewStreamingChoice;
3913 navType = STREAMING_NAV;
3914 pageTitle = NULL;
3915
3916 genericContext.genericContents.contentsList = localContentsList;
3917 genericContext.genericContents.nbContents = 1;
3918 memset(localContentsList, 0, 1 * sizeof(nbgl_content_t));
3919
3920 // Then the tag/value pairs
3921 STARTING_CONTENT.type = TAG_VALUE_LIST;
3922 memcpy(
3923 &STARTING_CONTENT.content.tagValueList, tagValueList, sizeof(nbgl_contentTagValueList_t));
3924
3925 // compute number of pages & fill navigation structure
3926 bundleNavContext.reviewStreaming.stepPageNb = getNbPagesForGenericContents(
3927 &genericContext.genericContents,
3928 0,
3929 (bundleNavContext.reviewStreaming.operationType & SKIPPABLE_OPERATION));
3930 prepareNavInfo(true,
3932 getRejectReviewText(bundleNavContext.reviewStreaming.operationType));
3933 // if the operation is skippable
3934 if (bundleNavContext.reviewStreaming.operationType & SKIPPABLE_OPERATION) {
3935 navInfo.progressIndicator = false;
3936 navInfo.skipText = "Skip";
3937 navInfo.skipToken = SKIP_TOKEN;
3938 }
3939
3940 displayGenericContextPage(0, true);
3941}
3942
3954 nbgl_choiceCallback_t choiceCallback)
3955{
3956 nbgl_useCaseReviewStreamingContinueExt(tagValueList, choiceCallback, NULL);
3957}
3958
3967void nbgl_useCaseReviewStreamingFinish(const char *finishTitle,
3968 nbgl_choiceCallback_t choiceCallback)
3969{
3970 // Should follow a call to nbgl_useCaseReviewStreamingContinue
3971 memset(&genericContext, 0, sizeof(genericContext));
3972
3973 bundleNavContext.reviewStreaming.choiceCallback = choiceCallback;
3974
3975 // memorize context
3976 onChoice = bundleNavReviewStreamingChoice;
3977 navType = STREAMING_NAV;
3978 pageTitle = NULL;
3979
3980 genericContext.genericContents.contentsList = localContentsList;
3981 genericContext.genericContents.nbContents = 1;
3982 memset(localContentsList, 0, 1 * sizeof(nbgl_content_t));
3983
3984 // Eventually the long press page
3985 STARTING_CONTENT.type = INFO_LONG_PRESS;
3986 prepareReviewLastPage(bundleNavContext.reviewStreaming.operationType,
3987 &STARTING_CONTENT.content.infoLongPress,
3988 bundleNavContext.reviewStreaming.icon,
3989 finishTitle);
3990
3991 // compute number of pages & fill navigation structure
3992 bundleNavContext.reviewStreaming.stepPageNb = getNbPagesForGenericContents(
3993 &genericContext.genericContents,
3994 0,
3995 (bundleNavContext.reviewStreaming.operationType & SKIPPABLE_OPERATION));
3996 prepareNavInfo(true, 1, getRejectReviewText(bundleNavContext.reviewStreaming.operationType));
3997
3998 displayGenericContextPage(0, true);
3999}
4000
4005void nbgl_useCaseAddressConfirmationExt(const char *address,
4006 nbgl_choiceCallback_t callback,
4007 const nbgl_contentTagValueList_t *tagValueList)
4008{
4009 reset_callbacks();
4010 memset(&genericContext, 0, sizeof(genericContext));
4011 memset(&addressConfirmationContext, 0, sizeof(addressConfirmationContext));
4012
4013 // save context
4014 onChoice = callback;
4015 navType = GENERIC_NAV;
4016 pageTitle = NULL;
4017
4018 genericContext.genericContents.contentsList = localContentsList;
4019 genericContext.genericContents.nbContents = (tagValueList == NULL) ? 1 : 2;
4020 memset(localContentsList, 0, 2 * sizeof(nbgl_content_t));
4021 prepareAddressConfirmationPages(
4022 address, tagValueList, &STARTING_CONTENT, &localContentsList[1]);
4023
4024 // fill navigation structure, common to all pages
4025 uint8_t nbPages = getNbPagesForGenericContents(&genericContext.genericContents, 0, false);
4026
4027 prepareNavInfo(true, nbPages, "Cancel");
4028
4029#ifdef HAVE_PIEZO_SOUND
4030 // Play notification sound
4031 os_io_seph_cmd_piezo_play_tune(TUNE_LOOK_AT_ME);
4032#endif // HAVE_PIEZO_SOUND
4033
4034 displayGenericContextPage(0, true);
4035}
4036
4053void nbgl_useCaseAddressReview(const char *address,
4054 const nbgl_contentTagValueList_t *additionalTagValueList,
4055 const nbgl_icon_details_t *icon,
4056 const char *reviewTitle,
4057 const char *reviewSubTitle,
4058 nbgl_choiceCallback_t choiceCallback)
4059{
4060 reset_callbacks();
4061 memset(&genericContext, 0, sizeof(genericContext));
4062 // release a potential modal
4063 if (addressConfirmationContext.modalLayout) {
4064 nbgl_layoutRelease(addressConfirmationContext.modalLayout);
4065 }
4066 memset(&addressConfirmationContext, 0, sizeof(addressConfirmationContext));
4067
4068 // save context
4069 onChoice = choiceCallback;
4070 navType = GENERIC_NAV;
4071 pageTitle = NULL;
4072 bundleNavContext.review.operationType = TYPE_OPERATION;
4073
4074 genericContext.genericContents.contentsList = localContentsList;
4075 genericContext.genericContents.nbContents = (additionalTagValueList == NULL) ? 2 : 3;
4076 memset(localContentsList, 0, 3 * sizeof(nbgl_content_t));
4077
4078 // First a centered info
4079 STARTING_CONTENT.type = EXTENDED_CENTER;
4080 prepareReviewFirstPage(
4081 &STARTING_CONTENT.content.extendedCenter.contentCenter, icon, reviewTitle, reviewSubTitle);
4082 STARTING_CONTENT.content.extendedCenter.contentCenter.subText = "Swipe to continue";
4083
4084 // Then the address confirmation pages
4085 prepareAddressConfirmationPages(
4086 address, additionalTagValueList, &localContentsList[1], &localContentsList[2]);
4087
4088 // fill navigation structure, common to all pages
4089 uint8_t nbPages = getNbPagesForGenericContents(&genericContext.genericContents, 0, false);
4090
4091 prepareNavInfo(true, nbPages, "Cancel");
4092
4093#ifdef HAVE_PIEZO_SOUND
4094 // Play notification sound
4095 os_io_seph_cmd_piezo_play_tune(TUNE_LOOK_AT_ME);
4096#endif // HAVE_PIEZO_SOUND
4097
4098 displayGenericContextPage(0, true);
4099}
4100
4109void nbgl_useCaseSpinner(const char *text)
4110{
4111 // if the previous Use Case was not Spinner, fresh start
4112 if (genericContext.type != USE_CASE_SPINNER) {
4113 memset(&genericContext, 0, sizeof(genericContext));
4114 genericContext.type = USE_CASE_SPINNER;
4115 nbgl_layoutDescription_t layoutDescription = {0};
4116
4117 layoutDescription.withLeftBorder = true;
4118
4119 genericContext.backgroundLayout = nbgl_layoutGet(&layoutDescription);
4120
4122 genericContext.backgroundLayout, text, NULL, genericContext.spinnerPosition);
4123
4124 nbgl_layoutDraw(genericContext.backgroundLayout);
4126 }
4127 else {
4128 // otherwise increment spinner
4129 genericContext.spinnerPosition++;
4130 // there are only NB_SPINNER_POSITIONSpositions
4131 if (genericContext.spinnerPosition == NB_SPINNER_POSITIONS) {
4132 genericContext.spinnerPosition = 0;
4133 }
4134 int ret = nbgl_layoutUpdateSpinner(
4135 genericContext.backgroundLayout, text, NULL, genericContext.spinnerPosition);
4136 if (ret == 1) {
4138 }
4139 else if (ret == 2) {
4141 }
4142 }
4143}
4144
4145#ifdef NBGL_KEYPAD
4165void nbgl_useCaseKeypadDigits(const char *title,
4166 uint8_t minDigits,
4167 uint8_t maxDigits,
4168 uint8_t backToken,
4169 bool shuffled,
4170 tune_index_e tuneId,
4171 nbgl_pinValidCallback_t validatePinCallback,
4172 nbgl_layoutTouchCallback_t actionCallback)
4173{
4174 keypadGenericUseCase(title,
4175 minDigits,
4176 maxDigits,
4177 backToken,
4178 shuffled,
4179 false,
4180 tuneId,
4181 validatePinCallback,
4182 actionCallback);
4183}
4203void nbgl_useCaseKeypadPIN(const char *title,
4204 uint8_t minDigits,
4205 uint8_t maxDigits,
4206 uint8_t backToken,
4207 bool shuffled,
4208 tune_index_e tuneId,
4209 nbgl_pinValidCallback_t validatePinCallback,
4210 nbgl_layoutTouchCallback_t actionCallback)
4211{
4212 keypadGenericUseCase(title,
4213 minDigits,
4214 maxDigits,
4215 backToken,
4216 shuffled,
4217 true,
4218 tuneId,
4219 validatePinCallback,
4220 actionCallback);
4221}
4222#endif // NBGL_KEYPAD
4223
4224#endif // HAVE_SE_TOUCH
4225#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
@ 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:562
nbgl_font_id_e
Definition nbgl_fonts.h:136
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:721
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
Definition nbgl_layout.h:95
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:32
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:29
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:37
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:185
void nbgl_refresh(void)
This functions refreshes the actual screen on display with what has changed since the last refresh.
Definition nbgl_obj.c:1660
#define KEYPAD_MAX_DIGITS
Definition nbgl_obj.h:63
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:1670
#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:1686
#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:336
#define MIN(x, y)
Definition nbgl_types.h:100
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:308
@ FULL_COLOR_CLEAN_REFRESH
to be used for lock screen display (cleaner but longer refresh)
Definition nbgl_types.h:311
@ BLACK_AND_WHITE_FAST_REFRESH
to be used for pure B&W area, when contrast is not priority
Definition nbgl_types.h:313
@ FULL_COLOR_PARTIAL_REFRESH
to be used for small partial refresh (radio buttons, switches)
Definition nbgl_types.h:310
@ FULL_COLOR_REFRESH
to be used for normal refresh
Definition nbgl_types.h:309
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)
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
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,...
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.
nbgl_contentActionCallback_t actionCallback
called when a valueIcon is touched on a given pair
const nbgl_contentTagValue_t * pairs
array of [tag,value] pairs (nbPairs items). If NULL, callback is used instead
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_contentTagValueCallback_t callback
function to call to retrieve 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 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)
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::@17::@19 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
struct nbgl_layoutHeader_t::@9::@12 backAndText
if type is HEADER_BACK_ICON_AND_TEXT or HEADER_BACK_AND_TEXT
bool separationLine
if true, a separation line is added at the bottom of this control
const char * text
can be NULL if no 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