Embedded SDK
Embedded SDK
Loading...
Searching...
No Matches
nbgl_use_case_nanos.c
Go to the documentation of this file.
1
6#ifdef NBGL_USE_CASE
7#ifndef 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_pic.h"
17#include "os_print.h"
18#include "os_helpers.h"
19#include "ux.h"
20
21/*********************
22 * DEFINES
23 *********************/
24
25/**********************
26 * TYPEDEFS
27 **********************/
28
29typedef struct ReviewContext_s {
30 nbgl_choiceCallback_t onChoice;
31 const nbgl_contentTagValueList_t *tagValueList;
32 const nbgl_icon_details_t *icon;
33 const char *reviewTitle;
34 const char *reviewSubTitle;
35 const char *finishTitle;
36 const char *address; // for address confirmation review
37 nbgl_callback_t skipCallback; // callback provided by used
38 uint8_t nbDataSets; // number of sets of data received by StreamingContinue
39 bool skipDisplay; // if set to true, means that we are displaying the skip page
40 uint8_t dataDirection; // used to know whether the skip page is reached from back or forward
41 uint8_t currentTagValueIndex;
42 uint8_t currentExtensionPage;
43 uint8_t nbExtensionPages;
44 const nbgl_contentValueExt_t *extension;
45 nbgl_step_t extensionStepCtx;
46
47} ReviewContext_t;
48
49typedef struct ChoiceContext_s {
50 const nbgl_icon_details_t *icon;
51 const char *message;
52 const char *subMessage;
53 const char *confirmText;
54 const char *cancelText;
55 nbgl_choiceCallback_t onChoice;
56 const nbgl_genericDetails_t *details;
57} ChoiceContext_t;
58
59typedef struct ConfirmContext_s {
60 const char *message;
61 const char *subMessage;
62 const char *confirmText;
63 const char *cancelText;
64 nbgl_callback_t onConfirm;
65 nbgl_step_t currentStep;
66} ConfirmContext_t;
67
68typedef struct ContentContext_s {
69 const char *title; // For CHOICES_LIST /BARS_LIST
70 nbgl_genericContents_t genericContents;
71 const char *rejectText;
72 nbgl_layoutTouchCallback_t controlsCallback;
73 nbgl_navCallback_t navCallback;
74 nbgl_callback_t quitCallback;
75} ContentContext_t;
76
77typedef struct HomeContext_s {
78 const char *appName;
79 const nbgl_icon_details_t *appIcon;
80 const char *tagline;
81 const nbgl_genericContents_t *settingContents;
82 const nbgl_contentInfoList_t *infosList;
83 const nbgl_homeAction_t *homeAction;
84 nbgl_callback_t quitCallback;
85} HomeContext_t;
86
87typedef struct ActionContext_s {
88 nbgl_callback_t actionCallback;
89} ActionContext_t;
90
91#ifdef NBGL_KEYPAD
92typedef struct KeypadContext_s {
93 uint8_t pinEntry[8];
94 uint8_t pinLen;
95 uint8_t pinMinDigits;
96 uint8_t pinMaxDigits;
97 nbgl_layout_t *layoutCtx;
98 bool hidden;
99 uint8_t keypadIndex;
100 nbgl_pinValidCallback_t validatePin;
101 nbgl_callback_t backCallback;
102} KeypadContext_t;
103#endif
104
105#ifdef NBGL_KEYBOARD
106typedef struct KeyboardContext_s {
108 char *entryBuffer;
109 uint16_t entryMaxLen;
110 nbgl_layout_t *layoutCtx;
111 uint8_t keyboardIndex;
112 uint8_t textIndex;
113 nbgl_callback_t actionCallback;
114 nbgl_callback_t backCallback;
115 nbgl_keyboardButtonsCallback_t getSuggestButtons;
116 nbgl_layoutTouchCallback_t onButtonCallback;
117} KeyboardContext_t;
118#endif
119
120typedef enum {
121 NONE_USE_CASE,
122 SPINNER_USE_CASE,
123 REVIEW_USE_CASE,
124 GENERIC_REVIEW_USE_CASE,
125 ADDRESS_REVIEW_USE_CASE,
126 STREAMING_START_REVIEW_USE_CASE,
127 STREAMING_CONTINUE_REVIEW_USE_CASE,
128 STREAMING_FINISH_REVIEW_USE_CASE,
129 CHOICE_USE_CASE,
130 STATUS_USE_CASE,
131 CONFIRM_USE_CASE,
132 KEYPAD_USE_CASE,
133 KEYBOARD_USE_CASE,
134 HOME_USE_CASE,
135 INFO_USE_CASE,
136 SETTINGS_USE_CASE,
137 GENERIC_SETTINGS,
138 CONTENT_USE_CASE,
139 ACTION_USE_CASE
140} ContextType_t;
141
142typedef struct UseCaseContext_s {
143 ContextType_t type;
144 nbgl_operationType_t operationType;
145 uint8_t nbPages;
146 uint8_t currentPage;
147 uint8_t firstPairPage;
148 bool forceAction;
150 stepCallback;
151 union {
152 ReviewContext_t review;
153 ChoiceContext_t choice;
154 ConfirmContext_t confirm;
155 HomeContext_t home;
156 ContentContext_t content;
157#ifdef NBGL_KEYPAD
158 KeypadContext_t keypad;
159#endif
160#ifdef NBGL_KEYBOARD
161 KeyboardContext_t keyboard;
162#endif
163 ActionContext_t action;
164 };
165} UseCaseContext_t;
166
167typedef struct PageContent_s {
168 bool isSwitch;
169 const char *text;
170 const char *subText;
171 const nbgl_icon_details_t *icon;
172 const nbgl_contentValueExt_t *extension;
173 nbgl_state_t state;
174 bool isCenteredInfo;
175 bool isAction;
176 bool bottomIcon;
179} PageContent_t;
180
181typedef struct ReviewWithWarningContext_s {
182 ContextType_t type;
183 nbgl_operationType_t operationType;
184 const nbgl_contentTagValueList_t *tagValueList;
185 const nbgl_icon_details_t *icon;
186 const char *reviewTitle;
187 const char *reviewSubTitle;
188 const char *finishTitle;
189 const nbgl_warning_t *warning;
190 nbgl_choiceCallback_t choiceCallback;
191 uint8_t securityReportLevel; // level 1 is the first level of menus
192 bool isIntro; // set to true during intro (before actual review)
193 uint8_t warningPage;
194 uint8_t nbWarningPages;
195 uint8_t firstWarningPage;
196 uint8_t barDetailIdx; // index of the bar whose detail is displayed
197} ReviewWithWarningContext_t;
198
199typedef enum {
200 NO_FORCED_TYPE = 0,
201 FORCE_BUTTON,
202 FORCE_CENTERED_INFO
203} ForcedType_t;
204
205/**********************
206 * STATIC VARIABLES
207 **********************/
208static UseCaseContext_t context;
209
210static ReviewWithWarningContext_t reviewWithWarnCtx;
211// configuration of warning when using @ref nbgl_useCaseReviewBlindSigning()
212static const nbgl_warning_t blindSigningWarning = {.predefinedSet = (1 << BLIND_SIGNING_WARN)};
213
214// Operation type for streaming (because the one in 'context' is reset at each streaming API call)
215nbgl_operationType_t streamingOpType;
216
217/**********************
218 * STATIC FUNCTIONS
219 **********************/
220static void displayReviewPage(nbgl_stepPosition_t pos);
221static void displayStreamingReviewPage(nbgl_stepPosition_t pos);
222static void displayHomePage(nbgl_stepPosition_t pos);
223static void displayInfoPage(nbgl_stepPosition_t pos);
224static void displaySettingsPage(nbgl_stepPosition_t pos, bool toogle_state);
225static void displayChoicePage(nbgl_stepPosition_t pos);
226static void displayConfirm(nbgl_stepPosition_t pos);
227static void displayContent(nbgl_stepPosition_t pos, bool toogle_state);
228static void displaySpinner(const char *text);
229
230static void startUseCaseHome(void);
231static void startUseCaseInfo(void);
232static void startUseCaseSettings(void);
233static void startUseCaseSettingsAtPage(uint8_t initSettingPage);
234static void startUseCaseContent(void);
235
236static void statusTickerCallback(void);
237
238static void displayExtensionStep(nbgl_stepPosition_t pos);
239static void displayWarningStep(void);
240
241// Simple helper to get the number of elements inside a nbgl_content_t
242static uint8_t getContentNbElement(const nbgl_content_t *content)
243{
244 switch (content->type) {
245 case CENTERED_INFO:
246 return 1;
247 case INFO_BUTTON:
248 return 1;
249 case TAG_VALUE_LIST:
250 return content->content.tagValueList.nbPairs;
252 // last element is for Confirm page
253 return content->content.tagValueConfirm.tagValueList.nbPairs + 1;
254 case SWITCHES_LIST:
255 return content->content.switchesList.nbSwitches;
256 case INFOS_LIST:
257 return content->content.infosList.nbInfos;
258 case CHOICES_LIST:
259 return content->content.choicesList.nbChoices;
260 case BARS_LIST:
261 return content->content.barsList.nbBars;
262 default:
263 return 0;
264 }
265}
266
267// Helper to retrieve the content inside a nbgl_genericContents_t using
268// either the contentsList or using the contentGetterCallback
269static const nbgl_content_t *getContentAtIdx(const nbgl_genericContents_t *genericContents,
270 uint8_t contentIdx,
271 nbgl_content_t *content)
272{
273 nbgl_pageContent_t pageContent = {0};
274 if (contentIdx >= genericContents->nbContents) {
275 LOG_DEBUG(USE_CASE_LOGGER, "No content available at %d\n", contentIdx);
276 return NULL;
277 }
278
279 if (genericContents->callbackCallNeeded) {
280 if (content == NULL) {
281 LOG_DEBUG(USE_CASE_LOGGER, "Invalid content variable\n");
282 return NULL;
283 }
284 // Retrieve content through callback, but first memset the content.
285 memset(content, 0, sizeof(nbgl_content_t));
286 if (context.content.navCallback) {
287 if (context.content.navCallback(contentIdx, &pageContent) == true) {
288 // Copy the Page Content to the Content variable
289 content->type = pageContent.type;
290 switch (content->type) {
291 case CENTERED_INFO:
292 content->content.centeredInfo = pageContent.centeredInfo;
293 break;
294 case INFO_BUTTON:
295 content->content.infoButton = pageContent.infoButton;
296 break;
297 case TAG_VALUE_LIST:
298 content->content.tagValueList = pageContent.tagValueList;
299 break;
301 content->content.tagValueConfirm = pageContent.tagValueConfirm;
302 break;
303 case SWITCHES_LIST:
304 content->content.switchesList = pageContent.switchesList;
305 break;
306 case INFOS_LIST:
307 content->content.infosList = pageContent.infosList;
308 break;
309 case CHOICES_LIST:
310 content->content.choicesList = pageContent.choicesList;
311 break;
312 case BARS_LIST:
313 content->content.barsList = pageContent.barsList;
314 break;
315 default:
316 LOG_DEBUG(USE_CASE_LOGGER, "Invalid content type\n");
317 return NULL;
318 }
319 }
320 else {
321 LOG_DEBUG(USE_CASE_LOGGER, "Error getting page content\n");
322 return NULL;
323 }
324 }
325 else {
326 genericContents->contentGetterCallback(contentIdx, content);
327 }
328 return content;
329 }
330 else {
331 // Retrieve content through list
332 return PIC(&genericContents->contentsList[contentIdx]);
333 }
334}
335
336// Helper to retrieve the content inside a nbgl_genericContents_t using
337// either the contentsList or using the contentGetterCallback
338static const nbgl_content_t *getContentElemAtIdx(uint8_t elemIdx,
339 uint8_t *elemContentIdx,
340 nbgl_content_t *content)
341{
342 const nbgl_genericContents_t *genericContents = NULL;
343 const nbgl_content_t *p_content = NULL;
344 uint8_t nbPages = 0;
345 uint8_t elemNbPages = 0;
346
347 switch (context.type) {
348 case SETTINGS_USE_CASE:
349 case HOME_USE_CASE:
350 case GENERIC_SETTINGS:
351 genericContents = context.home.settingContents;
352 break;
353 case CONTENT_USE_CASE:
354 case GENERIC_REVIEW_USE_CASE:
355 genericContents = &context.content.genericContents;
356 break;
357 default:
358 return NULL;
359 }
360 for (unsigned int i = 0; i < genericContents->nbContents; i++) {
361 p_content = getContentAtIdx(genericContents, i, content);
362 elemNbPages = getContentNbElement(p_content);
363 if (nbPages + elemNbPages > elemIdx) {
364 *elemContentIdx = context.currentPage - nbPages;
365 break;
366 }
367 nbPages += elemNbPages;
368 }
369
370 return p_content;
371}
372
373static const char *getChoiceName(uint8_t choiceIndex)
374{
375 uint8_t elemIdx;
376 uint8_t nbValues;
377 const nbgl_content_t *p_content = NULL;
378 nbgl_content_t content = {0};
379 nbgl_contentRadioChoice_t *contentChoices = NULL;
380 nbgl_contentBarsList_t *contentBars = NULL;
381 char **names = NULL;
382
383 p_content = getContentElemAtIdx(context.currentPage, &elemIdx, &content);
384 if (p_content == NULL) {
385 return NULL;
386 }
387 switch (p_content->type) {
388 case CHOICES_LIST:
389 contentChoices = (nbgl_contentRadioChoice_t *) PIC(&p_content->content.choicesList);
390 names = (char **) PIC(contentChoices->names);
391 nbValues = contentChoices->nbChoices;
392 break;
393 case BARS_LIST:
394 contentBars = ((nbgl_contentBarsList_t *) PIC(&p_content->content.barsList));
395 names = (char **) PIC(contentBars->barTexts);
396 nbValues = contentBars->nbBars;
397 break;
398 default:
399 // Not supported as vertical MenuList
400 return NULL;
401 }
402 if (choiceIndex >= nbValues) {
403 // Last item is always "Back" button
404 return "Back";
405 }
406 return (const char *) PIC(names[choiceIndex]);
407}
408
409static void onChoiceSelected(uint8_t choiceIndex)
410{
411 uint8_t elemIdx;
412 uint8_t token = 255;
413 const nbgl_content_t *p_content = NULL;
414 nbgl_content_t content = {0};
415 nbgl_contentRadioChoice_t *contentChoices = NULL;
416 nbgl_contentBarsList_t *contentBars = NULL;
417
418 p_content = getContentElemAtIdx(context.currentPage, &elemIdx, &content);
419 if (p_content == NULL) {
420 return;
421 }
422 switch (p_content->type) {
423 case CHOICES_LIST:
424 contentChoices = (nbgl_contentRadioChoice_t *) PIC(&p_content->content.choicesList);
425 if (choiceIndex < contentChoices->nbChoices) {
426 token = contentChoices->token;
427 }
428 break;
429 case BARS_LIST:
430 contentBars = ((nbgl_contentBarsList_t *) PIC(&p_content->content.barsList));
431 if (choiceIndex < contentBars->nbBars) {
432 token = ((const uint8_t *) PIC(contentBars->tokens))[choiceIndex];
433 }
434 break;
435 default:
436 // Not supported as vertical MenuList
437 break;
438 }
439 if (token != 255) {
440 // Prefer the per-content action callback (used by settings/content
441 // flows). Forward both the choice index and the absolute page so the
442 // app can react identically to the horizontal flow.
443 if (p_content->contentActionCallback != NULL) {
444 nbgl_contentActionCallback_t actionCallback = PIC(p_content->contentActionCallback);
445 actionCallback(token, choiceIndex, context.currentPage);
446 return;
447 }
448 if (context.content.controlsCallback != NULL) {
449 context.content.controlsCallback(token, choiceIndex);
450 return;
451 }
452 }
453 // Back entry (the extra item appended by drawStep at index nbChoices) or
454 // unknown selection: mirror the horizontal-flow Back behaviour in
455 // displaySettingsPage so SETTINGS_USE_CASE / GENERIC_SETTINGS / content
456 // callers all exit consistently.
457 if ((context.type == GENERIC_SETTINGS) && (context.home.quitCallback != NULL)) {
458 context.home.quitCallback();
459 return;
460 }
461 if (context.type == SETTINGS_USE_CASE) {
462 startUseCaseHome();
463 return;
464 }
465 if (context.content.quitCallback != NULL) {
466 context.content.quitCallback();
467 }
468}
469
470static void getPairData(const nbgl_contentTagValueList_t *tagValueList,
471 uint8_t index,
472 const char **item,
473 const char **value,
474 const nbgl_contentValueExt_t **extension,
475 const nbgl_icon_details_t **icon,
476 bool *isCenteredInfo)
477{
478 const nbgl_contentTagValue_t *pair;
479
480 if (tagValueList->pairs != NULL) {
481 pair = PIC(&tagValueList->pairs[index]);
482 }
483 else {
484 pair = PIC(tagValueList->callback(index));
485 }
486 *item = pair->item;
487 *value = pair->value;
488 if (pair->aliasValue) {
489 *extension = pair->extension;
490 }
491 else if (pair->centeredInfo) {
492 *isCenteredInfo = true;
493 *icon = pair->valueIcon;
494 }
495 else {
496 *extension = NULL;
497 }
498}
499
500static void onReviewAccept(void)
501{
502 if (context.review.onChoice) {
503 context.review.onChoice(true);
504 }
505}
506
507static void onReviewReject(void)
508{
509 if (context.review.onChoice) {
510 context.review.onChoice(false);
511 }
512}
513
514static void onChoiceAccept(void)
515{
516 if (context.choice.onChoice) {
517 context.choice.onChoice(true);
518 }
519}
520
521static void onChoiceReject(void)
522{
523 if (context.choice.onChoice) {
524 context.choice.onChoice(false);
525 }
526}
527
528static void onConfirmAccept(void)
529{
530 if (context.confirm.currentStep) {
531 nbgl_stepRelease(context.confirm.currentStep);
532 }
533 if (context.confirm.onConfirm) {
534 context.confirm.onConfirm();
535 }
536}
537
538static void onConfirmReject(void)
539{
540 if (context.confirm.currentStep) {
541 nbgl_stepRelease(context.confirm.currentStep);
543 nbgl_refresh();
544 }
545}
546
547static void onSwitchAction(void)
548{
549 const nbgl_contentSwitch_t *contentSwitch = NULL;
550 const nbgl_content_t *p_content = NULL;
551 nbgl_content_t content = {0};
552 uint8_t elemIdx;
553
554 p_content = getContentElemAtIdx(context.currentPage, &elemIdx, &content);
555 if ((p_content == NULL) || (p_content->type != SWITCHES_LIST)) {
556 return;
557 }
558 contentSwitch
559 = &((const nbgl_contentSwitch_t *) PIC(p_content->content.switchesList.switches))[elemIdx];
560 switch (context.type) {
561 case SETTINGS_USE_CASE:
562 case HOME_USE_CASE:
563 case GENERIC_SETTINGS:
564 displaySettingsPage(FORWARD_DIRECTION, true);
565 break;
566 case CONTENT_USE_CASE:
567 case GENERIC_REVIEW_USE_CASE:
568 displayContent(FORWARD_DIRECTION, true);
569 break;
570 default:
571 break;
572 }
573 if (p_content->contentActionCallback != NULL) {
574 nbgl_contentActionCallback_t actionCallback = PIC(p_content->contentActionCallback);
575 actionCallback(contentSwitch->token,
576 (contentSwitch->initState == ON_STATE) ? OFF_STATE : ON_STATE,
577 context.currentPage);
578 }
579 else if (context.content.controlsCallback != NULL) {
580 context.content.controlsCallback(contentSwitch->token, 0);
581 }
582}
583
584static void drawStep(nbgl_stepPosition_t pos,
585 const nbgl_icon_details_t *icon,
586 const char *txt,
587 const char *subTxt,
588 nbgl_stepButtonCallback_t onActionCallback,
589 bool modal,
590 ForcedType_t forcedType,
591 bool bottomIcon)
592{
593 uint8_t elemIdx;
594 nbgl_step_t newStep = NULL;
595 const nbgl_content_t *p_content = NULL;
596 nbgl_content_t content = {0};
597 nbgl_contentRadioChoice_t *contentChoices = NULL;
598 nbgl_contentBarsList_t *contentBars = NULL;
599 nbgl_screenTickerConfiguration_t *p_ticker = NULL;
600 nbgl_layoutMenuList_t list = {0};
601 nbgl_screenTickerConfiguration_t ticker = {.tickerCallback = PIC(statusTickerCallback),
602 .tickerIntervale = 0, // not periodic
603 .tickerValue = STATUS_SCREEN_DURATION};
604
605 pos |= GET_POS_OF_STEP(context.currentPage, context.nbPages);
606 // if we are in streaming+skip case, enable going backward even for first tag/value of the set
607 // (except the first set) because the set starts with a "skip" page
608 if ((context.type == STREAMING_CONTINUE_REVIEW_USE_CASE)
609 && (context.review.skipCallback != NULL) && (context.review.nbDataSets > 1)) {
610 pos |= LAST_STEP;
611 }
612 if ((context.type == STATUS_USE_CASE) || (context.type == SPINNER_USE_CASE)) {
613 p_ticker = &ticker;
614 }
615 if ((context.type == CONFIRM_USE_CASE) && (context.confirm.currentStep != NULL)) {
616 nbgl_stepRelease(context.confirm.currentStep);
617 }
618
619 if (txt == NULL) {
620 p_content = getContentElemAtIdx(context.currentPage, &elemIdx, &content);
621 if (p_content) {
622 switch (p_content->type) {
623 case CHOICES_LIST:
624 contentChoices
625 = ((nbgl_contentRadioChoice_t *) PIC(&p_content->content.choicesList));
626 list.nbChoices = contentChoices->nbChoices + 1; // For Back button
627 list.selectedChoice = contentChoices->initChoice;
628 list.callback = getChoiceName;
629 newStep = nbgl_stepDrawMenuList(onChoiceSelected, p_ticker, &list, modal);
630 break;
631 case BARS_LIST:
632 contentBars = ((nbgl_contentBarsList_t *) PIC(&p_content->content.barsList));
633 list.nbChoices = contentBars->nbBars + 1; // For Back button
634 list.selectedChoice = 0;
635 list.callback = getChoiceName;
636 newStep = nbgl_stepDrawMenuList(onChoiceSelected, p_ticker, &list, modal);
637 break;
638 default:
639 // Not supported as vertical MenuList
640 break;
641 }
642 }
643 }
644 else if ((icon == NULL) && (forcedType != FORCE_CENTERED_INFO)) {
646 if (subTxt != NULL) {
647 style = (forcedType == FORCE_BUTTON) ? BUTTON_INFO : BOLD_TEXT1_INFO;
648 }
649 else {
650 style = REGULAR_INFO;
651 }
652 newStep = nbgl_stepDrawText(pos, onActionCallback, p_ticker, txt, subTxt, style, modal);
653 }
654 else {
655 nbgl_layoutCenteredInfo_t info = {0};
656 info.icon = icon;
657 info.text1 = txt;
658 info.text2 = subTxt;
659 info.bottomIcon = bottomIcon;
660 if ((subTxt != NULL) || (context.stepCallback != NULL) || context.forceAction) {
661 info.style = BOLD_TEXT1_INFO;
662 }
663 else {
664 info.style = REGULAR_INFO;
665 }
666 newStep = nbgl_stepDrawCenteredInfo(pos, onActionCallback, p_ticker, &info, modal);
667 }
668 if (context.type == CONFIRM_USE_CASE) {
669 context.confirm.currentStep = newStep;
670 }
671}
672
673static void drawSwitchStep(nbgl_stepPosition_t pos,
674 const char *title,
675 const char *description,
676 bool state,
677 nbgl_stepButtonCallback_t onActionCallback,
678 bool modal)
679{
680 nbgl_layoutSwitch_t switchInfo = {0};
681
682 pos |= GET_POS_OF_STEP(context.currentPage, context.nbPages);
683 switchInfo.initState = state;
684 switchInfo.text = title;
685 switchInfo.subText = description;
686 nbgl_stepDrawSwitch(pos, onActionCallback, NULL, &switchInfo, modal);
687}
688
689static bool buttonGenericCallback(nbgl_buttonEvent_t event, nbgl_stepPosition_t *pos)
690{
691 uint8_t elemIdx;
692 uint8_t token = 0;
693 uint8_t index = 0;
694 const nbgl_content_t *p_content = NULL;
695 nbgl_content_t content = {0};
696
697 if (event == BUTTON_LEFT_PRESSED) {
698 if (context.currentPage > 0) {
699 context.currentPage--;
700 }
701 // in streaming+skip case, it is allowed to go backward at the first tag/value, except for
702 // the first set
703 else if ((context.type != STREAMING_CONTINUE_REVIEW_USE_CASE)
704 || (context.review.skipCallback == NULL) || (context.review.nbDataSets == 1)) {
705 // Drop the event
706 return false;
707 }
708 *pos = BACKWARD_DIRECTION;
709 }
710 else if (event == BUTTON_RIGHT_PRESSED) {
711 if (context.currentPage < (int) (context.nbPages - 1)) {
712 context.currentPage++;
713 }
714 else {
715 // Drop the event
716 return false;
717 }
718 *pos = FORWARD_DIRECTION;
719 }
720 else {
721 if (event == BUTTON_BOTH_PRESSED) {
722 if (context.stepCallback != NULL) {
723 context.stepCallback();
724 }
725 else if ((context.type == CONTENT_USE_CASE) || (context.type == SETTINGS_USE_CASE)
726 || (context.type == GENERIC_SETTINGS)
727 || (context.type == GENERIC_REVIEW_USE_CASE)) {
728 p_content = getContentElemAtIdx(context.currentPage, &elemIdx, &content);
729 if (p_content != NULL) {
730 switch (p_content->type) {
731 case CENTERED_INFO:
732 // No associated callback
733 return false;
734 case INFO_BUTTON:
735 token = p_content->content.infoButton.buttonToken;
736 break;
737 case SWITCHES_LIST:
738 token = p_content->content.switchesList.switches->token;
739 break;
740 case BARS_LIST:
741 if (elemIdx >= p_content->content.barsList.nbBars) {
742 return false;
743 }
744 token = ((const uint8_t *) PIC(
745 p_content->content.barsList.tokens))[elemIdx];
746 index = elemIdx;
747 break;
748 case CHOICES_LIST:
749 token = p_content->content.choicesList.token;
750 index = elemIdx;
751 break;
752 case TAG_VALUE_LIST:
753 return false;
755 if (elemIdx < p_content->content.tagValueConfirm.tagValueList.nbPairs) {
756 return false;
757 }
758 token = p_content->content.tagValueConfirm.confirmationToken;
759 break;
760 default:
761 break;
762 }
763
764 bool used_action_callback
765 = (p_content != NULL) && (p_content->contentActionCallback != NULL);
766 ContextType_t prev_type = context.type;
767 if (used_action_callback) {
768 nbgl_contentActionCallback_t actionCallback
770 actionCallback(token, index, context.currentPage);
771 }
772 else if (context.content.controlsCallback != NULL) {
773 context.content.controlsCallback(token, index);
774 }
775 // If the callback swapped the use case (e.g. by entering
776 // a new screen via display_home_page / nbgl_useCaseXxx),
777 // the global context has already been reset — leave it
778 // alone, do not try to redraw the previous layout.
779 if (context.type != prev_type) {
780 return false;
781 }
782 // After a CHOICES_LIST or BARS_LIST selection that went
783 // through the per-content action callback (settings-style
784 // flows), request a redraw of the current page so any
785 // visual side effect of the pick (e.g. the selectionIcon
786 // moving below the new initChoice) becomes visible.
787 // The controlsCallback path (navigable content, generic
788 // review, ...) is excluded because such callbacks
789 // commonly route to a brand-new use case (review,
790 // warning, ...) that owns the screen without resetting
791 // context.type — forcing a redraw there would clobber it.
792 if (used_action_callback
793 && ((p_content->type == CHOICES_LIST) || (p_content->type == BARS_LIST))) {
794 *pos = FORWARD_DIRECTION;
795 return true;
796 }
797 }
798 }
799 }
800 return false;
801 }
802 return true;
803}
804
805static void reviewCallback(nbgl_step_t stepCtx, nbgl_buttonEvent_t event)
806{
807 UNUSED(stepCtx);
809
810 if (!buttonGenericCallback(event, &pos)) {
811 return;
812 }
813 else {
814 // memorize last direction
815 context.review.dataDirection = pos;
816 }
817 displayReviewPage(pos);
818}
819
820// this is the callback used when button action on the "skip" page
821static void buttonSkipCallback(nbgl_step_t stepCtx, nbgl_buttonEvent_t event)
822{
823 UNUSED(stepCtx);
825
826 if (event == BUTTON_LEFT_PRESSED) {
827 // only decrement page if we are going backward but coming from forward (back & forth)
828 if ((context.review.dataDirection == FORWARD_DIRECTION)
829 && (context.currentPage > context.firstPairPage)) {
830 context.currentPage--;
831 }
832 pos = BACKWARD_DIRECTION;
833 }
834 else if (event == BUTTON_RIGHT_PRESSED) {
835 // only increment page if we are going forward but coming from backward (back & forth)
836 if ((context.review.dataDirection == BACKWARD_DIRECTION)
837 && (context.currentPage < (int) (context.nbPages - 1))
838 && (context.currentPage > context.firstPairPage)) {
839 context.currentPage++;
840 }
841 pos = FORWARD_DIRECTION;
842 }
843 else if (event == BUTTON_BOTH_PRESSED) {
844 // the first tag/value page is at page 0 only in streaming case
845 if (context.firstPairPage == 0) {
846 // in streaming, we have to call the provided callback
847 context.review.skipCallback();
848 }
849 else {
850 // if not in streaming, go directly to the "approve" page
851 context.currentPage = context.nbPages - 2;
852 displayReviewPage(FORWARD_DIRECTION);
853 }
854 return;
855 }
856 else {
857 return;
858 }
859 // the first tag/value page is at page 0 only in streaming case
860 if (context.firstPairPage == 0) {
861 displayStreamingReviewPage(pos);
862 }
863 else {
864 displayReviewPage(pos);
865 }
866}
867
868// this is the callback used when buttons in "Action" use case are pressed
869static void useCaseActionCallback(nbgl_step_t stepCtx, nbgl_buttonEvent_t event)
870{
871 UNUSED(stepCtx);
872
873 if (event == BUTTON_BOTH_PRESSED) {
874 context.action.actionCallback();
875 }
876}
877
878static void streamingReviewCallback(nbgl_step_t stepCtx, nbgl_buttonEvent_t event)
879{
881 UNUSED(stepCtx);
882
883 if (!buttonGenericCallback(event, &pos)) {
884 return;
885 }
886 else {
887 // memorize last direction
888 context.review.dataDirection = pos;
889 }
890
891 displayStreamingReviewPage(pos);
892}
893
894static void settingsCallback(nbgl_step_t stepCtx, nbgl_buttonEvent_t event)
895{
896 UNUSED(stepCtx);
898
899 if (!buttonGenericCallback(event, &pos)) {
900 return;
901 }
902
903 displaySettingsPage(pos, false);
904}
905
906static void infoCallback(nbgl_step_t stepCtx, nbgl_buttonEvent_t event)
907{
908 UNUSED(stepCtx);
910
911 if (!buttonGenericCallback(event, &pos)) {
912 return;
913 }
914
915 displayInfoPage(pos);
916}
917
918static void homeCallback(nbgl_step_t stepCtx, nbgl_buttonEvent_t event)
919{
920 UNUSED(stepCtx);
922
923 if (!buttonGenericCallback(event, &pos)) {
924 return;
925 }
926
927 displayHomePage(pos);
928}
929
930static void genericChoiceCallback(nbgl_step_t stepCtx, nbgl_buttonEvent_t event)
931{
932 UNUSED(stepCtx);
934
935 if (!buttonGenericCallback(event, &pos)) {
936 return;
937 }
938
939 displayChoicePage(pos);
940}
941
942static void genericConfirmCallback(nbgl_step_t stepCtx, nbgl_buttonEvent_t event)
943{
944 UNUSED(stepCtx);
946
947 if (!buttonGenericCallback(event, &pos)) {
948 return;
949 }
950
951 displayConfirm(pos);
952}
953
954static void statusButtonCallback(nbgl_step_t stepCtx, nbgl_buttonEvent_t event)
955{
956 UNUSED(stepCtx);
957 // any button press should dismiss the status screen
958 if ((event == BUTTON_BOTH_PRESSED) || (event == BUTTON_LEFT_PRESSED)
959 || (event == BUTTON_RIGHT_PRESSED)) {
960 if (context.stepCallback != NULL) {
961 context.stepCallback();
962 }
963 }
964}
965
966static void contentCallback(nbgl_step_t stepCtx, nbgl_buttonEvent_t event)
967{
968 UNUSED(stepCtx);
970
971 if (!buttonGenericCallback(event, &pos)) {
972 return;
973 }
974
975 displayContent(pos, false);
976}
977
978// callback used for timeout
979static void statusTickerCallback(void)
980{
981 if (context.stepCallback != NULL) {
982 context.stepCallback();
983 }
984}
985
986// this is the callback used when navigating in extension pages
987static void extensionNavigate(nbgl_step_t stepCtx, nbgl_buttonEvent_t event)
988{
990 UNUSED(stepCtx);
991
992 if (event == BUTTON_LEFT_PRESSED) {
993 // only decrement page if we are not at the first page
994 if (context.review.currentExtensionPage > 0) {
995 context.review.currentExtensionPage--;
996 }
997 pos = BACKWARD_DIRECTION;
998 }
999 else if (event == BUTTON_RIGHT_PRESSED) {
1000 // only increment page if not at last page
1001 if (context.review.currentExtensionPage < (context.review.nbExtensionPages - 1)) {
1002 context.review.currentExtensionPage++;
1003 }
1004 pos = FORWARD_DIRECTION;
1005 }
1006 else if (event == BUTTON_BOTH_PRESSED) {
1007 // if at last page, leave modal context
1008 if (context.review.currentExtensionPage == (context.review.nbExtensionPages - 1)) {
1009 nbgl_stepRelease(context.review.extensionStepCtx);
1011 nbgl_refresh();
1012 }
1013 return;
1014 }
1015 else {
1016 return;
1017 }
1018 displayExtensionStep(pos);
1019}
1020
1021// function used to display the extension pages
1022static void displayExtensionStep(nbgl_stepPosition_t pos)
1023{
1024 nbgl_layoutCenteredInfo_t info = {0};
1025 const nbgl_contentTagValueList_t *tagValueList = NULL;
1026 const nbgl_contentInfoList_t *infoList = NULL;
1027 const char *text = NULL;
1028 const char *subText = NULL;
1029
1030 if (context.review.extensionStepCtx != NULL) {
1031 nbgl_stepRelease(context.review.extensionStepCtx);
1032 }
1033 if (context.review.currentExtensionPage < (context.review.nbExtensionPages - 1)) {
1034 if (context.review.currentExtensionPage == 0) {
1035 pos |= FIRST_STEP;
1036 }
1037 else {
1039 }
1040
1041 switch (context.review.extension->aliasType) {
1042 case ENS_ALIAS:
1043 text = context.review.extension->title;
1044 subText = context.review.extension->fullValue;
1045 break;
1046 case ADDRESS_BOOK_ALIAS: {
1047 bool has_scope = (context.review.extension->aliasSubName != NULL);
1048 bool has_tn = (context.review.extension->explanation != NULL);
1049 uint8_t page = context.review.currentExtensionPage;
1050 if (page == 0) {
1051 text = context.review.extension->title;
1052 subText = has_scope ? context.review.extension->aliasSubName
1053 : context.review.extension->fullValue;
1054 }
1055 else if (has_scope && page == 1) {
1056 text = context.review.extension->title;
1057 subText = context.review.extension->fullValue;
1058 }
1059 else if (has_tn) {
1060 text = context.review.extension->explanation;
1061 }
1062 break;
1063 }
1064 case INFO_LIST_ALIAS:
1065 infoList = context.review.extension->infolist;
1066 text = PIC(infoList->infoTypes[context.review.currentExtensionPage]);
1067 subText = PIC(infoList->infoContents[context.review.currentExtensionPage]);
1068 break;
1070 tagValueList = context.review.extension->tagValuelist;
1071 text = PIC(tagValueList->pairs[context.review.currentExtensionPage].item);
1072 subText = PIC(tagValueList->pairs[context.review.currentExtensionPage].value);
1073 break;
1074 default:
1075 break;
1076 }
1077 if (text != NULL) {
1078 context.review.extensionStepCtx = nbgl_stepDrawText(
1079 pos, extensionNavigate, NULL, text, subText, BOLD_TEXT1_INFO, true);
1080 }
1081 }
1082 else if (context.review.currentExtensionPage == (context.review.nbExtensionPages - 1)) {
1083 // draw the back page
1084 info.icon = &C_icon_back_x;
1085 info.text1 = "Back";
1086 info.style = BOLD_TEXT1_INFO;
1087 pos |= LAST_STEP;
1088 context.review.extensionStepCtx
1089 = nbgl_stepDrawCenteredInfo(pos, extensionNavigate, NULL, &info, true);
1090 }
1091 nbgl_refresh();
1092}
1093
1094static void displayAliasFullValue(void)
1095{
1096 const char *text = NULL;
1097 const char *subText = NULL;
1098 const nbgl_icon_details_t *icon;
1099 bool isCenteredInfo;
1100
1101 getPairData(context.review.tagValueList,
1102 context.review.currentTagValueIndex,
1103 &text,
1104 &subText,
1105 &context.review.extension,
1106 &icon,
1107 &isCenteredInfo);
1108 if (context.review.extension == NULL) {
1109 // probably an error
1111 "displayAliasFullValue: extension nor found for pair %d\n",
1112 context.review.currentTagValueIndex);
1113 return;
1114 }
1115 context.review.currentExtensionPage = 0;
1116 context.review.extensionStepCtx = NULL;
1117 // create a modal flow to display this extension
1118 switch (context.review.extension->aliasType) {
1119 case ENS_ALIAS:
1120 context.review.nbExtensionPages = 2;
1121 break;
1122 case ADDRESS_BOOK_ALIAS:
1123 context.review.nbExtensionPages
1124 = 2 + ((context.review.extension->aliasSubName != NULL) ? 1 : 0)
1125 + ((context.review.extension->explanation != NULL) ? 1 : 0);
1126 break;
1127 case INFO_LIST_ALIAS:
1128 context.review.nbExtensionPages = context.review.extension->infolist->nbInfos + 1;
1129 break;
1131 context.review.nbExtensionPages = context.review.extension->tagValuelist->nbPairs + 1;
1132 break;
1133 default:
1135 "displayAliasFullValue: unsupported alias type %d\n",
1136 context.review.extension->aliasType);
1137 return;
1138 }
1139 displayExtensionStep(FORWARD_DIRECTION);
1140}
1141
1142static void getLastPageInfo(bool approve, const nbgl_icon_details_t **icon, const char **text)
1143{
1144 if (approve) {
1145 // Approve page
1146 *icon = &C_icon_validate_14;
1147 if (context.operationType & ADDRESS_BOOK_OPERATION) {
1148 if ((context.type == REVIEW_USE_CASE) && (context.review.finishTitle != NULL)) {
1149 *text = context.review.finishTitle;
1150 }
1151 else {
1152 *text = "Confirm contact details";
1153 }
1154 }
1155 else if (context.type == ADDRESS_REVIEW_USE_CASE) {
1156 *text = "Confirm";
1157 }
1158 else {
1159 // if finish title is provided, use it
1160 if (context.review.finishTitle != NULL) {
1161 *text = context.review.finishTitle;
1162 }
1163 else {
1164 switch (context.operationType & REAL_TYPE_MASK) {
1165 case TYPE_TRANSACTION:
1166 if (context.operationType & RISKY_OPERATION) {
1167 *text = "Accept risk and sign transaction";
1168 }
1169 else {
1170 *text = "Sign transaction";
1171 }
1172 break;
1173 case TYPE_MESSAGE:
1174 if (context.operationType & RISKY_OPERATION) {
1175 *text = "Accept risk and sign message";
1176 }
1177 else {
1178 *text = "Sign message";
1179 }
1180 break;
1181 default:
1182 if (context.operationType & RISKY_OPERATION) {
1183 *text = "Accept risk and sign operation";
1184 }
1185 else {
1186 *text = "Sign operation";
1187 }
1188 break;
1189 }
1190 }
1191 }
1192 context.stepCallback = onReviewAccept;
1193 }
1194 else {
1195 // Reject page
1196 *icon = &C_icon_crossmark;
1197 if ((context.type == ADDRESS_REVIEW_USE_CASE)
1198 || (context.operationType & ADDRESS_BOOK_OPERATION)) {
1199 *text = "Cancel";
1200 }
1201 else if ((context.operationType & REAL_TYPE_MASK) == TYPE_TRANSACTION) {
1202 *text = "Reject transaction";
1203 }
1204 else if ((context.operationType & REAL_TYPE_MASK) == TYPE_MESSAGE) {
1205 *text = "Reject message";
1206 }
1207 else {
1208 *text = "Reject operation";
1209 }
1210 context.stepCallback = onReviewReject;
1211 }
1212}
1213
1214// function used to display the current page in review
1215static void displayReviewPage(nbgl_stepPosition_t pos)
1216{
1217 uint8_t reviewPages = 0;
1218 uint8_t finalPages = 0;
1219 uint8_t pairIndex = 0;
1220 const char *text = NULL;
1221 const char *subText = NULL;
1222 const nbgl_icon_details_t *icon = NULL;
1223 uint8_t currentIndex = 0;
1224 uint8_t titleIndex = 255;
1225 uint8_t subIndex = 255;
1226 uint8_t approveIndex = 255;
1227 uint8_t rejectIndex = 255;
1228 const nbgl_contentValueExt_t *extension = NULL;
1229 ForcedType_t forcedType = NO_FORCED_TYPE;
1230
1231 context.stepCallback = NULL;
1232
1233 // Determine the 1st page to display tag/values
1234 // Title page to display
1235 titleIndex = currentIndex++;
1236 reviewPages++;
1237 if (context.review.reviewSubTitle) {
1238 // subtitle page to display
1239 subIndex = currentIndex++;
1240 reviewPages++;
1241 }
1242 approveIndex = context.nbPages - 2;
1243 rejectIndex = context.nbPages - 1;
1244 finalPages = approveIndex;
1245
1246 // Determine which page to display
1247 if (context.currentPage >= finalPages) {
1248 if (context.currentPage == approveIndex) {
1249 // Approve page
1250 getLastPageInfo(true, &icon, &text);
1251 }
1252 else if (context.currentPage == rejectIndex) {
1253 // Reject page
1254 getLastPageInfo(false, &icon, &text);
1255 }
1256 }
1257 else if (context.currentPage < reviewPages) {
1258 if (context.currentPage == titleIndex) {
1259 // Title page
1260 icon = context.review.icon;
1261 text = context.review.reviewTitle;
1262 }
1263 else if (context.currentPage == subIndex) {
1264 // SubTitle page
1265 text = context.review.reviewSubTitle;
1266 }
1267 }
1268 else if ((context.review.address != NULL) && (context.currentPage == reviewPages)) {
1269 // address confirmation and 2nd page
1270 text = "Address";
1271 subText = context.review.address;
1272 }
1273 else {
1274 // if there is a skip, and we are not already displaying the "skip" page
1275 // and we are not at the first tag/value of the first set of data (except if going
1276 // backward) then display the "skip" page
1277 if ((context.operationType & SKIPPABLE_OPERATION) && (context.review.skipDisplay == false)
1278 && ((context.currentPage > reviewPages)
1279 || (context.review.dataDirection == BACKWARD_DIRECTION))) {
1280 nbgl_stepPosition_t directions = (pos & BACKWARD_DIRECTION) | FIRST_STEP;
1281 nbgl_layoutCenteredInfo_t info = {0};
1282 if ((context.review.nbDataSets == 1) || (context.currentPage > 0)) {
1283 directions |= LAST_STEP;
1284 }
1285 info.icon = &C_Information_circle_14px;
1286 info.text1 = "Press right button to continue message or \bpress both to skip\b";
1287 nbgl_stepDrawCenteredInfo(directions, buttonSkipCallback, NULL, &info, false);
1288 nbgl_refresh();
1289 context.review.skipDisplay = true;
1290 context.firstPairPage = reviewPages;
1291 return;
1292 }
1293 context.review.skipDisplay = false;
1294 bool isCenteredInfo = false;
1295 pairIndex = context.currentPage - reviewPages;
1296 if (context.review.address != NULL) {
1297 pairIndex--;
1298 }
1299 getPairData(context.review.tagValueList,
1300 pairIndex,
1301 &text,
1302 &subText,
1303 &extension,
1304 &icon,
1305 &isCenteredInfo);
1306 if (extension != NULL) {
1307 context.stepCallback = displayAliasFullValue;
1308 context.review.currentTagValueIndex = pairIndex;
1309 forcedType = FORCE_BUTTON;
1310 }
1311 else {
1312 if (isCenteredInfo) {
1313 forcedType = FORCE_CENTERED_INFO;
1314 }
1315 }
1316 }
1317
1318 drawStep(pos, icon, text, subText, reviewCallback, false, forcedType, false);
1319 nbgl_refresh();
1320}
1321
1322// function used to display the current page in review
1323static void displayStreamingReviewPage(nbgl_stepPosition_t pos)
1324{
1325 const char *text = NULL;
1326 const char *subText = NULL;
1327 const nbgl_icon_details_t *icon = NULL;
1328 uint8_t reviewPages = 0;
1329 uint8_t titleIndex = 255;
1330 uint8_t subIndex = 255;
1331 const nbgl_contentValueExt_t *extension = NULL;
1332 ForcedType_t forcedType = NO_FORCED_TYPE;
1333
1334 context.stepCallback = NULL;
1335 switch (context.type) {
1336 case STREAMING_START_REVIEW_USE_CASE:
1337 // Title page to display
1338 titleIndex = reviewPages++;
1339 if (context.review.reviewSubTitle) {
1340 // subtitle page to display
1341 subIndex = reviewPages++;
1342 }
1343 // Determine which page to display
1344 if (context.currentPage >= reviewPages) {
1345 onReviewAccept();
1346 return;
1347 }
1348 // header page(s)
1349 if (context.currentPage == titleIndex) {
1350 // title page
1351 icon = context.review.icon;
1352 text = context.review.reviewTitle;
1353 }
1354 else if (context.currentPage == subIndex) {
1355 // subtitle page
1356 text = context.review.reviewSubTitle;
1357 }
1358 break;
1359
1360 case STREAMING_CONTINUE_REVIEW_USE_CASE:
1361 if (context.currentPage >= context.review.tagValueList->nbPairs) {
1362 onReviewAccept();
1363 return;
1364 }
1365 // if there is a skip, and we are not already displaying the "skip" page
1366 // and we are not at the first tag/value of the first set of data (except if going
1367 // backward) then display the "skip" page
1368 if ((context.review.skipCallback != NULL) && (context.review.skipDisplay == false)
1369 && ((context.review.nbDataSets > 1) || (context.currentPage > 0)
1370 || (context.review.dataDirection == BACKWARD_DIRECTION))) {
1371 nbgl_stepPosition_t directions = (pos & BACKWARD_DIRECTION) | FIRST_STEP;
1372 nbgl_layoutCenteredInfo_t info = {0};
1373 if ((context.review.nbDataSets == 1) || (context.currentPage > 0)) {
1374 directions |= LAST_STEP;
1375 }
1376 info.icon = &C_Information_circle_14px;
1377 info.text1 = "Press right button to continue message or \bpress both to skip\b";
1378 nbgl_stepDrawCenteredInfo(directions, buttonSkipCallback, NULL, &info, false);
1379 nbgl_refresh();
1380 context.review.skipDisplay = true;
1381 return;
1382 }
1383 context.review.skipDisplay = false;
1384 bool isCenteredInfo = false;
1385 getPairData(context.review.tagValueList,
1386 context.currentPage,
1387 &text,
1388 &subText,
1389 &extension,
1390 &icon,
1391 &isCenteredInfo);
1392 if (extension != NULL) {
1393 forcedType = FORCE_BUTTON;
1394 }
1395 else {
1396 if (isCenteredInfo) {
1397 forcedType = FORCE_CENTERED_INFO;
1398 }
1399 }
1400 break;
1401
1402 case STREAMING_FINISH_REVIEW_USE_CASE:
1403 default:
1404 if (context.currentPage == 0) {
1405 // accept page
1406 getLastPageInfo(true, &icon, &text);
1407 }
1408 else {
1409 // reject page
1410 getLastPageInfo(false, &icon, &text);
1411 }
1412 break;
1413 }
1414
1415 drawStep(pos, icon, text, subText, streamingReviewCallback, false, forcedType, false);
1416 nbgl_refresh();
1417}
1418
1419// function used to display the current page in info
1420static void displayInfoPage(nbgl_stepPosition_t pos)
1421{
1422 const char *text = NULL;
1423 const char *subText = NULL;
1424 const nbgl_icon_details_t *icon = NULL;
1425
1426 context.stepCallback = NULL;
1427
1428 if (context.currentPage < (context.nbPages - 1)) {
1429 text = PIC(
1430 ((const char *const *) PIC(context.home.infosList->infoTypes))[context.currentPage]);
1431 subText = PIC(
1432 ((const char *const *) PIC(context.home.infosList->infoContents))[context.currentPage]);
1433 }
1434 else {
1435 icon = &C_icon_back_x;
1436 text = "Back";
1437 context.stepCallback = startUseCaseHome;
1438 }
1439
1440 drawStep(pos, icon, text, subText, infoCallback, false, FORCE_CENTERED_INFO, false);
1441 nbgl_refresh();
1442}
1443
1444// function used to get the current page content
1445static void getContentPage(bool toogle_state, PageContent_t *contentPage)
1446{
1447 uint8_t elemIdx = 0;
1448 const nbgl_content_t *p_content = NULL;
1449 nbgl_content_t content = {0};
1450 nbgl_contentSwitch_t *contentSwitch = NULL;
1451 nbgl_contentRadioChoice_t *contentChoices = NULL;
1452 char **names = NULL;
1453 nbgl_contentBarsList_t *contentBars = NULL;
1454 char **texts = NULL;
1455 p_content = getContentElemAtIdx(context.currentPage, &elemIdx, &content);
1456 if (p_content == NULL) {
1457 return;
1458 }
1459 switch (p_content->type) {
1460 case CENTERED_INFO:
1461 contentPage->text = PIC(p_content->content.centeredInfo.text1);
1462 contentPage->subText = PIC(p_content->content.centeredInfo.text2);
1463 contentPage->icon = PIC(p_content->content.centeredInfo.icon);
1464 contentPage->bottomIcon = p_content->content.centeredInfo.bottomIcon;
1465 break;
1466 case INFO_BUTTON:
1467 contentPage->icon = PIC(p_content->content.infoButton.icon);
1468 contentPage->text = PIC(p_content->content.infoButton.text);
1469 contentPage->subText = PIC(p_content->content.infoButton.buttonText);
1470 contentPage->bottomIcon = p_content->content.infoButton.bottomIcon;
1471 break;
1472 case TAG_VALUE_LIST:
1473 getPairData(&p_content->content.tagValueList,
1474 elemIdx,
1475 &contentPage->text,
1476 &contentPage->subText,
1477 &contentPage->extension,
1478 &contentPage->icon,
1479 &contentPage->isCenteredInfo);
1480 break;
1481 case TAG_VALUE_CONFIRM:
1482 if (elemIdx < p_content->content.tagValueConfirm.tagValueList.nbPairs) {
1483 getPairData(&p_content->content.tagValueConfirm.tagValueList,
1484 elemIdx,
1485 &contentPage->text,
1486 &contentPage->subText,
1487 &contentPage->extension,
1488 &contentPage->icon,
1489 &contentPage->isCenteredInfo);
1490 }
1491 else {
1492 contentPage->text = p_content->content.tagValueConfirm.confirmationText;
1493 contentPage->icon = &C_icon_validate_14;
1494 contentPage->isAction = true;
1495 }
1496 break;
1497 case SWITCHES_LIST:
1498 contentPage->isSwitch = true;
1499 contentSwitch = &(
1500 (nbgl_contentSwitch_t *) PIC(p_content->content.switchesList.switches))[elemIdx];
1501 contentPage->text = contentSwitch->text;
1502 contentPage->state = contentSwitch->initState;
1503 if (toogle_state) {
1504 contentPage->state = (contentPage->state == ON_STATE) ? OFF_STATE : ON_STATE;
1505 }
1506 context.stepCallback = onSwitchAction;
1507 contentPage->subText = contentSwitch->subText;
1508 break;
1509 case INFOS_LIST:
1510 contentPage->text
1511 = ((const char *const *) PIC(p_content->content.infosList.infoTypes))[elemIdx];
1512 contentPage->subText
1513 = ((const char *const *) PIC(p_content->content.infosList.infoContents))[elemIdx];
1514 break;
1515 case CHOICES_LIST:
1516 contentChoices = (nbgl_contentRadioChoice_t *) PIC(&p_content->content.choicesList);
1517 // When the content opts into the vertical layout, leave
1518 // contentPage->text NULL so drawStep falls through to the menu-list
1519 // rendering (all choices visible, with a radio button on
1520 // initChoice). Default is the horizontal one-page-per-choice flow.
1521 if (contentChoices->vertical) {
1522 break;
1523 }
1524 names = (char **) PIC(contentChoices->names);
1525 // The caller-provided per-content title wins over the contextual
1526 // fallback (app name / use case title).
1527 if (contentChoices->title != NULL) {
1528 contentPage->text = PIC(contentChoices->title);
1529 contentPage->subText = (const char *) PIC(names[elemIdx]);
1530 }
1531 else if ((context.type == CONTENT_USE_CASE) && (context.content.title != NULL)) {
1532 contentPage->text = PIC(context.content.title);
1533 contentPage->subText = (const char *) PIC(names[elemIdx]);
1534 }
1535 else if (((context.type == GENERIC_SETTINGS) || (context.type == SETTINGS_USE_CASE))
1536 && (context.home.appName != NULL)) {
1537 // Both nbgl_useCaseGenericSettings (GENERIC_SETTINGS) and
1538 // nbgl_useCaseHomeAndSettings -> Settings (SETTINGS_USE_CASE)
1539 // populate context.home.appName, so use it as the page title
1540 // for the latter too -- otherwise a horizontally-paged
1541 // CHOICES_LIST would render the choice name only with no
1542 // contextual header at all.
1543 contentPage->text = PIC(context.home.appName);
1544 contentPage->subText = (const char *) PIC(names[elemIdx]);
1545 }
1546 else {
1547 contentPage->text = (const char *) PIC(names[elemIdx]);
1548 }
1549 // Mark the currently-selected choice with the caller-provided icon
1550 // placed at the bottom of the screen (see contentCenteredInfo
1551 // bottomIcon handling in nbgl_layoutAddCenteredInfo). Skipped when
1552 // the caller did not opt in.
1553 if ((contentChoices->selectionIcon != NULL)
1554 && (elemIdx == contentChoices->initChoice)) {
1555 contentPage->icon = PIC(contentChoices->selectionIcon);
1556 contentPage->bottomIcon = true;
1557 }
1558 break;
1559 case BARS_LIST:
1560 contentBars = (nbgl_contentBarsList_t *) PIC(&p_content->content.barsList);
1561 if (contentBars->vertical) {
1562 break; // see CHOICES_LIST comment above
1563 }
1564 texts = (char **) PIC(contentBars->barTexts);
1565 // Same precedence as CHOICES_LIST: caller-provided title overrides
1566 // the contextual fallback.
1567 if (contentBars->title != NULL) {
1568 contentPage->text = PIC(contentBars->title);
1569 contentPage->subText = PIC(texts[elemIdx]);
1570 }
1571 else if ((context.type == CONTENT_USE_CASE) && (context.content.title != NULL)) {
1572 contentPage->text = PIC(context.content.title);
1573 contentPage->subText = PIC(texts[elemIdx]);
1574 }
1575 else if ((context.type == GENERIC_SETTINGS) && (context.home.appName != NULL)) {
1576 contentPage->text = PIC(context.home.appName);
1577 contentPage->subText = PIC(texts[elemIdx]);
1578 }
1579 else {
1580 contentPage->text = PIC(texts[elemIdx]);
1581 }
1582 // Sub-menu indicator: always pinned at the bottom of every bar's
1583 // page. Mirrors the hardcoded PUSH_ICON ('>') that Stax/Flex draw
1584 // on the right of touchable bars (see nbgl_page.c). On Nano the
1585 // right button is reserved for navigation, so we pick a downward
1586 // chevron and place it at the screen bottom via the centeredInfo
1587 // bottomIcon path.
1588 contentPage->icon = &C_icon_down;
1589 contentPage->bottomIcon = true;
1590 break;
1591 default:
1592 break;
1593 }
1594}
1595
1596// function used to display the current page in settings
1597static void displaySettingsPage(nbgl_stepPosition_t pos, bool toogle_state)
1598{
1599 PageContent_t contentPage = {0};
1600
1601 context.stepCallback = NULL;
1602
1603 if (context.currentPage < (context.nbPages - 1)) {
1604 getContentPage(toogle_state, &contentPage);
1605 }
1606 else { // last page is for quit
1607 contentPage.icon = &C_icon_back_x;
1608 contentPage.text = "Back";
1609 if (context.type == GENERIC_SETTINGS) {
1610 context.stepCallback = context.home.quitCallback;
1611 }
1612 else {
1613 context.stepCallback = startUseCaseHome;
1614 }
1615 }
1616
1617 if (contentPage.isSwitch) {
1618 drawSwitchStep(
1619 pos, contentPage.text, contentPage.subText, contentPage.state, settingsCallback, false);
1620 }
1621 else {
1622 drawStep(pos,
1623 contentPage.icon,
1624 contentPage.text,
1625 contentPage.subText,
1626 settingsCallback,
1627 false,
1628 NO_FORCED_TYPE,
1629 contentPage.bottomIcon);
1630 }
1631
1632 nbgl_refresh();
1633}
1634
1635static void startUseCaseHome(void)
1636{
1637 switch (context.type) {
1638 case SETTINGS_USE_CASE:
1639 // Settings page index
1640 context.currentPage = 1;
1641 if (context.home.homeAction) {
1642 // Action page is before Settings page
1643 context.currentPage++;
1644 }
1645 break;
1646 case INFO_USE_CASE:
1647 // Info page index
1648 context.currentPage = 1;
1649 if (context.home.homeAction) {
1650 // Action page is before Settings and Info pages
1651 context.currentPage++;
1652 }
1653 if (context.home.settingContents) {
1654 // Settings page is before Info pages
1655 context.currentPage++;
1656 }
1657 break;
1658 default:
1659 // Home page index
1660 context.currentPage = 0;
1661 break;
1662 }
1663
1664 context.type = HOME_USE_CASE;
1665 context.nbPages = 2; // Home + Quit
1666 if (context.home.settingContents) {
1667 context.nbPages++;
1668 }
1669 if (context.home.infosList) {
1670 context.nbPages++;
1671 }
1672 if (context.home.homeAction) {
1673 context.nbPages++;
1674 }
1675 displayHomePage(FORWARD_DIRECTION);
1676}
1677
1678static void startUseCaseInfo(void)
1679{
1680 context.type = INFO_USE_CASE;
1681 context.nbPages = context.home.infosList->nbInfos + 1; // For back screen
1682 context.currentPage = 0;
1683
1684 displayInfoPage(FORWARD_DIRECTION);
1685}
1686
1687static void startUseCaseSettingsAtPage(uint8_t initSettingPage)
1688{
1689 nbgl_content_t content = {0};
1690 const nbgl_content_t *p_content = NULL;
1691
1692 // if not coming from GENERIC_SETTINGS, force to SETTINGS_USE_CASE
1693 if (context.type != GENERIC_SETTINGS) {
1694 context.type = SETTINGS_USE_CASE;
1695 }
1696
1697 context.nbPages = 1; // For back screen
1698 for (unsigned int i = 0; i < context.home.settingContents->nbContents; i++) {
1699 p_content = getContentAtIdx(context.home.settingContents, i, &content);
1700 context.nbPages += getContentNbElement(p_content);
1701 }
1702 context.currentPage = initSettingPage;
1703
1704 displaySettingsPage(FORWARD_DIRECTION, false);
1705}
1706
1707static void startUseCaseSettings(void)
1708{
1709 startUseCaseSettingsAtPage(0);
1710}
1711
1712static void startUseCaseContent(void)
1713{
1714 uint8_t contentIdx = 0;
1715 const nbgl_content_t *p_content = NULL;
1716 nbgl_content_t content = {0};
1717
1718 context.nbPages = 1; // Quit
1719
1720 for (contentIdx = 0; contentIdx < context.content.genericContents.nbContents; contentIdx++) {
1721 p_content = getContentAtIdx(&context.content.genericContents, contentIdx, &content);
1722 context.nbPages += getContentNbElement(p_content);
1723 }
1724
1725 // Ensure currentPage is valid
1726 if (context.currentPage >= context.nbPages) {
1727 return;
1728 }
1729
1730 displayContent(FORWARD_DIRECTION, false);
1731}
1732
1733// function used to display the current page in home
1734static void displayHomePage(nbgl_stepPosition_t pos)
1735{
1736 const char *text = NULL;
1737 const char *subText = NULL;
1738 const nbgl_icon_details_t *icon = NULL;
1739 uint8_t currentIndex = 0;
1740 uint8_t homeIndex = 255;
1741 uint8_t actionIndex = 255;
1742 uint8_t settingsIndex = 255;
1743 uint8_t infoIndex = 255;
1744
1745 context.stepCallback = NULL;
1746
1747 // Determine which pages are present
1748 homeIndex = currentIndex++;
1749 if (context.home.homeAction) {
1750 actionIndex = currentIndex++;
1751 }
1752 if (context.home.settingContents) {
1753 settingsIndex = currentIndex++;
1754 }
1755 if (context.home.infosList) {
1756 infoIndex = currentIndex++;
1757 }
1758
1759 if (context.currentPage == homeIndex) {
1760 // Home page
1761 icon = context.home.appIcon;
1762 if (context.home.tagline != NULL) {
1763 text = context.home.tagline;
1764 }
1765 else {
1766 text = context.home.appName;
1767 subText = "app is ready";
1768 }
1769 }
1770 else if (context.currentPage == actionIndex) {
1771 // Action page
1772 icon = context.home.homeAction->icon;
1773 text = PIC(context.home.homeAction->text);
1774 context.stepCallback = context.home.homeAction->callback;
1775 }
1776 else if (context.currentPage == settingsIndex) {
1777 // Settings page
1778 icon = &C_icon_coggle;
1779 text = "App settings";
1780 context.stepCallback = startUseCaseSettings;
1781 }
1782 else if (context.currentPage == infoIndex) {
1783 // About page
1784 icon = &C_Information_circle_14px;
1785 text = "App info";
1786 context.stepCallback = startUseCaseInfo;
1787 }
1788 else {
1789 icon = &C_Quit_14px;
1790 text = "Quit app";
1791 context.stepCallback = context.home.quitCallback;
1792 }
1793
1794 drawStep(pos, icon, text, subText, homeCallback, false, NO_FORCED_TYPE, false);
1795 nbgl_refresh();
1796}
1797
1798// function used to display the current page in choice
1799static void displayChoicePage(nbgl_stepPosition_t pos)
1800{
1801 const char *text = NULL;
1802 const char *subText = NULL;
1803 const nbgl_icon_details_t *icon = NULL;
1804 // set to 1 if there is only one page for intro (if either icon or subMessage is NULL)
1805 // acceptPage = number of intro pages:
1806 // 0 if no message, 1 if message with icon OR subMessage only, 2 if both icon and subMessage
1807 uint8_t acceptPage = 0;
1808 // nbDetailPages = number of BAR_LIST bar pages shown before confirm/cancel (0 if none)
1809 // Page order: intro(s) | bar pages | confirm | cancel
1810 uint8_t nbDetailPages = 0;
1811
1812 if (context.choice.message != NULL) {
1813 if ((context.choice.icon == NULL) || (context.choice.subMessage == NULL)) {
1814 acceptPage = 1;
1815 }
1816 else {
1817 acceptPage = 2;
1818 }
1819 }
1820 if ((context.choice.details != NULL) && (context.choice.details->type == BAR_LIST_WARNING)) {
1821 nbDetailPages = context.choice.details->barList.nbBars;
1822 }
1823 context.stepCallback = NULL;
1824
1825 if (context.currentPage < acceptPage) {
1826 if (context.currentPage == 0) { // title page
1827 text = context.choice.message;
1828 if (context.choice.icon != NULL) {
1829 icon = context.choice.icon;
1830 }
1831 else {
1832 subText = context.choice.subMessage;
1833 }
1834 }
1835 else if ((acceptPage == 2) && (context.currentPage == 1)) { // sub-title page
1836 // displayed only if there is both icon and subMessage
1837 text = context.choice.message;
1838 subText = context.choice.subMessage;
1839 }
1840 }
1841 else if (context.currentPage < (acceptPage + nbDetailPages)) {
1842 // BAR_LIST detail pages, shown before confirm/cancel (one page per bar)
1843 uint8_t idx = context.currentPage - acceptPage;
1844 text = (context.choice.details->barList.texts != NULL)
1845 ? context.choice.details->barList.texts[idx]
1846 : NULL;
1847 subText = (context.choice.details->barList.subTexts != NULL)
1848 ? context.choice.details->barList.subTexts[idx]
1849 : NULL;
1850 }
1851 else if (context.currentPage == (acceptPage + nbDetailPages)) { // confirm page
1852 icon = &C_icon_validate_14;
1853 text = context.choice.confirmText;
1854 context.stepCallback = onChoiceAccept;
1855 }
1856 else { // cancel page (last page)
1857 icon = &C_icon_crossmark;
1858 text = context.choice.cancelText;
1859 context.stepCallback = onChoiceReject;
1860 }
1861 // other detail types (non-BAR_LIST) are not navigated on Nano
1862
1863 drawStep(pos, icon, text, subText, genericChoiceCallback, false, NO_FORCED_TYPE, false);
1864 nbgl_refresh();
1865}
1866
1867// function used to display the Confirm page
1868static void displayConfirm(nbgl_stepPosition_t pos)
1869{
1870 const char *text = NULL;
1871 const char *subText = NULL;
1872 const nbgl_icon_details_t *icon = NULL;
1873
1874 context.stepCallback = NULL;
1875 switch (context.currentPage) {
1876 case 0:
1877 // title page
1878 text = context.confirm.message;
1879 subText = context.confirm.subMessage;
1880 break;
1881 case 1:
1882 // confirm page
1883 icon = &C_icon_validate_14;
1884 text = context.confirm.confirmText;
1885 context.stepCallback = onConfirmAccept;
1886 break;
1887 case 2:
1888 // cancel page
1889 icon = &C_icon_crossmark;
1890 text = context.confirm.cancelText;
1891 context.stepCallback = onConfirmReject;
1892 break;
1893 }
1894
1895 drawStep(pos, icon, text, subText, genericConfirmCallback, true, NO_FORCED_TYPE, false);
1896 nbgl_refresh();
1897}
1898
1899// function used to display the current navigable content
1900static void displayContent(nbgl_stepPosition_t pos, bool toogle_state)
1901{
1902 PageContent_t contentPage = {0};
1903 ForcedType_t forcedType = NO_FORCED_TYPE;
1904
1905 context.stepCallback = NULL;
1906
1907 if (context.currentPage < (context.nbPages - 1)) {
1908 getContentPage(toogle_state, &contentPage);
1909 if (contentPage.isCenteredInfo) {
1910 forcedType = FORCE_CENTERED_INFO;
1911 }
1912 context.forceAction = contentPage.isAction;
1913 }
1914 else { // last page is for quit
1915 if (context.content.rejectText) {
1916 contentPage.text = context.content.rejectText;
1917 }
1918 else {
1919 contentPage.text = "Back";
1920 }
1921 if (context.type == GENERIC_REVIEW_USE_CASE) {
1922 contentPage.icon = &C_icon_crossmark;
1923 }
1924 else {
1925 contentPage.icon = &C_icon_back_x;
1926 }
1927 context.stepCallback = context.content.quitCallback;
1928 }
1929
1930 if (contentPage.isSwitch) {
1931 drawSwitchStep(
1932 pos, contentPage.text, contentPage.subText, contentPage.state, contentCallback, false);
1933 }
1934 else {
1935 drawStep(pos,
1936 contentPage.icon,
1937 contentPage.text,
1938 contentPage.subText,
1939 contentCallback,
1940 false,
1941 forcedType,
1942 contentPage.bottomIcon);
1943 }
1944 context.forceAction = false;
1945 nbgl_refresh();
1946}
1947
1948static void displaySpinner(const char *text)
1949{
1950 drawStep(SINGLE_STEP, &C_icon_processing, text, NULL, NULL, false, NO_FORCED_TYPE, false);
1951 nbgl_refresh();
1952}
1953
1954// function to factorize code for all simple reviews
1955static void useCaseReview(ContextType_t type,
1956 nbgl_operationType_t operationType,
1957 const nbgl_contentTagValueList_t *tagValueList,
1958 const nbgl_icon_details_t *icon,
1959 const char *reviewTitle,
1960 const char *reviewSubTitle,
1961 const char *finishTitle,
1962 nbgl_choiceCallback_t choiceCallback)
1963{
1964 memset(&context, 0, sizeof(UseCaseContext_t));
1965 context.type = type;
1966 context.operationType = operationType;
1967 context.review.tagValueList = tagValueList;
1968 context.review.reviewTitle = reviewTitle;
1969 context.review.reviewSubTitle = reviewSubTitle;
1970 context.review.finishTitle = finishTitle;
1971 context.review.icon = icon;
1972 context.review.onChoice = choiceCallback;
1973 context.currentPage = 0;
1974 // 1 page for title and 2 pages at the end for accept/reject
1975 context.nbPages = tagValueList->nbPairs + 3;
1976 if (reviewSubTitle) {
1977 context.nbPages++; // 1 page for subtitle page
1978 }
1979
1980 displayReviewPage(FORWARD_DIRECTION);
1981}
1982
1983#ifdef NBGL_KEYPAD
1984static void setPinCodeText(void)
1985{
1986 bool enableValidate = false;
1987 bool enableBackspace = true;
1988
1989 // pin can be validated when min digits is entered
1990 enableValidate = (context.keypad.pinLen >= context.keypad.pinMinDigits);
1991 // backspace is disabled when no digit is entered and back vallback is not provided
1992 enableBackspace = (context.keypad.pinLen > 0) || (context.keypad.backCallback != NULL);
1993 nbgl_layoutUpdateKeypadContent(context.keypad.layoutCtx,
1994 context.keypad.hidden,
1995 context.keypad.pinLen,
1996 (const char *) context.keypad.pinEntry);
1998 context.keypad.layoutCtx, context.keypad.keypadIndex, enableValidate, enableBackspace);
1999 nbgl_layoutDraw(context.keypad.layoutCtx);
2000 nbgl_refresh();
2001}
2002
2003// called when a key is touched on the keypad
2004static void keypadCallback(char touchedKey)
2005{
2006 switch (touchedKey) {
2007 case BACKSPACE_KEY:
2008 if (context.keypad.pinLen > 0) {
2009 context.keypad.pinLen--;
2010 context.keypad.pinEntry[context.keypad.pinLen] = 0;
2011 }
2012 else if (context.keypad.backCallback != NULL) {
2013 context.keypad.backCallback();
2014 break;
2015 }
2016 setPinCodeText();
2017 break;
2018
2019 case VALIDATE_KEY:
2020 context.keypad.validatePin(context.keypad.pinEntry, context.keypad.pinLen);
2021 break;
2022
2023 default:
2024 if ((touchedKey >= 0x30) && (touchedKey < 0x40)) {
2025 if (context.keypad.pinLen < context.keypad.pinMaxDigits) {
2026 context.keypad.pinEntry[context.keypad.pinLen] = touchedKey;
2027 context.keypad.pinLen++;
2028 }
2029 setPinCodeText();
2030 }
2031 break;
2032 }
2033}
2034#endif // NBGL_KEYPAD
2035
2036#ifdef NBGL_KEYBOARD
2037// Saved keyboard context for suggestion selection
2038// (needed because switching to CONTENT_USE_CASE overwrites the union context)
2039static struct {
2040 const char **buttons;
2041 int firstButtonToken;
2042 uint8_t nbUsedButtons;
2043 nbgl_layoutTouchCallback_t onButtonCallback;
2044 nbgl_callback_t backCallback;
2045 char title[KEYBOARD_MAX_TITLE];
2046} savedKeyboardContext;
2047
2048// Navigation callback to fill the suggestion choices page
2049static bool suggestionNavCallback(uint8_t page, nbgl_pageContent_t *content)
2050{
2051 UNUSED(page);
2052 content->type = CHOICES_LIST;
2053 content->choicesList.names = savedKeyboardContext.buttons;
2054 content->choicesList.token = savedKeyboardContext.firstButtonToken;
2055 content->choicesList.initChoice = 0;
2056 content->choicesList.nbChoices = savedKeyboardContext.nbUsedButtons;
2057 return true;
2058}
2059
2060// Display suggestion selection page
2061static void displaySuggestionSelection(void)
2062{
2063 // Save keyboard context before it gets overwritten
2064 savedKeyboardContext.buttons = context.keyboard.content.suggestionButtons.buttons;
2065 savedKeyboardContext.firstButtonToken
2066 = context.keyboard.content.suggestionButtons.firstButtonToken;
2067 savedKeyboardContext.nbUsedButtons = context.keyboard.content.suggestionButtons.nbUsedButtons;
2068 savedKeyboardContext.onButtonCallback = context.keyboard.onButtonCallback;
2069 savedKeyboardContext.backCallback = context.keyboard.backCallback;
2070
2071 // Release the keyboard layout
2072 nbgl_layoutRelease(context.keyboard.layoutCtx);
2073 context.keyboard.layoutCtx = NULL;
2074
2075 snprintf(savedKeyboardContext.title,
2076 sizeof(savedKeyboardContext.title),
2077 "Select word #%d",
2078 context.keyboard.content.number);
2079 nbgl_useCaseNavigableContent(savedKeyboardContext.title,
2080 0,
2081 1,
2082 savedKeyboardContext.backCallback,
2083 suggestionNavCallback,
2084 savedKeyboardContext.onButtonCallback);
2085}
2086
2087// called when a key is touched on the keyboard
2088static void keyboardCallback(char touchedKey)
2089{
2090 uint32_t mask = 0;
2091 size_t textLen = strlen(context.keyboard.entryBuffer);
2092 PRINTF("[keyboardCallback] touchedKey: '%c'\n", touchedKey);
2093 if (touchedKey == BACKSPACE_KEY) {
2094 if (textLen == 0) {
2095 // Used to exit the keyboard when backspace is pressed on an empty entry
2096 context.keyboard.backCallback();
2097 return;
2098 }
2099 context.keyboard.entryBuffer[--textLen] = '\0';
2100 }
2101 else if (touchedKey == VALIDATE_KEY) {
2102 context.keyboard.actionCallback();
2103 return;
2104 }
2105 else {
2106 context.keyboard.entryBuffer[textLen] = touchedKey;
2107 context.keyboard.entryBuffer[++textLen] = '\0';
2108 }
2109 // Set the keyMask to disable some keys
2110 if (context.keyboard.content.type == KEYBOARD_WITH_SUGGESTIONS) {
2111 // if suggestions are displayed, we update them at each key press
2112 context.keyboard.getSuggestButtons(&context.keyboard.content, &mask);
2113 const nbgl_layoutSuggestionButtons_t *suggestions
2114 = &context.keyboard.content.suggestionButtons;
2115 // On Nano, when all the matching candidates can be displayed, switch to a selection page
2116 // instead of continuing with keyboard entry. When the caller provides the real number of
2117 // candidates (nbCandidates != 0), rely on it: this lets a fully-typed word that is also
2118 // the prefix of other words (e.g. "can") be selected even though the displayed buttons are
2119 // capped at NB_MAX_SUGGESTION_BUTTONS. Otherwise fall back to the legacy heuristic based on
2120 // the number of displayed buttons.
2121 bool allCandidatesFit;
2122 if (suggestions->nbCandidates != 0) {
2123 allCandidatesFit = (suggestions->nbCandidates <= NB_MAX_SUGGESTION_BUTTONS);
2124 }
2125 else {
2126 allCandidatesFit = (suggestions->nbUsedButtons < NB_MAX_SUGGESTION_BUTTONS);
2127 }
2128 if ((suggestions->nbUsedButtons > 0) && allCandidatesFit) {
2129 displaySuggestionSelection();
2130 return; // Don't update keyboard, we're in suggestion mode
2131 }
2132 }
2133 else if (textLen >= context.keyboard.entryMaxLen) {
2134 // entry length can't be greater, so we mask every characters
2135 mask = -1;
2136 }
2137 nbgl_layoutUpdateKeyboard(context.keyboard.layoutCtx, context.keyboard.keyboardIndex, mask);
2139 context.keyboard.layoutCtx, context.keyboard.textIndex, context.keyboard.entryBuffer);
2140 nbgl_refresh();
2141}
2142#endif
2143
2144// this is the function called to start the actual review, from the initial warning pages
2145static void launchReviewAfterWarning(void)
2146{
2147 if (reviewWithWarnCtx.type == REVIEW_USE_CASE) {
2148 useCaseReview(reviewWithWarnCtx.type,
2149 reviewWithWarnCtx.operationType,
2150 reviewWithWarnCtx.tagValueList,
2151 reviewWithWarnCtx.icon,
2152 reviewWithWarnCtx.reviewTitle,
2153 reviewWithWarnCtx.reviewSubTitle,
2154 reviewWithWarnCtx.finishTitle,
2155 reviewWithWarnCtx.choiceCallback);
2156 }
2157 else if (reviewWithWarnCtx.type == STREAMING_START_REVIEW_USE_CASE) {
2158 displayStreamingReviewPage(FORWARD_DIRECTION);
2159 }
2160}
2161
2162// callback used when navigating in a bar detail sub-page
2163static void barDetailNavigate(nbgl_step_t stepCtx, nbgl_buttonEvent_t event)
2164{
2165 UNUSED(stepCtx);
2166 if (event == BUTTON_LEFT_PRESSED) {
2167 displayWarningStep();
2168 }
2169}
2170
2171// display a bar detail sub-page (CENTERED_INFO_WARNING type)
2172static void displayBarDetailStep(void)
2173{
2174 const nbgl_genericDetails_t *detail
2175 = &reviewWithWarnCtx.warning->introDetails->barList.details[reviewWithWarnCtx.barDetailIdx];
2176
2177 if (detail->type == CENTERED_INFO_WARNING) {
2178 nbgl_layoutCenteredInfo_t info = {0};
2179 info.icon = detail->centeredInfo.icon;
2180 info.text1 = detail->centeredInfo.title;
2181 info.text2 = detail->centeredInfo.description;
2182 info.style = BOLD_TEXT1_INFO;
2183 // LAST_STEP: only left arrow shown, pressing LEFT goes back via barDetailNavigate
2185 LAST_STEP | BACKWARD_DIRECTION, barDetailNavigate, NULL, &info, false);
2186 nbgl_refresh();
2187 }
2188}
2189
2190// this is the callback used when navigating in warning pages
2191static void warningNavigate(nbgl_step_t stepCtx, nbgl_buttonEvent_t event)
2192{
2193 UNUSED(stepCtx);
2194 uint8_t barIdx = 0;
2195
2196 if (event == BUTTON_LEFT_PRESSED) {
2197 // only decrement page if we are not at the first page
2198 if (reviewWithWarnCtx.warningPage > 0) {
2199 reviewWithWarnCtx.warningPage--;
2200 }
2201 }
2202 else if (event == BUTTON_RIGHT_PRESSED) {
2203 // only increment page if not at last page
2204 if (reviewWithWarnCtx.warningPage < (reviewWithWarnCtx.nbWarningPages - 1)) {
2205 reviewWithWarnCtx.warningPage++;
2206 }
2207 else if ((reviewWithWarnCtx.warning->predefinedSet == 0)
2208 && (reviewWithWarnCtx.warning->info != NULL)) {
2209 launchReviewAfterWarning();
2210 return;
2211 }
2212 }
2213 else if ((event == BUTTON_BOTH_PRESSED)
2214 && (reviewWithWarnCtx.warning->predefinedSet & (1 << BLIND_SIGNING_WARN))) {
2215 // if at first page, double press leads to start of review
2216 if (reviewWithWarnCtx.warningPage == reviewWithWarnCtx.firstWarningPage) {
2217 launchReviewAfterWarning();
2218 }
2219 // if at last page, reject operation
2220 else if (reviewWithWarnCtx.warningPage == (reviewWithWarnCtx.nbWarningPages - 1)) {
2221 reviewWithWarnCtx.choiceCallback(false);
2222 }
2223 return;
2224 }
2225 else if ((event == BUTTON_BOTH_PRESSED) && (reviewWithWarnCtx.warning->introDetails != NULL)
2226 && (reviewWithWarnCtx.warning->introDetails->type == BAR_LIST_WARNING)
2227 && (reviewWithWarnCtx.warningPage > reviewWithWarnCtx.firstWarningPage)) {
2228 // enter the detail sub-page for the current bar (if it has one)
2229 barIdx = reviewWithWarnCtx.warningPage - reviewWithWarnCtx.firstWarningPage - 1;
2230 if ((reviewWithWarnCtx.warning->introDetails->barList.details != NULL)
2231 && (reviewWithWarnCtx.warning->introDetails->barList.details[barIdx].type
2232 != NO_TYPE_WARNING)) {
2233 reviewWithWarnCtx.barDetailIdx = barIdx;
2234 displayBarDetailStep();
2235 }
2236 return;
2237 }
2238 else {
2239 return;
2240 }
2241 displayWarningStep();
2242}
2243
2244// function used to display the initial warning pages when starting a "review with warning"
2245static void displayWarningStep(void)
2246{
2247 nbgl_layoutCenteredInfo_t info = {0};
2248 nbgl_stepPosition_t pos = 0;
2249 uint8_t barIdx = 0;
2250 if ((reviewWithWarnCtx.warning->prelude) && (reviewWithWarnCtx.warningPage == 0)) {
2251 // for prelude, only draw text as a single step
2253 warningNavigate,
2254 NULL,
2255 reviewWithWarnCtx.warning->prelude->title,
2256 reviewWithWarnCtx.warning->prelude->description,
2257 REGULAR_INFO,
2258 false);
2259 nbgl_refresh();
2260 return;
2261 }
2262 else if (reviewWithWarnCtx.warning->predefinedSet & (1 << BLIND_SIGNING_WARN)) {
2263 if (reviewWithWarnCtx.warningPage == reviewWithWarnCtx.firstWarningPage) {
2264 // draw the main warning page
2265 info.icon = &C_icon_warning;
2266 info.text1 = "Blind signing ahead";
2267 info.text2 = "To accept risk, press both buttons";
2268 pos = (reviewWithWarnCtx.firstWarningPage == 0) ? FIRST_STEP
2270 pos |= FORWARD_DIRECTION;
2271 }
2272 else if (reviewWithWarnCtx.warningPage == (reviewWithWarnCtx.nbWarningPages - 1)) {
2273 getLastPageInfo(false, &info.icon, &info.text1);
2275 }
2276 }
2277 else if ((reviewWithWarnCtx.warning->predefinedSet == 0)
2278 && (reviewWithWarnCtx.warning->info != NULL)) {
2279 if (reviewWithWarnCtx.warningPage == reviewWithWarnCtx.firstWarningPage) {
2280 info.icon = reviewWithWarnCtx.warning->info->icon;
2281 info.text1 = reviewWithWarnCtx.warning->info->title;
2282 info.text2 = reviewWithWarnCtx.warning->info->description;
2283 pos = (reviewWithWarnCtx.firstWarningPage == 0) ? FIRST_STEP
2285 pos |= FORWARD_DIRECTION;
2286 }
2287 else if ((reviewWithWarnCtx.warning->introDetails != NULL)
2288 && (reviewWithWarnCtx.warning->introDetails->type == BAR_LIST_WARNING)) {
2289 // intermediate or last bar page
2290 barIdx = reviewWithWarnCtx.warningPage - reviewWithWarnCtx.firstWarningPage - 1;
2291 if (reviewWithWarnCtx.warning->introDetails->barList.icons) {
2292 info.icon = reviewWithWarnCtx.warning->introDetails->barList.icons[barIdx];
2293 }
2294 info.text1 = reviewWithWarnCtx.warning->introDetails->barList.texts[barIdx];
2295 if (reviewWithWarnCtx.warning->introDetails->barList.subTexts) {
2296 info.text2 = reviewWithWarnCtx.warning->introDetails->barList.subTexts[barIdx];
2297 }
2299 }
2300 else if (reviewWithWarnCtx.warningPage == (reviewWithWarnCtx.nbWarningPages - 1)) {
2301 if ((reviewWithWarnCtx.warning->introDetails != NULL)
2302 && (reviewWithWarnCtx.warning->introDetails->type == CENTERED_INFO_WARNING)) {
2303 info.icon = reviewWithWarnCtx.warning->introDetails->centeredInfo.icon;
2304 info.text1 = reviewWithWarnCtx.warning->introDetails->centeredInfo.title;
2305 info.text2 = reviewWithWarnCtx.warning->introDetails->centeredInfo.description;
2307 }
2308 else {
2309 // not supported
2310 return;
2311 }
2312 }
2313 }
2314 else {
2315 // not supported
2316 return;
2317 }
2318 info.style = BOLD_TEXT1_INFO;
2319 nbgl_stepDrawCenteredInfo(pos, warningNavigate, NULL, &info, false);
2320 nbgl_refresh();
2321}
2322
2323// function used to display the initial warning page when starting a "review with warning"
2324static void displayInitialWarning(void)
2325{
2326 // draw the main warning page
2327 reviewWithWarnCtx.warningPage = 0;
2328 if ((reviewWithWarnCtx.warning->predefinedSet & (1 << BLIND_SIGNING_WARN))
2329 || ((reviewWithWarnCtx.warning->introDetails)
2330 && (reviewWithWarnCtx.warning->introDetails->type == CENTERED_INFO_WARNING))) {
2331 reviewWithWarnCtx.nbWarningPages = 2;
2332 }
2333 else if ((reviewWithWarnCtx.warning->introDetails)
2334 && (reviewWithWarnCtx.warning->introDetails->type == BAR_LIST_WARNING)) {
2335 reviewWithWarnCtx.nbWarningPages
2336 = reviewWithWarnCtx.warning->introDetails->barList.nbBars + 1;
2337 }
2338 else {
2339 // if no intro details and not Blind Signing warning, only one page
2340 reviewWithWarnCtx.nbWarningPages = 1;
2341 }
2342
2343 reviewWithWarnCtx.firstWarningPage = 0;
2344 displayWarningStep();
2345}
2346
2347// function used to display the prelude page when starting a "review with warning"
2348static void displayPrelude(void)
2349{
2350 // draw the main warning page
2351 reviewWithWarnCtx.warningPage = 0;
2352 if ((reviewWithWarnCtx.warning->predefinedSet & (1 << BLIND_SIGNING_WARN))
2353 || ((reviewWithWarnCtx.warning->introDetails)
2354 && (reviewWithWarnCtx.warning->introDetails->type == CENTERED_INFO_WARNING))) {
2355 reviewWithWarnCtx.nbWarningPages = 3;
2356 }
2357 else {
2358 // if no intro details and not Blind Signing warning, only 2 pages
2359 reviewWithWarnCtx.nbWarningPages = 2;
2360 }
2361 reviewWithWarnCtx.firstWarningPage = 1;
2362 displayWarningStep();
2363}
2364
2365/**********************
2366 * GLOBAL FUNCTIONS
2367 **********************/
2368
2380uint8_t nbgl_useCaseGetNbTagValuesInPage(uint8_t nbPairs,
2381 const nbgl_contentTagValueList_t *tagValueList,
2382 uint8_t startIndex,
2383 bool *requireSpecificDisplay)
2384{
2385 UNUSED(nbPairs);
2386 UNUSED(tagValueList);
2387 UNUSED(startIndex);
2388 *requireSpecificDisplay = true;
2389 return 1;
2390}
2391
2404uint8_t nbgl_useCaseGetNbTagValuesInPageExt(uint8_t nbPairs,
2405 const nbgl_contentTagValueList_t *tagValueList,
2406 uint8_t startIndex,
2407 bool isSkippable,
2408 bool *requireSpecificDisplay)
2409{
2410 UNUSED(nbPairs);
2411 UNUSED(tagValueList);
2412 UNUSED(startIndex);
2413 UNUSED(isSkippable);
2414 *requireSpecificDisplay = true;
2415 return 1;
2416}
2417
2426uint8_t nbgl_useCaseGetNbInfosInPage(uint8_t nbInfos,
2427 const nbgl_contentInfoList_t *infosList,
2428 uint8_t startIndex,
2429 bool withNav)
2430{
2431 UNUSED(nbInfos);
2432 UNUSED(infosList);
2433 UNUSED(startIndex);
2434 UNUSED(withNav);
2435 return 1;
2436}
2437
2446uint8_t nbgl_useCaseGetNbSwitchesInPage(uint8_t nbSwitches,
2447 const nbgl_contentSwitchesList_t *switchesList,
2448 uint8_t startIndex,
2449 bool withNav)
2450{
2451 UNUSED(nbSwitches);
2452 UNUSED(switchesList);
2453 UNUSED(startIndex);
2454 UNUSED(withNav);
2455 return 1;
2456}
2457
2466uint8_t nbgl_useCaseGetNbBarsInPage(uint8_t nbBars,
2467 const nbgl_contentBarsList_t *barsList,
2468 uint8_t startIndex,
2469 bool withNav)
2470{
2471 UNUSED(nbBars);
2472 UNUSED(barsList);
2473 UNUSED(startIndex);
2474 UNUSED(withNav);
2475 return 1;
2476}
2477
2486uint8_t nbgl_useCaseGetNbChoicesInPage(uint8_t nbChoices,
2487 const nbgl_contentRadioChoice_t *choicesList,
2488 uint8_t startIndex,
2489 bool withNav)
2490{
2491 UNUSED(nbChoices);
2492 UNUSED(choicesList);
2493 UNUSED(startIndex);
2494 UNUSED(withNav);
2495 return 1;
2496}
2497
2505{
2506 uint8_t nbPages = 0;
2507 uint8_t nbPairs = tagValueList->nbPairs;
2508 uint8_t nbPairsInPage;
2509 uint8_t i = 0;
2510 bool flag;
2511
2512 while (i < tagValueList->nbPairs) {
2513 // upper margin
2514 nbPairsInPage = nbgl_useCaseGetNbTagValuesInPageExt(nbPairs, tagValueList, i, false, &flag);
2515 i += nbPairsInPage;
2516 nbPairs -= nbPairsInPage;
2517 nbPages++;
2518 }
2519 return nbPages;
2520}
2521
2535void nbgl_useCaseNavigableContent(const char *title,
2536 uint8_t initPage,
2537 uint8_t nbPages,
2538 nbgl_callback_t quitCallback,
2539 nbgl_navCallback_t navCallback,
2540 nbgl_layoutTouchCallback_t controlsCallback)
2541{
2542 memset(&context, 0, sizeof(UseCaseContext_t));
2543 context.type = CONTENT_USE_CASE;
2544 context.currentPage = initPage;
2545 context.content.title = title;
2546 context.content.quitCallback = quitCallback;
2547 context.content.navCallback = navCallback;
2548 context.content.controlsCallback = controlsCallback;
2549 context.content.genericContents.callbackCallNeeded = true;
2550 context.content.genericContents.nbContents = nbPages;
2551
2552 startUseCaseContent();
2553}
2554
2569void nbgl_useCaseHomeAndSettings(const char *appName,
2570 const nbgl_icon_details_t *appIcon,
2571 const char *tagline,
2572 const uint8_t initSettingPage,
2573 const nbgl_genericContents_t *settingContents,
2574 const nbgl_contentInfoList_t *infosList,
2575 const nbgl_homeAction_t *action,
2576 nbgl_callback_t quitCallback)
2577{
2578 memset(&context, 0, sizeof(UseCaseContext_t));
2579 context.home.appName = appName;
2580 context.home.appIcon = appIcon;
2581 context.home.tagline = tagline;
2582 context.home.settingContents = PIC(settingContents);
2583 context.home.infosList = PIC(infosList);
2584 context.home.homeAction = action;
2585 context.home.quitCallback = quitCallback;
2586
2587 if ((initSettingPage != INIT_HOME_PAGE) && (settingContents != NULL)) {
2588 startUseCaseSettingsAtPage(initSettingPage);
2589 }
2590 else {
2591 startUseCaseHome();
2592 }
2593}
2594
2607void nbgl_useCaseGenericSettings(const char *appName,
2608 uint8_t initPage,
2609 const nbgl_genericContents_t *settingContents,
2610 const nbgl_contentInfoList_t *infosList,
2611 nbgl_callback_t quitCallback)
2612{
2613 memset(&context, 0, sizeof(UseCaseContext_t));
2614 context.type = GENERIC_SETTINGS;
2615 context.home.appName = appName;
2616 context.home.settingContents = PIC(settingContents);
2617 context.home.infosList = PIC(infosList);
2618 context.home.quitCallback = quitCallback;
2619
2620 startUseCaseSettingsAtPage(initPage);
2621}
2622
2634void nbgl_useCaseGenericConfiguration(const char *title,
2635 uint8_t initPage,
2636 const nbgl_genericContents_t *contents,
2637 nbgl_callback_t quitCallback)
2638{
2639 nbgl_useCaseGenericSettings(title, initPage, contents, NULL, quitCallback);
2640}
2641
2656void nbgl_useCaseReview(nbgl_operationType_t operationType,
2657 const nbgl_contentTagValueList_t *tagValueList,
2658 const nbgl_icon_details_t *icon,
2659 const char *reviewTitle,
2660 const char *reviewSubTitle,
2661 const char *finishTitle,
2662 nbgl_choiceCallback_t choiceCallback)
2663{
2664 useCaseReview(REVIEW_USE_CASE,
2665 operationType,
2666 tagValueList,
2667 icon,
2668 reviewTitle,
2669 reviewSubTitle,
2670 finishTitle,
2671 choiceCallback);
2672}
2673
2697 const nbgl_contentTagValueList_t *tagValueList,
2698 const nbgl_icon_details_t *icon,
2699 const char *reviewTitle,
2700 const char *reviewSubTitle,
2701 const char *finishTitle,
2702 const nbgl_tipBox_t *tipBox,
2703 const nbgl_warning_t *warning,
2704 nbgl_choiceCallback_t choiceCallback)
2705{
2706 UNUSED(tipBox);
2707 ContextType_t type = REVIEW_USE_CASE;
2708
2709 // if no warning at all, it's a simple review
2710 if ((warning == NULL)
2711 || ((warning->predefinedSet == 0) && (warning->info == NULL)
2712 && (warning->reviewDetails == NULL) && (warning->prelude == NULL))) {
2713 useCaseReview(type,
2714 operationType,
2715 tagValueList,
2716 icon,
2717 reviewTitle,
2718 reviewSubTitle,
2719 finishTitle,
2720 choiceCallback);
2721 return;
2722 }
2723 if (warning->predefinedSet == (1 << W3C_NO_THREAT_WARN)) {
2724 operationType |= NO_THREAT_OPERATION;
2725 }
2726 else if (warning->predefinedSet != 0) {
2727 operationType |= RISKY_OPERATION;
2728 }
2729
2730 memset(&reviewWithWarnCtx, 0, sizeof(reviewWithWarnCtx));
2731 reviewWithWarnCtx.type = type;
2732 reviewWithWarnCtx.operationType = operationType;
2733 reviewWithWarnCtx.tagValueList = tagValueList;
2734 reviewWithWarnCtx.icon = icon;
2735 reviewWithWarnCtx.reviewTitle = reviewTitle;
2736 reviewWithWarnCtx.reviewSubTitle = reviewSubTitle;
2737 reviewWithWarnCtx.finishTitle = finishTitle;
2738 reviewWithWarnCtx.warning = warning;
2739 reviewWithWarnCtx.choiceCallback = choiceCallback;
2740
2741 // if the warning contains a prelude, display it first
2742 if (reviewWithWarnCtx.warning->prelude) {
2743 displayPrelude();
2744 }
2745 // display the initial warning only of a risk/threat or blind signing or prelude
2746 else if (HAS_INITIAL_WARNING(warning)) {
2747 displayInitialWarning();
2748 }
2749 else {
2750 useCaseReview(type,
2751 operationType,
2752 tagValueList,
2753 icon,
2754 reviewTitle,
2755 reviewSubTitle,
2756 finishTitle,
2757 choiceCallback);
2758 }
2759}
2760
2781 const nbgl_contentTagValueList_t *tagValueList,
2782 const nbgl_icon_details_t *icon,
2783 const char *reviewTitle,
2784 const char *reviewSubTitle,
2785 const char *finishTitle,
2786 const nbgl_tipBox_t *dummy,
2787 nbgl_choiceCallback_t choiceCallback)
2788{
2789 nbgl_useCaseAdvancedReview(operationType,
2790 tagValueList,
2791 icon,
2792 reviewTitle,
2793 reviewSubTitle,
2794 finishTitle,
2795 dummy,
2796 &blindSigningWarning,
2797 choiceCallback);
2798}
2799
2815 const nbgl_contentTagValueList_t *tagValueList,
2816 const nbgl_icon_details_t *icon,
2817 const char *reviewTitle,
2818 const char *reviewSubTitle,
2819 const char *finishTitle,
2820 nbgl_choiceCallback_t choiceCallback)
2821{
2822 nbgl_useCaseReview(operationType,
2823 tagValueList,
2824 icon,
2825 reviewTitle,
2826 reviewSubTitle,
2827 finishTitle,
2828 choiceCallback);
2829}
2830
2847void nbgl_useCaseAddressReview(const char *address,
2848 const nbgl_contentTagValueList_t *additionalTagValueList,
2849 const nbgl_icon_details_t *icon,
2850 const char *reviewTitle,
2851 const char *reviewSubTitle,
2852 nbgl_choiceCallback_t choiceCallback)
2853{
2854 memset(&context, 0, sizeof(UseCaseContext_t));
2855 context.type = ADDRESS_REVIEW_USE_CASE;
2856 context.review.address = address;
2857 context.review.reviewTitle = reviewTitle;
2858 context.review.reviewSubTitle = reviewSubTitle;
2859 context.review.icon = icon;
2860 context.review.onChoice = choiceCallback;
2861 context.currentPage = 0;
2862 // + 4 because 1 page for title, 1 for address and 2 pages at the end for approve/reject
2863 // + 1 if sub Title
2864 context.nbPages = reviewSubTitle ? 5 : 4;
2865 if (additionalTagValueList) {
2866 context.review.tagValueList = PIC(additionalTagValueList);
2867 context.nbPages += additionalTagValueList->nbPairs;
2868 }
2869
2870 displayReviewPage(FORWARD_DIRECTION);
2871}
2872
2882 const char *rejectText,
2883 nbgl_callback_t rejectCallback)
2884{
2885 memset(&context, 0, sizeof(UseCaseContext_t));
2886 context.type = GENERIC_REVIEW_USE_CASE;
2887 context.content.rejectText = rejectText;
2888 context.content.quitCallback = rejectCallback;
2889 context.content.genericContents.nbContents = contents->nbContents;
2890 context.content.genericContents.callbackCallNeeded = contents->callbackCallNeeded;
2891 if (contents->callbackCallNeeded) {
2892 context.content.genericContents.contentGetterCallback = contents->contentGetterCallback;
2893 }
2894 else {
2895 context.content.genericContents.contentsList = PIC(contents->contentsList);
2896 }
2897
2898 startUseCaseContent();
2899}
2900
2909void nbgl_useCaseStatus(const char *message, bool isSuccess, nbgl_callback_t quitCallback)
2910{
2911 UNUSED(isSuccess);
2912 memset(&context, 0, sizeof(UseCaseContext_t));
2913 context.type = STATUS_USE_CASE;
2914 context.stepCallback = quitCallback;
2915 context.currentPage = 0;
2916 context.nbPages = 1;
2917
2919 NULL,
2920 message,
2921 NULL,
2922 statusButtonCallback,
2923 false,
2924 NO_FORCED_TYPE,
2925 false);
2926}
2927
2935 nbgl_callback_t quitCallback)
2936{
2937 const char *msg;
2938 bool isSuccess;
2939 switch (reviewStatusType) {
2941 msg = "Operation signed";
2942 isSuccess = true;
2943 break;
2945 msg = "Operation rejected";
2946 isSuccess = false;
2947 break;
2949 msg = "Transaction signed";
2950 isSuccess = true;
2951 break;
2953 msg = "Transaction rejected";
2954 isSuccess = false;
2955 break;
2957 msg = "Message signed";
2958 isSuccess = true;
2959 break;
2961 msg = "Message rejected";
2962 isSuccess = false;
2963 break;
2965 msg = "Address verified";
2966 isSuccess = true;
2967 break;
2969 msg = "Address verification cancelled";
2970 isSuccess = false;
2971 break;
2972 default:
2973 return;
2974 }
2975 nbgl_useCaseStatus(msg, isSuccess, quitCallback);
2976}
2977
2991 const nbgl_icon_details_t *icon,
2992 const char *reviewTitle,
2993 const char *reviewSubTitle,
2994 nbgl_choiceCallback_t choiceCallback)
2995{
2996 // memorize streaming operation type for future API calls
2997 streamingOpType = operationType;
2998
2999 memset(&context, 0, sizeof(UseCaseContext_t));
3000 context.type = STREAMING_START_REVIEW_USE_CASE;
3001 context.operationType = operationType;
3002 context.review.reviewTitle = reviewTitle;
3003 context.review.reviewSubTitle = reviewSubTitle;
3004 context.review.icon = icon;
3005 context.review.onChoice = choiceCallback;
3006 context.currentPage = 0;
3007 context.nbPages = reviewSubTitle ? 3 : 2; // Start page(s) + trick for review continue
3008
3009 displayStreamingReviewPage(FORWARD_DIRECTION);
3010}
3011
3026 const nbgl_icon_details_t *icon,
3027 const char *reviewTitle,
3028 const char *reviewSubTitle,
3029 nbgl_choiceCallback_t choiceCallback)
3030{
3032 operationType, icon, reviewTitle, reviewSubTitle, &blindSigningWarning, choiceCallback);
3033}
3034
3051 const nbgl_icon_details_t *icon,
3052 const char *reviewTitle,
3053 const char *reviewSubTitle,
3054 const nbgl_warning_t *warning,
3055 nbgl_choiceCallback_t choiceCallback)
3056{
3057 memset(&context, 0, sizeof(UseCaseContext_t));
3058 context.type = STREAMING_START_REVIEW_USE_CASE;
3059 context.operationType = operationType;
3060 context.review.reviewTitle = reviewTitle;
3061 context.review.reviewSubTitle = reviewSubTitle;
3062 context.review.icon = icon;
3063 context.review.onChoice = choiceCallback;
3064 context.currentPage = 0;
3065 context.nbPages = reviewSubTitle ? 3 : 2; // Start page(s) + trick for review continue
3066
3067 // memorize streaming operation type for future API calls
3068 streamingOpType = operationType;
3069
3070 // if no warning at all, it's a simple review
3071 if ((warning == NULL)
3072 || ((warning->predefinedSet == 0) && (warning->info == NULL)
3073 && (warning->reviewDetails == NULL) && (warning->prelude == NULL))) {
3074 displayStreamingReviewPage(FORWARD_DIRECTION);
3075 return;
3076 }
3077 if (warning->predefinedSet == (1 << W3C_NO_THREAT_WARN)) {
3078 operationType |= NO_THREAT_OPERATION;
3079 }
3080 else if (warning->predefinedSet != 0) {
3081 operationType |= RISKY_OPERATION;
3082 }
3083 memset(&reviewWithWarnCtx, 0, sizeof(reviewWithWarnCtx));
3084
3085 reviewWithWarnCtx.type = context.type;
3086 reviewWithWarnCtx.operationType = operationType;
3087 reviewWithWarnCtx.icon = icon;
3088 reviewWithWarnCtx.reviewTitle = reviewTitle;
3089 reviewWithWarnCtx.reviewSubTitle = reviewSubTitle;
3090 reviewWithWarnCtx.choiceCallback = choiceCallback;
3091 reviewWithWarnCtx.warning = warning;
3092
3093 // if the warning contains a prelude, display it first
3094 if (reviewWithWarnCtx.warning->prelude) {
3095 displayPrelude();
3096 }
3097 // display the initial warning only of a risk/threat or blind signing or prelude
3098 else if (HAS_INITIAL_WARNING(warning)) {
3099 displayInitialWarning();
3100 }
3101 else {
3102 displayStreamingReviewPage(FORWARD_DIRECTION);
3103 }
3104}
3105
3120 nbgl_choiceCallback_t choiceCallback,
3121 nbgl_callback_t skipCallback)
3122{
3123 uint8_t curNbDataSets = context.review.nbDataSets;
3124
3125 memset(&context, 0, sizeof(UseCaseContext_t));
3126 context.type = STREAMING_CONTINUE_REVIEW_USE_CASE;
3127 context.operationType = streamingOpType;
3128 context.review.tagValueList = tagValueList;
3129 context.review.onChoice = choiceCallback;
3130 context.currentPage = 0;
3131 context.nbPages = tagValueList->nbPairs + 1; // data + trick for review continue
3132 context.review.skipCallback = skipCallback;
3133 context.review.nbDataSets = curNbDataSets + 1;
3134
3135 displayStreamingReviewPage(FORWARD_DIRECTION);
3136}
3137
3149 nbgl_choiceCallback_t choiceCallback)
3150{
3151 nbgl_useCaseReviewStreamingContinueExt(tagValueList, choiceCallback, NULL);
3152}
3153
3154void nbgl_useCaseReviewStreamingFinish(const char *finishTitle,
3155 nbgl_choiceCallback_t choiceCallback)
3156{
3157 memset(&context, 0, sizeof(UseCaseContext_t));
3158 context.type = STREAMING_FINISH_REVIEW_USE_CASE;
3159 context.operationType = streamingOpType;
3160 context.review.onChoice = choiceCallback;
3161 context.review.finishTitle = finishTitle;
3162 context.currentPage = 0;
3163 context.nbPages = 2; // 2 pages at the end for accept/reject
3164
3165 displayStreamingReviewPage(FORWARD_DIRECTION);
3166}
3167
3173void nbgl_useCaseSpinner(const char *text)
3174{
3175 memset(&context, 0, sizeof(UseCaseContext_t));
3176 context.type = SPINNER_USE_CASE;
3177 context.currentPage = 0;
3178 context.nbPages = 1;
3179
3180 displaySpinner(text);
3181}
3182
3196 const char *message,
3197 const char *subMessage,
3198 const char *confirmText,
3199 const char *cancelText,
3200 nbgl_choiceCallback_t callback)
3201{
3203 icon, message, subMessage, confirmText, cancelText, NULL, callback);
3204};
3205
3221 const char *message,
3222 const char *subMessage,
3223 const char *confirmText,
3224 const char *cancelText,
3225 nbgl_genericDetails_t *details,
3226 nbgl_choiceCallback_t callback)
3227{
3228 memset(&context, 0, sizeof(UseCaseContext_t));
3229 context.type = CHOICE_USE_CASE;
3230 context.choice.icon = icon;
3231 context.choice.message = message;
3232 context.choice.subMessage = subMessage;
3233 context.choice.confirmText = confirmText;
3234 context.choice.cancelText = cancelText;
3235 context.choice.onChoice = callback;
3236 context.choice.details = details;
3237 context.currentPage = 0;
3238 context.nbPages = 2; // 2 pages for confirm/cancel
3239 if (message != NULL) {
3240 context.nbPages++;
3241 // if both icon and subMessage are non NULL, add a page
3242 if ((icon != NULL) && (subMessage != NULL)) {
3243 context.nbPages++;
3244 }
3245 }
3246 if (details != NULL) {
3247 // only the first level of details and BAR_LIST type are supported
3248 if (details->type == BAR_LIST_WARNING) {
3249 context.nbPages += details->barList.nbBars;
3250 }
3251 }
3252
3253 displayChoicePage(FORWARD_DIRECTION);
3254};
3255
3256// On Nano, the advanced variant falls back to the basic one:
3257// - title → message (first text line)
3258// - message → subMessage (second text line; subMessage/gray address is not shown)
3259// - headerIcon is ignored (no top-right button on Nano)
3261 const nbgl_icon_details_t *headerIcon,
3262 const char *title,
3263 const char *message,
3264 const char *subMessage,
3265 const char *confirmText,
3266 const char *cancelText,
3267 nbgl_genericDetails_t *details,
3268 nbgl_choiceCallback_t callback)
3269{
3270 UNUSED(headerIcon);
3271 UNUSED(subMessage);
3273 centerIcon, title, message, confirmText, cancelText, details, callback);
3274}
3275
3289void nbgl_useCaseConfirm(const char *message,
3290 const char *subMessage,
3291 const char *confirmText,
3292 const char *cancelText,
3293 nbgl_callback_t callback)
3294{
3295 memset(&context, 0, sizeof(UseCaseContext_t));
3296 context.type = CONFIRM_USE_CASE;
3297 context.confirm.message = message;
3298 context.confirm.subMessage = subMessage;
3299 context.confirm.confirmText = confirmText;
3300 context.confirm.cancelText = cancelText;
3301 context.confirm.onConfirm = callback;
3302 context.currentPage = 0;
3303 context.nbPages = 1 + 2; // 2 pages at the end for confirm/cancel
3304
3305 displayConfirm(FORWARD_DIRECTION);
3306}
3307
3318 const char *message,
3319 const char *actionText,
3320 nbgl_callback_t callback)
3321{
3322 nbgl_layoutCenteredInfo_t centeredInfo = {0};
3323
3324 UNUSED(actionText);
3325
3326 // memorize callback in context
3327 memset(&context, 0, sizeof(UseCaseContext_t));
3328 context.type = ACTION_USE_CASE;
3329 context.action.actionCallback = callback;
3330
3331 centeredInfo.icon = icon;
3332 centeredInfo.text1 = message;
3333 centeredInfo.style = BOLD_TEXT1_INFO;
3334 nbgl_stepDrawCenteredInfo(0, useCaseActionCallback, NULL, &centeredInfo, false);
3335}
3336
3337#ifdef NBGL_KEYPAD
3356void nbgl_useCaseKeypad(const char *title,
3357 uint8_t minDigits,
3358 uint8_t maxDigits,
3359 bool shuffled,
3360 bool hidden,
3361 nbgl_pinValidCallback_t validatePinCallback,
3362 nbgl_callback_t backCallback)
3363{
3364 nbgl_layoutDescription_t layoutDescription = {0};
3365 int status = -1;
3366
3367 // reset the keypad context
3368 memset(&context, 0, sizeof(KeypadContext_t));
3369 context.type = KEYPAD_USE_CASE;
3370 context.currentPage = 0;
3371 context.nbPages = 1;
3372 context.keypad.validatePin = validatePinCallback;
3373 context.keypad.backCallback = backCallback;
3374 context.keypad.pinMinDigits = minDigits;
3375 context.keypad.pinMaxDigits = maxDigits;
3376 context.keypad.hidden = hidden;
3377 context.keypad.layoutCtx = nbgl_layoutGet(&layoutDescription);
3378
3379 // add keypad
3380 status = nbgl_layoutAddKeypad(context.keypad.layoutCtx, keypadCallback, title, shuffled);
3381 if (status < 0) {
3382 return;
3383 }
3384 context.keypad.keypadIndex = status;
3385 // add digits
3386 status = nbgl_layoutAddKeypadContent(context.keypad.layoutCtx, hidden, maxDigits, "");
3387 if (status < 0) {
3388 return;
3389 }
3390
3391 nbgl_layoutDraw(context.keypad.layoutCtx);
3392 if (context.keypad.backCallback != NULL) {
3393 // force backspace to be visible at first digit, to be used as quit
3394 nbgl_layoutUpdateKeypad(context.keypad.layoutCtx, context.keypad.keypadIndex, false, true);
3395 }
3396 nbgl_refresh();
3397}
3398#endif // NBGL_KEYPAD
3399
3400#ifdef NBGL_KEYBOARD
3424void nbgl_useCaseKeyboard(const nbgl_keyboardParams_t *params, nbgl_callback_t backCallback)
3425{
3426 // clang-format off
3427 nbgl_layoutDescription_t layoutDescription = {0};
3428 nbgl_layoutCenteredInfo_t centeredInfo = {.text1 = params->title,
3429 .onTop = true};
3431 .indication = LEFT_ARROW | RIGHT_ARROW};
3432 nbgl_layoutKbd_t kbdInfo = {.callback = &keyboardCallback,
3433 .mode = params->mode,
3434 .lettersOnly = params->lettersOnly};
3435 int status = -1;
3436 // clang-format on
3437
3438 // reset the keypad context
3439 memset(&context, 0, sizeof(UseCaseContext_t));
3440 context.type = KEYBOARD_USE_CASE;
3441 context.currentPage = 0;
3442 context.nbPages = 1;
3443 context.keyboard.entryBuffer = PIC(params->entryBuffer);
3444 context.keyboard.entryMaxLen = params->entryMaxLen;
3445 context.keyboard.entryBuffer[0] = '\0';
3446
3447 context.keyboard.content.type = params->type;
3448
3449 // memorize context
3450 context.keyboard.backCallback = PIC(backCallback);
3451
3452 switch (params->type) {
3454 context.keyboard.actionCallback = PIC(params->confirmationParams.onButtonCallback);
3455 break;
3457 // No need for 'Validate' or 'Case switch' buttons
3458 kbdInfo.keyMask = (1 << VALIDATE_INDEX) | (1 << SHIFT_KEY_INDEX);
3459 context.keyboard.getSuggestButtons
3461 context.keyboard.onButtonCallback = PIC(params->suggestionParams.onButtonCallback);
3462 context.keyboard.content.suggestionButtons = (nbgl_layoutSuggestionButtons_t){
3463 .buttons = PIC(params->suggestionParams.buttons),
3464 .firstButtonToken = params->suggestionParams.firstButtonToken,
3465 };
3466 break;
3467 default:
3468 return;
3469 }
3470
3471 // get a layout
3472 context.keyboard.layoutCtx = nbgl_layoutGet(&layoutDescription);
3473
3474 // add description
3475 nbgl_layoutAddCenteredInfo(context.keyboard.layoutCtx, &centeredInfo);
3476
3477 // Add keyboard
3478 status = nbgl_layoutAddKeyboard(context.keyboard.layoutCtx, &kbdInfo);
3479 if (status < 0) {
3480 return;
3481 }
3482 context.keyboard.keyboardIndex = status;
3483
3484 // add empty entered text
3485 status = nbgl_layoutAddEnteredText(context.keyboard.layoutCtx, "", true);
3486 if (status < 0) {
3487 return;
3488 }
3489 context.keyboard.textIndex = status;
3490
3491 nbgl_layoutAddNavigation(context.keyboard.layoutCtx, &navInfo);
3492
3493 nbgl_layoutDraw(context.keyboard.layoutCtx);
3494 nbgl_refresh();
3495}
3496#endif // NBGL_KEYBOARD
3497
3498#endif // HAVE_SE_TOUCH
3499#endif // NBGL_USE_CASE
nbgl_contentCenteredInfoStyle_t
possible styles for Centered Info Area
@ CHOICES_LIST
list of choices through radio buttons
@ CENTERED_INFO
a centered info
@ SWITCHES_LIST
list of switches with descriptions
@ INFOS_LIST
list of infos with titles
@ TAG_VALUE_CONFIRM
tag/value pairs and a black button/footer to confirm/cancel.
@ TAG_VALUE_LIST
list of tag/value pairs
@ BARS_LIST
list of touchable bars (with > on the right to go to sub-pages)
@ INFO_BUTTON
a centered info and a simple black button
@ INFO_LIST_ALIAS
alias is list of infos
@ TAG_VALUE_LIST_ALIAS
@ ENS_ALIAS
alias comes from ENS
@ ADDRESS_BOOK_ALIAS
alias comes from Address Book
void(* nbgl_contentActionCallback_t)(int token, uint8_t index, int page)
prototype of function to be called when an action on a content object occurs
debug traces management
#define LOG_WARN(__logger,...)
Definition nbgl_debug.h:87
#define LOG_DEBUG(__logger,...)
Definition nbgl_debug.h:86
@ USE_CASE_LOGGER
Definition nbgl_debug.h:35
void(* nbgl_stepCallback_t)(void)
prototype of function to be called when a step is using a callback on "double-key" action
Definition nbgl_flow.h:38
void(* nbgl_layoutTouchCallback_t)(int token, uint8_t index)
prototype of function to be called when an object is touched
DEPRECATED int nbgl_layoutAddEnteredText(nbgl_layout_t *layout, bool numbered, uint8_t number, const char *text, bool grayedOut, int offsetY, int token)
Adds a "text entry" area under the previously entered object. This area can be preceded (beginning of...
int nbgl_layoutUpdateKeyboard(nbgl_layout_t *layout, uint8_t index, uint32_t keyMask, bool updateCasing, keyboardCase_t casing)
Updates an existing keyboard on bottom of the screen, with the given configuration.
int nbgl_layoutAddKeyboard(nbgl_layout_t *layout, const nbgl_layoutKbd_t *kbdInfo)
Creates a keyboard on bottom of the screen, with the given configuration.
@ HORIZONTAL_NAV
'<' and '>' are displayed, to navigate between pages and steps
@ LEFT_ARROW
left arrow is used
@ RIGHT_ARROW
right arrow is used
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_layoutAddCenteredInfo(nbgl_layout_t *layout, const nbgl_layoutCenteredInfo_t *info)
Creates an area on the center of the main panel, with a possible icon/image, a possible text in black...
int nbgl_layoutDraw(nbgl_layout_t *layout)
Applies given layout. The screen will be redrawn.
void * nbgl_layout_t
type shared externally
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...
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
@ KEYBOARD_WITH_BUTTON
text entry area + confirmation button
@ KEYBOARD_WITH_SUGGESTIONS
text entry area + suggestion buttons
int nbgl_layoutRelease(nbgl_layout_t *layout)
Release the layout obtained with nbgl_layoutGet()
DEPRECATED int nbgl_layoutUpdateEnteredText(nbgl_layout_t *layout, uint8_t index, bool numbered, uint8_t number, const char *text, bool grayedOut)
Updates an existing "text entry" area, created with nbgl_layoutAddEnteredText()
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 SHIFT_KEY_INDEX
Definition nbgl_obj.h:41
void nbgl_refresh(void)
nbgl_buttonEvent_t
Definition nbgl_obj.h:315
@ BUTTON_BOTH_PRESSED
Sent when both buttons are released.
Definition nbgl_obj.h:322
@ BUTTON_LEFT_PRESSED
Sent when Left button is released.
Definition nbgl_obj.h:316
@ BUTTON_RIGHT_PRESSED
Send when Right button is released.
Definition nbgl_obj.h:317
#define BACKSPACE_KEY
Definition nbgl_obj.h:26
#define VALIDATE_KEY
Definition nbgl_obj.h:27
struct PACKED__ nbgl_screenTickerConfiguration_s nbgl_screenTickerConfiguration_t
struct to configure a screen layer
void nbgl_screenRedraw(void)
void(* nbgl_stepButtonCallback_t)(nbgl_step_t stepCtx, nbgl_buttonEvent_t event)
prototype of function to be called when buttons are touched on a screen
Definition nbgl_step.h:57
#define GET_POS_OF_STEP(_step, _nb_steps)
Definition nbgl_step.h:30
nbgl_step_t nbgl_stepDrawSwitch(nbgl_stepPosition_t pos, nbgl_stepButtonCallback_t onActionCallback, nbgl_screenTickerConfiguration_t *ticker, nbgl_layoutSwitch_t *switchInfo, bool modal)
void * nbgl_step_t
type shared externally
Definition nbgl_step.h:44
uint8_t nbgl_stepPosition_t
this type is a bitfield containing:
Definition nbgl_step.h:88
nbgl_step_t nbgl_stepDrawMenuList(nbgl_stepMenuListCallback_t onActionCallback, nbgl_screenTickerConfiguration_t *ticker, nbgl_layoutMenuList_t *list, bool modal)
#define ACTION_ON_ANY_BUTTON
When action callback applies only on both button press.
Definition nbgl_step.h:75
@ NEITHER_FIRST_NOR_LAST_STEP
neither first nor last in a multiple steps flow
Definition nbgl_step.h:67
@ SINGLE_STEP
single step flow
Definition nbgl_step.h:64
@ LAST_STEP
last in a multiple steps flow
Definition nbgl_step.h:66
@ FIRST_STEP
first in a multiple steps flow
Definition nbgl_step.h:65
int nbgl_stepRelease(nbgl_step_t step)
#define FORWARD_DIRECTION
When the flow is navigated from last to first step.
Definition nbgl_step.h:71
nbgl_step_t nbgl_stepDrawText(nbgl_stepPosition_t pos, nbgl_stepButtonCallback_t onActionCallback, nbgl_screenTickerConfiguration_t *ticker, const char *text, const char *subText, nbgl_contentCenteredInfoStyle_t style, bool modal)
nbgl_step_t nbgl_stepDrawCenteredInfo(nbgl_stepPosition_t pos, nbgl_stepButtonCallback_t onActionCallback, nbgl_screenTickerConfiguration_t *ticker, nbgl_layoutCenteredInfo_t *info, bool modal)
#define BACKWARD_DIRECTION
When action callback applies on any button press.
Definition nbgl_step.h:73
nbgl_state_t
to represent a boolean state.
Definition nbgl_types.h:199
@ ON_STATE
Definition nbgl_types.h:201
@ OFF_STATE
Definition nbgl_types.h:200
struct PACKED__ nbgl_icon_details_s nbgl_icon_details_t
Represents all information about an icon.
API of the Advanced BOLOS Graphical Library, for typical application use-cases.
@ NO_TYPE_WARNING
Invalid type (to use for bars leading to nothing)
@ CENTERED_INFO_WARNING
Centered info.
@ BAR_LIST_WARNING
list of touchable bars, to display sub-pages
uint8_t nbgl_useCaseGetNbTagValuesInPageExt(uint8_t nbPairs, const nbgl_contentTagValueList_t *tagValueList, uint8_t startIndex, bool isSkippable, bool *requireSpecificDisplay)
void(* nbgl_callback_t)(void)
prototype of generic callback function
#define SKIPPABLE_OPERATION
This is to use in nbgl_operationType_t when the operation is skippable. This is used.
void nbgl_useCaseGenericSettings(const char *appName, uint8_t initPage, const nbgl_genericContents_t *settingContents, const nbgl_contentInfoList_t *infosList, nbgl_callback_t quitCallback)
uint32_t nbgl_operationType_t
This mask is used to describe the type of operation to review with additional options It is a mask of...
void nbgl_useCaseReview(nbgl_operationType_t operationType, const nbgl_contentTagValueList_t *tagValueList, const nbgl_icon_details_t *icon, const char *reviewTitle, const char *reviewSubTitle, const char *finishTitle, nbgl_choiceCallback_t choiceCallback)
void nbgl_useCaseAction(const nbgl_icon_details_t *icon, const char *message, const char *actionText, nbgl_callback_t callback)
uint8_t nbgl_useCaseGetNbTagValuesInPage(uint8_t nbPairs, const nbgl_contentTagValueList_t *tagValueList, uint8_t startIndex, bool *requireSpecificDisplay)
uint8_t nbgl_useCaseGetNbPagesForTagValueList(const nbgl_contentTagValueList_t *tagValueList)
void(* nbgl_pinValidCallback_t)(const uint8_t *pin, uint8_t pinLen)
prototype of pin validation callback function
void nbgl_useCaseReviewStreamingStart(nbgl_operationType_t operationType, const nbgl_icon_details_t *icon, const char *reviewTitle, const char *reviewSubTitle, nbgl_choiceCallback_t choiceCallback)
void nbgl_useCaseHomeAndSettings(const char *appName, const nbgl_icon_details_t *appIcon, const char *tagline, const uint8_t initSettingPage, const nbgl_genericContents_t *settingContents, const nbgl_contentInfoList_t *infosList, const nbgl_homeAction_t *action, nbgl_callback_t quitCallback)
#define RISKY_OPERATION
This is to use in nbgl_operationType_t when the operation is concerned by an internal warning....
uint8_t nbgl_useCaseGetNbInfosInPage(uint8_t nbInfos, const nbgl_contentInfoList_t *infosList, uint8_t startIndex, bool withNav)
void(* nbgl_choiceCallback_t)(bool confirm)
prototype of choice callback function
uint8_t nbgl_useCaseGetNbBarsInPage(uint8_t nbBars, const nbgl_contentBarsList_t *barsList, uint8_t startIndex, bool withNav)
void nbgl_useCaseNavigableContent(const char *title, uint8_t initPage, uint8_t nbPages, nbgl_callback_t quitCallback, nbgl_navCallback_t navCallback, nbgl_layoutTouchCallback_t controlsCallback)
void nbgl_useCaseReviewStreamingFinish(const char *finishTitle, nbgl_choiceCallback_t choiceCallback)
#define NO_THREAT_OPERATION
This is to use in nbgl_operationType_t when the operation is concerned by an internal information....
void nbgl_useCaseChoiceWithDetails(const nbgl_icon_details_t *icon, const char *message, const char *subMessage, const char *confirmText, const char *cancelText, nbgl_warningDetails_t *details, nbgl_choiceCallback_t callback)
void nbgl_useCaseAdvancedChoiceWithDetails(const nbgl_icon_details_t *centerIcon, const nbgl_icon_details_t *headerIcon, const char *title, const char *message, const char *subMessage, const char *confirmText, const char *cancelText, nbgl_warningDetails_t *details, nbgl_choiceCallback_t callback)
void nbgl_useCaseSpinner(const char *text)
void nbgl_useCaseConfirm(const char *message, const char *subMessage, const char *confirmText, const char *rejectText, nbgl_callback_t callback)
void nbgl_useCaseStatus(const char *message, bool isSuccess, nbgl_callback_t quitCallback)
@ BLIND_SIGNING_WARN
Blind signing.
@ W3C_NO_THREAT_WARN
Web3 Checks: No Threat detected.
void nbgl_useCaseGenericConfiguration(const char *title, uint8_t initPage, const nbgl_genericContents_t *contents, nbgl_callback_t quitCallback)
#define INIT_HOME_PAGE
Value to pass to nbgl_useCaseHomeAndSettings() initSettingPage parameter to initialize the use case o...
void nbgl_useCaseKeypad(const char *title, uint8_t minDigits, uint8_t maxDigits, bool shuffled, bool hidden, nbgl_pinValidCallback_t validatePinCallback, nbgl_callback_t backCallback)
void nbgl_useCaseReviewStreamingContinueExt(const nbgl_contentTagValueList_t *tagValueList, nbgl_choiceCallback_t choiceCallback, nbgl_callback_t skipCallback)
void nbgl_useCaseReviewBlindSigning(nbgl_operationType_t operationType, const nbgl_contentTagValueList_t *tagValueList, const nbgl_icon_details_t *icon, const char *reviewTitle, const char *reviewSubTitle, const char *finishTitle, const nbgl_tipBox_t *tipBox, nbgl_choiceCallback_t choiceCallback)
void nbgl_useCaseChoice(const nbgl_icon_details_t *icon, const char *message, const char *subMessage, const char *confirmText, const char *rejectString, nbgl_choiceCallback_t callback)
void nbgl_useCaseGenericReview(const nbgl_genericContents_t *contents, const char *rejectText, nbgl_callback_t rejectCallback)
#define STATUS_SCREEN_DURATION
void nbgl_useCaseKeyboard(const nbgl_keyboardParams_t *params, nbgl_callback_t backCallback)
void nbgl_useCaseReviewStreamingBlindSigningStart(nbgl_operationType_t operationType, const nbgl_icon_details_t *icon, const char *reviewTitle, const char *reviewSubTitle, nbgl_choiceCallback_t choiceCallback)
void nbgl_useCaseReviewStreamingContinue(const nbgl_contentTagValueList_t *tagValueList, nbgl_choiceCallback_t choiceCallback)
void nbgl_useCaseReviewLight(nbgl_operationType_t operationType, const nbgl_contentTagValueList_t *tagValueList, const nbgl_icon_details_t *icon, const char *reviewTitle, const char *reviewSubTitle, const char *finishTitle, nbgl_choiceCallback_t choiceCallback)
uint8_t nbgl_useCaseGetNbChoicesInPage(uint8_t nbChoices, const nbgl_contentRadioChoice_t *choicesList, uint8_t startIndex, bool withNav)
void(* nbgl_keyboardButtonsCallback_t)(nbgl_layoutKeyboardContent_t *content, uint32_t *mask)
prototype of keyboard buttons callback function
#define REAL_TYPE_MASK
Mask to apply on nbgl_operationType_t to extract the base nbgl_opType_t. Bits 0-3 are reserved for th...
void nbgl_useCaseReviewStatus(nbgl_reviewStatusType_t reviewStatusType, nbgl_callback_t quitCallback)
#define ADDRESS_BOOK_OPERATION
This is to use in nbgl_operationType_t when the operation is related to "Address Book" to adapt wordi...
nbgl_reviewStatusType_t
The different types of review status.
@ STATUS_TYPE_TRANSACTION_REJECTED
@ STATUS_TYPE_ADDRESS_REJECTED
@ STATUS_TYPE_TRANSACTION_SIGNED
@ STATUS_TYPE_OPERATION_REJECTED
@ STATUS_TYPE_OPERATION_SIGNED
@ STATUS_TYPE_ADDRESS_VERIFIED
@ STATUS_TYPE_MESSAGE_SIGNED
@ STATUS_TYPE_MESSAGE_REJECTED
bool(* nbgl_navCallback_t)(uint8_t page, nbgl_pageContent_t *content)
prototype of navigation callback function
#define HAS_INITIAL_WARNING(_warning)
void nbgl_useCaseAddressReview(const char *address, const nbgl_contentTagValueList_t *additionalTagValueList, const nbgl_icon_details_t *icon, const char *reviewTitle, const char *reviewSubTitle, nbgl_choiceCallback_t choiceCallback)
@ TYPE_MESSAGE
@ TYPE_TRANSACTION
For operations transferring a coin or taken from an account to another.
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
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)
This structure contains info to build a centered (vertically and horizontally) area,...
const char * text2
second text (can be null)
const char * text1
first text (can be null)
nbgl_contentCenteredInfoStyle_t style
style to apply to this info
const nbgl_icon_details_t * icon
a buffer containing the 1BPP icon
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)
uint8_t nbInfos
number of elements in infoTypes and infoContents array
This structure contains a list of names to build a list of radio buttons (on the right part of screen...
uint8_t token
the token that will be used as argument of the callback
uint8_t initChoice
index of the current choice
const char *const * names
array of strings giving the choices (nbChoices)
uint8_t nbChoices
number of choices
This structure contains info to build a switch (on the right) with a description (on the left),...
const char * text
main text for the switch
uint8_t token
the token that will be used as argument of the callback (unused on Nano)
nbgl_state_t initState
initial state of the switch
const char * subText
description under main text (NULL terminated, single line, may be null)
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
This structure contains a list of [tag,value] pairs.
const nbgl_contentTagValue_t * pairs
array of [tag,value] pairs (nbPairs items). If NULL, callback is used instead
nbgl_contentTagValueCallback_t callback
function to call to retrieve a given pair
This structure contains a [tag,value] pair and possible extensions.
const nbgl_contentValueExt_t * extension
if not NULL, gives additional info on value field
const nbgl_icon_details_t * valueIcon
const char * value
string giving the value name
const char * item
string giving the tag name
uint8_t centeredInfo
if set to 1, the tag will be displayed as a centered info
This structure contains additions to a tag/value pair, to be able to build a screen to display these ...
This structure contains data to build a content.
nbgl_content_u content
nbgl_contentActionCallback_t contentActionCallback
callback to be called when an action on an object occurs
nbgl_contentType_t type
type of page content in the content union
const struct nbgl_genericDetails_s * details
array of nbBars structures giving what to display when each bar is touched.
uint8_t nbBars
number of touchable bars
uint8_t nbContents
number of contents
const nbgl_content_t * contentsList
array of nbgl_content_t (nbContents items).
nbgl_contentCallback_t contentGetterCallback
function to call to retrieve a given content
The necessary parameters to build the page(s) displayed when the top-right button is touched in intro...
nbgl_genericBarList_t barList
touchable bars list, if type == BAR_LIST_WARNING
nbgl_genericDetailsType_t type
type of content in the page, determining what to use in the following union
nbgl_contentCenter_t centeredInfo
centered info, if type == CENTERED_INFO_WARNING
Structure describing the action button in Home Screen.
nbgl_callback_t onButtonCallback
callback to call when the button is pressed
nbgl_keyboardButtonsCallback_t updateButtonsCallback
callback to call when a key is pressed to update suggestions
nbgl_layoutTouchCallback_t onButtonCallback
callback to call when one of the buttons is pressed
const char ** buttons
array of strings for buttons (last ones can be NULL)
int firstButtonToken
first token used for buttons, provided in onButtonCallback
Structure containing all parameters for keyboard use case.
keyboardMode_t mode
keyboard mode to start with
nbgl_kbdSuggestParams_t suggestionParams
nbgl_kbdButtonParams_t confirmationParams
used if type is KEYBOARD_WITH_SUGGESTIONS
bool lettersOnly
if true, only display letter keys and Backspace
uint8_t entryMaxLen
maximum length of text that can be entered
nbgl_layoutKeyboardContentType_t type
type of content
const char * title
centered title explaining the screen
char * entryBuffer
already entered text
Structure containing all information when creating a layout. This structure must be passed as argumen...
This structure contains info to build a keyboard with nbgl_layoutAddKeyboard()
keyboardCallback_t callback
function called when an active key is pressed
This structure contains info to build a keyboard content (controls that are linked to keyboard)
This structure contains a list of names to build a menu list on Nanos, with for each item a descripti...
nbgl_menuListCallback_t callback
function to call to retrieve a menu list item text
uint8_t nbChoices
total number of choices in the menu list
uint8_t selectedChoice
index of the selected choice (centered, in bold)
This structure contains info to build a navigation bar at the bottom of the screen.
nbgl_layoutNavDirection_t direction
vertical or horizontal navigation
This structure contains info to build suggestion buttons.
uint8_t nbUsedButtons
the number of actually used (displayed) buttons
This structure contains data to build a page in multi-pages mode (nbgl_pageDrawGenericContent)
Definition nbgl_flow.h:58
nbgl_contentRadioChoice_t choicesList
CHOICES_LIST type
Definition nbgl_flow.h:67
nbgl_contentSwitchesList_t switchesList
SWITCHES_LIST type
Definition nbgl_flow.h:65
nbgl_contentBarsList_t barsList
BARS_LIST type
Definition nbgl_flow.h:68
nbgl_contentInfoButton_t infoButton
INFO_BUTTON type
Definition nbgl_flow.h:62
nbgl_contentInfoList_t infosList
INFOS_LIST type
Definition nbgl_flow.h:66
nbgl_contentTagValueList_t tagValueList
TAG_VALUE_LIST type
Definition nbgl_flow.h:63
nbgl_contentType_t type
type of page content in the following union
Definition nbgl_flow.h:59
nbgl_contentCenteredInfo_t centeredInfo
CENTERED_INFO type
Definition nbgl_flow.h:61
nbgl_contentTagValueConfirm_t tagValueConfirm
TAG_VALUE_CONFIRM type
Definition nbgl_flow.h:64
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...
The necessary parameters to build a warning page preceding a review. One can either use predefinedSet...
const nbgl_preludeDetails_t * prelude
if not null, means that the review can start by a prelude
const nbgl_warningDetails_t * reviewDetails
uint32_t predefinedSet
const nbgl_contentCenter_t * info
parameters to build the intro warning page, if not using pre-defined
nbgl_contentInfoList_t infosList
INFOS_LIST 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_contentSwitchesList_t switchesList
SWITCHES_LIST type
nbgl_contentInfoButton_t infoButton
INFO_BUTTON type
nbgl_contentRadioChoice_t choicesList
CHOICES_LIST type