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 {
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;
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) {
386 "getChoiceName(): p_content is NULL for page %d\n",
387 context.currentPage);
388 return NULL;
389 }
390 switch (p_content->type) {
391 case CHOICES_LIST:
392 contentChoices = (nbgl_contentRadioChoice_t *) PIC(&p_content->content.choicesList);
393 names = (char **) PIC(contentChoices->names);
394 nbValues = contentChoices->nbChoices;
395 break;
396 case BARS_LIST:
397 contentBars = ((nbgl_contentBarsList_t *) PIC(&p_content->content.barsList));
398 names = (char **) PIC(contentBars->barTexts);
399 nbValues = contentBars->nbBars;
400 break;
401 default:
402 // Not supported as vertical MenuList
403 return NULL;
404 }
405 if (choiceIndex >= nbValues) {
406 // Last item is always "Back" button
407 return "Back";
408 }
409 return (const char *) PIC(names[choiceIndex]);
410}
411
412static void onChoiceSelected(uint8_t choiceIndex)
413{
414 uint8_t elemIdx;
415 uint8_t token = 255;
416 const nbgl_content_t *p_content = NULL;
417 nbgl_content_t content = {0};
418 nbgl_contentRadioChoice_t *contentChoices = NULL;
419 nbgl_contentBarsList_t *contentBars = NULL;
420
421 p_content = getContentElemAtIdx(context.currentPage, &elemIdx, &content);
422 if (p_content == NULL) {
424 "onChoiceSelected(): p_content is NULL for page %d\n",
425 context.currentPage);
426 return;
427 }
428 switch (p_content->type) {
429 case CHOICES_LIST:
430 contentChoices = (nbgl_contentRadioChoice_t *) PIC(&p_content->content.choicesList);
431 if (choiceIndex < contentChoices->nbChoices) {
432 token = contentChoices->token;
433 }
434 break;
435 case BARS_LIST:
436 contentBars = ((nbgl_contentBarsList_t *) PIC(&p_content->content.barsList));
437 if (choiceIndex < contentBars->nbBars) {
438 token = ((const uint8_t *) PIC(contentBars->tokens))[choiceIndex];
439 }
440 break;
441 default:
442 // Not supported as vertical MenuList
443 break;
444 }
445 if (token != 255) {
446 // Prefer the per-content action callback (used by settings/content
447 // flows). Forward both the choice index and the absolute page so the
448 // app can react identically to the horizontal flow.
449 if (p_content->contentActionCallback != NULL) {
451 actionCallback(token, choiceIndex, context.currentPage);
452 return;
453 }
454 if (context.content.controlsCallback != NULL) {
455 context.content.controlsCallback(token, choiceIndex);
456 return;
457 }
458 }
459 // Back entry (the extra item appended by drawStep at index nbChoices) or
460 // unknown selection: mirror the horizontal-flow Back behaviour in
461 // displaySettingsPage so SETTINGS_USE_CASE / GENERIC_SETTINGS / content
462 // callers all exit consistently.
463 if ((context.type == GENERIC_SETTINGS) && (context.home.quitCallback != NULL)) {
464 context.home.quitCallback();
465 return;
466 }
467 if (context.type == SETTINGS_USE_CASE) {
468 startUseCaseHome();
469 return;
470 }
471 if (context.content.quitCallback != NULL) {
472 context.content.quitCallback();
473 }
474}
475
476static void getPairData(const nbgl_contentTagValueList_t *tagValueList,
477 uint8_t index,
478 const char **item,
479 const char **value,
480 const nbgl_contentValueExt_t **extension,
481 const nbgl_icon_details_t **icon,
482 bool *isCenteredInfo)
483{
484 const nbgl_contentTagValue_t *pair;
485
486 if (tagValueList->pairs != NULL) {
487 pair = PIC(&tagValueList->pairs[index]);
488 }
489 else {
490 pair = PIC(tagValueList->callback(index));
491 }
492 *item = pair->item;
493 *value = pair->value;
494 if (pair->aliasValue) {
495 *extension = pair->extension;
496 }
497 else if (pair->centeredInfo) {
498 *isCenteredInfo = true;
499 *icon = pair->valueIcon;
500 }
501 else {
502 *extension = NULL;
503 }
504}
505
506static void onReviewAccept(void)
507{
508 if (context.review.onChoice) {
509 context.review.onChoice(true);
510 }
511}
512
513static void onReviewReject(void)
514{
515 if (context.review.onChoice) {
516 context.review.onChoice(false);
517 }
518}
519
520static void onChoiceAccept(void)
521{
522 if (context.choice.onChoice) {
523 context.choice.onChoice(true);
524 }
525}
526
527static void onChoiceReject(void)
528{
529 if (context.choice.onChoice) {
530 context.choice.onChoice(false);
531 }
532}
533
534static void onConfirmAccept(void)
535{
536 if (context.confirm.currentStep) {
537 nbgl_stepRelease(context.confirm.currentStep);
538 }
539 if (context.confirm.onConfirm) {
540 context.confirm.onConfirm();
541 }
542}
543
544static void onConfirmReject(void)
545{
546 if (context.confirm.currentStep) {
547 nbgl_stepRelease(context.confirm.currentStep);
549 nbgl_refresh();
550 }
551}
552
553static void onSwitchAction(void)
554{
555 const nbgl_contentSwitch_t *contentSwitch = NULL;
556 const nbgl_content_t *p_content = NULL;
557 nbgl_content_t content = {0};
558 uint8_t elemIdx;
559
560 p_content = getContentElemAtIdx(context.currentPage, &elemIdx, &content);
561 if ((p_content == NULL) || (p_content->type != SWITCHES_LIST)) {
563 "onSwitchAction(): p_content is NULL or not a switches list for page %d\n",
564 context.currentPage);
565 return;
566 }
567 contentSwitch
568 = &((const nbgl_contentSwitch_t *) PIC(p_content->content.switchesList.switches))[elemIdx];
569 switch (context.type) {
570 case SETTINGS_USE_CASE:
571 case HOME_USE_CASE:
572 case GENERIC_SETTINGS:
573 displaySettingsPage(FORWARD_DIRECTION, true);
574 break;
575 case CONTENT_USE_CASE:
576 case GENERIC_REVIEW_USE_CASE:
577 displayContent(FORWARD_DIRECTION, true);
578 break;
579 default:
580 break;
581 }
582 if (p_content->contentActionCallback != NULL) {
584 actionCallback(contentSwitch->token,
585 (contentSwitch->initState == ON_STATE) ? OFF_STATE : ON_STATE,
586 context.currentPage);
587 }
588 else if (context.content.controlsCallback != NULL) {
589 context.content.controlsCallback(contentSwitch->token, 0);
590 }
591}
592
593static void drawStep(nbgl_stepPosition_t pos,
594 const nbgl_icon_details_t *icon,
595 const char *txt,
596 const char *subTxt,
597 nbgl_stepButtonCallback_t onActionCallback,
598 bool modal,
599 ForcedType_t forcedType,
600 bool bottomIcon)
601{
602 uint8_t elemIdx;
603 nbgl_step_t newStep = NULL;
604 const nbgl_content_t *p_content = NULL;
605 nbgl_content_t content = {0};
606 nbgl_contentRadioChoice_t *contentChoices = NULL;
607 nbgl_contentBarsList_t *contentBars = NULL;
608 nbgl_screenTickerConfiguration_t *p_ticker = NULL;
609 nbgl_layoutMenuList_t list = {0};
610 nbgl_screenTickerConfiguration_t ticker = {.tickerCallback = PIC(statusTickerCallback),
611 .tickerIntervale = 0, // not periodic
612 .tickerValue = STATUS_SCREEN_DURATION};
613
614 pos |= GET_POS_OF_STEP(context.currentPage, context.nbPages);
615 // if we are in streaming+skip case, enable going backward even for first tag/value of the set
616 // (except the first set) because the set starts with a "skip" page
617 if ((context.type == STREAMING_CONTINUE_REVIEW_USE_CASE)
618 && (context.review.skipCallback != NULL) && (context.review.nbDataSets > 1)) {
619 pos |= LAST_STEP;
620 }
621 if ((context.type == STATUS_USE_CASE) || (context.type == SPINNER_USE_CASE)) {
622 p_ticker = &ticker;
623 }
624 if ((context.type == CONFIRM_USE_CASE) && (context.confirm.currentStep != NULL)) {
625 nbgl_stepRelease(context.confirm.currentStep);
626 }
627
628 if (txt == NULL) {
629 p_content = getContentElemAtIdx(context.currentPage, &elemIdx, &content);
630 if (p_content) {
631 switch (p_content->type) {
632 case CHOICES_LIST:
633 contentChoices
634 = ((nbgl_contentRadioChoice_t *) PIC(&p_content->content.choicesList));
635 list.nbChoices = contentChoices->nbChoices + 1; // For Back button
636 list.selectedChoice = contentChoices->initChoice;
637 list.callback = getChoiceName;
638 newStep = nbgl_stepDrawMenuList(onChoiceSelected, p_ticker, &list, modal);
639 break;
640 case BARS_LIST:
641 contentBars = ((nbgl_contentBarsList_t *) PIC(&p_content->content.barsList));
642 list.nbChoices = contentBars->nbBars + 1; // For Back button
643 list.selectedChoice = 0;
644 list.callback = getChoiceName;
645 newStep = nbgl_stepDrawMenuList(onChoiceSelected, p_ticker, &list, modal);
646 break;
647 default:
648 // Not supported as vertical MenuList
649 break;
650 }
651 }
652 }
653 else if ((icon == NULL) && (forcedType != FORCE_CENTERED_INFO)) {
655 if (subTxt != NULL) {
656 style = (forcedType == FORCE_BUTTON) ? BUTTON_INFO : BOLD_TEXT1_INFO;
657 }
658 else {
659 style = REGULAR_INFO;
660 }
661 newStep = nbgl_stepDrawText(pos, onActionCallback, p_ticker, txt, subTxt, style, modal);
662 }
663 else {
664 nbgl_layoutCenteredInfo_t info = {0};
665 info.icon = icon;
666 info.text1 = txt;
667 info.text2 = subTxt;
668 info.bottomIcon = bottomIcon;
669 if ((subTxt != NULL) || (context.stepCallback != NULL) || context.forceAction) {
670 info.style = BOLD_TEXT1_INFO;
671 }
672 else {
673 info.style = REGULAR_INFO;
674 }
675 newStep = nbgl_stepDrawCenteredInfo(pos, onActionCallback, p_ticker, &info, modal);
676 }
677 if (context.type == CONFIRM_USE_CASE) {
678 context.confirm.currentStep = newStep;
679 }
680}
681
682static void drawSwitchStep(nbgl_stepPosition_t pos,
683 const char *title,
684 const char *description,
685 bool state,
686 nbgl_stepButtonCallback_t onActionCallback,
687 bool modal)
688{
689 nbgl_layoutSwitch_t switchInfo = {0};
690
691 pos |= GET_POS_OF_STEP(context.currentPage, context.nbPages);
692 switchInfo.initState = state;
693 switchInfo.text = title;
694 switchInfo.subText = description;
695 nbgl_stepDrawSwitch(pos, onActionCallback, NULL, &switchInfo, modal);
696}
697
698static bool buttonGenericCallback(nbgl_buttonEvent_t event, nbgl_stepPosition_t *pos)
699{
700 uint8_t elemIdx;
701 uint8_t token = 0;
702 uint8_t index = 0;
703 const nbgl_content_t *p_content = NULL;
704 nbgl_content_t content = {0};
705
706 if (event == BUTTON_LEFT_PRESSED) {
707 if (context.currentPage > 0) {
708 context.currentPage--;
709 }
710 // in streaming+skip case, it is allowed to go backward at the first tag/value, except for
711 // the first set
712 else if ((context.type != STREAMING_CONTINUE_REVIEW_USE_CASE)
713 || (context.review.skipCallback == NULL) || (context.review.nbDataSets == 1)) {
714 // Drop the event
715 return false;
716 }
717 *pos = BACKWARD_DIRECTION;
718 }
719 else if (event == BUTTON_RIGHT_PRESSED) {
720 if (context.currentPage < (int) (context.nbPages - 1)) {
721 context.currentPage++;
722 }
723 else {
724 // Drop the event
725 return false;
726 }
727 *pos = FORWARD_DIRECTION;
728 }
729 else {
730 if (event == BUTTON_BOTH_PRESSED) {
731 if (context.stepCallback != NULL) {
732 context.stepCallback();
733 }
734 else if ((context.type == CONTENT_USE_CASE) || (context.type == SETTINGS_USE_CASE)
735 || (context.type == GENERIC_SETTINGS)
736 || (context.type == GENERIC_REVIEW_USE_CASE)) {
737 p_content = getContentElemAtIdx(context.currentPage, &elemIdx, &content);
738 if (p_content != NULL) {
739 switch (p_content->type) {
740 case CENTERED_INFO:
741 // No associated callback
742 return false;
743 case INFO_BUTTON:
744 token = p_content->content.infoButton.buttonToken;
745 break;
746 case SWITCHES_LIST:
747 token = p_content->content.switchesList.switches->token;
748 break;
749 case BARS_LIST:
750 if (elemIdx >= p_content->content.barsList.nbBars) {
751 return false;
752 }
753 token = ((const uint8_t *) PIC(
754 p_content->content.barsList.tokens))[elemIdx];
755 index = elemIdx;
756 break;
757 case CHOICES_LIST:
758 token = p_content->content.choicesList.token;
759 index = elemIdx;
760 break;
761 case TAG_VALUE_LIST:
762 return false;
764 if (elemIdx < p_content->content.tagValueConfirm.tagValueList.nbPairs) {
765 return false;
766 }
767 token = p_content->content.tagValueConfirm.confirmationToken;
768 break;
769 default:
770 break;
771 }
772
773 bool used_action_callback
774 = (p_content != NULL) && (p_content->contentActionCallback != NULL);
775 ContextType_t prev_type = context.type;
776 if (used_action_callback) {
779 actionCallback(token, index, context.currentPage);
780 }
781 else if (context.content.controlsCallback != NULL) {
782 context.content.controlsCallback(token, index);
783 }
784 // If the callback swapped the use case (e.g. by entering
785 // a new screen via display_home_page / nbgl_useCaseXxx),
786 // the global context has already been reset — leave it
787 // alone, do not try to redraw the previous layout.
788 if (context.type != prev_type) {
789 return false;
790 }
791 // After a CHOICES_LIST or BARS_LIST selection that went
792 // through the per-content action callback (settings-style
793 // flows), request a redraw of the current page so any
794 // visual side effect of the pick (e.g. the selectionIcon
795 // moving below the new initChoice) becomes visible.
796 // The controlsCallback path (navigable content, generic
797 // review, ...) is excluded because such callbacks
798 // commonly route to a brand-new use case (review,
799 // warning, ...) that owns the screen without resetting
800 // context.type — forcing a redraw there would clobber it.
801 if (used_action_callback
802 && ((p_content->type == CHOICES_LIST) || (p_content->type == BARS_LIST))) {
803 *pos = FORWARD_DIRECTION;
804 return true;
805 }
806 }
807 }
808 }
809 return false;
810 }
811 return true;
812}
813
814static void reviewCallback(nbgl_step_t stepCtx, nbgl_buttonEvent_t event)
815{
816 UNUSED(stepCtx);
818
819 if (!buttonGenericCallback(event, &pos)) {
820 return;
821 }
822 else {
823 // memorize last direction
824 context.review.dataDirection = pos;
825 }
826 displayReviewPage(pos);
827}
828
829// this is the callback used when button action on the "skip" page
830static void buttonSkipCallback(nbgl_step_t stepCtx, nbgl_buttonEvent_t event)
831{
832 UNUSED(stepCtx);
834
835 if (event == BUTTON_LEFT_PRESSED) {
836 // only decrement page if we are going backward but coming from forward (back & forth)
837 if ((context.review.dataDirection == FORWARD_DIRECTION)
838 && (context.currentPage > context.firstPairPage)) {
839 context.currentPage--;
840 }
841 pos = BACKWARD_DIRECTION;
842 }
843 else if (event == BUTTON_RIGHT_PRESSED) {
844 // only increment page if we are going forward but coming from backward (back & forth)
845 if ((context.review.dataDirection == BACKWARD_DIRECTION)
846 && (context.currentPage < (int) (context.nbPages - 1))
847 && (context.currentPage > context.firstPairPage)) {
848 context.currentPage++;
849 }
850 pos = FORWARD_DIRECTION;
851 }
852 else if (event == BUTTON_BOTH_PRESSED) {
853 // the first tag/value page is at page 0 only in streaming case
854 if (context.firstPairPage == 0) {
855 // in streaming, we have to call the provided callback
856 context.review.skipCallback();
857 }
858 else {
859 // if not in streaming, go directly to the "approve" page
860 context.currentPage = context.nbPages - 2;
861 displayReviewPage(FORWARD_DIRECTION);
862 }
863 return;
864 }
865 else {
866 return;
867 }
868 // the first tag/value page is at page 0 only in streaming case
869 if (context.firstPairPage == 0) {
870 displayStreamingReviewPage(pos);
871 }
872 else {
873 displayReviewPage(pos);
874 }
875}
876
877// this is the callback used when buttons in "Action" use case are pressed
878static void useCaseActionCallback(nbgl_step_t stepCtx, nbgl_buttonEvent_t event)
879{
880 UNUSED(stepCtx);
881
882 if (event == BUTTON_BOTH_PRESSED) {
883 context.action.actionCallback();
884 }
885}
886
887static void streamingReviewCallback(nbgl_step_t stepCtx, nbgl_buttonEvent_t event)
888{
890 UNUSED(stepCtx);
891
892 if (!buttonGenericCallback(event, &pos)) {
893 return;
894 }
895 else {
896 // memorize last direction
897 context.review.dataDirection = pos;
898 }
899
900 displayStreamingReviewPage(pos);
901}
902
903static void settingsCallback(nbgl_step_t stepCtx, nbgl_buttonEvent_t event)
904{
905 UNUSED(stepCtx);
907
908 if (!buttonGenericCallback(event, &pos)) {
909 return;
910 }
911
912 displaySettingsPage(pos, false);
913}
914
915static void infoCallback(nbgl_step_t stepCtx, nbgl_buttonEvent_t event)
916{
917 UNUSED(stepCtx);
919
920 if (!buttonGenericCallback(event, &pos)) {
921 return;
922 }
923
924 displayInfoPage(pos);
925}
926
927static void homeCallback(nbgl_step_t stepCtx, nbgl_buttonEvent_t event)
928{
929 UNUSED(stepCtx);
931
932 if (!buttonGenericCallback(event, &pos)) {
933 return;
934 }
935
936 displayHomePage(pos);
937}
938
939static void genericChoiceCallback(nbgl_step_t stepCtx, nbgl_buttonEvent_t event)
940{
941 UNUSED(stepCtx);
943
944 if (!buttonGenericCallback(event, &pos)) {
945 return;
946 }
947
948 displayChoicePage(pos);
949}
950
951static void genericConfirmCallback(nbgl_step_t stepCtx, nbgl_buttonEvent_t event)
952{
953 UNUSED(stepCtx);
955
956 if (!buttonGenericCallback(event, &pos)) {
957 return;
958 }
959
960 displayConfirm(pos);
961}
962
963static void statusButtonCallback(nbgl_step_t stepCtx, nbgl_buttonEvent_t event)
964{
965 UNUSED(stepCtx);
966 // any button press should dismiss the status screen
967 if ((event == BUTTON_BOTH_PRESSED) || (event == BUTTON_LEFT_PRESSED)
968 || (event == BUTTON_RIGHT_PRESSED)) {
969 if (context.stepCallback != NULL) {
970 context.stepCallback();
971 }
972 }
973}
974
975static void contentCallback(nbgl_step_t stepCtx, nbgl_buttonEvent_t event)
976{
977 UNUSED(stepCtx);
979
980 if (!buttonGenericCallback(event, &pos)) {
981 return;
982 }
983
984 displayContent(pos, false);
985}
986
987// callback used for timeout
988static void statusTickerCallback(void)
989{
990 if (context.stepCallback != NULL) {
991 context.stepCallback();
992 }
993}
994
995// this is the callback used when navigating in extension pages
996static void extensionNavigate(nbgl_step_t stepCtx, nbgl_buttonEvent_t event)
997{
999 UNUSED(stepCtx);
1000
1001 if (event == BUTTON_LEFT_PRESSED) {
1002 // only decrement page if we are not at the first page
1003 if (context.review.currentExtensionPage > 0) {
1004 context.review.currentExtensionPage--;
1005 }
1006 pos = BACKWARD_DIRECTION;
1007 }
1008 else if (event == BUTTON_RIGHT_PRESSED) {
1009 // only increment page if not at last page
1010 if (context.review.currentExtensionPage < (context.review.nbExtensionPages - 1)) {
1011 context.review.currentExtensionPage++;
1012 }
1013 pos = FORWARD_DIRECTION;
1014 }
1015 else if (event == BUTTON_BOTH_PRESSED) {
1016 // if at last page, leave modal context
1017 if (context.review.currentExtensionPage == (context.review.nbExtensionPages - 1)) {
1018 nbgl_stepRelease(context.review.extensionStepCtx);
1020 nbgl_refresh();
1021 }
1022 return;
1023 }
1024 else {
1025 return;
1026 }
1027 displayExtensionStep(pos);
1028}
1029
1030// function used to display the extension pages
1031static void displayExtensionStep(nbgl_stepPosition_t pos)
1032{
1033 nbgl_layoutCenteredInfo_t info = {0};
1034 const nbgl_contentTagValueList_t *tagValueList = NULL;
1035 const nbgl_contentInfoList_t *infoList = NULL;
1036 const char *text = NULL;
1037 const char *subText = NULL;
1038
1039 if (context.review.extensionStepCtx != NULL) {
1040 nbgl_stepRelease(context.review.extensionStepCtx);
1041 }
1042 if (context.review.currentExtensionPage < (context.review.nbExtensionPages - 1)) {
1043 if (context.review.currentExtensionPage == 0) {
1044 pos |= FIRST_STEP;
1045 }
1046 else {
1048 }
1049
1050 switch (context.review.extension->aliasType) {
1051 case ENS_ALIAS:
1052 text = context.review.extension->title;
1053 subText = context.review.extension->fullValue;
1054 break;
1055 case ADDRESS_BOOK_ALIAS: {
1056 bool has_scope = (context.review.extension->aliasSubName != NULL);
1057 bool has_tn = (context.review.extension->explanation != NULL);
1058 uint8_t page = context.review.currentExtensionPage;
1059 if (page == 0) {
1060 text = context.review.extension->title;
1061 subText = has_scope ? context.review.extension->aliasSubName
1062 : context.review.extension->fullValue;
1063 }
1064 else if (has_scope && page == 1) {
1065 text = context.review.extension->title;
1066 subText = context.review.extension->fullValue;
1067 }
1068 else if (has_tn) {
1069 text = context.review.extension->explanation;
1070 }
1071 break;
1072 }
1073 case INFO_LIST_ALIAS:
1074 infoList = context.review.extension->infolist;
1075 text = PIC(infoList->infoTypes[context.review.currentExtensionPage]);
1076 subText = PIC(infoList->infoContents[context.review.currentExtensionPage]);
1077 break;
1079 tagValueList = context.review.extension->tagValuelist;
1080 text = PIC(tagValueList->pairs[context.review.currentExtensionPage].item);
1081 subText = PIC(tagValueList->pairs[context.review.currentExtensionPage].value);
1082 break;
1083 default:
1084 break;
1085 }
1086 if (text != NULL) {
1087 context.review.extensionStepCtx = nbgl_stepDrawText(
1088 pos, extensionNavigate, NULL, text, subText, BOLD_TEXT1_INFO, true);
1089 }
1090 }
1091 else if (context.review.currentExtensionPage == (context.review.nbExtensionPages - 1)) {
1092 // draw the back page
1093 info.icon = &C_icon_back_x;
1094 info.text1 = "Back";
1095 info.style = BOLD_TEXT1_INFO;
1096 pos |= LAST_STEP;
1097 context.review.extensionStepCtx
1098 = nbgl_stepDrawCenteredInfo(pos, extensionNavigate, NULL, &info, true);
1099 }
1100 nbgl_refresh();
1101}
1102
1103static void displayAliasFullValue(void)
1104{
1105 const char *text = NULL;
1106 const char *subText = NULL;
1107 const nbgl_icon_details_t *icon;
1108 bool isCenteredInfo;
1109
1110 getPairData(context.review.tagValueList,
1111 context.review.currentTagValueIndex,
1112 &text,
1113 &subText,
1114 &context.review.extension,
1115 &icon,
1116 &isCenteredInfo);
1117 if (context.review.extension == NULL) {
1118 // probably an error
1120 "displayAliasFullValue: extension nor found for pair %d\n",
1121 context.review.currentTagValueIndex);
1122 return;
1123 }
1124 context.review.currentExtensionPage = 0;
1125 context.review.extensionStepCtx = NULL;
1126 // create a modal flow to display this extension
1127 switch (context.review.extension->aliasType) {
1128 case ENS_ALIAS:
1129 context.review.nbExtensionPages = 2;
1130 break;
1131 case ADDRESS_BOOK_ALIAS:
1132 context.review.nbExtensionPages
1133 = 2 + ((context.review.extension->aliasSubName != NULL) ? 1 : 0)
1134 + ((context.review.extension->explanation != NULL) ? 1 : 0);
1135 break;
1136 case INFO_LIST_ALIAS:
1137 context.review.nbExtensionPages = context.review.extension->infolist->nbInfos + 1;
1138 break;
1140 context.review.nbExtensionPages = context.review.extension->tagValuelist->nbPairs + 1;
1141 break;
1142 default:
1144 "displayAliasFullValue: unsupported alias type %d\n",
1145 context.review.extension->aliasType);
1146 return;
1147 }
1148 displayExtensionStep(FORWARD_DIRECTION);
1149}
1150
1151static void getLastPageInfo(bool approve, const nbgl_icon_details_t **icon, const char **text)
1152{
1153 if (approve) {
1154 // Approve page
1155 *icon = &C_icon_validate_14;
1156 if (context.operationType & ADDRESS_BOOK_OPERATION) {
1157 if ((context.type == REVIEW_USE_CASE) && (context.review.finishTitle != NULL)) {
1158 *text = context.review.finishTitle;
1159 }
1160 else {
1161 *text = "Confirm contact details";
1162 }
1163 }
1164 else if (context.type == ADDRESS_REVIEW_USE_CASE) {
1165 *text = "Confirm";
1166 }
1167 else {
1168 // if finish title is provided, use it
1169 if (context.review.finishTitle != NULL) {
1170 *text = context.review.finishTitle;
1171 }
1172 else {
1173 switch (context.operationType & REAL_TYPE_MASK) {
1174 case TYPE_TRANSACTION:
1175 if (context.operationType & RISKY_OPERATION) {
1176 *text = "Accept risk and sign transaction";
1177 }
1178 else {
1179 *text = "Sign transaction";
1180 }
1181 break;
1182 case TYPE_MESSAGE:
1183 if (context.operationType & RISKY_OPERATION) {
1184 *text = "Accept risk and sign message";
1185 }
1186 else {
1187 *text = "Sign message";
1188 }
1189 break;
1190 default:
1191 if (context.operationType & RISKY_OPERATION) {
1192 *text = "Accept risk and sign operation";
1193 }
1194 else {
1195 *text = "Sign operation";
1196 }
1197 break;
1198 }
1199 }
1200 }
1201 context.stepCallback = onReviewAccept;
1202 }
1203 else {
1204 // Reject page
1205 *icon = &C_icon_crossmark;
1206 if ((context.type == ADDRESS_REVIEW_USE_CASE)
1207 || (context.operationType & ADDRESS_BOOK_OPERATION)) {
1208 *text = "Cancel";
1209 }
1210 else if ((context.operationType & REAL_TYPE_MASK) == TYPE_TRANSACTION) {
1211 *text = "Reject transaction";
1212 }
1213 else if ((context.operationType & REAL_TYPE_MASK) == TYPE_MESSAGE) {
1214 *text = "Reject message";
1215 }
1216 else {
1217 *text = "Reject operation";
1218 }
1219 context.stepCallback = onReviewReject;
1220 }
1221}
1222
1223// function used to display the current page in review
1224static void displayReviewPage(nbgl_stepPosition_t pos)
1225{
1226 uint8_t reviewPages = 0;
1227 uint8_t finalPages = 0;
1228 uint8_t pairIndex = 0;
1229 const char *text = NULL;
1230 const char *subText = NULL;
1231 const nbgl_icon_details_t *icon = NULL;
1232 uint8_t currentIndex = 0;
1233 uint8_t titleIndex = 255;
1234 uint8_t subIndex = 255;
1235 uint8_t approveIndex = 255;
1236 uint8_t rejectIndex = 255;
1237 const nbgl_contentValueExt_t *extension = NULL;
1238 ForcedType_t forcedType = NO_FORCED_TYPE;
1239
1240 context.stepCallback = NULL;
1241
1242 // Determine the 1st page to display tag/values
1243 // Title page to display
1244 titleIndex = currentIndex++;
1245 reviewPages++;
1246 if (context.review.reviewSubTitle) {
1247 // subtitle page to display
1248 subIndex = currentIndex++;
1249 reviewPages++;
1250 }
1251 approveIndex = context.nbPages - 2;
1252 rejectIndex = context.nbPages - 1;
1253 finalPages = approveIndex;
1254
1255 // Determine which page to display
1256 if (context.currentPage >= finalPages) {
1257 if (context.currentPage == approveIndex) {
1258 // Approve page
1259 getLastPageInfo(true, &icon, &text);
1260 }
1261 else if (context.currentPage == rejectIndex) {
1262 // Reject page
1263 getLastPageInfo(false, &icon, &text);
1264 }
1265 }
1266 else if (context.currentPage < reviewPages) {
1267 if (context.currentPage == titleIndex) {
1268 // Title page
1269 icon = context.review.icon;
1270 text = context.review.reviewTitle;
1271 }
1272 else if (context.currentPage == subIndex) {
1273 // SubTitle page
1274 text = context.review.reviewSubTitle;
1275 }
1276 }
1277 else if ((context.review.address != NULL) && (context.currentPage == reviewPages)) {
1278 // address confirmation and 2nd page
1279 text = "Address";
1280 subText = context.review.address;
1281 }
1282 else {
1283 // if there is a skip, and we are not already displaying the "skip" page
1284 // and we are not at the first tag/value of the first set of data (except if going
1285 // backward) then display the "skip" page
1286 if ((context.operationType & SKIPPABLE_OPERATION) && (context.review.skipDisplay == false)
1287 && ((context.currentPage > reviewPages)
1288 || (context.review.dataDirection == BACKWARD_DIRECTION))) {
1289 nbgl_stepPosition_t directions = (pos & BACKWARD_DIRECTION) | FIRST_STEP;
1290 nbgl_layoutCenteredInfo_t info = {0};
1291 if ((context.review.nbDataSets == 1) || (context.currentPage > 0)) {
1292 directions |= LAST_STEP;
1293 }
1294 info.icon = &C_Information_circle_14px;
1295 info.text1 = "Press right button to continue message or \bpress both to skip\b";
1296 nbgl_stepDrawCenteredInfo(directions, buttonSkipCallback, NULL, &info, false);
1297 nbgl_refresh();
1298 context.review.skipDisplay = true;
1299 context.firstPairPage = reviewPages;
1300 return;
1301 }
1302 context.review.skipDisplay = false;
1303 bool isCenteredInfo = false;
1304 pairIndex = context.currentPage - reviewPages;
1305 if (context.review.address != NULL) {
1306 pairIndex--;
1307 }
1308 getPairData(context.review.tagValueList,
1309 pairIndex,
1310 &text,
1311 &subText,
1312 &extension,
1313 &icon,
1314 &isCenteredInfo);
1315 if (extension != NULL) {
1316 context.stepCallback = displayAliasFullValue;
1317 context.review.currentTagValueIndex = pairIndex;
1318 forcedType = FORCE_BUTTON;
1319 }
1320 else {
1321 if (isCenteredInfo) {
1322 forcedType = FORCE_CENTERED_INFO;
1323 }
1324 }
1325 }
1326
1327 drawStep(pos, icon, text, subText, reviewCallback, false, forcedType, false);
1328 nbgl_refresh();
1329}
1330
1331// function used to display the current page in review
1332static void displayStreamingReviewPage(nbgl_stepPosition_t pos)
1333{
1334 const char *text = NULL;
1335 const char *subText = NULL;
1336 const nbgl_icon_details_t *icon = NULL;
1337 uint8_t reviewPages = 0;
1338 uint8_t titleIndex = 255;
1339 uint8_t subIndex = 255;
1340 const nbgl_contentValueExt_t *extension = NULL;
1341 ForcedType_t forcedType = NO_FORCED_TYPE;
1342
1343 context.stepCallback = NULL;
1344 switch (context.type) {
1345 case STREAMING_START_REVIEW_USE_CASE:
1346 // Title page to display
1347 titleIndex = reviewPages++;
1348 if (context.review.reviewSubTitle) {
1349 // subtitle page to display
1350 subIndex = reviewPages++;
1351 }
1352 // Determine which page to display
1353 if (context.currentPage >= reviewPages) {
1354 onReviewAccept();
1355 return;
1356 }
1357 // header page(s)
1358 if (context.currentPage == titleIndex) {
1359 // title page
1360 icon = context.review.icon;
1361 text = context.review.reviewTitle;
1362 }
1363 else if (context.currentPage == subIndex) {
1364 // subtitle page
1365 text = context.review.reviewSubTitle;
1366 }
1367 break;
1368
1369 case STREAMING_CONTINUE_REVIEW_USE_CASE:
1370 if (context.currentPage >= context.review.tagValueList->nbPairs) {
1371 onReviewAccept();
1372 return;
1373 }
1374 // if there is a skip, and we are not already displaying the "skip" page
1375 // and we are not at the first tag/value of the first set of data (except if going
1376 // backward) then display the "skip" page
1377 if ((context.review.skipCallback != NULL) && (context.review.skipDisplay == false)
1378 && ((context.review.nbDataSets > 1) || (context.currentPage > 0)
1379 || (context.review.dataDirection == BACKWARD_DIRECTION))) {
1380 nbgl_stepPosition_t directions = (pos & BACKWARD_DIRECTION) | FIRST_STEP;
1381 nbgl_layoutCenteredInfo_t info = {0};
1382 if ((context.review.nbDataSets == 1) || (context.currentPage > 0)) {
1383 directions |= LAST_STEP;
1384 }
1385 info.icon = &C_Information_circle_14px;
1386 info.text1 = "Press right button to continue message or \bpress both to skip\b";
1387 nbgl_stepDrawCenteredInfo(directions, buttonSkipCallback, NULL, &info, false);
1388 nbgl_refresh();
1389 context.review.skipDisplay = true;
1390 return;
1391 }
1392 context.review.skipDisplay = false;
1393 bool isCenteredInfo = false;
1394 getPairData(context.review.tagValueList,
1395 context.currentPage,
1396 &text,
1397 &subText,
1398 &extension,
1399 &icon,
1400 &isCenteredInfo);
1401 if (extension != NULL) {
1402 forcedType = FORCE_BUTTON;
1403 }
1404 else {
1405 if (isCenteredInfo) {
1406 forcedType = FORCE_CENTERED_INFO;
1407 }
1408 }
1409 break;
1410
1411 case STREAMING_FINISH_REVIEW_USE_CASE:
1412 default:
1413 if (context.currentPage == 0) {
1414 // accept page
1415 getLastPageInfo(true, &icon, &text);
1416 }
1417 else {
1418 // reject page
1419 getLastPageInfo(false, &icon, &text);
1420 }
1421 break;
1422 }
1423
1424 drawStep(pos, icon, text, subText, streamingReviewCallback, false, forcedType, false);
1425 nbgl_refresh();
1426}
1427
1428// function used to display the current page in info
1429static void displayInfoPage(nbgl_stepPosition_t pos)
1430{
1431 const char *text = NULL;
1432 const char *subText = NULL;
1433 const nbgl_icon_details_t *icon = NULL;
1434
1435 context.stepCallback = NULL;
1436
1437 if (context.currentPage < (context.nbPages - 1)) {
1438 text = PIC(
1439 ((const char *const *) PIC(context.home.infosList->infoTypes))[context.currentPage]);
1440 subText = PIC(
1441 ((const char *const *) PIC(context.home.infosList->infoContents))[context.currentPage]);
1442 }
1443 else {
1444 icon = &C_icon_back_x;
1445 text = "Back";
1446 context.stepCallback = startUseCaseHome;
1447 }
1448
1449 drawStep(pos, icon, text, subText, infoCallback, false, FORCE_CENTERED_INFO, false);
1450 nbgl_refresh();
1451}
1452
1453// function used to get the current page content
1454static void getContentPage(bool toogle_state, PageContent_t *contentPage)
1455{
1456 uint8_t elemIdx = 0;
1457 const nbgl_content_t *p_content = NULL;
1458 nbgl_content_t content = {0};
1459 nbgl_contentSwitch_t *contentSwitch = NULL;
1460 nbgl_contentRadioChoice_t *contentChoices = NULL;
1461 char **names = NULL;
1462 nbgl_contentBarsList_t *contentBars = NULL;
1463 char **texts = NULL;
1464 p_content = getContentElemAtIdx(context.currentPage, &elemIdx, &content);
1465 if (p_content == NULL) {
1467 "getContentPage(): p_content is NULL for page %d\n",
1468 context.currentPage);
1469 return;
1470 }
1471 switch (p_content->type) {
1472 case CENTERED_INFO:
1473 contentPage->text = PIC(p_content->content.centeredInfo.text1);
1474 contentPage->subText = PIC(p_content->content.centeredInfo.text2);
1475 contentPage->icon = PIC(p_content->content.centeredInfo.icon);
1476 contentPage->bottomIcon = p_content->content.centeredInfo.bottomIcon;
1477 break;
1478 case INFO_BUTTON:
1479 contentPage->icon = PIC(p_content->content.infoButton.icon);
1480 contentPage->text = PIC(p_content->content.infoButton.text);
1481 contentPage->subText = PIC(p_content->content.infoButton.buttonText);
1482 contentPage->bottomIcon = p_content->content.infoButton.bottomIcon;
1483 break;
1484 case TAG_VALUE_LIST:
1485 getPairData(&p_content->content.tagValueList,
1486 elemIdx,
1487 &contentPage->text,
1488 &contentPage->subText,
1489 &contentPage->extension,
1490 &contentPage->icon,
1491 &contentPage->isCenteredInfo);
1492 break;
1493 case TAG_VALUE_CONFIRM:
1494 if (elemIdx < p_content->content.tagValueConfirm.tagValueList.nbPairs) {
1495 getPairData(&p_content->content.tagValueConfirm.tagValueList,
1496 elemIdx,
1497 &contentPage->text,
1498 &contentPage->subText,
1499 &contentPage->extension,
1500 &contentPage->icon,
1501 &contentPage->isCenteredInfo);
1502 }
1503 else {
1504 contentPage->text = p_content->content.tagValueConfirm.confirmationText;
1505 contentPage->icon = &C_icon_validate_14;
1506 contentPage->isAction = true;
1507 }
1508 break;
1509 case SWITCHES_LIST:
1510 contentPage->isSwitch = true;
1511 contentSwitch = &(
1512 (nbgl_contentSwitch_t *) PIC(p_content->content.switchesList.switches))[elemIdx];
1513 contentPage->text = contentSwitch->text;
1514 contentPage->state = contentSwitch->initState;
1515 if (toogle_state) {
1516 contentPage->state = (contentPage->state == ON_STATE) ? OFF_STATE : ON_STATE;
1517 }
1518 context.stepCallback = onSwitchAction;
1519 contentPage->subText = contentSwitch->subText;
1520 break;
1521 case INFOS_LIST:
1522 contentPage->text
1523 = ((const char *const *) PIC(p_content->content.infosList.infoTypes))[elemIdx];
1524 contentPage->subText
1525 = ((const char *const *) PIC(p_content->content.infosList.infoContents))[elemIdx];
1526 break;
1527 case CHOICES_LIST:
1528 contentChoices = (nbgl_contentRadioChoice_t *) PIC(&p_content->content.choicesList);
1529 // When the content opts into the vertical layout, leave
1530 // contentPage->text NULL so drawStep falls through to the menu-list
1531 // rendering (all choices visible, with a radio button on
1532 // initChoice). Default is the horizontal one-page-per-choice flow.
1533 if (contentChoices->vertical) {
1534 break;
1535 }
1536 names = (char **) PIC(contentChoices->names);
1537 // The caller-provided per-content title wins over the contextual
1538 // fallback (app name / use case title).
1539 if (contentChoices->title != NULL) {
1540 contentPage->text = PIC(contentChoices->title);
1541 contentPage->subText = (const char *) PIC(names[elemIdx]);
1542 }
1543 else if ((context.type == CONTENT_USE_CASE) && (context.content.title != NULL)) {
1544 contentPage->text = PIC(context.content.title);
1545 contentPage->subText = (const char *) PIC(names[elemIdx]);
1546 }
1547 else if (((context.type == GENERIC_SETTINGS) || (context.type == SETTINGS_USE_CASE))
1548 && (context.home.appName != NULL)) {
1549 // Both nbgl_useCaseGenericSettings (GENERIC_SETTINGS) and
1550 // nbgl_useCaseHomeAndSettings -> Settings (SETTINGS_USE_CASE)
1551 // populate context.home.appName, so use it as the page title
1552 // for the latter too -- otherwise a horizontally-paged
1553 // CHOICES_LIST would render the choice name only with no
1554 // contextual header at all.
1555 contentPage->text = PIC(context.home.appName);
1556 contentPage->subText = (const char *) PIC(names[elemIdx]);
1557 }
1558 else {
1559 contentPage->text = (const char *) PIC(names[elemIdx]);
1560 }
1561 // Mark the currently-selected choice with the caller-provided icon
1562 // placed at the bottom of the screen (see contentCenteredInfo
1563 // bottomIcon handling in nbgl_layoutAddCenteredInfo). Skipped when
1564 // the caller did not opt in.
1565 if ((contentChoices->selectionIcon != NULL)
1566 && (elemIdx == contentChoices->initChoice)) {
1567 contentPage->icon = PIC(contentChoices->selectionIcon);
1568 contentPage->bottomIcon = true;
1569 }
1570 break;
1571 case BARS_LIST:
1572 contentBars = (nbgl_contentBarsList_t *) PIC(&p_content->content.barsList);
1573 if (contentBars->vertical) {
1574 break; // see CHOICES_LIST comment above
1575 }
1576 texts = (char **) PIC(contentBars->barTexts);
1577 // Same precedence as CHOICES_LIST: caller-provided title overrides
1578 // the contextual fallback.
1579 if (contentBars->title != NULL) {
1580 contentPage->text = PIC(contentBars->title);
1581 contentPage->subText = PIC(texts[elemIdx]);
1582 }
1583 else if ((context.type == CONTENT_USE_CASE) && (context.content.title != NULL)) {
1584 contentPage->text = PIC(context.content.title);
1585 contentPage->subText = PIC(texts[elemIdx]);
1586 }
1587 else if ((context.type == GENERIC_SETTINGS) && (context.home.appName != NULL)) {
1588 contentPage->text = PIC(context.home.appName);
1589 contentPage->subText = PIC(texts[elemIdx]);
1590 }
1591 else {
1592 contentPage->text = PIC(texts[elemIdx]);
1593 }
1594 // Sub-menu indicator: always pinned at the bottom of every bar's
1595 // page. Mirrors the hardcoded PUSH_ICON ('>') that Stax/Flex draw
1596 // on the right of touchable bars (see nbgl_page.c). On Nano the
1597 // right button is reserved for navigation, so we pick a downward
1598 // chevron and place it at the screen bottom via the centeredInfo
1599 // bottomIcon path.
1600 contentPage->icon = &C_icon_down;
1601 contentPage->bottomIcon = true;
1602 break;
1603 default:
1604 break;
1605 }
1606}
1607
1608// function used to display the current page in settings
1609static void displaySettingsPage(nbgl_stepPosition_t pos, bool toogle_state)
1610{
1611 PageContent_t contentPage = {0};
1612
1613 context.stepCallback = NULL;
1614
1615 if (context.currentPage < (context.nbPages - 1)) {
1616 getContentPage(toogle_state, &contentPage);
1617 }
1618 else { // last page is for quit
1619 contentPage.icon = &C_icon_back_x;
1620 contentPage.text = "Back";
1621 if (context.type == GENERIC_SETTINGS) {
1622 context.stepCallback = context.home.quitCallback;
1623 }
1624 else {
1625 context.stepCallback = startUseCaseHome;
1626 }
1627 }
1628
1629 if (contentPage.isSwitch) {
1630 drawSwitchStep(
1631 pos, contentPage.text, contentPage.subText, contentPage.state, settingsCallback, false);
1632 }
1633 else {
1634 drawStep(pos,
1635 contentPage.icon,
1636 contentPage.text,
1637 contentPage.subText,
1638 settingsCallback,
1639 false,
1640 NO_FORCED_TYPE,
1641 contentPage.bottomIcon);
1642 }
1643
1644 nbgl_refresh();
1645}
1646
1647static void startUseCaseHome(void)
1648{
1649 switch (context.type) {
1650 case SETTINGS_USE_CASE:
1651 // Settings page index
1652 context.currentPage = 1;
1653 if (context.home.homeAction) {
1654 // Action page is before Settings page
1655 context.currentPage++;
1656 }
1657 break;
1658 case INFO_USE_CASE:
1659 // Info page index
1660 context.currentPage = 1;
1661 if (context.home.homeAction) {
1662 // Action page is before Settings and Info pages
1663 context.currentPage++;
1664 }
1665 if (context.home.settingContents) {
1666 // Settings page is before Info pages
1667 context.currentPage++;
1668 }
1669 break;
1670 default:
1671 // Home page index
1672 context.currentPage = 0;
1673 break;
1674 }
1675
1676 context.type = HOME_USE_CASE;
1677 context.nbPages = 2; // Home + Quit
1678 if (context.home.settingContents) {
1679 context.nbPages++;
1680 }
1681 if (context.home.infosList) {
1682 context.nbPages++;
1683 }
1684 if (context.home.homeAction) {
1685 context.nbPages++;
1686 }
1687 displayHomePage(FORWARD_DIRECTION);
1688}
1689
1690static void startUseCaseInfo(void)
1691{
1692 context.type = INFO_USE_CASE;
1693 context.nbPages = context.home.infosList->nbInfos + 1; // For back screen
1694 context.currentPage = 0;
1695
1696 displayInfoPage(FORWARD_DIRECTION);
1697}
1698
1699static void startUseCaseSettingsAtPage(uint8_t initSettingPage)
1700{
1701 nbgl_content_t content = {0};
1702 const nbgl_content_t *p_content = NULL;
1703
1704 // if not coming from GENERIC_SETTINGS, force to SETTINGS_USE_CASE
1705 if (context.type != GENERIC_SETTINGS) {
1706 context.type = SETTINGS_USE_CASE;
1707 }
1708
1709 context.nbPages = 1; // For back screen
1710 for (unsigned int i = 0; i < context.home.settingContents->nbContents; i++) {
1711 p_content = getContentAtIdx(context.home.settingContents, i, &content);
1712 context.nbPages += getContentNbElement(p_content);
1713 }
1714 context.currentPage = initSettingPage;
1715
1716 displaySettingsPage(FORWARD_DIRECTION, false);
1717}
1718
1719static void startUseCaseSettings(void)
1720{
1721 startUseCaseSettingsAtPage(0);
1722}
1723
1724static void startUseCaseContent(void)
1725{
1726 uint8_t contentIdx = 0;
1727 const nbgl_content_t *p_content = NULL;
1728 nbgl_content_t content = {0};
1729
1730 context.nbPages = 1; // Quit
1731
1732 for (contentIdx = 0; contentIdx < context.content.genericContents.nbContents; contentIdx++) {
1733 p_content = getContentAtIdx(&context.content.genericContents, contentIdx, &content);
1734 context.nbPages += getContentNbElement(p_content);
1735 }
1736
1737 // Ensure currentPage is valid
1738 if (context.currentPage >= context.nbPages) {
1739 return;
1740 }
1741
1742 displayContent(FORWARD_DIRECTION, false);
1743}
1744
1745// function used to display the current page in home
1746static void displayHomePage(nbgl_stepPosition_t pos)
1747{
1748 const char *text = NULL;
1749 const char *subText = NULL;
1750 const nbgl_icon_details_t *icon = NULL;
1751 uint8_t currentIndex = 0;
1752 uint8_t homeIndex = 255;
1753 uint8_t actionIndex = 255;
1754 uint8_t settingsIndex = 255;
1755 uint8_t infoIndex = 255;
1756
1757 context.stepCallback = NULL;
1758
1759 // Determine which pages are present
1760 homeIndex = currentIndex++;
1761 if (context.home.homeAction) {
1762 actionIndex = currentIndex++;
1763 }
1764 if (context.home.settingContents) {
1765 settingsIndex = currentIndex++;
1766 }
1767 if (context.home.infosList) {
1768 infoIndex = currentIndex++;
1769 }
1770
1771 if (context.currentPage == homeIndex) {
1772 // Home page
1773 icon = context.home.appIcon;
1774 if (context.home.tagline != NULL) {
1775 text = context.home.tagline;
1776 }
1777 else {
1778 text = context.home.appName;
1779 subText = "app is ready";
1780 }
1781 }
1782 else if (context.currentPage == actionIndex) {
1783 // Action page
1784 icon = context.home.homeAction->icon;
1785 text = PIC(context.home.homeAction->text);
1786 context.stepCallback = context.home.homeAction->callback;
1787 }
1788 else if (context.currentPage == settingsIndex) {
1789 // Settings page
1790 icon = &C_icon_coggle;
1791 text = "App settings";
1792 context.stepCallback = startUseCaseSettings;
1793 }
1794 else if (context.currentPage == infoIndex) {
1795 // About page
1796 icon = &C_Information_circle_14px;
1797 text = "App info";
1798 context.stepCallback = startUseCaseInfo;
1799 }
1800 else {
1801 icon = &C_Quit_14px;
1802 text = "Quit app";
1803 context.stepCallback = context.home.quitCallback;
1804 }
1805
1806 drawStep(pos, icon, text, subText, homeCallback, false, NO_FORCED_TYPE, false);
1807 nbgl_refresh();
1808}
1809
1810// function used to display the current page in choice
1811static void displayChoicePage(nbgl_stepPosition_t pos)
1812{
1813 const char *text = NULL;
1814 const char *subText = NULL;
1815 const nbgl_icon_details_t *icon = NULL;
1816 // set to 1 if there is only one page for intro (if either icon or subMessage is NULL)
1817 // acceptPage = number of intro pages:
1818 // 0 if no message, 1 if message with icon OR subMessage only, 2 if both icon and subMessage
1819 uint8_t acceptPage = 0;
1820 // nbDetailPages = number of BAR_LIST bar pages shown before confirm/cancel (0 if none)
1821 // Page order: intro(s) | bar pages | confirm | cancel
1822 uint8_t nbDetailPages = 0;
1823
1824 if (context.choice.message != NULL) {
1825 if ((context.choice.icon == NULL) || (context.choice.subMessage == NULL)) {
1826 acceptPage = 1;
1827 }
1828 else {
1829 acceptPage = 2;
1830 }
1831 }
1832 if ((context.choice.details != NULL) && (context.choice.details->type == BAR_LIST_WARNING)) {
1833 nbDetailPages = context.choice.details->barList.nbBars;
1834 }
1835 context.stepCallback = NULL;
1836
1837 if (context.currentPage < acceptPage) {
1838 if (context.currentPage == 0) { // title page
1839 text = context.choice.message;
1840 if (context.choice.icon != NULL) {
1841 icon = context.choice.icon;
1842 }
1843 else {
1844 subText = context.choice.subMessage;
1845 }
1846 }
1847 else if ((acceptPage == 2) && (context.currentPage == 1)) { // sub-title page
1848 // displayed only if there is both icon and subMessage
1849 text = context.choice.message;
1850 subText = context.choice.subMessage;
1851 }
1852 }
1853 else if (context.currentPage < (acceptPage + nbDetailPages)) {
1854 // BAR_LIST detail pages, shown before confirm/cancel (one page per bar)
1855 uint8_t idx = context.currentPage - acceptPage;
1856 text = (context.choice.details->barList.texts != NULL)
1857 ? context.choice.details->barList.texts[idx]
1858 : NULL;
1859 subText = (context.choice.details->barList.subTexts != NULL)
1860 ? context.choice.details->barList.subTexts[idx]
1861 : NULL;
1862 }
1863 else if (context.currentPage == (acceptPage + nbDetailPages)) { // confirm page
1864 icon = &C_icon_validate_14;
1865 text = context.choice.confirmText;
1866 context.stepCallback = onChoiceAccept;
1867 }
1868 else { // cancel page (last page)
1869 icon = &C_icon_crossmark;
1870 text = context.choice.cancelText;
1871 context.stepCallback = onChoiceReject;
1872 }
1873 // other detail types (non-BAR_LIST) are not navigated on Nano
1874
1875 drawStep(pos, icon, text, subText, genericChoiceCallback, false, NO_FORCED_TYPE, false);
1876 nbgl_refresh();
1877}
1878
1879// function used to display the Confirm page
1880static void displayConfirm(nbgl_stepPosition_t pos)
1881{
1882 const char *text = NULL;
1883 const char *subText = NULL;
1884 const nbgl_icon_details_t *icon = NULL;
1885
1886 context.stepCallback = NULL;
1887 switch (context.currentPage) {
1888 case 0:
1889 // title page
1890 text = context.confirm.message;
1891 subText = context.confirm.subMessage;
1892 break;
1893 case 1:
1894 // confirm page
1895 icon = &C_icon_validate_14;
1896 text = context.confirm.confirmText;
1897 context.stepCallback = onConfirmAccept;
1898 break;
1899 case 2:
1900 // cancel page
1901 icon = &C_icon_crossmark;
1902 text = context.confirm.cancelText;
1903 context.stepCallback = onConfirmReject;
1904 break;
1905 }
1906
1907 drawStep(pos, icon, text, subText, genericConfirmCallback, true, NO_FORCED_TYPE, false);
1908 nbgl_refresh();
1909}
1910
1911// function used to display the current navigable content
1912static void displayContent(nbgl_stepPosition_t pos, bool toogle_state)
1913{
1914 PageContent_t contentPage = {0};
1915 ForcedType_t forcedType = NO_FORCED_TYPE;
1916
1917 context.stepCallback = NULL;
1918
1919 if (context.currentPage < (context.nbPages - 1)) {
1920 getContentPage(toogle_state, &contentPage);
1921 if (contentPage.isCenteredInfo) {
1922 forcedType = FORCE_CENTERED_INFO;
1923 }
1924 context.forceAction = contentPage.isAction;
1925 }
1926 else { // last page is for quit
1927 if (context.content.rejectText) {
1928 contentPage.text = context.content.rejectText;
1929 }
1930 else {
1931 contentPage.text = "Back";
1932 }
1933 if (context.type == GENERIC_REVIEW_USE_CASE) {
1934 contentPage.icon = &C_icon_crossmark;
1935 }
1936 else {
1937 contentPage.icon = &C_icon_back_x;
1938 }
1939 context.stepCallback = context.content.quitCallback;
1940 }
1941
1942 if (contentPage.isSwitch) {
1943 drawSwitchStep(
1944 pos, contentPage.text, contentPage.subText, contentPage.state, contentCallback, false);
1945 }
1946 else {
1947 drawStep(pos,
1948 contentPage.icon,
1949 contentPage.text,
1950 contentPage.subText,
1951 contentCallback,
1952 false,
1953 forcedType,
1954 contentPage.bottomIcon);
1955 }
1956 context.forceAction = false;
1957 nbgl_refresh();
1958}
1959
1960static void displaySpinner(const char *text)
1961{
1962 drawStep(SINGLE_STEP, &C_icon_processing, text, NULL, NULL, false, NO_FORCED_TYPE, false);
1963 nbgl_refresh();
1964}
1965
1966// function to factorize code for all simple reviews
1967static void useCaseReview(ContextType_t type,
1968 nbgl_operationType_t operationType,
1969 const nbgl_contentTagValueList_t *tagValueList,
1970 const nbgl_icon_details_t *icon,
1971 const char *reviewTitle,
1972 const char *reviewSubTitle,
1973 const char *finishTitle,
1974 nbgl_choiceCallback_t choiceCallback)
1975{
1976 memset(&context, 0, sizeof(UseCaseContext_t));
1977 context.type = type;
1978 context.operationType = operationType;
1979 context.review.tagValueList = tagValueList;
1980 context.review.reviewTitle = reviewTitle;
1981 context.review.reviewSubTitle = reviewSubTitle;
1982 context.review.finishTitle = finishTitle;
1983 context.review.icon = icon;
1984 context.review.onChoice = choiceCallback;
1985 context.currentPage = 0;
1986 // 1 page for title and 2 pages at the end for accept/reject
1987 context.nbPages = tagValueList->nbPairs + 3;
1988 if (reviewSubTitle) {
1989 context.nbPages++; // 1 page for subtitle page
1990 }
1991
1992 displayReviewPage(FORWARD_DIRECTION);
1993}
1994
1995#ifdef NBGL_KEYPAD
1996static void setPinCodeText(void)
1997{
1998 bool enableValidate = false;
1999 bool enableBackspace = true;
2000
2001 // pin can be validated when min digits is entered
2002 enableValidate = (context.keypad.pinLen >= context.keypad.pinMinDigits);
2003 // backspace is disabled when no digit is entered and back vallback is not provided
2004 enableBackspace = (context.keypad.pinLen > 0) || (context.keypad.backCallback != NULL);
2005 nbgl_layoutUpdateKeypadContent(context.keypad.layoutCtx,
2006 context.keypad.hidden,
2007 context.keypad.pinLen,
2008 (const char *) context.keypad.pinEntry);
2010 context.keypad.layoutCtx, context.keypad.keypadIndex, enableValidate, enableBackspace);
2011 nbgl_layoutDraw(context.keypad.layoutCtx);
2012 nbgl_refresh();
2013}
2014
2015// called when a key is touched on the keypad
2016static void keypadCallback(char touchedKey)
2017{
2018 switch (touchedKey) {
2019 case BACKSPACE_KEY:
2020 if (context.keypad.pinLen > 0) {
2021 context.keypad.pinLen--;
2022 context.keypad.pinEntry[context.keypad.pinLen] = 0;
2023 }
2024 else if (context.keypad.backCallback != NULL) {
2025 context.keypad.backCallback();
2026 break;
2027 }
2028 setPinCodeText();
2029 break;
2030
2031 case VALIDATE_KEY:
2032 context.keypad.validatePin(context.keypad.pinEntry, context.keypad.pinLen);
2033 break;
2034
2035 default:
2036 if ((touchedKey >= 0x30) && (touchedKey < 0x40)) {
2037 if (context.keypad.pinLen < context.keypad.pinMaxDigits) {
2038 context.keypad.pinEntry[context.keypad.pinLen] = touchedKey;
2039 context.keypad.pinLen++;
2040 }
2041 setPinCodeText();
2042 }
2043 break;
2044 }
2045}
2046#endif // NBGL_KEYPAD
2047
2048#ifdef NBGL_KEYBOARD
2049// Saved keyboard context for suggestion selection
2050// (needed because switching to CONTENT_USE_CASE overwrites the union context)
2051static struct {
2052 const char **buttons;
2053 int firstButtonToken;
2054 uint8_t nbUsedButtons;
2055 nbgl_layoutTouchCallback_t onButtonCallback;
2056 nbgl_callback_t backCallback;
2057 char title[KEYBOARD_MAX_TITLE];
2058} savedKeyboardContext;
2059
2060// Navigation callback to fill the suggestion choices page
2061static bool suggestionNavCallback(uint8_t page, nbgl_pageContent_t *content)
2062{
2063 UNUSED(page);
2064 content->type = CHOICES_LIST;
2065 content->choicesList.names = savedKeyboardContext.buttons;
2066 content->choicesList.token = savedKeyboardContext.firstButtonToken;
2067 content->choicesList.initChoice = 0;
2068 content->choicesList.nbChoices = savedKeyboardContext.nbUsedButtons;
2069 return true;
2070}
2071
2072// Display suggestion selection page
2073static void displaySuggestionSelection(void)
2074{
2075 // Save keyboard context before it gets overwritten
2076 savedKeyboardContext.buttons = context.keyboard.content.suggestionButtons.buttons;
2077 savedKeyboardContext.firstButtonToken
2078 = context.keyboard.content.suggestionButtons.firstButtonToken;
2079 savedKeyboardContext.nbUsedButtons = context.keyboard.content.suggestionButtons.nbUsedButtons;
2080 savedKeyboardContext.onButtonCallback = context.keyboard.onButtonCallback;
2081 savedKeyboardContext.backCallback = context.keyboard.backCallback;
2082
2083 // Release the keyboard layout
2084 nbgl_layoutRelease(context.keyboard.layoutCtx);
2085 context.keyboard.layoutCtx = NULL;
2086
2087 snprintf(savedKeyboardContext.title,
2088 sizeof(savedKeyboardContext.title),
2089 "Select word #%d",
2090 context.keyboard.content.number);
2091 nbgl_useCaseNavigableContent(savedKeyboardContext.title,
2092 0,
2093 1,
2094 savedKeyboardContext.backCallback,
2095 suggestionNavCallback,
2096 savedKeyboardContext.onButtonCallback);
2097}
2098
2099// called when a key is touched on the keyboard
2100static void keyboardCallback(char touchedKey)
2101{
2102 uint32_t mask = 0;
2103 size_t textLen = strlen(context.keyboard.entryBuffer);
2104 PRINTF("[keyboardCallback] touchedKey: '%c'\n", touchedKey);
2105 if (touchedKey == BACKSPACE_KEY) {
2106 if (textLen == 0) {
2107 // Used to exit the keyboard when backspace is pressed on an empty entry
2108 context.keyboard.backCallback();
2109 return;
2110 }
2111 context.keyboard.entryBuffer[--textLen] = '\0';
2112 }
2113 else if (touchedKey == VALIDATE_KEY) {
2114 context.keyboard.actionCallback();
2115 return;
2116 }
2117 else {
2118 context.keyboard.entryBuffer[textLen] = touchedKey;
2119 context.keyboard.entryBuffer[++textLen] = '\0';
2120 }
2121 // Set the keyMask to disable some keys
2122 if (context.keyboard.content.type == KEYBOARD_WITH_SUGGESTIONS) {
2123 // if suggestions are displayed, we update them at each key press
2124 context.keyboard.getSuggestButtons(&context.keyboard.content, &mask);
2125 const nbgl_layoutSuggestionButtons_t *suggestions
2126 = &context.keyboard.content.suggestionButtons;
2127 // On Nano, when all the matching candidates can be displayed, switch to a selection page
2128 // instead of continuing with keyboard entry. When the caller provides the real number of
2129 // candidates (nbCandidates != 0), rely on it: this lets a fully-typed word that is also
2130 // the prefix of other words (e.g. "can") be selected even though the displayed buttons are
2131 // capped at NB_MAX_SUGGESTION_BUTTONS. Otherwise fall back to the legacy heuristic based on
2132 // the number of displayed buttons.
2133 bool allCandidatesFit;
2134 if (suggestions->nbCandidates != 0) {
2135 allCandidatesFit = (suggestions->nbCandidates <= NB_MAX_SUGGESTION_BUTTONS);
2136 }
2137 else {
2138 allCandidatesFit = (suggestions->nbUsedButtons < NB_MAX_SUGGESTION_BUTTONS);
2139 }
2140 if ((suggestions->nbUsedButtons > 0) && allCandidatesFit) {
2141 displaySuggestionSelection();
2142 return; // Don't update keyboard, we're in suggestion mode
2143 }
2144 }
2145 else if (textLen >= context.keyboard.entryMaxLen) {
2146 // entry length can't be greater, so we mask every characters
2147 mask = -1;
2148 }
2149 nbgl_layoutUpdateKeyboard(context.keyboard.layoutCtx, context.keyboard.keyboardIndex, mask);
2151 context.keyboard.layoutCtx, context.keyboard.textIndex, context.keyboard.entryBuffer);
2152 nbgl_refresh();
2153}
2154#endif
2155
2156// this is the function called to start the actual review, from the initial warning pages
2157static void launchReviewAfterWarning(void)
2158{
2159 if (reviewWithWarnCtx.type == REVIEW_USE_CASE) {
2160 useCaseReview(reviewWithWarnCtx.type,
2161 reviewWithWarnCtx.operationType,
2162 reviewWithWarnCtx.tagValueList,
2163 reviewWithWarnCtx.icon,
2164 reviewWithWarnCtx.reviewTitle,
2165 reviewWithWarnCtx.reviewSubTitle,
2166 reviewWithWarnCtx.finishTitle,
2167 reviewWithWarnCtx.choiceCallback);
2168 }
2169 else if (reviewWithWarnCtx.type == STREAMING_START_REVIEW_USE_CASE) {
2170 displayStreamingReviewPage(FORWARD_DIRECTION);
2171 }
2172}
2173
2174// callback used when navigating in a bar detail sub-page
2175static void barDetailNavigate(nbgl_step_t stepCtx, nbgl_buttonEvent_t event)
2176{
2177 UNUSED(stepCtx);
2178 if (event == BUTTON_LEFT_PRESSED) {
2179 displayWarningStep();
2180 }
2181}
2182
2183// display a bar detail sub-page (CENTERED_INFO_WARNING type)
2184static void displayBarDetailStep(void)
2185{
2186 const nbgl_genericDetails_t *detail
2187 = &reviewWithWarnCtx.warning->introDetails->barList.details[reviewWithWarnCtx.barDetailIdx];
2188
2189 if (detail->type == CENTERED_INFO_WARNING) {
2190 nbgl_layoutCenteredInfo_t info = {0};
2191 info.icon = detail->centeredInfo.icon;
2192 info.text1 = detail->centeredInfo.title;
2193 info.text2 = detail->centeredInfo.description;
2194 info.style = BOLD_TEXT1_INFO;
2195 // LAST_STEP: only left arrow shown, pressing LEFT goes back via barDetailNavigate
2197 LAST_STEP | BACKWARD_DIRECTION, barDetailNavigate, NULL, &info, false);
2198 nbgl_refresh();
2199 }
2200}
2201
2202// this is the callback used when navigating in warning pages
2203static void warningNavigate(nbgl_step_t stepCtx, nbgl_buttonEvent_t event)
2204{
2205 UNUSED(stepCtx);
2206 uint8_t barIdx = 0;
2207
2208 if (event == BUTTON_LEFT_PRESSED) {
2209 // only decrement page if we are not at the first page
2210 if (reviewWithWarnCtx.warningPage > 0) {
2211 reviewWithWarnCtx.warningPage--;
2212 }
2213 }
2214 else if (event == BUTTON_RIGHT_PRESSED) {
2215 // only increment page if not at last page
2216 if (reviewWithWarnCtx.warningPage < (reviewWithWarnCtx.nbWarningPages - 1)) {
2217 reviewWithWarnCtx.warningPage++;
2218 }
2219 else if ((reviewWithWarnCtx.warning->predefinedSet == 0)
2220 && (reviewWithWarnCtx.warning->info != NULL)) {
2221 launchReviewAfterWarning();
2222 return;
2223 }
2224 }
2225 else if ((event == BUTTON_BOTH_PRESSED)
2226 && (reviewWithWarnCtx.warning->predefinedSet & (1 << BLIND_SIGNING_WARN))) {
2227 // if at first page, double press leads to start of review
2228 if (reviewWithWarnCtx.warningPage == reviewWithWarnCtx.firstWarningPage) {
2229 launchReviewAfterWarning();
2230 }
2231 // if at last page, reject operation
2232 else if (reviewWithWarnCtx.warningPage == (reviewWithWarnCtx.nbWarningPages - 1)) {
2233 reviewWithWarnCtx.choiceCallback(false);
2234 }
2235 return;
2236 }
2237 else if ((event == BUTTON_BOTH_PRESSED) && (reviewWithWarnCtx.warning->introDetails != NULL)
2238 && (reviewWithWarnCtx.warning->introDetails->type == BAR_LIST_WARNING)
2239 && (reviewWithWarnCtx.warningPage > reviewWithWarnCtx.firstWarningPage)) {
2240 // enter the detail sub-page for the current bar (if it has one)
2241 barIdx = reviewWithWarnCtx.warningPage - reviewWithWarnCtx.firstWarningPage - 1;
2242 if ((reviewWithWarnCtx.warning->introDetails->barList.details != NULL)
2243 && (reviewWithWarnCtx.warning->introDetails->barList.details[barIdx].type
2244 != NO_TYPE_WARNING)) {
2245 reviewWithWarnCtx.barDetailIdx = barIdx;
2246 displayBarDetailStep();
2247 }
2248 return;
2249 }
2250 else {
2251 return;
2252 }
2253 displayWarningStep();
2254}
2255
2256// function used to display the initial warning pages when starting a "review with warning"
2257static void displayWarningStep(void)
2258{
2259 nbgl_layoutCenteredInfo_t info = {0};
2260 nbgl_stepPosition_t pos = 0;
2261 uint8_t barIdx = 0;
2262 if ((reviewWithWarnCtx.warning->prelude) && (reviewWithWarnCtx.warningPage == 0)) {
2263 // for prelude, only draw text as a single step
2265 warningNavigate,
2266 NULL,
2267 reviewWithWarnCtx.warning->prelude->title,
2268 reviewWithWarnCtx.warning->prelude->description,
2270 false);
2271 nbgl_refresh();
2272 return;
2273 }
2274 else if (reviewWithWarnCtx.warning->predefinedSet & (1 << BLIND_SIGNING_WARN)) {
2275 if (reviewWithWarnCtx.warningPage == reviewWithWarnCtx.firstWarningPage) {
2276 // draw the main warning page
2277 info.icon = &C_icon_warning;
2278 info.text1 = "Blind signing ahead";
2279 info.text2 = "To accept risk, press both buttons";
2280 pos = (reviewWithWarnCtx.firstWarningPage == 0) ? FIRST_STEP
2282 pos |= FORWARD_DIRECTION;
2283 }
2284 else if (reviewWithWarnCtx.warningPage == (reviewWithWarnCtx.nbWarningPages - 1)) {
2285 getLastPageInfo(false, &info.icon, &info.text1);
2287 }
2288 }
2289 else if ((reviewWithWarnCtx.warning->predefinedSet == 0)
2290 && (reviewWithWarnCtx.warning->info != NULL)) {
2291 if (reviewWithWarnCtx.warningPage == reviewWithWarnCtx.firstWarningPage) {
2292 info.icon = reviewWithWarnCtx.warning->info->icon;
2293 info.text1 = reviewWithWarnCtx.warning->info->title;
2294 info.text2 = reviewWithWarnCtx.warning->info->description;
2295 pos = (reviewWithWarnCtx.firstWarningPage == 0) ? FIRST_STEP
2297 pos |= FORWARD_DIRECTION;
2298 }
2299 else if ((reviewWithWarnCtx.warning->introDetails != NULL)
2300 && (reviewWithWarnCtx.warning->introDetails->type == BAR_LIST_WARNING)) {
2301 // intermediate or last bar page
2302 barIdx = reviewWithWarnCtx.warningPage - reviewWithWarnCtx.firstWarningPage - 1;
2303 if (reviewWithWarnCtx.warning->introDetails->barList.icons) {
2304 info.icon = reviewWithWarnCtx.warning->introDetails->barList.icons[barIdx];
2305 }
2306 info.text1 = reviewWithWarnCtx.warning->introDetails->barList.texts[barIdx];
2307 if (reviewWithWarnCtx.warning->introDetails->barList.subTexts) {
2308 info.text2 = reviewWithWarnCtx.warning->introDetails->barList.subTexts[barIdx];
2309 }
2311 }
2312 else if (reviewWithWarnCtx.warningPage == (reviewWithWarnCtx.nbWarningPages - 1)) {
2313 if ((reviewWithWarnCtx.warning->introDetails != NULL)
2314 && (reviewWithWarnCtx.warning->introDetails->type == CENTERED_INFO_WARNING)) {
2315 info.icon = reviewWithWarnCtx.warning->introDetails->centeredInfo.icon;
2316 info.text1 = reviewWithWarnCtx.warning->introDetails->centeredInfo.title;
2317 info.text2 = reviewWithWarnCtx.warning->introDetails->centeredInfo.description;
2319 }
2320 else {
2321 // not supported
2322 return;
2323 }
2324 }
2325 }
2326 else {
2327 // not supported
2328 return;
2329 }
2330 info.style = BOLD_TEXT1_INFO;
2331 nbgl_stepDrawCenteredInfo(pos, warningNavigate, NULL, &info, false);
2332 nbgl_refresh();
2333}
2334
2335// function used to display the initial warning page when starting a "review with warning"
2336static void displayInitialWarning(void)
2337{
2338 // draw the main warning page
2339 reviewWithWarnCtx.warningPage = 0;
2340 if ((reviewWithWarnCtx.warning->predefinedSet & (1 << BLIND_SIGNING_WARN))
2341 || ((reviewWithWarnCtx.warning->introDetails)
2342 && (reviewWithWarnCtx.warning->introDetails->type == CENTERED_INFO_WARNING))) {
2343 reviewWithWarnCtx.nbWarningPages = 2;
2344 }
2345 else if ((reviewWithWarnCtx.warning->introDetails)
2346 && (reviewWithWarnCtx.warning->introDetails->type == BAR_LIST_WARNING)) {
2347 reviewWithWarnCtx.nbWarningPages
2348 = reviewWithWarnCtx.warning->introDetails->barList.nbBars + 1;
2349 }
2350 else {
2351 // if no intro details and not Blind Signing warning, only one page
2352 reviewWithWarnCtx.nbWarningPages = 1;
2353 }
2354
2355 reviewWithWarnCtx.firstWarningPage = 0;
2356 displayWarningStep();
2357}
2358
2359// function used to display the prelude page when starting a "review with warning"
2360static void displayPrelude(void)
2361{
2362 // draw the main warning page
2363 reviewWithWarnCtx.warningPage = 0;
2364 if ((reviewWithWarnCtx.warning->predefinedSet & (1 << BLIND_SIGNING_WARN))
2365 || ((reviewWithWarnCtx.warning->introDetails)
2366 && (reviewWithWarnCtx.warning->introDetails->type == CENTERED_INFO_WARNING))) {
2367 reviewWithWarnCtx.nbWarningPages = 3;
2368 }
2369 else {
2370 // if no intro details and not Blind Signing warning, only 2 pages
2371 reviewWithWarnCtx.nbWarningPages = 2;
2372 }
2373 reviewWithWarnCtx.firstWarningPage = 1;
2374 displayWarningStep();
2375}
2376
2377/**********************
2378 * GLOBAL FUNCTIONS
2379 **********************/
2380
2392uint8_t nbgl_useCaseGetNbTagValuesInPage(uint8_t nbPairs,
2393 const nbgl_contentTagValueList_t *tagValueList,
2394 uint8_t startIndex,
2395 bool *requireSpecificDisplay)
2396{
2397 UNUSED(nbPairs);
2398 UNUSED(tagValueList);
2399 UNUSED(startIndex);
2400 *requireSpecificDisplay = true;
2401 return 1;
2402}
2403
2416uint8_t nbgl_useCaseGetNbTagValuesInPageExt(uint8_t nbPairs,
2417 const nbgl_contentTagValueList_t *tagValueList,
2418 uint8_t startIndex,
2419 bool isSkippable,
2420 bool *requireSpecificDisplay)
2421{
2422 UNUSED(nbPairs);
2423 UNUSED(tagValueList);
2424 UNUSED(startIndex);
2425 UNUSED(isSkippable);
2426 *requireSpecificDisplay = true;
2427 return 1;
2428}
2429
2438uint8_t nbgl_useCaseGetNbInfosInPage(uint8_t nbInfos,
2439 const nbgl_contentInfoList_t *infosList,
2440 uint8_t startIndex,
2441 bool withNav)
2442{
2443 UNUSED(nbInfos);
2444 UNUSED(infosList);
2445 UNUSED(startIndex);
2446 UNUSED(withNav);
2447 return 1;
2448}
2449
2458uint8_t nbgl_useCaseGetNbSwitchesInPage(uint8_t nbSwitches,
2459 const nbgl_contentSwitchesList_t *switchesList,
2460 uint8_t startIndex,
2461 bool withNav)
2462{
2463 UNUSED(nbSwitches);
2464 UNUSED(switchesList);
2465 UNUSED(startIndex);
2466 UNUSED(withNav);
2467 return 1;
2468}
2469
2478uint8_t nbgl_useCaseGetNbBarsInPage(uint8_t nbBars,
2479 const nbgl_contentBarsList_t *barsList,
2480 uint8_t startIndex,
2481 bool withNav)
2482{
2483 UNUSED(nbBars);
2484 UNUSED(barsList);
2485 UNUSED(startIndex);
2486 UNUSED(withNav);
2487 return 1;
2488}
2489
2498uint8_t nbgl_useCaseGetNbChoicesInPage(uint8_t nbChoices,
2499 const nbgl_contentRadioChoice_t *choicesList,
2500 uint8_t startIndex,
2501 bool withNav)
2502{
2503 UNUSED(nbChoices);
2504 UNUSED(choicesList);
2505 UNUSED(startIndex);
2506 UNUSED(withNav);
2507 return 1;
2508}
2509
2517{
2518 uint8_t nbPages = 0;
2519 uint8_t nbPairs = tagValueList->nbPairs;
2520 uint8_t nbPairsInPage;
2521 uint8_t i = 0;
2522 bool flag;
2523
2524 while (i < tagValueList->nbPairs) {
2525 // upper margin
2526 nbPairsInPage = nbgl_useCaseGetNbTagValuesInPageExt(nbPairs, tagValueList, i, false, &flag);
2527 i += nbPairsInPage;
2528 nbPairs -= nbPairsInPage;
2529 nbPages++;
2530 }
2531 return nbPages;
2532}
2533
2547void nbgl_useCaseNavigableContent(const char *title,
2548 uint8_t initPage,
2549 uint8_t nbPages,
2550 nbgl_callback_t quitCallback,
2551 nbgl_navCallback_t navCallback,
2552 nbgl_layoutTouchCallback_t controlsCallback)
2553{
2554 memset(&context, 0, sizeof(UseCaseContext_t));
2555 context.type = CONTENT_USE_CASE;
2556 context.currentPage = initPage;
2557 context.content.title = title;
2558 context.content.quitCallback = quitCallback;
2559 context.content.navCallback = navCallback;
2560 context.content.controlsCallback = controlsCallback;
2561 context.content.genericContents.callbackCallNeeded = true;
2562 context.content.genericContents.nbContents = nbPages;
2563
2564 startUseCaseContent();
2565}
2566
2581void nbgl_useCaseHomeAndSettings(const char *appName,
2582 const nbgl_icon_details_t *appIcon,
2583 const char *tagline,
2584 const uint8_t initSettingPage,
2585 const nbgl_genericContents_t *settingContents,
2586 const nbgl_contentInfoList_t *infosList,
2587 const nbgl_homeAction_t *action,
2588 nbgl_callback_t quitCallback)
2589{
2590 memset(&context, 0, sizeof(UseCaseContext_t));
2591 context.home.appName = appName;
2592 context.home.appIcon = appIcon;
2593 context.home.tagline = tagline;
2594 context.home.settingContents = PIC(settingContents);
2595 context.home.infosList = PIC(infosList);
2596 context.home.homeAction = action;
2597 context.home.quitCallback = quitCallback;
2598
2599 if ((initSettingPage != INIT_HOME_PAGE) && (settingContents != NULL)) {
2600 startUseCaseSettingsAtPage(initSettingPage);
2601 }
2602 else {
2603 startUseCaseHome();
2604 }
2605}
2606
2619void nbgl_useCaseGenericSettings(const char *appName,
2620 uint8_t initPage,
2621 const nbgl_genericContents_t *settingContents,
2622 const nbgl_contentInfoList_t *infosList,
2623 nbgl_callback_t quitCallback)
2624{
2625 memset(&context, 0, sizeof(UseCaseContext_t));
2626 context.type = GENERIC_SETTINGS;
2627 context.home.appName = appName;
2628 context.home.settingContents = PIC(settingContents);
2629 context.home.infosList = PIC(infosList);
2630 context.home.quitCallback = quitCallback;
2631
2632 startUseCaseSettingsAtPage(initPage);
2633}
2634
2646void nbgl_useCaseGenericConfiguration(const char *title,
2647 uint8_t initPage,
2648 const nbgl_genericContents_t *contents,
2649 nbgl_callback_t quitCallback)
2650{
2651 nbgl_useCaseGenericSettings(title, initPage, contents, NULL, quitCallback);
2652}
2653
2668void nbgl_useCaseReview(nbgl_operationType_t operationType,
2669 const nbgl_contentTagValueList_t *tagValueList,
2670 const nbgl_icon_details_t *icon,
2671 const char *reviewTitle,
2672 const char *reviewSubTitle,
2673 const char *finishTitle,
2674 nbgl_choiceCallback_t choiceCallback)
2675{
2676 useCaseReview(REVIEW_USE_CASE,
2677 operationType,
2678 tagValueList,
2679 icon,
2680 reviewTitle,
2681 reviewSubTitle,
2682 finishTitle,
2683 choiceCallback);
2684}
2685
2709 const nbgl_contentTagValueList_t *tagValueList,
2710 const nbgl_icon_details_t *icon,
2711 const char *reviewTitle,
2712 const char *reviewSubTitle,
2713 const char *finishTitle,
2714 const nbgl_tipBox_t *tipBox,
2715 const nbgl_warning_t *warning,
2716 nbgl_choiceCallback_t choiceCallback)
2717{
2718 UNUSED(tipBox);
2719 ContextType_t type = REVIEW_USE_CASE;
2720
2721 // if no warning at all, it's a simple review
2722 if ((warning == NULL)
2723 || ((warning->predefinedSet == 0) && (warning->info == NULL)
2724 && (warning->reviewDetails == NULL) && (warning->prelude == NULL))) {
2725 useCaseReview(type,
2726 operationType,
2727 tagValueList,
2728 icon,
2729 reviewTitle,
2730 reviewSubTitle,
2731 finishTitle,
2732 choiceCallback);
2733 return;
2734 }
2735 if (warning->predefinedSet == (1 << W3C_NO_THREAT_WARN)) {
2736 operationType |= NO_THREAT_OPERATION;
2737 }
2738 else if (warning->predefinedSet != 0) {
2739 operationType |= RISKY_OPERATION;
2740 }
2741
2742 memset(&reviewWithWarnCtx, 0, sizeof(reviewWithWarnCtx));
2743 reviewWithWarnCtx.type = type;
2744 reviewWithWarnCtx.operationType = operationType;
2745 reviewWithWarnCtx.tagValueList = tagValueList;
2746 reviewWithWarnCtx.icon = icon;
2747 reviewWithWarnCtx.reviewTitle = reviewTitle;
2748 reviewWithWarnCtx.reviewSubTitle = reviewSubTitle;
2749 reviewWithWarnCtx.finishTitle = finishTitle;
2750 reviewWithWarnCtx.warning = warning;
2751 reviewWithWarnCtx.choiceCallback = choiceCallback;
2752
2753 // if the warning contains a prelude, display it first
2754 if (reviewWithWarnCtx.warning->prelude) {
2755 displayPrelude();
2756 }
2757 // display the initial warning only of a risk/threat or blind signing or prelude
2758 else if (HAS_INITIAL_WARNING(warning)) {
2759 displayInitialWarning();
2760 }
2761 else {
2762 useCaseReview(type,
2763 operationType,
2764 tagValueList,
2765 icon,
2766 reviewTitle,
2767 reviewSubTitle,
2768 finishTitle,
2769 choiceCallback);
2770 }
2771}
2772
2793 const nbgl_contentTagValueList_t *tagValueList,
2794 const nbgl_icon_details_t *icon,
2795 const char *reviewTitle,
2796 const char *reviewSubTitle,
2797 const char *finishTitle,
2798 const nbgl_tipBox_t *dummy,
2799 nbgl_choiceCallback_t choiceCallback)
2800{
2801 nbgl_useCaseAdvancedReview(operationType,
2802 tagValueList,
2803 icon,
2804 reviewTitle,
2805 reviewSubTitle,
2806 finishTitle,
2807 dummy,
2808 &blindSigningWarning,
2809 choiceCallback);
2810}
2811
2827 const nbgl_contentTagValueList_t *tagValueList,
2828 const nbgl_icon_details_t *icon,
2829 const char *reviewTitle,
2830 const char *reviewSubTitle,
2831 const char *finishTitle,
2832 nbgl_choiceCallback_t choiceCallback)
2833{
2834 nbgl_useCaseReview(operationType,
2835 tagValueList,
2836 icon,
2837 reviewTitle,
2838 reviewSubTitle,
2839 finishTitle,
2840 choiceCallback);
2841}
2842
2859void nbgl_useCaseAddressReview(const char *address,
2860 const nbgl_contentTagValueList_t *additionalTagValueList,
2861 const nbgl_icon_details_t *icon,
2862 const char *reviewTitle,
2863 const char *reviewSubTitle,
2864 nbgl_choiceCallback_t choiceCallback)
2865{
2866 memset(&context, 0, sizeof(UseCaseContext_t));
2867 context.type = ADDRESS_REVIEW_USE_CASE;
2868 context.review.address = address;
2869 context.review.reviewTitle = reviewTitle;
2870 context.review.reviewSubTitle = reviewSubTitle;
2871 context.review.icon = icon;
2872 context.review.onChoice = choiceCallback;
2873 context.currentPage = 0;
2874 // + 4 because 1 page for title, 1 for address and 2 pages at the end for approve/reject
2875 // + 1 if sub Title
2876 context.nbPages = reviewSubTitle ? 5 : 4;
2877 if (additionalTagValueList) {
2878 context.review.tagValueList = PIC(additionalTagValueList);
2879 context.nbPages += additionalTagValueList->nbPairs;
2880 }
2881
2882 displayReviewPage(FORWARD_DIRECTION);
2883}
2884
2894 const char *rejectText,
2895 nbgl_callback_t rejectCallback)
2896{
2897 memset(&context, 0, sizeof(UseCaseContext_t));
2898 context.type = GENERIC_REVIEW_USE_CASE;
2899 context.content.rejectText = rejectText;
2900 context.content.quitCallback = rejectCallback;
2901 context.content.genericContents.nbContents = contents->nbContents;
2902 context.content.genericContents.callbackCallNeeded = contents->callbackCallNeeded;
2903 if (contents->callbackCallNeeded) {
2904 context.content.genericContents.contentGetterCallback = contents->contentGetterCallback;
2905 }
2906 else {
2907 context.content.genericContents.contentsList = PIC(contents->contentsList);
2908 }
2909
2910 startUseCaseContent();
2911}
2912
2921void nbgl_useCaseStatus(const char *message, bool isSuccess, nbgl_callback_t quitCallback)
2922{
2923 UNUSED(isSuccess);
2924 memset(&context, 0, sizeof(UseCaseContext_t));
2925 context.type = STATUS_USE_CASE;
2926 context.stepCallback = quitCallback;
2927 context.currentPage = 0;
2928 context.nbPages = 1;
2929
2931 NULL,
2932 message,
2933 NULL,
2934 statusButtonCallback,
2935 false,
2936 NO_FORCED_TYPE,
2937 false);
2938}
2939
2947 nbgl_callback_t quitCallback)
2948{
2949 const char *msg;
2950 bool isSuccess;
2951 switch (reviewStatusType) {
2953 msg = "Operation signed";
2954 isSuccess = true;
2955 break;
2957 msg = "Operation rejected";
2958 isSuccess = false;
2959 break;
2961 msg = "Transaction signed";
2962 isSuccess = true;
2963 break;
2965 msg = "Transaction rejected";
2966 isSuccess = false;
2967 break;
2969 msg = "Message signed";
2970 isSuccess = true;
2971 break;
2973 msg = "Message rejected";
2974 isSuccess = false;
2975 break;
2977 msg = "Address verified";
2978 isSuccess = true;
2979 break;
2981 msg = "Address verification cancelled";
2982 isSuccess = false;
2983 break;
2984 default:
2985 return;
2986 }
2987 nbgl_useCaseStatus(msg, isSuccess, quitCallback);
2988}
2989
3003 const nbgl_icon_details_t *icon,
3004 const char *reviewTitle,
3005 const char *reviewSubTitle,
3006 nbgl_choiceCallback_t choiceCallback)
3007{
3008 // memorize streaming operation type for future API calls
3009 streamingOpType = operationType;
3010
3011 memset(&context, 0, sizeof(UseCaseContext_t));
3012 context.type = STREAMING_START_REVIEW_USE_CASE;
3013 context.operationType = operationType;
3014 context.review.reviewTitle = reviewTitle;
3015 context.review.reviewSubTitle = reviewSubTitle;
3016 context.review.icon = icon;
3017 context.review.onChoice = choiceCallback;
3018 context.currentPage = 0;
3019 context.nbPages = reviewSubTitle ? 3 : 2; // Start page(s) + trick for review continue
3020
3021 displayStreamingReviewPage(FORWARD_DIRECTION);
3022}
3023
3038 const nbgl_icon_details_t *icon,
3039 const char *reviewTitle,
3040 const char *reviewSubTitle,
3041 nbgl_choiceCallback_t choiceCallback)
3042{
3044 operationType, icon, reviewTitle, reviewSubTitle, &blindSigningWarning, choiceCallback);
3045}
3046
3063 const nbgl_icon_details_t *icon,
3064 const char *reviewTitle,
3065 const char *reviewSubTitle,
3066 const nbgl_warning_t *warning,
3067 nbgl_choiceCallback_t choiceCallback)
3068{
3069 memset(&context, 0, sizeof(UseCaseContext_t));
3070 context.type = STREAMING_START_REVIEW_USE_CASE;
3071 context.operationType = operationType;
3072 context.review.reviewTitle = reviewTitle;
3073 context.review.reviewSubTitle = reviewSubTitle;
3074 context.review.icon = icon;
3075 context.review.onChoice = choiceCallback;
3076 context.currentPage = 0;
3077 context.nbPages = reviewSubTitle ? 3 : 2; // Start page(s) + trick for review continue
3078
3079 // memorize streaming operation type for future API calls
3080 streamingOpType = operationType;
3081
3082 // if no warning at all, it's a simple review
3083 if ((warning == NULL)
3084 || ((warning->predefinedSet == 0) && (warning->info == NULL)
3085 && (warning->reviewDetails == NULL) && (warning->prelude == NULL))) {
3086 displayStreamingReviewPage(FORWARD_DIRECTION);
3087 return;
3088 }
3089 if (warning->predefinedSet == (1 << W3C_NO_THREAT_WARN)) {
3090 operationType |= NO_THREAT_OPERATION;
3091 }
3092 else if (warning->predefinedSet != 0) {
3093 operationType |= RISKY_OPERATION;
3094 }
3095 memset(&reviewWithWarnCtx, 0, sizeof(reviewWithWarnCtx));
3096
3097 reviewWithWarnCtx.type = context.type;
3098 reviewWithWarnCtx.operationType = operationType;
3099 reviewWithWarnCtx.icon = icon;
3100 reviewWithWarnCtx.reviewTitle = reviewTitle;
3101 reviewWithWarnCtx.reviewSubTitle = reviewSubTitle;
3102 reviewWithWarnCtx.choiceCallback = choiceCallback;
3103 reviewWithWarnCtx.warning = warning;
3104
3105 // if the warning contains a prelude, display it first
3106 if (reviewWithWarnCtx.warning->prelude) {
3107 displayPrelude();
3108 }
3109 // display the initial warning only of a risk/threat or blind signing or prelude
3110 else if (HAS_INITIAL_WARNING(warning)) {
3111 displayInitialWarning();
3112 }
3113 else {
3114 displayStreamingReviewPage(FORWARD_DIRECTION);
3115 }
3116}
3117
3132 nbgl_choiceCallback_t choiceCallback,
3133 nbgl_callback_t skipCallback)
3134{
3135 uint8_t curNbDataSets = context.review.nbDataSets;
3136
3137 memset(&context, 0, sizeof(UseCaseContext_t));
3138 context.type = STREAMING_CONTINUE_REVIEW_USE_CASE;
3139 context.operationType = streamingOpType;
3140 context.review.tagValueList = tagValueList;
3141 context.review.onChoice = choiceCallback;
3142 context.currentPage = 0;
3143 context.nbPages = tagValueList->nbPairs + 1; // data + trick for review continue
3144 context.review.skipCallback = skipCallback;
3145 context.review.nbDataSets = curNbDataSets + 1;
3146
3147 displayStreamingReviewPage(FORWARD_DIRECTION);
3148}
3149
3161 nbgl_choiceCallback_t choiceCallback)
3162{
3163 nbgl_useCaseReviewStreamingContinueExt(tagValueList, choiceCallback, NULL);
3164}
3165
3166void nbgl_useCaseReviewStreamingFinish(const char *finishTitle,
3167 nbgl_choiceCallback_t choiceCallback)
3168{
3169 memset(&context, 0, sizeof(UseCaseContext_t));
3170 context.type = STREAMING_FINISH_REVIEW_USE_CASE;
3171 context.operationType = streamingOpType;
3172 context.review.onChoice = choiceCallback;
3173 context.review.finishTitle = finishTitle;
3174 context.currentPage = 0;
3175 context.nbPages = 2; // 2 pages at the end for accept/reject
3176
3177 displayStreamingReviewPage(FORWARD_DIRECTION);
3178}
3179
3185void nbgl_useCaseSpinner(const char *text)
3186{
3187 memset(&context, 0, sizeof(UseCaseContext_t));
3188 context.type = SPINNER_USE_CASE;
3189 context.currentPage = 0;
3190 context.nbPages = 1;
3191
3192 displaySpinner(text);
3193}
3194
3208 const char *message,
3209 const char *subMessage,
3210 const char *confirmText,
3211 const char *cancelText,
3212 nbgl_choiceCallback_t callback)
3213{
3215 icon, message, subMessage, confirmText, cancelText, NULL, callback);
3216};
3217
3233 const char *message,
3234 const char *subMessage,
3235 const char *confirmText,
3236 const char *cancelText,
3237 nbgl_genericDetails_t *details,
3238 nbgl_choiceCallback_t callback)
3239{
3240 memset(&context, 0, sizeof(UseCaseContext_t));
3241 context.type = CHOICE_USE_CASE;
3242 context.choice.icon = icon;
3243 context.choice.message = message;
3244 context.choice.subMessage = subMessage;
3245 context.choice.confirmText = confirmText;
3246 context.choice.cancelText = cancelText;
3247 context.choice.onChoice = callback;
3248 context.choice.details = details;
3249 context.currentPage = 0;
3250 context.nbPages = 2; // 2 pages for confirm/cancel
3251 if (message != NULL) {
3252 context.nbPages++;
3253 // if both icon and subMessage are non NULL, add a page
3254 if ((icon != NULL) && (subMessage != NULL)) {
3255 context.nbPages++;
3256 }
3257 }
3258 if (details != NULL) {
3259 // only the first level of details and BAR_LIST type are supported
3260 if (details->type == BAR_LIST_WARNING) {
3261 context.nbPages += details->barList.nbBars;
3262 }
3263 }
3264
3265 displayChoicePage(FORWARD_DIRECTION);
3266};
3267
3268// On Nano, the advanced variant falls back to the basic one:
3269// - title → message (first text line)
3270// - message → subMessage (second text line; subMessage/gray address is not shown)
3271// - headerIcon is ignored (no top-right button on Nano)
3273 const nbgl_icon_details_t *headerIcon,
3274 const char *title,
3275 const char *message,
3276 const char *subMessage,
3277 const char *confirmText,
3278 const char *cancelText,
3279 nbgl_genericDetails_t *details,
3280 nbgl_choiceCallback_t callback)
3281{
3282 UNUSED(headerIcon);
3283 UNUSED(subMessage);
3285 centerIcon, title, message, confirmText, cancelText, details, callback);
3286}
3287
3301void nbgl_useCaseConfirm(const char *message,
3302 const char *subMessage,
3303 const char *confirmText,
3304 const char *cancelText,
3305 nbgl_callback_t callback)
3306{
3307 memset(&context, 0, sizeof(UseCaseContext_t));
3308 context.type = CONFIRM_USE_CASE;
3309 context.confirm.message = message;
3310 context.confirm.subMessage = subMessage;
3311 context.confirm.confirmText = confirmText;
3312 context.confirm.cancelText = cancelText;
3313 context.confirm.onConfirm = callback;
3314 context.currentPage = 0;
3315 context.nbPages = 1 + 2; // 2 pages at the end for confirm/cancel
3316
3317 displayConfirm(FORWARD_DIRECTION);
3318}
3319
3330 const char *message,
3331 const char *actionText,
3332 nbgl_callback_t callback)
3333{
3334 nbgl_layoutCenteredInfo_t centeredInfo = {0};
3335
3336 UNUSED(actionText);
3337
3338 // memorize callback in context
3339 memset(&context, 0, sizeof(UseCaseContext_t));
3340 context.type = ACTION_USE_CASE;
3341 context.action.actionCallback = callback;
3342
3343 centeredInfo.icon = icon;
3344 centeredInfo.text1 = message;
3345 centeredInfo.style = BOLD_TEXT1_INFO;
3346 nbgl_stepDrawCenteredInfo(0, useCaseActionCallback, NULL, &centeredInfo, false);
3347}
3348
3349#ifdef NBGL_KEYPAD
3368void nbgl_useCaseKeypad(const char *title,
3369 uint8_t minDigits,
3370 uint8_t maxDigits,
3371 bool shuffled,
3372 bool hidden,
3373 nbgl_pinValidCallback_t validatePinCallback,
3374 nbgl_callback_t backCallback)
3375{
3376 nbgl_layoutDescription_t layoutDescription = {0};
3377 int status = -1;
3378
3379 // reset the keypad context
3380 memset(&context, 0, sizeof(KeypadContext_t));
3381 context.type = KEYPAD_USE_CASE;
3382 context.currentPage = 0;
3383 context.nbPages = 1;
3384 context.keypad.validatePin = validatePinCallback;
3385 context.keypad.backCallback = backCallback;
3386 context.keypad.pinMinDigits = minDigits;
3387 context.keypad.pinMaxDigits = maxDigits;
3388 context.keypad.hidden = hidden;
3389 context.keypad.layoutCtx = nbgl_layoutGet(&layoutDescription);
3390
3391 // add keypad
3392 status = nbgl_layoutAddKeypad(context.keypad.layoutCtx, keypadCallback, title, shuffled);
3393 if (status < 0) {
3394 LOG_WARN(
3395 USE_CASE_LOGGER, "nbgl_useCaseKeypad(): layout setup failed (status=%d)\n", status);
3396 return;
3397 }
3398 context.keypad.keypadIndex = status;
3399 // add digits
3400 status = nbgl_layoutAddKeypadContent(context.keypad.layoutCtx, hidden, maxDigits, "");
3401 if (status < 0) {
3402 LOG_WARN(
3403 USE_CASE_LOGGER, "nbgl_useCaseKeypad(): layout setup failed (status=%d)\n", status);
3404 return;
3405 }
3406
3407 nbgl_layoutDraw(context.keypad.layoutCtx);
3408 if (context.keypad.backCallback != NULL) {
3409 // force backspace to be visible at first digit, to be used as quit
3410 nbgl_layoutUpdateKeypad(context.keypad.layoutCtx, context.keypad.keypadIndex, false, true);
3411 }
3412 nbgl_refresh();
3413}
3414#endif // NBGL_KEYPAD
3415
3416#ifdef NBGL_KEYBOARD
3440void nbgl_useCaseKeyboard(const nbgl_keyboardParams_t *params, nbgl_callback_t backCallback)
3441{
3442 // clang-format off
3443 nbgl_layoutDescription_t layoutDescription = {0};
3444 nbgl_layoutCenteredInfo_t centeredInfo = {.text1 = params->title,
3445 .onTop = true};
3447 .indication = LEFT_ARROW | RIGHT_ARROW};
3448 nbgl_layoutKbd_t kbdInfo = {.callback = &keyboardCallback,
3449 .mode = params->mode,
3450 .lettersOnly = params->lettersOnly};
3451 int status = -1;
3452 // clang-format on
3453
3454 // reset the keypad context
3455 memset(&context, 0, sizeof(UseCaseContext_t));
3456 context.type = KEYBOARD_USE_CASE;
3457 context.currentPage = 0;
3458 context.nbPages = 1;
3459 context.keyboard.entryBuffer = PIC(params->entryBuffer);
3460 context.keyboard.entryMaxLen = params->entryMaxLen;
3461 context.keyboard.entryBuffer[0] = '\0';
3462
3463 context.keyboard.content.type = params->type;
3464
3465 // memorize context
3466 context.keyboard.backCallback = PIC(backCallback);
3467
3468 switch (params->type) {
3470 context.keyboard.actionCallback = PIC(params->confirmationParams.onButtonCallback);
3471 break;
3473 // No need for 'Validate' or 'Case switch' buttons
3474 kbdInfo.keyMask = (1 << VALIDATE_INDEX) | (1 << SHIFT_KEY_INDEX);
3475 context.keyboard.getSuggestButtons
3477 context.keyboard.onButtonCallback = PIC(params->suggestionParams.onButtonCallback);
3478 context.keyboard.content.suggestionButtons = (nbgl_layoutSuggestionButtons_t){
3479 .buttons = PIC(params->suggestionParams.buttons),
3480 .firstButtonToken = params->suggestionParams.firstButtonToken,
3481 };
3482 break;
3483 default:
3484 return;
3485 }
3486
3487 // get a layout
3488 context.keyboard.layoutCtx = nbgl_layoutGet(&layoutDescription);
3489
3490 // add description
3491 nbgl_layoutAddCenteredInfo(context.keyboard.layoutCtx, &centeredInfo);
3492
3493 // Add keyboard
3494 status = nbgl_layoutAddKeyboard(context.keyboard.layoutCtx, &kbdInfo);
3495 if (status < 0) {
3496 LOG_WARN(
3497 USE_CASE_LOGGER, "nbgl_useCaseKeyboard(): layout setup failed (status=%d)\n", status);
3498 return;
3499 }
3500 context.keyboard.keyboardIndex = status;
3501
3502 // add empty entered text
3503 status = nbgl_layoutAddEnteredText(context.keyboard.layoutCtx, "", true);
3504 if (status < 0) {
3505 LOG_WARN(
3506 USE_CASE_LOGGER, "nbgl_useCaseKeyboard(): layout setup failed (status=%d)\n", status);
3507 return;
3508 }
3509 context.keyboard.textIndex = status;
3510
3511 nbgl_layoutAddNavigation(context.keyboard.layoutCtx, &navInfo);
3512
3513 nbgl_layoutDraw(context.keyboard.layoutCtx);
3514 nbgl_refresh();
3515}
3516#endif // NBGL_KEYBOARD
3517
3518#endif // HAVE_SE_TOUCH
3519#endif // NBGL_USE_CASE
nbgl_contentCenteredInfoStyle_t
possible styles for Centered Info Area
@ BOLD_TEXT1_INFO
bold is used for text1 (but '\b' can switch to regular)
@ BUTTON_INFO
bold is used for text1 and text2 as a white button
@ REGULAR_INFO
both texts regular (but '\b' can switch to bold)
@ 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
@ USE_CASE_LOGGER
Definition nbgl_debug.h:35
#define LOG_DEBUG(__logger,...)
Definition nbgl_debug.h:86
static void actionCallback(nbgl_step_t stepCtx, nbgl_buttonEvent_t event)
Definition nbgl_flow.c:133
static void drawStep(FlowContext_t *ctx, nbgl_stepPosition_t pos, bool modal, const nbgl_icon_details_t *icon, const char *txt, const char *subTxt)
Definition nbgl_flow.c:103
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
int nbgl_layoutAddKeyboard(nbgl_layout_t *layout, const nbgl_layoutKbd_t *kbdInfo)
Creates a keyboard on bottom of the screen, with the given configuration.
int nbgl_layoutUpdateKeyboard(nbgl_layout_t *layout, uint8_t index, uint32_t keyMask)
Updates an existing keyboard on bottom of the screen, with the given configuration.
int nbgl_layoutUpdateKeypad(nbgl_layout_t *layout, uint8_t index, bool enableValidate, bool enableBackspace)
Updates an existing keypad on bottom of the screen, with the given configuration.
@ HORIZONTAL_NAV
'<' and '>' are displayed, to navigate between pages and steps
int nbgl_layoutAddKeypadContent(nbgl_layout_t *layout, bool hidden, uint8_t nbDigits, const char *text)
Adds an area with a title and a placeholder for hidden digits on top of a keypad, to represent the en...
int nbgl_layoutUpdateEnteredText(nbgl_layout_t *layout, uint8_t index, const char *text)
Updates an existing "text entry" area, created with nbgl_layoutAddEnteredText()
@ LEFT_ARROW
left arrow is used
@ RIGHT_ARROW
right arrow is used
int nbgl_layoutAddNavigation(nbgl_layout_t *layout, nbgl_layoutNavigation_t *info)
Creates navigation arrows on side(s) of the screen.
int nbgl_layoutAddEnteredText(nbgl_layout_t *layout, const char *text, bool lettersOnly)
Adds a "text entry" area under the previously entered object. The max number of really displayable ch...
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.
#define NB_MAX_SUGGESTION_BUTTONS
void * nbgl_layout_t
type shared externally
int nbgl_layoutUpdateKeypadContent(nbgl_layout_t *layout, bool hidden, uint8_t nbActiveDigits, const char *text)
Updates an existing set of hidden digits, with the given configuration.
nbgl_layout_t * nbgl_layoutGet(const nbgl_layoutDescription_t *description)
returns a layout of the given type. The layout is reset
int nbgl_layoutAddKeypad(nbgl_layout_t *layout, keyboardCallback_t callback, const char *text, bool shuffled)
Adds a keypad on bottom of the screen, with the associated callback.
@ KEYBOARD_WITH_BUTTON
text entry area + confirmation button
@ KEYBOARD_WITH_SUGGESTIONS
text entry area + suggestion buttons
int nbgl_layoutRelease(nbgl_layout_t *layout)
Release the layout obtained with nbgl_layoutGet()
#define SHIFT_KEY_INDEX
Definition nbgl_obj.h:55
void nbgl_refresh(void)
#define KEYBOARD_MAX_TITLE
Definition nbgl_obj.h:56
nbgl_buttonEvent_t
Definition nbgl_obj.h:193
@ BUTTON_BOTH_PRESSED
Sent when both buttons are released.
Definition nbgl_obj.h:200
@ BUTTON_LEFT_PRESSED
Sent when Left button is released.
Definition nbgl_obj.h:194
@ BUTTON_RIGHT_PRESSED
Send when Right button is released.
Definition nbgl_obj.h:195
#define BACKSPACE_KEY
Definition nbgl_obj.h:27
#define VALIDATE_INDEX
Definition nbgl_obj.h:54
#define VALIDATE_KEY
Definition nbgl_obj.h:28
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)
draw a step page with a switch and navigation arrows to navigate to other pages.
Definition nbgl_step.c:680
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)
draw a step page with a menu list and navigation arrows to parse it. This step must be alone
Definition nbgl_step.c:623
#define ACTION_ON_ANY_BUTTON
When action callback applies only on both button press.
Definition nbgl_step.h:75
int nbgl_stepRelease(nbgl_step_t step)
Release the step obtained with any of the nbgl_stepDrawXXX() functions.
Definition nbgl_step.c:732
#define FORWARD_DIRECTION
When the flow is navigated from last to first step.
Definition nbgl_step.h:71
@ 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
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)
draws a text type step, that can be multi-pages, depending of the length of text and subText....
Definition nbgl_step.c:458
nbgl_step_t nbgl_stepDrawCenteredInfo(nbgl_stepPosition_t pos, nbgl_stepButtonCallback_t onActionCallback, nbgl_screenTickerConfiguration_t *ticker, nbgl_layoutCenteredInfo_t *info, bool modal)
draw a step with a centered info (icon + text). This is always a single page step
Definition nbgl_step.c:567
#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:211
@ ON_STATE
Definition nbgl_types.h:213
@ OFF_STATE
Definition nbgl_types.h:212
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 to add a Skip bu...
void nbgl_useCaseGenericSettings(const char *appName, uint8_t initPage, const nbgl_genericContents_t *settingContents, const nbgl_contentInfoList_t *infosList, nbgl_callback_t quitCallback)
uint32_t nbgl_operationType_t
This mask is used to describe the type of operation to review with additional options It is a mask of...
void nbgl_useCaseReview(nbgl_operationType_t operationType, const nbgl_contentTagValueList_t *tagValueList, const nbgl_icon_details_t *icon, const char *reviewTitle, const char *reviewSubTitle, const char *finishTitle, nbgl_choiceCallback_t choiceCallback)
void nbgl_useCaseAction(const nbgl_icon_details_t *icon, const char *message, const char *actionText, nbgl_callback_t callback)
uint8_t nbgl_useCaseGetNbTagValuesInPage(uint8_t nbPairs, const nbgl_contentTagValueList_t *tagValueList, uint8_t startIndex, bool *requireSpecificDisplay)
uint8_t nbgl_useCaseGetNbPagesForTagValueList(const nbgl_contentTagValueList_t *tagValueList)
void(* nbgl_pinValidCallback_t)(const uint8_t *pin, uint8_t pinLen)
prototype of pin validation callback function
void nbgl_useCaseReviewStreamingStart(nbgl_operationType_t operationType, const nbgl_icon_details_t *icon, const char *reviewTitle, const char *reviewSubTitle, nbgl_choiceCallback_t choiceCallback)
void nbgl_useCaseHomeAndSettings(const char *appName, const nbgl_icon_details_t *appIcon, const char *tagline, const uint8_t initSettingPage, const nbgl_genericContents_t *settingContents, const nbgl_contentInfoList_t *infosList, const nbgl_homeAction_t *action, nbgl_callback_t quitCallback)
#define RISKY_OPERATION
This is to use in nbgl_operationType_t when the operation is concerned by an internal warning....
uint8_t nbgl_useCaseGetNbInfosInPage(uint8_t nbInfos, const nbgl_contentInfoList_t *infosList, uint8_t startIndex, bool withNav)
void(* nbgl_choiceCallback_t)(bool confirm)
prototype of choice callback function
uint8_t nbgl_useCaseGetNbBarsInPage(uint8_t nbBars, const nbgl_contentBarsList_t *barsList, uint8_t startIndex, bool withNav)
void nbgl_useCaseNavigableContent(const char *title, uint8_t initPage, uint8_t nbPages, nbgl_callback_t quitCallback, nbgl_navCallback_t navCallback, nbgl_layoutTouchCallback_t controlsCallback)
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)
const char * title
Nano only and used in the horizontal flow: per-content header text. Same semantics as nbgl_contentRad...
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)
const nbgl_icon_details_t * selectionIcon
uint8_t nbChoices
number of choices
const char * title
Nano only and used in the horizontal flow (i.e. vertical is false): per-content header text shown abo...
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