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 *title;
52 const char *message;
53 const char *subMessage;
54 const char *confirmText;
55 const char *cancelText;
56 nbgl_choiceCallback_t onChoice;
57 const nbgl_genericDetails_t *details;
58} ChoiceContext_t;
59
60typedef struct ConfirmContext_s {
61 const char *message;
62 const char *subMessage;
63 const char *confirmText;
64 const char *cancelText;
65 nbgl_callback_t onConfirm;
66 nbgl_step_t currentStep;
67} ConfirmContext_t;
68
69typedef struct ContentContext_s {
70 const char *title; // For CHOICES_LIST /BARS_LIST
71 nbgl_genericContents_t genericContents;
72 const char *rejectText;
73 nbgl_layoutTouchCallback_t controlsCallback;
74 nbgl_navCallback_t navCallback;
75 nbgl_callback_t quitCallback;
76} ContentContext_t;
77
78typedef struct HomeContext_s {
79 const char *appName;
80 const nbgl_icon_details_t *appIcon;
81 const char *tagline;
82 const nbgl_genericContents_t *settingContents;
83 const nbgl_contentInfoList_t *infosList;
84 const nbgl_homeAction_t *homeAction;
85 nbgl_callback_t quitCallback;
86} HomeContext_t;
87
88typedef struct ActionContext_s {
90} ActionContext_t;
91
92#ifdef NBGL_KEYPAD
93typedef struct KeypadContext_s {
94 uint8_t pinEntry[8];
95 uint8_t pinLen;
96 uint8_t pinMinDigits;
97 uint8_t pinMaxDigits;
98 nbgl_layout_t *layoutCtx;
99 bool hidden;
100 uint8_t keypadIndex;
101 nbgl_pinValidCallback_t validatePin;
102 nbgl_callback_t backCallback;
103} KeypadContext_t;
104#endif
105
106#ifdef NBGL_KEYBOARD
107typedef struct KeyboardContext_s {
109 char *entryBuffer;
110 uint16_t entryMaxLen;
111 nbgl_layout_t *layoutCtx;
112 uint8_t keyboardIndex;
113 uint8_t textIndex;
115 nbgl_callback_t backCallback;
116 nbgl_keyboardButtonsCallback_t getSuggestButtons;
117 nbgl_layoutTouchCallback_t onButtonCallback;
118} KeyboardContext_t;
119#endif
120
121typedef enum {
122 NONE_USE_CASE,
123 SPINNER_USE_CASE,
124 REVIEW_USE_CASE,
125 GENERIC_REVIEW_USE_CASE,
126 ADDRESS_REVIEW_USE_CASE,
127 STREAMING_START_REVIEW_USE_CASE,
128 STREAMING_CONTINUE_REVIEW_USE_CASE,
129 STREAMING_FINISH_REVIEW_USE_CASE,
130 CHOICE_USE_CASE,
131 STATUS_USE_CASE,
132 CONFIRM_USE_CASE,
133 KEYPAD_USE_CASE,
134 KEYBOARD_USE_CASE,
135 HOME_USE_CASE,
136 INFO_USE_CASE,
137 SETTINGS_USE_CASE,
138 GENERIC_SETTINGS,
139 CONTENT_USE_CASE,
140 ACTION_USE_CASE
141} ContextType_t;
142
143typedef struct UseCaseContext_s {
144 ContextType_t type;
145 nbgl_operationType_t operationType;
146 uint8_t nbPages;
147 uint8_t currentPage;
148 uint8_t firstPairPage;
149 bool forceAction;
151 stepCallback;
152 union {
153 ReviewContext_t review;
154 ChoiceContext_t choice;
155 ConfirmContext_t confirm;
156 HomeContext_t home;
157 ContentContext_t content;
158#ifdef NBGL_KEYPAD
159 KeypadContext_t keypad;
160#endif
161#ifdef NBGL_KEYBOARD
162 KeyboardContext_t keyboard;
163#endif
164 ActionContext_t action;
165 };
166} UseCaseContext_t;
167
168typedef struct PageContent_s {
169 bool isSwitch;
170 const char *text;
171 const char *subText;
172 const nbgl_icon_details_t *icon;
173 const nbgl_contentValueExt_t *extension;
174 nbgl_state_t state;
175 bool isCenteredInfo;
176 bool isAction;
177 bool bottomIcon;
180} PageContent_t;
181
182typedef struct ReviewWithWarningContext_s {
183 ContextType_t type;
184 nbgl_operationType_t operationType;
185 const nbgl_contentTagValueList_t *tagValueList;
186 const nbgl_icon_details_t *icon;
187 const char *reviewTitle;
188 const char *reviewSubTitle;
189 const char *finishTitle;
190 const nbgl_warning_t *warning;
191 nbgl_choiceCallback_t choiceCallback;
192 uint8_t securityReportLevel; // level 1 is the first level of menus
193 bool isIntro; // set to true during intro (before actual review)
194 uint8_t warningPage;
195 uint8_t nbWarningPages;
196 uint8_t firstWarningPage;
197 uint8_t barDetailIdx; // index of the bar whose detail is displayed
198} ReviewWithWarningContext_t;
199
200typedef enum {
201 NO_FORCED_TYPE = 0,
202 FORCE_BUTTON,
203 FORCE_CENTERED_INFO
204} ForcedType_t;
205
206/**********************
207 * STATIC VARIABLES
208 **********************/
209static UseCaseContext_t context;
210
211static ReviewWithWarningContext_t reviewWithWarnCtx;
212// configuration of warning when using @ref nbgl_useCaseReviewBlindSigning()
213static const nbgl_warning_t blindSigningWarning = {.predefinedSet = (1 << BLIND_SIGNING_WARN)};
214
215// Operation type for streaming (because the one in 'context' is reset at each streaming API call)
216nbgl_operationType_t streamingOpType;
217
218/**********************
219 * STATIC FUNCTIONS
220 **********************/
221static void displayReviewPage(nbgl_stepPosition_t pos);
222static void displayStreamingReviewPage(nbgl_stepPosition_t pos);
223static void displayHomePage(nbgl_stepPosition_t pos);
224static void displayInfoPage(nbgl_stepPosition_t pos);
225static void displaySettingsPage(nbgl_stepPosition_t pos, bool toogle_state);
226static void displayChoicePage(nbgl_stepPosition_t pos);
227static void displayConfirm(nbgl_stepPosition_t pos);
228static void displayContent(nbgl_stepPosition_t pos, bool toogle_state);
229static void displaySpinner(const char *text);
230
231static void startUseCaseHome(void);
232static void startUseCaseInfo(void);
233static void startUseCaseSettings(void);
234static void startUseCaseSettingsAtPage(uint8_t initSettingPage);
235static void startUseCaseContent(void);
236
237static void statusTickerCallback(void);
238
239static void displayExtensionStep(nbgl_stepPosition_t pos);
240static void displayWarningStep(void);
241
242// Simple helper to get the number of elements inside a nbgl_content_t
243static uint8_t getContentNbElement(const nbgl_content_t *content)
244{
245 switch (content->type) {
246 case CENTERED_INFO:
247 return 1;
248 case INFO_BUTTON:
249 return 1;
250 case TAG_VALUE_LIST:
251 return content->content.tagValueList.nbPairs;
253 // last element is for Confirm page
254 return content->content.tagValueConfirm.tagValueList.nbPairs + 1;
255 case SWITCHES_LIST:
256 return content->content.switchesList.nbSwitches;
257 case INFOS_LIST:
258 return content->content.infosList.nbInfos;
259 case CHOICES_LIST:
260 return content->content.choicesList.nbChoices;
261 case BARS_LIST:
262 return content->content.barsList.nbBars;
263 default:
264 return 0;
265 }
266}
267
268// Helper to retrieve the content inside a nbgl_genericContents_t using
269// either the contentsList or using the contentGetterCallback
270static const nbgl_content_t *getContentAtIdx(const nbgl_genericContents_t *genericContents,
271 uint8_t contentIdx,
272 nbgl_content_t *content)
273{
274 nbgl_pageContent_t pageContent = {0};
275 if (contentIdx >= genericContents->nbContents) {
276 LOG_DEBUG(USE_CASE_LOGGER, "No content available at %d\n", contentIdx);
277 return NULL;
278 }
279
280 if (genericContents->callbackCallNeeded) {
281 if (content == NULL) {
282 LOG_DEBUG(USE_CASE_LOGGER, "Invalid content variable\n");
283 return NULL;
284 }
285 // Retrieve content through callback, but first memset the content.
286 memset(content, 0, sizeof(nbgl_content_t));
287 if (context.content.navCallback) {
288 if (context.content.navCallback(contentIdx, &pageContent) == true) {
289 // Copy the Page Content to the Content variable
290 content->type = pageContent.type;
291 switch (content->type) {
292 case CENTERED_INFO:
293 content->content.centeredInfo = pageContent.centeredInfo;
294 break;
295 case INFO_BUTTON:
296 content->content.infoButton = pageContent.infoButton;
297 break;
298 case TAG_VALUE_LIST:
299 content->content.tagValueList = pageContent.tagValueList;
300 break;
302 content->content.tagValueConfirm = pageContent.tagValueConfirm;
303 break;
304 case SWITCHES_LIST:
305 content->content.switchesList = pageContent.switchesList;
306 break;
307 case INFOS_LIST:
308 content->content.infosList = pageContent.infosList;
309 break;
310 case CHOICES_LIST:
311 content->content.choicesList = pageContent.choicesList;
312 break;
313 case BARS_LIST:
314 content->content.barsList = pageContent.barsList;
315 break;
316 default:
317 LOG_DEBUG(USE_CASE_LOGGER, "Invalid content type\n");
318 return NULL;
319 }
320 }
321 else {
322 LOG_DEBUG(USE_CASE_LOGGER, "Error getting page content\n");
323 return NULL;
324 }
325 }
326 else {
327 genericContents->contentGetterCallback(contentIdx, content);
328 }
329 return content;
330 }
331 else {
332 // Retrieve content through list
333 return PIC(&genericContents->contentsList[contentIdx]);
334 }
335}
336
337// Helper to retrieve the content inside a nbgl_genericContents_t using
338// either the contentsList or using the contentGetterCallback
339static const nbgl_content_t *getContentElemAtIdx(uint8_t elemIdx,
340 uint8_t *elemContentIdx,
341 nbgl_content_t *content)
342{
343 const nbgl_genericContents_t *genericContents = NULL;
344 const nbgl_content_t *p_content = NULL;
345 uint8_t nbPages = 0;
346 uint8_t elemNbPages = 0;
347
348 switch (context.type) {
349 case SETTINGS_USE_CASE:
350 case HOME_USE_CASE:
351 case GENERIC_SETTINGS:
352 genericContents = context.home.settingContents;
353 break;
354 case CONTENT_USE_CASE:
355 case GENERIC_REVIEW_USE_CASE:
356 genericContents = &context.content.genericContents;
357 break;
358 default:
359 return NULL;
360 }
361 for (unsigned int i = 0; i < genericContents->nbContents; i++) {
362 p_content = getContentAtIdx(genericContents, i, content);
363 elemNbPages = getContentNbElement(p_content);
364 if (nbPages + elemNbPages > elemIdx) {
365 *elemContentIdx = context.currentPage - nbPages;
366 break;
367 }
368 nbPages += elemNbPages;
369 }
370
371 return p_content;
372}
373
374static const char *getChoiceName(uint8_t choiceIndex)
375{
376 uint8_t elemIdx;
377 uint8_t nbValues;
378 const nbgl_content_t *p_content = NULL;
379 nbgl_content_t content = {0};
380 nbgl_contentRadioChoice_t *contentChoices = NULL;
381 nbgl_contentBarsList_t *contentBars = NULL;
382 char **names = NULL;
383
384 p_content = getContentElemAtIdx(context.currentPage, &elemIdx, &content);
385 if (p_content == NULL) {
387 "getChoiceName(): p_content is NULL for page %d\n",
388 context.currentPage);
389 return NULL;
390 }
391 switch (p_content->type) {
392 case CHOICES_LIST:
393 contentChoices = (nbgl_contentRadioChoice_t *) PIC(&p_content->content.choicesList);
394 names = (char **) PIC(contentChoices->names);
395 nbValues = contentChoices->nbChoices;
396 break;
397 case BARS_LIST:
398 contentBars = ((nbgl_contentBarsList_t *) PIC(&p_content->content.barsList));
399 names = (char **) PIC(contentBars->barTexts);
400 nbValues = contentBars->nbBars;
401 break;
402 default:
403 // Not supported as vertical MenuList
404 return NULL;
405 }
406 if (choiceIndex >= nbValues) {
407 // Last item is always "Back" button
408 return "Back";
409 }
410 return (const char *) PIC(names[choiceIndex]);
411}
412
413static void onChoiceSelected(uint8_t choiceIndex)
414{
415 uint8_t elemIdx;
416 uint8_t token = 255;
417 const nbgl_content_t *p_content = NULL;
418 nbgl_content_t content = {0};
419 nbgl_contentRadioChoice_t *contentChoices = NULL;
420 nbgl_contentBarsList_t *contentBars = NULL;
421
422 p_content = getContentElemAtIdx(context.currentPage, &elemIdx, &content);
423 if (p_content == NULL) {
425 "onChoiceSelected(): p_content is NULL for page %d\n",
426 context.currentPage);
427 return;
428 }
429 switch (p_content->type) {
430 case CHOICES_LIST:
431 contentChoices = (nbgl_contentRadioChoice_t *) PIC(&p_content->content.choicesList);
432 if (choiceIndex < contentChoices->nbChoices) {
433 token = contentChoices->token;
434 }
435 break;
436 case BARS_LIST:
437 contentBars = ((nbgl_contentBarsList_t *) PIC(&p_content->content.barsList));
438 if (choiceIndex < contentBars->nbBars) {
439 token = ((const uint8_t *) PIC(contentBars->tokens))[choiceIndex];
440 }
441 break;
442 default:
443 // Not supported as vertical MenuList
444 break;
445 }
446 if (token != 255) {
447 // Prefer the per-content action callback (used by settings/content
448 // flows). Forward both the choice index and the absolute page so the
449 // app can react identically to the horizontal flow.
450 if (p_content->contentActionCallback != NULL) {
452 actionCallback(token, choiceIndex, context.currentPage);
453 return;
454 }
455 if (context.content.controlsCallback != NULL) {
456 context.content.controlsCallback(token, choiceIndex);
457 return;
458 }
459 }
460 // Back entry (the extra item appended by drawStep at index nbChoices) or
461 // unknown selection: mirror the horizontal-flow Back behaviour in
462 // displaySettingsPage so SETTINGS_USE_CASE / GENERIC_SETTINGS / content
463 // callers all exit consistently.
464 if ((context.type == GENERIC_SETTINGS) && (context.home.quitCallback != NULL)) {
465 context.home.quitCallback();
466 return;
467 }
468 if (context.type == SETTINGS_USE_CASE) {
469 startUseCaseHome();
470 return;
471 }
472 if (context.content.quitCallback != NULL) {
473 context.content.quitCallback();
474 }
475}
476
477static void getPairData(const nbgl_contentTagValueList_t *tagValueList,
478 uint8_t index,
479 const char **item,
480 const char **value,
481 const nbgl_contentValueExt_t **extension,
482 const nbgl_icon_details_t **icon,
483 bool *isCenteredInfo)
484{
485 const nbgl_contentTagValue_t *pair;
486
487 if (tagValueList->pairs != NULL) {
488 pair = PIC(&tagValueList->pairs[index]);
489 }
490 else {
491 pair = PIC(tagValueList->callback(index));
492 }
493 *item = pair->item;
494 *value = pair->value;
495 if (pair->aliasValue) {
496 *extension = pair->extension;
497 }
498 else if (pair->centeredInfo) {
499 *isCenteredInfo = true;
500 *icon = pair->valueIcon;
501 }
502 else {
503 *extension = NULL;
504 }
505}
506
507static void onReviewAccept(void)
508{
509 if (context.review.onChoice) {
510 context.review.onChoice(true);
511 }
512}
513
514static void onReviewReject(void)
515{
516 if (context.review.onChoice) {
517 context.review.onChoice(false);
518 }
519}
520
521static void onChoiceAccept(void)
522{
523 if (context.choice.onChoice) {
524 context.choice.onChoice(true);
525 }
526}
527
528static void onChoiceReject(void)
529{
530 if (context.choice.onChoice) {
531 context.choice.onChoice(false);
532 }
533}
534
535static void onConfirmAccept(void)
536{
537 if (context.confirm.currentStep) {
538 nbgl_stepRelease(context.confirm.currentStep);
539 }
540 if (context.confirm.onConfirm) {
541 context.confirm.onConfirm();
542 }
543}
544
545static void onConfirmReject(void)
546{
547 if (context.confirm.currentStep) {
548 nbgl_stepRelease(context.confirm.currentStep);
550 nbgl_refresh();
551 }
552}
553
554static void onSwitchAction(void)
555{
556 const nbgl_contentSwitch_t *contentSwitch = NULL;
557 const nbgl_content_t *p_content = NULL;
558 nbgl_content_t content = {0};
559 uint8_t elemIdx;
560
561 p_content = getContentElemAtIdx(context.currentPage, &elemIdx, &content);
562 if ((p_content == NULL) || (p_content->type != SWITCHES_LIST)) {
564 "onSwitchAction(): p_content is NULL or not a switches list for page %d\n",
565 context.currentPage);
566 return;
567 }
568 contentSwitch
569 = &((const nbgl_contentSwitch_t *) PIC(p_content->content.switchesList.switches))[elemIdx];
570 switch (context.type) {
571 case SETTINGS_USE_CASE:
572 case HOME_USE_CASE:
573 case GENERIC_SETTINGS:
574 displaySettingsPage(FORWARD_DIRECTION, true);
575 break;
576 case CONTENT_USE_CASE:
577 case GENERIC_REVIEW_USE_CASE:
578 displayContent(FORWARD_DIRECTION, true);
579 break;
580 default:
581 break;
582 }
583 if (p_content->contentActionCallback != NULL) {
585 actionCallback(contentSwitch->token,
586 (contentSwitch->initState == ON_STATE) ? OFF_STATE : ON_STATE,
587 context.currentPage);
588 }
589 else if (context.content.controlsCallback != NULL) {
590 context.content.controlsCallback(contentSwitch->token, 0);
591 }
592}
593
594static void drawStep(nbgl_stepPosition_t pos,
595 const nbgl_icon_details_t *icon,
596 const char *txt,
597 const char *subTxt,
598 nbgl_stepButtonCallback_t onActionCallback,
599 bool modal,
600 ForcedType_t forcedType,
601 bool bottomIcon)
602{
603 uint8_t elemIdx;
604 nbgl_step_t newStep = NULL;
605 const nbgl_content_t *p_content = NULL;
606 nbgl_content_t content = {0};
607 nbgl_contentRadioChoice_t *contentChoices = NULL;
608 nbgl_contentBarsList_t *contentBars = NULL;
609 nbgl_screenTickerConfiguration_t *p_ticker = NULL;
610 nbgl_layoutMenuList_t list = {0};
611 nbgl_screenTickerConfiguration_t ticker = {.tickerCallback = PIC(statusTickerCallback),
612 .tickerIntervale = 0, // not periodic
613 .tickerValue = STATUS_SCREEN_DURATION};
614
615 pos |= GET_POS_OF_STEP(context.currentPage, context.nbPages);
616 // if we are in streaming+skip case, enable going backward even for first tag/value of the set
617 // (except the first set) because the set starts with a "skip" page
618 if ((context.type == STREAMING_CONTINUE_REVIEW_USE_CASE)
619 && (context.review.skipCallback != NULL) && (context.review.nbDataSets > 1)) {
620 pos |= LAST_STEP;
621 }
622 if ((context.type == STATUS_USE_CASE) || (context.type == SPINNER_USE_CASE)) {
623 p_ticker = &ticker;
624 }
625 if ((context.type == CONFIRM_USE_CASE) && (context.confirm.currentStep != NULL)) {
626 nbgl_stepRelease(context.confirm.currentStep);
627 }
628
629 if (txt == NULL) {
630 p_content = getContentElemAtIdx(context.currentPage, &elemIdx, &content);
631 if (p_content) {
632 switch (p_content->type) {
633 case CHOICES_LIST:
634 contentChoices
635 = ((nbgl_contentRadioChoice_t *) PIC(&p_content->content.choicesList));
636 list.nbChoices = contentChoices->nbChoices + 1; // For Back button
637 list.selectedChoice = contentChoices->initChoice;
638 list.callback = getChoiceName;
639 newStep = nbgl_stepDrawMenuList(onChoiceSelected, p_ticker, &list, modal);
640 break;
641 case BARS_LIST:
642 contentBars = ((nbgl_contentBarsList_t *) PIC(&p_content->content.barsList));
643 list.nbChoices = contentBars->nbBars + 1; // For Back button
644 list.selectedChoice = 0;
645 list.callback = getChoiceName;
646 newStep = nbgl_stepDrawMenuList(onChoiceSelected, p_ticker, &list, modal);
647 break;
648 default:
649 // Not supported as vertical MenuList
650 break;
651 }
652 }
653 }
654 else if ((icon == NULL) && (forcedType != FORCE_CENTERED_INFO)) {
656 if (subTxt != NULL) {
657 style = (forcedType == FORCE_BUTTON) ? BUTTON_INFO : BOLD_TEXT1_INFO;
658 }
659 else {
660 style = REGULAR_INFO;
661 }
662 newStep = nbgl_stepDrawText(pos, onActionCallback, p_ticker, txt, subTxt, style, modal);
663 }
664 else {
665 nbgl_layoutCenteredInfo_t info = {0};
666 info.icon = icon;
667 info.text1 = txt;
668 info.text2 = subTxt;
669 info.bottomIcon = bottomIcon;
670 if ((subTxt != NULL) || (context.stepCallback != NULL) || context.forceAction) {
671 info.style = BOLD_TEXT1_INFO;
672 }
673 else {
674 info.style = REGULAR_INFO;
675 }
676 newStep = nbgl_stepDrawCenteredInfo(pos, onActionCallback, p_ticker, &info, modal);
677 }
678 if (context.type == CONFIRM_USE_CASE) {
679 context.confirm.currentStep = newStep;
680 }
681}
682
683static void drawSwitchStep(nbgl_stepPosition_t pos,
684 const char *title,
685 const char *description,
686 bool state,
687 nbgl_stepButtonCallback_t onActionCallback,
688 bool modal)
689{
690 nbgl_layoutSwitch_t switchInfo = {0};
691
692 pos |= GET_POS_OF_STEP(context.currentPage, context.nbPages);
693 switchInfo.initState = state;
694 switchInfo.text = title;
695 switchInfo.subText = description;
696 nbgl_stepDrawSwitch(pos, onActionCallback, NULL, &switchInfo, modal);
697}
698
699static bool buttonGenericCallback(nbgl_buttonEvent_t event, nbgl_stepPosition_t *pos)
700{
701 uint8_t elemIdx;
702 uint8_t token = 0;
703 uint8_t index = 0;
704 const nbgl_content_t *p_content = NULL;
705 nbgl_content_t content = {0};
706
707 if (event == BUTTON_LEFT_PRESSED) {
708 if (context.currentPage > 0) {
709 context.currentPage--;
710 }
711 // in streaming+skip case, it is allowed to go backward at the first tag/value, except for
712 // the first set
713 else if ((context.type != STREAMING_CONTINUE_REVIEW_USE_CASE)
714 || (context.review.skipCallback == NULL) || (context.review.nbDataSets == 1)) {
715 // Drop the event
716 return false;
717 }
718 *pos = BACKWARD_DIRECTION;
719 }
720 else if (event == BUTTON_RIGHT_PRESSED) {
721 if (context.currentPage < (int) (context.nbPages - 1)) {
722 context.currentPage++;
723 }
724 else {
725 // Drop the event
726 return false;
727 }
728 *pos = FORWARD_DIRECTION;
729 }
730 else {
731 if (event == BUTTON_BOTH_PRESSED) {
732 if (context.stepCallback != NULL) {
733 context.stepCallback();
734 }
735 else if ((context.type == CONTENT_USE_CASE) || (context.type == SETTINGS_USE_CASE)
736 || (context.type == GENERIC_SETTINGS)
737 || (context.type == GENERIC_REVIEW_USE_CASE)) {
738 p_content = getContentElemAtIdx(context.currentPage, &elemIdx, &content);
739 if (p_content != NULL) {
740 switch (p_content->type) {
741 case CENTERED_INFO:
742 // No associated callback
743 return false;
744 case INFO_BUTTON:
745 token = p_content->content.infoButton.buttonToken;
746 break;
747 case SWITCHES_LIST:
748 token = p_content->content.switchesList.switches->token;
749 break;
750 case BARS_LIST:
751 if (elemIdx >= p_content->content.barsList.nbBars) {
752 return false;
753 }
754 token = ((const uint8_t *) PIC(
755 p_content->content.barsList.tokens))[elemIdx];
756 index = elemIdx;
757 break;
758 case CHOICES_LIST:
759 token = p_content->content.choicesList.token;
760 index = elemIdx;
761 break;
762 case TAG_VALUE_LIST:
763 return false;
765 if (elemIdx < p_content->content.tagValueConfirm.tagValueList.nbPairs) {
766 return false;
767 }
768 token = p_content->content.tagValueConfirm.confirmationToken;
769 break;
770 default:
771 break;
772 }
773
774 bool used_action_callback
775 = (p_content != NULL) && (p_content->contentActionCallback != NULL);
776 ContextType_t prev_type = context.type;
777 if (used_action_callback) {
780 actionCallback(token, index, context.currentPage);
781 }
782 else if (context.content.controlsCallback != NULL) {
783 context.content.controlsCallback(token, index);
784 }
785 // If the callback swapped the use case (e.g. by entering
786 // a new screen via display_home_page / nbgl_useCaseXxx),
787 // the global context has already been reset — leave it
788 // alone, do not try to redraw the previous layout.
789 if (context.type != prev_type) {
790 return false;
791 }
792 // After a CHOICES_LIST or BARS_LIST selection that went
793 // through the per-content action callback (settings-style
794 // flows), request a redraw of the current page so any
795 // visual side effect of the pick (e.g. the selectionIcon
796 // moving below the new initChoice) becomes visible.
797 // The controlsCallback path (navigable content, generic
798 // review, ...) is excluded because such callbacks
799 // commonly route to a brand-new use case (review,
800 // warning, ...) that owns the screen without resetting
801 // context.type — forcing a redraw there would clobber it.
802 if (used_action_callback
803 && ((p_content->type == CHOICES_LIST) || (p_content->type == BARS_LIST))) {
804 *pos = FORWARD_DIRECTION;
805 return true;
806 }
807 }
808 }
809 }
810 return false;
811 }
812 return true;
813}
814
815static void reviewCallback(nbgl_step_t stepCtx, nbgl_buttonEvent_t event)
816{
817 UNUSED(stepCtx);
819
820 if (!buttonGenericCallback(event, &pos)) {
821 return;
822 }
823 else {
824 // memorize last direction
825 context.review.dataDirection = pos;
826 }
827 displayReviewPage(pos);
828}
829
830// this is the callback used when button action on the "skip" page
831static void buttonSkipCallback(nbgl_step_t stepCtx, nbgl_buttonEvent_t event)
832{
833 UNUSED(stepCtx);
835
836 if (event == BUTTON_LEFT_PRESSED) {
837 // only decrement page if we are going backward but coming from forward (back & forth)
838 if ((context.review.dataDirection == FORWARD_DIRECTION)
839 && (context.currentPage > context.firstPairPage)) {
840 context.currentPage--;
841 }
842 pos = BACKWARD_DIRECTION;
843 }
844 else if (event == BUTTON_RIGHT_PRESSED) {
845 // only increment page if we are going forward but coming from backward (back & forth)
846 if ((context.review.dataDirection == BACKWARD_DIRECTION)
847 && (context.currentPage < (int) (context.nbPages - 1))
848 && (context.currentPage > context.firstPairPage)) {
849 context.currentPage++;
850 }
851 pos = FORWARD_DIRECTION;
852 }
853 else if (event == BUTTON_BOTH_PRESSED) {
854 // the first tag/value page is at page 0 only in streaming case
855 if (context.firstPairPage == 0) {
856 // in streaming, we have to call the provided callback
857 context.review.skipCallback();
858 }
859 else {
860 // if not in streaming, go directly to the "approve" page
861 context.currentPage = context.nbPages - 2;
862 displayReviewPage(FORWARD_DIRECTION);
863 }
864 return;
865 }
866 else {
867 return;
868 }
869 // the first tag/value page is at page 0 only in streaming case
870 if (context.firstPairPage == 0) {
871 displayStreamingReviewPage(pos);
872 }
873 else {
874 displayReviewPage(pos);
875 }
876}
877
878// this is the callback used when buttons in "Action" use case are pressed
879static void useCaseActionCallback(nbgl_step_t stepCtx, nbgl_buttonEvent_t event)
880{
881 UNUSED(stepCtx);
882
883 if (event == BUTTON_BOTH_PRESSED) {
884 context.action.actionCallback();
885 }
886}
887
888static void streamingReviewCallback(nbgl_step_t stepCtx, nbgl_buttonEvent_t event)
889{
891 UNUSED(stepCtx);
892
893 if (!buttonGenericCallback(event, &pos)) {
894 return;
895 }
896 else {
897 // memorize last direction
898 context.review.dataDirection = pos;
899 }
900
901 displayStreamingReviewPage(pos);
902}
903
904static void settingsCallback(nbgl_step_t stepCtx, nbgl_buttonEvent_t event)
905{
906 UNUSED(stepCtx);
908
909 if (!buttonGenericCallback(event, &pos)) {
910 return;
911 }
912
913 displaySettingsPage(pos, false);
914}
915
916static void infoCallback(nbgl_step_t stepCtx, nbgl_buttonEvent_t event)
917{
918 UNUSED(stepCtx);
920
921 if (!buttonGenericCallback(event, &pos)) {
922 return;
923 }
924
925 displayInfoPage(pos);
926}
927
928static void homeCallback(nbgl_step_t stepCtx, nbgl_buttonEvent_t event)
929{
930 UNUSED(stepCtx);
932
933 if (!buttonGenericCallback(event, &pos)) {
934 return;
935 }
936
937 displayHomePage(pos);
938}
939
940static void genericChoiceCallback(nbgl_step_t stepCtx, nbgl_buttonEvent_t event)
941{
942 UNUSED(stepCtx);
944
945 if (!buttonGenericCallback(event, &pos)) {
946 return;
947 }
948
949 displayChoicePage(pos);
950}
951
952static void genericConfirmCallback(nbgl_step_t stepCtx, nbgl_buttonEvent_t event)
953{
954 UNUSED(stepCtx);
956
957 if (!buttonGenericCallback(event, &pos)) {
958 return;
959 }
960
961 displayConfirm(pos);
962}
963
964static void statusButtonCallback(nbgl_step_t stepCtx, nbgl_buttonEvent_t event)
965{
966 UNUSED(stepCtx);
967 // any button press should dismiss the status screen
968 if ((event == BUTTON_BOTH_PRESSED) || (event == BUTTON_LEFT_PRESSED)
969 || (event == BUTTON_RIGHT_PRESSED)) {
970 if (context.stepCallback != NULL) {
971 context.stepCallback();
972 }
973 }
974}
975
976static void contentCallback(nbgl_step_t stepCtx, nbgl_buttonEvent_t event)
977{
978 UNUSED(stepCtx);
980
981 if (!buttonGenericCallback(event, &pos)) {
982 return;
983 }
984
985 displayContent(pos, false);
986}
987
988// callback used for timeout
989static void statusTickerCallback(void)
990{
991 if (context.stepCallback != NULL) {
992 context.stepCallback();
993 }
994}
995
996// this is the callback used when navigating in extension pages
997static void extensionNavigate(nbgl_step_t stepCtx, nbgl_buttonEvent_t event)
998{
1000 UNUSED(stepCtx);
1001
1002 if (event == BUTTON_LEFT_PRESSED) {
1003 // only decrement page if we are not at the first page
1004 if (context.review.currentExtensionPage > 0) {
1005 context.review.currentExtensionPage--;
1006 }
1007 pos = BACKWARD_DIRECTION;
1008 }
1009 else if (event == BUTTON_RIGHT_PRESSED) {
1010 // only increment page if not at last page
1011 if (context.review.currentExtensionPage < (context.review.nbExtensionPages - 1)) {
1012 context.review.currentExtensionPage++;
1013 }
1014 pos = FORWARD_DIRECTION;
1015 }
1016 else if (event == BUTTON_BOTH_PRESSED) {
1017 // if at last page, leave modal context
1018 if (context.review.currentExtensionPage == (context.review.nbExtensionPages - 1)) {
1019 nbgl_stepRelease(context.review.extensionStepCtx);
1021 nbgl_refresh();
1022 }
1023 return;
1024 }
1025 else {
1026 return;
1027 }
1028 displayExtensionStep(pos);
1029}
1030
1031// function used to display the extension pages
1032static void displayExtensionStep(nbgl_stepPosition_t pos)
1033{
1034 nbgl_layoutCenteredInfo_t info = {0};
1035 const nbgl_contentTagValueList_t *tagValueList = NULL;
1036 const nbgl_contentInfoList_t *infoList = NULL;
1037 const char *text = NULL;
1038 const char *subText = NULL;
1039
1040 if (context.review.extensionStepCtx != NULL) {
1041 nbgl_stepRelease(context.review.extensionStepCtx);
1042 }
1043 if (context.review.currentExtensionPage < (context.review.nbExtensionPages - 1)) {
1044 if (context.review.currentExtensionPage == 0) {
1045 pos |= FIRST_STEP;
1046 }
1047 else {
1049 }
1050
1051 switch (context.review.extension->aliasType) {
1052 case ENS_ALIAS:
1053 text = context.review.extension->title;
1054 subText = context.review.extension->fullValue;
1055 break;
1056 case ADDRESS_BOOK_ALIAS: {
1057 bool has_scope = (context.review.extension->aliasSubName != NULL);
1058 bool has_tn = (context.review.extension->explanation != NULL);
1059 uint8_t page = context.review.currentExtensionPage;
1060 if (page == 0) {
1061 text = context.review.extension->title;
1062 subText = has_scope ? context.review.extension->aliasSubName
1063 : context.review.extension->fullValue;
1064 }
1065 else if (has_scope && page == 1) {
1066 text = context.review.extension->title;
1067 subText = context.review.extension->fullValue;
1068 }
1069 else if (has_tn) {
1070 text = context.review.extension->explanation;
1071 }
1072 break;
1073 }
1074 case INFO_LIST_ALIAS:
1075 infoList = context.review.extension->infolist;
1076 text = PIC(infoList->infoTypes[context.review.currentExtensionPage]);
1077 subText = PIC(infoList->infoContents[context.review.currentExtensionPage]);
1078 break;
1080 tagValueList = context.review.extension->tagValuelist;
1081 text = PIC(tagValueList->pairs[context.review.currentExtensionPage].item);
1082 subText = PIC(tagValueList->pairs[context.review.currentExtensionPage].value);
1083 break;
1084 default:
1085 break;
1086 }
1087 if (text != NULL) {
1088 context.review.extensionStepCtx = nbgl_stepDrawText(
1089 pos, extensionNavigate, NULL, text, subText, BOLD_TEXT1_INFO, true);
1090 }
1091 }
1092 else if (context.review.currentExtensionPage == (context.review.nbExtensionPages - 1)) {
1093 // draw the back page
1094 info.icon = &C_icon_back_x;
1095 info.text1 = "Back";
1096 info.style = BOLD_TEXT1_INFO;
1097 pos |= LAST_STEP;
1098 context.review.extensionStepCtx
1099 = nbgl_stepDrawCenteredInfo(pos, extensionNavigate, NULL, &info, true);
1100 }
1101 nbgl_refresh();
1102}
1103
1104static void displayAliasFullValue(void)
1105{
1106 const char *text = NULL;
1107 const char *subText = NULL;
1108 const nbgl_icon_details_t *icon;
1109 bool isCenteredInfo;
1110
1111 getPairData(context.review.tagValueList,
1112 context.review.currentTagValueIndex,
1113 &text,
1114 &subText,
1115 &context.review.extension,
1116 &icon,
1117 &isCenteredInfo);
1118 if (context.review.extension == NULL) {
1119 // probably an error
1121 "displayAliasFullValue: extension nor found for pair %d\n",
1122 context.review.currentTagValueIndex);
1123 return;
1124 }
1125 context.review.currentExtensionPage = 0;
1126 context.review.extensionStepCtx = NULL;
1127 // create a modal flow to display this extension
1128 switch (context.review.extension->aliasType) {
1129 case ENS_ALIAS:
1130 context.review.nbExtensionPages = 2;
1131 break;
1132 case ADDRESS_BOOK_ALIAS:
1133 context.review.nbExtensionPages
1134 = 2 + ((context.review.extension->aliasSubName != NULL) ? 1 : 0)
1135 + ((context.review.extension->explanation != NULL) ? 1 : 0);
1136 break;
1137 case INFO_LIST_ALIAS:
1138 context.review.nbExtensionPages = context.review.extension->infolist->nbInfos + 1;
1139 break;
1141 context.review.nbExtensionPages = context.review.extension->tagValuelist->nbPairs + 1;
1142 break;
1143 default:
1145 "displayAliasFullValue: unsupported alias type %d\n",
1146 context.review.extension->aliasType);
1147 return;
1148 }
1149 displayExtensionStep(FORWARD_DIRECTION);
1150}
1151
1152static void getLastPageInfo(bool approve, const nbgl_icon_details_t **icon, const char **text)
1153{
1154 if (approve) {
1155 // Approve page
1156 *icon = &C_icon_validate_14;
1157 if (context.operationType & ADDRESS_BOOK_OPERATION) {
1158 if ((context.type == REVIEW_USE_CASE) && (context.review.finishTitle != NULL)) {
1159 *text = context.review.finishTitle;
1160 }
1161 else {
1162 *text = "Confirm contact details";
1163 }
1164 }
1165 else if (context.type == ADDRESS_REVIEW_USE_CASE) {
1166 *text = "Confirm";
1167 }
1168 else {
1169 // if finish title is provided, use it
1170 if (context.review.finishTitle != NULL) {
1171 *text = context.review.finishTitle;
1172 }
1173 else {
1174 switch (context.operationType & REAL_TYPE_MASK) {
1175 case TYPE_TRANSACTION:
1176 if (context.operationType & RISKY_OPERATION) {
1177 *text = "Accept risk and sign transaction";
1178 }
1179 else {
1180 *text = "Sign transaction";
1181 }
1182 break;
1183 case TYPE_MESSAGE:
1184 if (context.operationType & RISKY_OPERATION) {
1185 *text = "Accept risk and sign message";
1186 }
1187 else {
1188 *text = "Sign message";
1189 }
1190 break;
1191 default:
1192 if (context.operationType & RISKY_OPERATION) {
1193 *text = "Accept risk and sign operation";
1194 }
1195 else {
1196 *text = "Sign operation";
1197 }
1198 break;
1199 }
1200 }
1201 }
1202 context.stepCallback = onReviewAccept;
1203 }
1204 else {
1205 // Reject page
1206 *icon = &C_icon_crossmark;
1207 if ((context.type == ADDRESS_REVIEW_USE_CASE)
1208 || (context.operationType & ADDRESS_BOOK_OPERATION)) {
1209 *text = "Cancel";
1210 }
1211 else if ((context.operationType & REAL_TYPE_MASK) == TYPE_TRANSACTION) {
1212 *text = "Reject transaction";
1213 }
1214 else if ((context.operationType & REAL_TYPE_MASK) == TYPE_MESSAGE) {
1215 *text = "Reject message";
1216 }
1217 else {
1218 *text = "Reject operation";
1219 }
1220 context.stepCallback = onReviewReject;
1221 }
1222}
1223
1224// function used to display the current page in review
1225static void displayReviewPage(nbgl_stepPosition_t pos)
1226{
1227 uint8_t reviewPages = 0;
1228 uint8_t finalPages = 0;
1229 uint8_t pairIndex = 0;
1230 const char *text = NULL;
1231 const char *subText = NULL;
1232 const nbgl_icon_details_t *icon = NULL;
1233 uint8_t currentIndex = 0;
1234 uint8_t titleIndex = 255;
1235 uint8_t subIndex = 255;
1236 uint8_t approveIndex = 255;
1237 uint8_t rejectIndex = 255;
1238 const nbgl_contentValueExt_t *extension = NULL;
1239 ForcedType_t forcedType = NO_FORCED_TYPE;
1240
1241 context.stepCallback = NULL;
1242
1243 // Determine the 1st page to display tag/values
1244 // Title page to display
1245 titleIndex = currentIndex++;
1246 reviewPages++;
1247 if (context.review.reviewSubTitle) {
1248 // subtitle page to display
1249 subIndex = currentIndex++;
1250 reviewPages++;
1251 }
1252 approveIndex = context.nbPages - 2;
1253 rejectIndex = context.nbPages - 1;
1254 finalPages = approveIndex;
1255
1256 // Determine which page to display
1257 if (context.currentPage >= finalPages) {
1258 if (context.currentPage == approveIndex) {
1259 // Approve page
1260 getLastPageInfo(true, &icon, &text);
1261 }
1262 else if (context.currentPage == rejectIndex) {
1263 // Reject page
1264 getLastPageInfo(false, &icon, &text);
1265 }
1266 }
1267 else if (context.currentPage < reviewPages) {
1268 if (context.currentPage == titleIndex) {
1269 // Title page
1270 icon = context.review.icon;
1271 text = context.review.reviewTitle;
1272 }
1273 else if (context.currentPage == subIndex) {
1274 // SubTitle page
1275 text = context.review.reviewSubTitle;
1276 }
1277 }
1278 else if ((context.review.address != NULL) && (context.currentPage == reviewPages)) {
1279 // address confirmation and 2nd page
1280 text = "Address";
1281 subText = context.review.address;
1282 }
1283 else {
1284 // if there is a skip, and we are not already displaying the "skip" page
1285 // and we are not at the first tag/value of the first set of data (except if going
1286 // backward) then display the "skip" page
1287 if ((context.operationType & SKIPPABLE_OPERATION) && (context.review.skipDisplay == false)
1288 && ((context.currentPage > reviewPages)
1289 || (context.review.dataDirection == BACKWARD_DIRECTION))) {
1290 nbgl_stepPosition_t directions = (pos & BACKWARD_DIRECTION) | FIRST_STEP;
1291 nbgl_layoutCenteredInfo_t info = {0};
1292 if ((context.review.nbDataSets == 1) || (context.currentPage > 0)) {
1293 directions |= LAST_STEP;
1294 }
1295 info.icon = &C_Information_circle_14px;
1296 info.text1 = "Press right button to continue message or \bpress both to skip\b";
1297 nbgl_stepDrawCenteredInfo(directions, buttonSkipCallback, NULL, &info, false);
1298 nbgl_refresh();
1299 context.review.skipDisplay = true;
1300 context.firstPairPage = reviewPages;
1301 return;
1302 }
1303 context.review.skipDisplay = false;
1304 bool isCenteredInfo = false;
1305 pairIndex = context.currentPage - reviewPages;
1306 if (context.review.address != NULL) {
1307 pairIndex--;
1308 }
1309 getPairData(context.review.tagValueList,
1310 pairIndex,
1311 &text,
1312 &subText,
1313 &extension,
1314 &icon,
1315 &isCenteredInfo);
1316 if (extension != NULL) {
1317 context.stepCallback = displayAliasFullValue;
1318 context.review.currentTagValueIndex = pairIndex;
1319 forcedType = FORCE_BUTTON;
1320 }
1321 else {
1322 if (isCenteredInfo) {
1323 forcedType = FORCE_CENTERED_INFO;
1324 }
1325 }
1326 }
1327
1328 drawStep(pos, icon, text, subText, reviewCallback, false, forcedType, false);
1329 nbgl_refresh();
1330}
1331
1332// function used to display the current page in review
1333static void displayStreamingReviewPage(nbgl_stepPosition_t pos)
1334{
1335 const char *text = NULL;
1336 const char *subText = NULL;
1337 const nbgl_icon_details_t *icon = NULL;
1338 uint8_t reviewPages = 0;
1339 uint8_t titleIndex = 255;
1340 uint8_t subIndex = 255;
1341 const nbgl_contentValueExt_t *extension = NULL;
1342 ForcedType_t forcedType = NO_FORCED_TYPE;
1343
1344 context.stepCallback = NULL;
1345 switch (context.type) {
1346 case STREAMING_START_REVIEW_USE_CASE:
1347 // Title page to display
1348 titleIndex = reviewPages++;
1349 if (context.review.reviewSubTitle) {
1350 // subtitle page to display
1351 subIndex = reviewPages++;
1352 }
1353 // Determine which page to display
1354 if (context.currentPage >= reviewPages) {
1355 onReviewAccept();
1356 return;
1357 }
1358 // header page(s)
1359 if (context.currentPage == titleIndex) {
1360 // title page
1361 icon = context.review.icon;
1362 text = context.review.reviewTitle;
1363 }
1364 else if (context.currentPage == subIndex) {
1365 // subtitle page
1366 text = context.review.reviewSubTitle;
1367 }
1368 break;
1369
1370 case STREAMING_CONTINUE_REVIEW_USE_CASE:
1371 if (context.currentPage >= context.review.tagValueList->nbPairs) {
1372 onReviewAccept();
1373 return;
1374 }
1375 // if there is a skip, and we are not already displaying the "skip" page
1376 // and we are not at the first tag/value of the first set of data (except if going
1377 // backward) then display the "skip" page
1378 if ((context.review.skipCallback != NULL) && (context.review.skipDisplay == false)
1379 && ((context.review.nbDataSets > 1) || (context.currentPage > 0)
1380 || (context.review.dataDirection == BACKWARD_DIRECTION))) {
1381 nbgl_stepPosition_t directions = (pos & BACKWARD_DIRECTION) | FIRST_STEP;
1382 nbgl_layoutCenteredInfo_t info = {0};
1383 if ((context.review.nbDataSets == 1) || (context.currentPage > 0)) {
1384 directions |= LAST_STEP;
1385 }
1386 info.icon = &C_Information_circle_14px;
1387 info.text1 = "Press right button to continue message or \bpress both to skip\b";
1388 nbgl_stepDrawCenteredInfo(directions, buttonSkipCallback, NULL, &info, false);
1389 nbgl_refresh();
1390 context.review.skipDisplay = true;
1391 return;
1392 }
1393 context.review.skipDisplay = false;
1394 bool isCenteredInfo = false;
1395 getPairData(context.review.tagValueList,
1396 context.currentPage,
1397 &text,
1398 &subText,
1399 &extension,
1400 &icon,
1401 &isCenteredInfo);
1402 if (extension != NULL) {
1403 forcedType = FORCE_BUTTON;
1404 }
1405 else {
1406 if (isCenteredInfo) {
1407 forcedType = FORCE_CENTERED_INFO;
1408 }
1409 }
1410 break;
1411
1412 case STREAMING_FINISH_REVIEW_USE_CASE:
1413 default:
1414 if (context.currentPage == 0) {
1415 // accept page
1416 getLastPageInfo(true, &icon, &text);
1417 }
1418 else {
1419 // reject page
1420 getLastPageInfo(false, &icon, &text);
1421 }
1422 break;
1423 }
1424
1425 drawStep(pos, icon, text, subText, streamingReviewCallback, false, forcedType, false);
1426 nbgl_refresh();
1427}
1428
1429// function used to display the current page in info
1430static void displayInfoPage(nbgl_stepPosition_t pos)
1431{
1432 const char *text = NULL;
1433 const char *subText = NULL;
1434 const nbgl_icon_details_t *icon = NULL;
1435
1436 context.stepCallback = NULL;
1437
1438 if (context.currentPage < (context.nbPages - 1)) {
1439 text = PIC(
1440 ((const char *const *) PIC(context.home.infosList->infoTypes))[context.currentPage]);
1441 subText = PIC(
1442 ((const char *const *) PIC(context.home.infosList->infoContents))[context.currentPage]);
1443 }
1444 else {
1445 icon = &C_icon_back_x;
1446 text = "Back";
1447 context.stepCallback = startUseCaseHome;
1448 }
1449
1450 drawStep(pos, icon, text, subText, infoCallback, false, FORCE_CENTERED_INFO, false);
1451 nbgl_refresh();
1452}
1453
1454// function used to get the current page content
1455static void getContentPage(bool toogle_state, PageContent_t *contentPage)
1456{
1457 uint8_t elemIdx = 0;
1458 const nbgl_content_t *p_content = NULL;
1459 nbgl_content_t content = {0};
1460 nbgl_contentSwitch_t *contentSwitch = NULL;
1461 nbgl_contentRadioChoice_t *contentChoices = NULL;
1462 char **names = NULL;
1463 nbgl_contentBarsList_t *contentBars = NULL;
1464 char **texts = NULL;
1465 p_content = getContentElemAtIdx(context.currentPage, &elemIdx, &content);
1466 if (p_content == NULL) {
1468 "getContentPage(): p_content is NULL for page %d\n",
1469 context.currentPage);
1470 return;
1471 }
1472 switch (p_content->type) {
1473 case CENTERED_INFO:
1474 contentPage->text = PIC(p_content->content.centeredInfo.text1);
1475 contentPage->subText = PIC(p_content->content.centeredInfo.text2);
1476 contentPage->icon = PIC(p_content->content.centeredInfo.icon);
1477 contentPage->bottomIcon = p_content->content.centeredInfo.bottomIcon;
1478 break;
1479 case INFO_BUTTON:
1480 contentPage->icon = PIC(p_content->content.infoButton.icon);
1481 contentPage->text = PIC(p_content->content.infoButton.text);
1482 contentPage->subText = PIC(p_content->content.infoButton.buttonText);
1483 contentPage->bottomIcon = p_content->content.infoButton.bottomIcon;
1484 break;
1485 case TAG_VALUE_LIST:
1486 getPairData(&p_content->content.tagValueList,
1487 elemIdx,
1488 &contentPage->text,
1489 &contentPage->subText,
1490 &contentPage->extension,
1491 &contentPage->icon,
1492 &contentPage->isCenteredInfo);
1493 break;
1494 case TAG_VALUE_CONFIRM:
1495 if (elemIdx < p_content->content.tagValueConfirm.tagValueList.nbPairs) {
1496 getPairData(&p_content->content.tagValueConfirm.tagValueList,
1497 elemIdx,
1498 &contentPage->text,
1499 &contentPage->subText,
1500 &contentPage->extension,
1501 &contentPage->icon,
1502 &contentPage->isCenteredInfo);
1503 }
1504 else {
1505 contentPage->text = p_content->content.tagValueConfirm.confirmationText;
1506 contentPage->icon = &C_icon_validate_14;
1507 contentPage->isAction = true;
1508 }
1509 break;
1510 case SWITCHES_LIST:
1511 contentPage->isSwitch = true;
1512 contentSwitch = &(
1513 (nbgl_contentSwitch_t *) PIC(p_content->content.switchesList.switches))[elemIdx];
1514 contentPage->text = contentSwitch->text;
1515 contentPage->state = contentSwitch->initState;
1516 if (toogle_state) {
1517 contentPage->state = (contentPage->state == ON_STATE) ? OFF_STATE : ON_STATE;
1518 }
1519 context.stepCallback = onSwitchAction;
1520 contentPage->subText = contentSwitch->subText;
1521 break;
1522 case INFOS_LIST:
1523 contentPage->text
1524 = ((const char *const *) PIC(p_content->content.infosList.infoTypes))[elemIdx];
1525 contentPage->subText
1526 = ((const char *const *) PIC(p_content->content.infosList.infoContents))[elemIdx];
1527 break;
1528 case CHOICES_LIST:
1529 contentChoices = (nbgl_contentRadioChoice_t *) PIC(&p_content->content.choicesList);
1530 // When the content opts into the vertical layout, leave
1531 // contentPage->text NULL so drawStep falls through to the menu-list
1532 // rendering (all choices visible, with a radio button on
1533 // initChoice). Default is the horizontal one-page-per-choice flow.
1534 if (contentChoices->vertical) {
1535 break;
1536 }
1537 names = (char **) PIC(contentChoices->names);
1538 // The caller-provided per-content title wins over the contextual
1539 // fallback (app name / use case title).
1540 if (contentChoices->title != NULL) {
1541 contentPage->text = PIC(contentChoices->title);
1542 contentPage->subText = (const char *) PIC(names[elemIdx]);
1543 }
1544 else if ((context.type == CONTENT_USE_CASE) && (context.content.title != NULL)) {
1545 contentPage->text = PIC(context.content.title);
1546 contentPage->subText = (const char *) PIC(names[elemIdx]);
1547 }
1548 else if (((context.type == GENERIC_SETTINGS) || (context.type == SETTINGS_USE_CASE))
1549 && (context.home.appName != NULL)) {
1550 // Both nbgl_useCaseGenericSettings (GENERIC_SETTINGS) and
1551 // nbgl_useCaseHomeAndSettings -> Settings (SETTINGS_USE_CASE)
1552 // populate context.home.appName, so use it as the page title
1553 // for the latter too -- otherwise a horizontally-paged
1554 // CHOICES_LIST would render the choice name only with no
1555 // contextual header at all.
1556 contentPage->text = PIC(context.home.appName);
1557 contentPage->subText = (const char *) PIC(names[elemIdx]);
1558 }
1559 else {
1560 contentPage->text = (const char *) PIC(names[elemIdx]);
1561 }
1562 // Mark the currently-selected choice with the caller-provided icon
1563 // placed at the bottom of the screen (see contentCenteredInfo
1564 // bottomIcon handling in nbgl_layoutAddCenteredInfo). Skipped when
1565 // the caller did not opt in.
1566 if ((contentChoices->selectionIcon != NULL)
1567 && (elemIdx == contentChoices->initChoice)) {
1568 contentPage->icon = PIC(contentChoices->selectionIcon);
1569 contentPage->bottomIcon = true;
1570 }
1571 break;
1572 case BARS_LIST:
1573 contentBars = (nbgl_contentBarsList_t *) PIC(&p_content->content.barsList);
1574 if (contentBars->vertical) {
1575 break; // see CHOICES_LIST comment above
1576 }
1577 texts = (char **) PIC(contentBars->barTexts);
1578 // Same precedence as CHOICES_LIST: caller-provided title overrides
1579 // the contextual fallback.
1580 if (contentBars->title != NULL) {
1581 contentPage->text = PIC(contentBars->title);
1582 contentPage->subText = PIC(texts[elemIdx]);
1583 }
1584 else if ((context.type == CONTENT_USE_CASE) && (context.content.title != NULL)) {
1585 contentPage->text = PIC(context.content.title);
1586 contentPage->subText = PIC(texts[elemIdx]);
1587 }
1588 else if ((context.type == GENERIC_SETTINGS) && (context.home.appName != NULL)) {
1589 contentPage->text = PIC(context.home.appName);
1590 contentPage->subText = PIC(texts[elemIdx]);
1591 }
1592 else {
1593 contentPage->text = PIC(texts[elemIdx]);
1594 }
1595 // Sub-menu indicator: always pinned at the bottom of every bar's
1596 // page. Mirrors the hardcoded PUSH_ICON ('>') that Stax/Flex draw
1597 // on the right of touchable bars (see nbgl_page.c). On Nano the
1598 // right button is reserved for navigation, so we pick a downward
1599 // chevron and place it at the screen bottom via the centeredInfo
1600 // bottomIcon path.
1601 contentPage->icon = &C_icon_down;
1602 contentPage->bottomIcon = true;
1603 break;
1604 default:
1605 break;
1606 }
1607}
1608
1609// function used to display the current page in settings
1610static void displaySettingsPage(nbgl_stepPosition_t pos, bool toogle_state)
1611{
1612 PageContent_t contentPage = {0};
1613
1614 context.stepCallback = NULL;
1615
1616 if (context.currentPage < (context.nbPages - 1)) {
1617 getContentPage(toogle_state, &contentPage);
1618 }
1619 else { // last page is for quit
1620 contentPage.icon = &C_icon_back_x;
1621 contentPage.text = "Back";
1622 if (context.type == GENERIC_SETTINGS) {
1623 context.stepCallback = context.home.quitCallback;
1624 }
1625 else {
1626 context.stepCallback = startUseCaseHome;
1627 }
1628 }
1629
1630 if (contentPage.isSwitch) {
1631 drawSwitchStep(
1632 pos, contentPage.text, contentPage.subText, contentPage.state, settingsCallback, false);
1633 }
1634 else {
1635 drawStep(pos,
1636 contentPage.icon,
1637 contentPage.text,
1638 contentPage.subText,
1639 settingsCallback,
1640 false,
1641 NO_FORCED_TYPE,
1642 contentPage.bottomIcon);
1643 }
1644
1645 nbgl_refresh();
1646}
1647
1648static void startUseCaseHome(void)
1649{
1650 switch (context.type) {
1651 case SETTINGS_USE_CASE:
1652 // Settings page index
1653 context.currentPage = 1;
1654 if (context.home.homeAction) {
1655 // Action page is before Settings page
1656 context.currentPage++;
1657 }
1658 break;
1659 case INFO_USE_CASE:
1660 // Info page index
1661 context.currentPage = 1;
1662 if (context.home.homeAction) {
1663 // Action page is before Settings and Info pages
1664 context.currentPage++;
1665 }
1666 if (context.home.settingContents) {
1667 // Settings page is before Info pages
1668 context.currentPage++;
1669 }
1670 break;
1671 default:
1672 // Home page index
1673 context.currentPage = 0;
1674 break;
1675 }
1676
1677 context.type = HOME_USE_CASE;
1678 context.nbPages = 2; // Home + Quit
1679 if (context.home.settingContents) {
1680 context.nbPages++;
1681 }
1682 if (context.home.infosList) {
1683 context.nbPages++;
1684 }
1685 if (context.home.homeAction) {
1686 context.nbPages++;
1687 }
1688 displayHomePage(FORWARD_DIRECTION);
1689}
1690
1691static void startUseCaseInfo(void)
1692{
1693 context.type = INFO_USE_CASE;
1694 context.nbPages = context.home.infosList->nbInfos + 1; // For back screen
1695 context.currentPage = 0;
1696
1697 displayInfoPage(FORWARD_DIRECTION);
1698}
1699
1700static void startUseCaseSettingsAtPage(uint8_t initSettingPage)
1701{
1702 nbgl_content_t content = {0};
1703 const nbgl_content_t *p_content = NULL;
1704
1705 // if not coming from GENERIC_SETTINGS, force to SETTINGS_USE_CASE
1706 if (context.type != GENERIC_SETTINGS) {
1707 context.type = SETTINGS_USE_CASE;
1708 }
1709
1710 context.nbPages = 1; // For back screen
1711 for (unsigned int i = 0; i < context.home.settingContents->nbContents; i++) {
1712 p_content = getContentAtIdx(context.home.settingContents, i, &content);
1713 context.nbPages += getContentNbElement(p_content);
1714 }
1715 context.currentPage = initSettingPage;
1716
1717 displaySettingsPage(FORWARD_DIRECTION, false);
1718}
1719
1720static void startUseCaseSettings(void)
1721{
1722 startUseCaseSettingsAtPage(0);
1723}
1724
1725static void startUseCaseContent(void)
1726{
1727 uint8_t contentIdx = 0;
1728 const nbgl_content_t *p_content = NULL;
1729 nbgl_content_t content = {0};
1730
1731 context.nbPages = 1; // Quit
1732
1733 for (contentIdx = 0; contentIdx < context.content.genericContents.nbContents; contentIdx++) {
1734 p_content = getContentAtIdx(&context.content.genericContents, contentIdx, &content);
1735 context.nbPages += getContentNbElement(p_content);
1736 }
1737
1738 // Ensure currentPage is valid
1739 if (context.currentPage >= context.nbPages) {
1740 return;
1741 }
1742
1743 displayContent(FORWARD_DIRECTION, false);
1744}
1745
1746// function used to display the current page in home
1747static void displayHomePage(nbgl_stepPosition_t pos)
1748{
1749 const char *text = NULL;
1750 const char *subText = NULL;
1751 const nbgl_icon_details_t *icon = NULL;
1752 uint8_t currentIndex = 0;
1753 uint8_t homeIndex = 255;
1754 uint8_t actionIndex = 255;
1755 uint8_t settingsIndex = 255;
1756 uint8_t infoIndex = 255;
1757
1758 context.stepCallback = NULL;
1759
1760 // Determine which pages are present
1761 homeIndex = currentIndex++;
1762 if (context.home.homeAction) {
1763 actionIndex = currentIndex++;
1764 }
1765 if (context.home.settingContents) {
1766 settingsIndex = currentIndex++;
1767 }
1768 if (context.home.infosList) {
1769 infoIndex = currentIndex++;
1770 }
1771
1772 if (context.currentPage == homeIndex) {
1773 // Home page
1774 icon = context.home.appIcon;
1775 if (context.home.tagline != NULL) {
1776 text = context.home.tagline;
1777 }
1778 else {
1779 text = context.home.appName;
1780 subText = "app is ready";
1781 }
1782 }
1783 else if (context.currentPage == actionIndex) {
1784 // Action page
1785 icon = context.home.homeAction->icon;
1786 text = PIC(context.home.homeAction->text);
1787 context.stepCallback = context.home.homeAction->callback;
1788 }
1789 else if (context.currentPage == settingsIndex) {
1790 // Settings page
1791 icon = &C_icon_coggle;
1792 text = "App settings";
1793 context.stepCallback = startUseCaseSettings;
1794 }
1795 else if (context.currentPage == infoIndex) {
1796 // About page
1797 icon = &C_Information_circle_14px;
1798 text = "App info";
1799 context.stepCallback = startUseCaseInfo;
1800 }
1801 else {
1802 icon = &C_Quit_14px;
1803 text = "Quit app";
1804 context.stepCallback = context.home.quitCallback;
1805 }
1806
1807 drawStep(pos, icon, text, subText, homeCallback, false, NO_FORCED_TYPE, false);
1808 nbgl_refresh();
1809}
1810
1811// An intro page of a choice flow displays the header text and, below it, at most one other
1812// element, as a step cannot hold more. Those elements take one page each, in this order:
1813// 1. the icon, if any
1814// 2. the message, when the advanced flow uses the title as header text
1815// 3. the subMessage, if any
1816// The header text is the title of the advanced flow, or the message of the basic one, in which
1817// case only the subMessage is left below it.
1818
1819// Number of pages displayed before the (optional) detail pages and the confirm/cancel ones.
1820static uint8_t getChoiceNbIntroPages(void)
1821{
1822 uint8_t nbPages = 0;
1823
1824 // no intro at all without a header text
1825 if ((context.choice.title == NULL) && (context.choice.message == NULL)) {
1826 return 0;
1827 }
1828 if (context.choice.icon != NULL) {
1829 nbPages++;
1830 }
1831 if ((context.choice.title != NULL) && (context.choice.message != NULL)) {
1832 nbPages++;
1833 }
1834 if (context.choice.subMessage != NULL) {
1835 nbPages++;
1836 }
1837 // the header text still needs a page of its own when nothing goes below it
1838 return (nbPages > 0) ? nbPages : 1;
1839}
1840
1841// Text to display under the header text on the given (0-based) intro page, NULL if this page
1842// displays the icon instead, or if nothing is left to display below the header.
1843static const char *getChoiceIntroSubText(uint8_t page)
1844{
1845 // Page holding the element being examined. The elements are walked in display order and each
1846 // one that is given takes a page, pushing the following ones one page further: when elemPage
1847 // reaches the requested page, the element examined at that point is the one to display.
1848 uint8_t elemPage = 0;
1849
1850 if (context.choice.icon != NULL) {
1851 if (page == elemPage) {
1852 // the icon takes the whole page next to the header text
1853 return NULL;
1854 }
1855 elemPage++;
1856 }
1857 if ((context.choice.title != NULL) && (context.choice.message != NULL)) {
1858 // in the advanced flow the header text is the title, so the message goes below it
1859 if (page == elemPage) {
1860 return context.choice.message;
1861 }
1862 elemPage++;
1863 }
1864 // the subMessage closes the intro, on the last page
1865 return (page == elemPage) ? context.choice.subMessage : NULL;
1866}
1867
1868// function used to display the current page in choice
1869static void displayChoicePage(nbgl_stepPosition_t pos)
1870{
1871 const char *text = NULL;
1872 const char *subText = NULL;
1873 const nbgl_icon_details_t *icon = NULL;
1874 // acceptPage = number of intro pages, see getChoiceNbIntroPages()
1875 uint8_t acceptPage = getChoiceNbIntroPages();
1876 // nbDetailPages = number of BAR_LIST bar pages shown before confirm/cancel (0 if none)
1877 // Page order: intro(s) | bar pages | confirm | cancel
1878 uint8_t nbDetailPages = 0;
1879
1880 if ((context.choice.details != NULL) && (context.choice.details->type == BAR_LIST_WARNING)) {
1881 nbDetailPages = context.choice.details->barList.nbBars;
1882 }
1883 context.stepCallback = NULL;
1884
1885 if (context.currentPage < acceptPage) {
1886 // the header text is repeated on every intro page, the icon (if any) is on the first one
1887 // and each remaining text gets a page of its own, see getChoiceIntroSubText()
1888 text = (context.choice.title != NULL) ? context.choice.title : context.choice.message;
1889 if (context.currentPage == 0) {
1890 icon = context.choice.icon;
1891 }
1892 subText = getChoiceIntroSubText(context.currentPage);
1893 }
1894 else if (context.currentPage < (acceptPage + nbDetailPages)) {
1895 // BAR_LIST detail pages, shown before confirm/cancel (one page per bar)
1896 uint8_t idx = context.currentPage - acceptPage;
1897 text = (context.choice.details->barList.texts != NULL)
1898 ? context.choice.details->barList.texts[idx]
1899 : NULL;
1900 subText = (context.choice.details->barList.subTexts != NULL)
1901 ? context.choice.details->barList.subTexts[idx]
1902 : NULL;
1903 }
1904 else if (context.currentPage == (acceptPage + nbDetailPages)) { // confirm page
1905 icon = &C_icon_validate_14;
1906 text = context.choice.confirmText;
1907 context.stepCallback = onChoiceAccept;
1908 }
1909 else { // cancel page (last page)
1910 icon = &C_icon_crossmark;
1911 text = context.choice.cancelText;
1912 context.stepCallback = onChoiceReject;
1913 }
1914 // other detail types (non-BAR_LIST) are not navigated on Nano
1915
1916 drawStep(pos, icon, text, subText, genericChoiceCallback, false, NO_FORCED_TYPE, false);
1917 nbgl_refresh();
1918}
1919
1920// function used to display the Confirm page
1921static void displayConfirm(nbgl_stepPosition_t pos)
1922{
1923 const char *text = NULL;
1924 const char *subText = NULL;
1925 const nbgl_icon_details_t *icon = NULL;
1926
1927 context.stepCallback = NULL;
1928 switch (context.currentPage) {
1929 case 0:
1930 // title page
1931 text = context.confirm.message;
1932 subText = context.confirm.subMessage;
1933 break;
1934 case 1:
1935 // confirm page
1936 icon = &C_icon_validate_14;
1937 text = context.confirm.confirmText;
1938 context.stepCallback = onConfirmAccept;
1939 break;
1940 case 2:
1941 // cancel page
1942 icon = &C_icon_crossmark;
1943 text = context.confirm.cancelText;
1944 context.stepCallback = onConfirmReject;
1945 break;
1946 }
1947
1948 drawStep(pos, icon, text, subText, genericConfirmCallback, true, NO_FORCED_TYPE, false);
1949 nbgl_refresh();
1950}
1951
1952// function used to display the current navigable content
1953static void displayContent(nbgl_stepPosition_t pos, bool toogle_state)
1954{
1955 PageContent_t contentPage = {0};
1956 ForcedType_t forcedType = NO_FORCED_TYPE;
1957
1958 context.stepCallback = NULL;
1959
1960 if (context.currentPage < (context.nbPages - 1)) {
1961 getContentPage(toogle_state, &contentPage);
1962 if (contentPage.isCenteredInfo) {
1963 forcedType = FORCE_CENTERED_INFO;
1964 }
1965 context.forceAction = contentPage.isAction;
1966 }
1967 else { // last page is for quit
1968 if (context.content.rejectText) {
1969 contentPage.text = context.content.rejectText;
1970 }
1971 else {
1972 contentPage.text = "Back";
1973 }
1974 if (context.type == GENERIC_REVIEW_USE_CASE) {
1975 contentPage.icon = &C_icon_crossmark;
1976 }
1977 else {
1978 contentPage.icon = &C_icon_back_x;
1979 }
1980 context.stepCallback = context.content.quitCallback;
1981 }
1982
1983 if (contentPage.isSwitch) {
1984 drawSwitchStep(
1985 pos, contentPage.text, contentPage.subText, contentPage.state, contentCallback, false);
1986 }
1987 else {
1988 drawStep(pos,
1989 contentPage.icon,
1990 contentPage.text,
1991 contentPage.subText,
1992 contentCallback,
1993 false,
1994 forcedType,
1995 contentPage.bottomIcon);
1996 }
1997 context.forceAction = false;
1998 nbgl_refresh();
1999}
2000
2001static void displaySpinner(const char *text)
2002{
2003 drawStep(SINGLE_STEP, &C_icon_processing, text, NULL, NULL, false, NO_FORCED_TYPE, false);
2004 nbgl_refresh();
2005}
2006
2007// function to factorize code for all simple reviews
2008static void useCaseReview(ContextType_t type,
2009 nbgl_operationType_t operationType,
2010 const nbgl_contentTagValueList_t *tagValueList,
2011 const nbgl_icon_details_t *icon,
2012 const char *reviewTitle,
2013 const char *reviewSubTitle,
2014 const char *finishTitle,
2015 nbgl_choiceCallback_t choiceCallback)
2016{
2017 memset(&context, 0, sizeof(UseCaseContext_t));
2018 context.type = type;
2019 context.operationType = operationType;
2020 context.review.tagValueList = tagValueList;
2021 context.review.reviewTitle = reviewTitle;
2022 context.review.reviewSubTitle = reviewSubTitle;
2023 context.review.finishTitle = finishTitle;
2024 context.review.icon = icon;
2025 context.review.onChoice = choiceCallback;
2026 context.currentPage = 0;
2027 // 1 page for title and 2 pages at the end for accept/reject
2028 context.nbPages = tagValueList->nbPairs + 3;
2029 if (reviewSubTitle) {
2030 context.nbPages++; // 1 page for subtitle page
2031 }
2032
2033 displayReviewPage(FORWARD_DIRECTION);
2034}
2035
2036#ifdef NBGL_KEYPAD
2037static void setPinCodeText(void)
2038{
2039 bool enableValidate = false;
2040 bool enableBackspace = true;
2041
2042 // pin can be validated when min digits is entered
2043 enableValidate = (context.keypad.pinLen >= context.keypad.pinMinDigits);
2044 // backspace is disabled when no digit is entered and back vallback is not provided
2045 enableBackspace = (context.keypad.pinLen > 0) || (context.keypad.backCallback != NULL);
2046 nbgl_layoutUpdateKeypadContent(context.keypad.layoutCtx,
2047 context.keypad.hidden,
2048 context.keypad.pinLen,
2049 (const char *) context.keypad.pinEntry);
2051 context.keypad.layoutCtx, context.keypad.keypadIndex, enableValidate, enableBackspace);
2052 nbgl_layoutDraw(context.keypad.layoutCtx);
2053 nbgl_refresh();
2054}
2055
2056// called when a key is touched on the keypad
2057static void keypadCallback(char touchedKey)
2058{
2059 switch (touchedKey) {
2060 case BACKSPACE_KEY:
2061 if (context.keypad.pinLen > 0) {
2062 context.keypad.pinLen--;
2063 context.keypad.pinEntry[context.keypad.pinLen] = 0;
2064 }
2065 else if (context.keypad.backCallback != NULL) {
2066 context.keypad.backCallback();
2067 break;
2068 }
2069 setPinCodeText();
2070 break;
2071
2072 case VALIDATE_KEY:
2073 context.keypad.validatePin(context.keypad.pinEntry, context.keypad.pinLen);
2074 break;
2075
2076 default:
2077 if ((touchedKey >= 0x30) && (touchedKey < 0x40)) {
2078 if (context.keypad.pinLen < context.keypad.pinMaxDigits) {
2079 context.keypad.pinEntry[context.keypad.pinLen] = touchedKey;
2080 context.keypad.pinLen++;
2081 }
2082 setPinCodeText();
2083 }
2084 break;
2085 }
2086}
2087#endif // NBGL_KEYPAD
2088
2089#ifdef NBGL_KEYBOARD
2090// Saved keyboard context for suggestion selection
2091// (needed because switching to CONTENT_USE_CASE overwrites the union context)
2092static struct {
2093 const char **buttons;
2094 int firstButtonToken;
2095 uint8_t nbUsedButtons;
2096 nbgl_layoutTouchCallback_t onButtonCallback;
2097 nbgl_callback_t backCallback;
2098 char title[KEYBOARD_MAX_TITLE];
2099} savedKeyboardContext;
2100
2101// Navigation callback to fill the suggestion choices page
2102static bool suggestionNavCallback(uint8_t page, nbgl_pageContent_t *content)
2103{
2104 UNUSED(page);
2105 content->type = CHOICES_LIST;
2106 content->choicesList.names = savedKeyboardContext.buttons;
2107 content->choicesList.token = savedKeyboardContext.firstButtonToken;
2108 content->choicesList.initChoice = 0;
2109 content->choicesList.nbChoices = savedKeyboardContext.nbUsedButtons;
2110 return true;
2111}
2112
2113// Display suggestion selection page
2114static void displaySuggestionSelection(void)
2115{
2116 // Save keyboard context before it gets overwritten
2117 savedKeyboardContext.buttons = context.keyboard.content.suggestionButtons.buttons;
2118 savedKeyboardContext.firstButtonToken
2119 = context.keyboard.content.suggestionButtons.firstButtonToken;
2120 savedKeyboardContext.nbUsedButtons = context.keyboard.content.suggestionButtons.nbUsedButtons;
2121 savedKeyboardContext.onButtonCallback = context.keyboard.onButtonCallback;
2122 savedKeyboardContext.backCallback = context.keyboard.backCallback;
2123
2124 // Release the keyboard layout
2125 nbgl_layoutRelease(context.keyboard.layoutCtx);
2126 context.keyboard.layoutCtx = NULL;
2127
2128 snprintf(savedKeyboardContext.title,
2129 sizeof(savedKeyboardContext.title),
2130 "Select word #%d",
2131 context.keyboard.content.number);
2132 nbgl_useCaseNavigableContent(savedKeyboardContext.title,
2133 0,
2134 1,
2135 savedKeyboardContext.backCallback,
2136 suggestionNavCallback,
2137 savedKeyboardContext.onButtonCallback);
2138}
2139
2140// called when a key is touched on the keyboard
2141static void keyboardCallback(char touchedKey)
2142{
2143 uint32_t mask = 0;
2144 size_t textLen = strlen(context.keyboard.entryBuffer);
2145 PRINTF("[keyboardCallback] touchedKey: '%c'\n", touchedKey);
2146 if (touchedKey == BACKSPACE_KEY) {
2147 if (textLen == 0) {
2148 // Used to exit the keyboard when backspace is pressed on an empty entry
2149 context.keyboard.backCallback();
2150 return;
2151 }
2152 context.keyboard.entryBuffer[--textLen] = '\0';
2153 }
2154 else if (touchedKey == VALIDATE_KEY) {
2155 context.keyboard.actionCallback();
2156 return;
2157 }
2158 else {
2159 context.keyboard.entryBuffer[textLen] = touchedKey;
2160 context.keyboard.entryBuffer[++textLen] = '\0';
2161 }
2162 // Set the keyMask to disable some keys
2163 if (context.keyboard.content.type == KEYBOARD_WITH_SUGGESTIONS) {
2164 // if suggestions are displayed, we update them at each key press
2165 context.keyboard.getSuggestButtons(&context.keyboard.content, &mask);
2166 const nbgl_layoutSuggestionButtons_t *suggestions
2167 = &context.keyboard.content.suggestionButtons;
2168 // On Nano, when all the matching candidates can be displayed, switch to a selection page
2169 // instead of continuing with keyboard entry. When the caller provides the real number of
2170 // candidates (nbCandidates != 0), rely on it: this lets a fully-typed word that is also
2171 // the prefix of other words (e.g. "can") be selected even though the displayed buttons are
2172 // capped at NB_MAX_SUGGESTION_BUTTONS. Otherwise fall back to the legacy heuristic based on
2173 // the number of displayed buttons.
2174 bool allCandidatesFit;
2175 if (suggestions->nbCandidates != 0) {
2176 allCandidatesFit = (suggestions->nbCandidates <= NB_MAX_SUGGESTION_BUTTONS);
2177 }
2178 else {
2179 allCandidatesFit = (suggestions->nbUsedButtons < NB_MAX_SUGGESTION_BUTTONS);
2180 }
2181 if ((suggestions->nbUsedButtons > 0) && allCandidatesFit) {
2182 displaySuggestionSelection();
2183 return; // Don't update keyboard, we're in suggestion mode
2184 }
2185 }
2186 else if (textLen >= context.keyboard.entryMaxLen) {
2187 // entry length can't be greater, so we mask every characters
2188 mask = -1;
2189 }
2190 nbgl_layoutUpdateKeyboard(context.keyboard.layoutCtx, context.keyboard.keyboardIndex, mask);
2192 context.keyboard.layoutCtx, context.keyboard.textIndex, context.keyboard.entryBuffer);
2193 nbgl_refresh();
2194}
2195#endif
2196
2197// this is the function called to start the actual review, from the initial warning pages
2198static void launchReviewAfterWarning(void)
2199{
2200 if (reviewWithWarnCtx.type == REVIEW_USE_CASE) {
2201 useCaseReview(reviewWithWarnCtx.type,
2202 reviewWithWarnCtx.operationType,
2203 reviewWithWarnCtx.tagValueList,
2204 reviewWithWarnCtx.icon,
2205 reviewWithWarnCtx.reviewTitle,
2206 reviewWithWarnCtx.reviewSubTitle,
2207 reviewWithWarnCtx.finishTitle,
2208 reviewWithWarnCtx.choiceCallback);
2209 }
2210 else if (reviewWithWarnCtx.type == STREAMING_START_REVIEW_USE_CASE) {
2211 displayStreamingReviewPage(FORWARD_DIRECTION);
2212 }
2213}
2214
2215// callback used when navigating in a bar detail sub-page
2216static void barDetailNavigate(nbgl_step_t stepCtx, nbgl_buttonEvent_t event)
2217{
2218 UNUSED(stepCtx);
2219 if (event == BUTTON_LEFT_PRESSED) {
2220 displayWarningStep();
2221 }
2222}
2223
2224// display a bar detail sub-page (CENTERED_INFO_WARNING type)
2225static void displayBarDetailStep(void)
2226{
2227 const nbgl_genericDetails_t *detail
2228 = &reviewWithWarnCtx.warning->introDetails->barList.details[reviewWithWarnCtx.barDetailIdx];
2229
2230 if (detail->type == CENTERED_INFO_WARNING) {
2231 nbgl_layoutCenteredInfo_t info = {0};
2232 info.icon = detail->centeredInfo.icon;
2233 info.text1 = detail->centeredInfo.title;
2234 info.text2 = detail->centeredInfo.description;
2235 info.style = BOLD_TEXT1_INFO;
2236 // LAST_STEP: only left arrow shown, pressing LEFT goes back via barDetailNavigate
2238 LAST_STEP | BACKWARD_DIRECTION, barDetailNavigate, NULL, &info, false);
2239 nbgl_refresh();
2240 }
2241}
2242
2243// this is the callback used when navigating in warning pages
2244static void warningNavigate(nbgl_step_t stepCtx, nbgl_buttonEvent_t event)
2245{
2246 UNUSED(stepCtx);
2247 uint8_t barIdx = 0;
2248
2249 if (event == BUTTON_LEFT_PRESSED) {
2250 // only decrement page if we are not at the first page
2251 if (reviewWithWarnCtx.warningPage > 0) {
2252 reviewWithWarnCtx.warningPage--;
2253 }
2254 }
2255 else if (event == BUTTON_RIGHT_PRESSED) {
2256 // only increment page if not at last page
2257 if (reviewWithWarnCtx.warningPage < (reviewWithWarnCtx.nbWarningPages - 1)) {
2258 reviewWithWarnCtx.warningPage++;
2259 }
2260 else if ((reviewWithWarnCtx.warning->predefinedSet == 0)
2261 && (reviewWithWarnCtx.warning->info != NULL)) {
2262 launchReviewAfterWarning();
2263 return;
2264 }
2265 }
2266 else if ((event == BUTTON_BOTH_PRESSED)
2267 && (reviewWithWarnCtx.warning->predefinedSet & (1 << BLIND_SIGNING_WARN))) {
2268 // if at first page, double press leads to start of review
2269 if (reviewWithWarnCtx.warningPage == reviewWithWarnCtx.firstWarningPage) {
2270 launchReviewAfterWarning();
2271 }
2272 // if at last page, reject operation
2273 else if (reviewWithWarnCtx.warningPage == (reviewWithWarnCtx.nbWarningPages - 1)) {
2274 reviewWithWarnCtx.choiceCallback(false);
2275 }
2276 return;
2277 }
2278 else if ((event == BUTTON_BOTH_PRESSED) && (reviewWithWarnCtx.warning->introDetails != NULL)
2279 && (reviewWithWarnCtx.warning->introDetails->type == BAR_LIST_WARNING)
2280 && (reviewWithWarnCtx.warningPage > reviewWithWarnCtx.firstWarningPage)) {
2281 // enter the detail sub-page for the current bar (if it has one)
2282 barIdx = reviewWithWarnCtx.warningPage - reviewWithWarnCtx.firstWarningPage - 1;
2283 if ((reviewWithWarnCtx.warning->introDetails->barList.details != NULL)
2284 && (reviewWithWarnCtx.warning->introDetails->barList.details[barIdx].type
2285 != NO_TYPE_WARNING)) {
2286 reviewWithWarnCtx.barDetailIdx = barIdx;
2287 displayBarDetailStep();
2288 }
2289 return;
2290 }
2291 else {
2292 return;
2293 }
2294 displayWarningStep();
2295}
2296
2297// function used to display the initial warning pages when starting a "review with warning"
2298static void displayWarningStep(void)
2299{
2300 nbgl_layoutCenteredInfo_t info = {0};
2301 nbgl_stepPosition_t pos = 0;
2302 uint8_t barIdx = 0;
2303 if ((reviewWithWarnCtx.warning->prelude) && (reviewWithWarnCtx.warningPage == 0)) {
2304 // for prelude, only draw text as a single step
2306 warningNavigate,
2307 NULL,
2308 reviewWithWarnCtx.warning->prelude->title,
2309 reviewWithWarnCtx.warning->prelude->description,
2311 false);
2312 nbgl_refresh();
2313 return;
2314 }
2315 else if (reviewWithWarnCtx.warning->predefinedSet & (1 << BLIND_SIGNING_WARN)) {
2316 if (reviewWithWarnCtx.warningPage == reviewWithWarnCtx.firstWarningPage) {
2317 // draw the main warning page
2318 info.icon = &C_icon_warning;
2319 info.text1 = "Blind signing ahead";
2320 info.text2 = "To accept risk, press both buttons";
2321 pos = (reviewWithWarnCtx.firstWarningPage == 0) ? FIRST_STEP
2323 pos |= FORWARD_DIRECTION;
2324 }
2325 else if (reviewWithWarnCtx.warningPage == (reviewWithWarnCtx.nbWarningPages - 1)) {
2326 getLastPageInfo(false, &info.icon, &info.text1);
2328 }
2329 }
2330 else if ((reviewWithWarnCtx.warning->predefinedSet == 0)
2331 && (reviewWithWarnCtx.warning->info != NULL)) {
2332 if (reviewWithWarnCtx.warningPage == reviewWithWarnCtx.firstWarningPage) {
2333 info.icon = reviewWithWarnCtx.warning->info->icon;
2334 info.text1 = reviewWithWarnCtx.warning->info->title;
2335 info.text2 = reviewWithWarnCtx.warning->info->description;
2336 pos = (reviewWithWarnCtx.firstWarningPage == 0) ? FIRST_STEP
2338 pos |= FORWARD_DIRECTION;
2339 }
2340 else if ((reviewWithWarnCtx.warning->introDetails != NULL)
2341 && (reviewWithWarnCtx.warning->introDetails->type == BAR_LIST_WARNING)) {
2342 // intermediate or last bar page
2343 barIdx = reviewWithWarnCtx.warningPage - reviewWithWarnCtx.firstWarningPage - 1;
2344 if (reviewWithWarnCtx.warning->introDetails->barList.icons) {
2345 info.icon = reviewWithWarnCtx.warning->introDetails->barList.icons[barIdx];
2346 }
2347 info.text1 = reviewWithWarnCtx.warning->introDetails->barList.texts[barIdx];
2348 if (reviewWithWarnCtx.warning->introDetails->barList.subTexts) {
2349 info.text2 = reviewWithWarnCtx.warning->introDetails->barList.subTexts[barIdx];
2350 }
2352 }
2353 else if (reviewWithWarnCtx.warningPage == (reviewWithWarnCtx.nbWarningPages - 1)) {
2354 if ((reviewWithWarnCtx.warning->introDetails != NULL)
2355 && (reviewWithWarnCtx.warning->introDetails->type == CENTERED_INFO_WARNING)) {
2356 info.icon = reviewWithWarnCtx.warning->introDetails->centeredInfo.icon;
2357 info.text1 = reviewWithWarnCtx.warning->introDetails->centeredInfo.title;
2358 info.text2 = reviewWithWarnCtx.warning->introDetails->centeredInfo.description;
2360 }
2361 else {
2362 // not supported
2363 return;
2364 }
2365 }
2366 }
2367 else {
2368 // not supported
2369 return;
2370 }
2371 info.style = BOLD_TEXT1_INFO;
2372 nbgl_stepDrawCenteredInfo(pos, warningNavigate, NULL, &info, false);
2373 nbgl_refresh();
2374}
2375
2376// function used to display the initial warning page when starting a "review with warning"
2377static void displayInitialWarning(void)
2378{
2379 // draw the main warning page
2380 reviewWithWarnCtx.warningPage = 0;
2381 if ((reviewWithWarnCtx.warning->predefinedSet & (1 << BLIND_SIGNING_WARN))
2382 || ((reviewWithWarnCtx.warning->introDetails)
2383 && (reviewWithWarnCtx.warning->introDetails->type == CENTERED_INFO_WARNING))) {
2384 reviewWithWarnCtx.nbWarningPages = 2;
2385 }
2386 else if ((reviewWithWarnCtx.warning->introDetails)
2387 && (reviewWithWarnCtx.warning->introDetails->type == BAR_LIST_WARNING)) {
2388 reviewWithWarnCtx.nbWarningPages
2389 = reviewWithWarnCtx.warning->introDetails->barList.nbBars + 1;
2390 }
2391 else {
2392 // if no intro details and not Blind Signing warning, only one page
2393 reviewWithWarnCtx.nbWarningPages = 1;
2394 }
2395
2396 reviewWithWarnCtx.firstWarningPage = 0;
2397 displayWarningStep();
2398}
2399
2400// function used to display the prelude page when starting a "review with warning"
2401static void displayPrelude(void)
2402{
2403 // draw the main warning page
2404 reviewWithWarnCtx.warningPage = 0;
2405 if ((reviewWithWarnCtx.warning->predefinedSet & (1 << BLIND_SIGNING_WARN))
2406 || ((reviewWithWarnCtx.warning->introDetails)
2407 && (reviewWithWarnCtx.warning->introDetails->type == CENTERED_INFO_WARNING))) {
2408 reviewWithWarnCtx.nbWarningPages = 3;
2409 }
2410 else {
2411 // if no intro details and not Blind Signing warning, only 2 pages
2412 reviewWithWarnCtx.nbWarningPages = 2;
2413 }
2414 reviewWithWarnCtx.firstWarningPage = 1;
2415 displayWarningStep();
2416}
2417
2418/**********************
2419 * GLOBAL FUNCTIONS
2420 **********************/
2421
2433uint8_t nbgl_useCaseGetNbTagValuesInPage(uint8_t nbPairs,
2434 const nbgl_contentTagValueList_t *tagValueList,
2435 uint8_t startIndex,
2436 bool *requireSpecificDisplay)
2437{
2438 UNUSED(nbPairs);
2439 UNUSED(tagValueList);
2440 UNUSED(startIndex);
2441 *requireSpecificDisplay = true;
2442 return 1;
2443}
2444
2457uint8_t nbgl_useCaseGetNbTagValuesInPageExt(uint8_t nbPairs,
2458 const nbgl_contentTagValueList_t *tagValueList,
2459 uint8_t startIndex,
2460 bool isSkippable,
2461 bool *requireSpecificDisplay)
2462{
2463 UNUSED(nbPairs);
2464 UNUSED(tagValueList);
2465 UNUSED(startIndex);
2466 UNUSED(isSkippable);
2467 *requireSpecificDisplay = true;
2468 return 1;
2469}
2470
2479uint8_t nbgl_useCaseGetNbInfosInPage(uint8_t nbInfos,
2480 const nbgl_contentInfoList_t *infosList,
2481 uint8_t startIndex,
2482 bool withNav)
2483{
2484 UNUSED(nbInfos);
2485 UNUSED(infosList);
2486 UNUSED(startIndex);
2487 UNUSED(withNav);
2488 return 1;
2489}
2490
2499uint8_t nbgl_useCaseGetNbSwitchesInPage(uint8_t nbSwitches,
2500 const nbgl_contentSwitchesList_t *switchesList,
2501 uint8_t startIndex,
2502 bool withNav)
2503{
2504 UNUSED(nbSwitches);
2505 UNUSED(switchesList);
2506 UNUSED(startIndex);
2507 UNUSED(withNav);
2508 return 1;
2509}
2510
2519uint8_t nbgl_useCaseGetNbBarsInPage(uint8_t nbBars,
2520 const nbgl_contentBarsList_t *barsList,
2521 uint8_t startIndex,
2522 bool withNav)
2523{
2524 UNUSED(nbBars);
2525 UNUSED(barsList);
2526 UNUSED(startIndex);
2527 UNUSED(withNav);
2528 return 1;
2529}
2530
2539uint8_t nbgl_useCaseGetNbChoicesInPage(uint8_t nbChoices,
2540 const nbgl_contentRadioChoice_t *choicesList,
2541 uint8_t startIndex,
2542 bool withNav)
2543{
2544 UNUSED(nbChoices);
2545 UNUSED(choicesList);
2546 UNUSED(startIndex);
2547 UNUSED(withNav);
2548 return 1;
2549}
2550
2558{
2559 uint8_t nbPages = 0;
2560 uint8_t nbPairs = tagValueList->nbPairs;
2561 uint8_t nbPairsInPage;
2562 uint8_t i = 0;
2563 bool flag;
2564
2565 while (i < tagValueList->nbPairs) {
2566 // upper margin
2567 nbPairsInPage = nbgl_useCaseGetNbTagValuesInPageExt(nbPairs, tagValueList, i, false, &flag);
2568 i += nbPairsInPage;
2569 nbPairs -= nbPairsInPage;
2570 nbPages++;
2571 }
2572 return nbPages;
2573}
2574
2588void nbgl_useCaseNavigableContent(const char *title,
2589 uint8_t initPage,
2590 uint8_t nbPages,
2591 nbgl_callback_t quitCallback,
2592 nbgl_navCallback_t navCallback,
2593 nbgl_layoutTouchCallback_t controlsCallback)
2594{
2595 memset(&context, 0, sizeof(UseCaseContext_t));
2596 context.type = CONTENT_USE_CASE;
2597 context.currentPage = initPage;
2598 context.content.title = title;
2599 context.content.quitCallback = quitCallback;
2600 context.content.navCallback = navCallback;
2601 context.content.controlsCallback = controlsCallback;
2602 context.content.genericContents.callbackCallNeeded = true;
2603 context.content.genericContents.nbContents = nbPages;
2604
2605 startUseCaseContent();
2606}
2607
2622void nbgl_useCaseHomeAndSettings(const char *appName,
2623 const nbgl_icon_details_t *appIcon,
2624 const char *tagline,
2625 const uint8_t initSettingPage,
2626 const nbgl_genericContents_t *settingContents,
2627 const nbgl_contentInfoList_t *infosList,
2628 const nbgl_homeAction_t *action,
2629 nbgl_callback_t quitCallback)
2630{
2631 memset(&context, 0, sizeof(UseCaseContext_t));
2632 context.home.appName = appName;
2633 context.home.appIcon = appIcon;
2634 context.home.tagline = tagline;
2635 context.home.settingContents = PIC(settingContents);
2636 context.home.infosList = PIC(infosList);
2637 context.home.homeAction = action;
2638 context.home.quitCallback = quitCallback;
2639
2640 if ((initSettingPage != INIT_HOME_PAGE) && (settingContents != NULL)) {
2641 startUseCaseSettingsAtPage(initSettingPage);
2642 }
2643 else {
2644 startUseCaseHome();
2645 }
2646}
2647
2660void nbgl_useCaseGenericSettings(const char *appName,
2661 uint8_t initPage,
2662 const nbgl_genericContents_t *settingContents,
2663 const nbgl_contentInfoList_t *infosList,
2664 nbgl_callback_t quitCallback)
2665{
2666 memset(&context, 0, sizeof(UseCaseContext_t));
2667 context.type = GENERIC_SETTINGS;
2668 context.home.appName = appName;
2669 context.home.settingContents = PIC(settingContents);
2670 context.home.infosList = PIC(infosList);
2671 context.home.quitCallback = quitCallback;
2672
2673 startUseCaseSettingsAtPage(initPage);
2674}
2675
2687void nbgl_useCaseGenericConfiguration(const char *title,
2688 uint8_t initPage,
2689 const nbgl_genericContents_t *contents,
2690 nbgl_callback_t quitCallback)
2691{
2692 nbgl_useCaseGenericSettings(title, initPage, contents, NULL, quitCallback);
2693}
2694
2709void nbgl_useCaseReview(nbgl_operationType_t operationType,
2710 const nbgl_contentTagValueList_t *tagValueList,
2711 const nbgl_icon_details_t *icon,
2712 const char *reviewTitle,
2713 const char *reviewSubTitle,
2714 const char *finishTitle,
2715 nbgl_choiceCallback_t choiceCallback)
2716{
2717 useCaseReview(REVIEW_USE_CASE,
2718 operationType,
2719 tagValueList,
2720 icon,
2721 reviewTitle,
2722 reviewSubTitle,
2723 finishTitle,
2724 choiceCallback);
2725}
2726
2750 const nbgl_contentTagValueList_t *tagValueList,
2751 const nbgl_icon_details_t *icon,
2752 const char *reviewTitle,
2753 const char *reviewSubTitle,
2754 const char *finishTitle,
2755 const nbgl_tipBox_t *tipBox,
2756 const nbgl_warning_t *warning,
2757 nbgl_choiceCallback_t choiceCallback)
2758{
2759 UNUSED(tipBox);
2760 ContextType_t type = REVIEW_USE_CASE;
2761
2762 // if no warning at all, it's a simple review
2763 if ((warning == NULL)
2764 || ((warning->predefinedSet == 0) && (warning->info == NULL)
2765 && (warning->reviewDetails == NULL) && (warning->prelude == NULL))) {
2766 useCaseReview(type,
2767 operationType,
2768 tagValueList,
2769 icon,
2770 reviewTitle,
2771 reviewSubTitle,
2772 finishTitle,
2773 choiceCallback);
2774 return;
2775 }
2776 if (warning->predefinedSet == (1 << W3C_NO_THREAT_WARN)) {
2777 operationType |= NO_THREAT_OPERATION;
2778 }
2779 else if (warning->predefinedSet != 0) {
2780 operationType |= RISKY_OPERATION;
2781 }
2782
2783 memset(&reviewWithWarnCtx, 0, sizeof(reviewWithWarnCtx));
2784 reviewWithWarnCtx.type = type;
2785 reviewWithWarnCtx.operationType = operationType;
2786 reviewWithWarnCtx.tagValueList = tagValueList;
2787 reviewWithWarnCtx.icon = icon;
2788 reviewWithWarnCtx.reviewTitle = reviewTitle;
2789 reviewWithWarnCtx.reviewSubTitle = reviewSubTitle;
2790 reviewWithWarnCtx.finishTitle = finishTitle;
2791 reviewWithWarnCtx.warning = warning;
2792 reviewWithWarnCtx.choiceCallback = choiceCallback;
2793
2794 // if the warning contains a prelude, display it first
2795 if (reviewWithWarnCtx.warning->prelude) {
2796 displayPrelude();
2797 }
2798 // display the initial warning only of a risk/threat or blind signing or prelude
2799 else if (HAS_INITIAL_WARNING(warning)) {
2800 displayInitialWarning();
2801 }
2802 else {
2803 useCaseReview(type,
2804 operationType,
2805 tagValueList,
2806 icon,
2807 reviewTitle,
2808 reviewSubTitle,
2809 finishTitle,
2810 choiceCallback);
2811 }
2812}
2813
2834 const nbgl_contentTagValueList_t *tagValueList,
2835 const nbgl_icon_details_t *icon,
2836 const char *reviewTitle,
2837 const char *reviewSubTitle,
2838 const char *finishTitle,
2839 const nbgl_tipBox_t *dummy,
2840 nbgl_choiceCallback_t choiceCallback)
2841{
2842 nbgl_useCaseAdvancedReview(operationType,
2843 tagValueList,
2844 icon,
2845 reviewTitle,
2846 reviewSubTitle,
2847 finishTitle,
2848 dummy,
2849 &blindSigningWarning,
2850 choiceCallback);
2851}
2852
2868 const nbgl_contentTagValueList_t *tagValueList,
2869 const nbgl_icon_details_t *icon,
2870 const char *reviewTitle,
2871 const char *reviewSubTitle,
2872 const char *finishTitle,
2873 nbgl_choiceCallback_t choiceCallback)
2874{
2875 nbgl_useCaseReview(operationType,
2876 tagValueList,
2877 icon,
2878 reviewTitle,
2879 reviewSubTitle,
2880 finishTitle,
2881 choiceCallback);
2882}
2883
2900void nbgl_useCaseAddressReview(const char *address,
2901 const nbgl_contentTagValueList_t *additionalTagValueList,
2902 const nbgl_icon_details_t *icon,
2903 const char *reviewTitle,
2904 const char *reviewSubTitle,
2905 nbgl_choiceCallback_t choiceCallback)
2906{
2907 memset(&context, 0, sizeof(UseCaseContext_t));
2908 context.type = ADDRESS_REVIEW_USE_CASE;
2909 context.review.address = address;
2910 context.review.reviewTitle = reviewTitle;
2911 context.review.reviewSubTitle = reviewSubTitle;
2912 context.review.icon = icon;
2913 context.review.onChoice = choiceCallback;
2914 context.currentPage = 0;
2915 // + 4 because 1 page for title, 1 for address and 2 pages at the end for approve/reject
2916 // + 1 if sub Title
2917 context.nbPages = reviewSubTitle ? 5 : 4;
2918 if (additionalTagValueList) {
2919 context.review.tagValueList = PIC(additionalTagValueList);
2920 context.nbPages += additionalTagValueList->nbPairs;
2921 }
2922
2923 displayReviewPage(FORWARD_DIRECTION);
2924}
2925
2935 const char *rejectText,
2936 nbgl_callback_t rejectCallback)
2937{
2938 memset(&context, 0, sizeof(UseCaseContext_t));
2939 context.type = GENERIC_REVIEW_USE_CASE;
2940 context.content.rejectText = rejectText;
2941 context.content.quitCallback = rejectCallback;
2942 context.content.genericContents.nbContents = contents->nbContents;
2943 context.content.genericContents.callbackCallNeeded = contents->callbackCallNeeded;
2944 if (contents->callbackCallNeeded) {
2945 context.content.genericContents.contentGetterCallback = contents->contentGetterCallback;
2946 }
2947 else {
2948 context.content.genericContents.contentsList = PIC(contents->contentsList);
2949 }
2950
2951 startUseCaseContent();
2952}
2953
2962void nbgl_useCaseStatus(const char *message, bool isSuccess, nbgl_callback_t quitCallback)
2963{
2964 UNUSED(isSuccess);
2965 memset(&context, 0, sizeof(UseCaseContext_t));
2966 context.type = STATUS_USE_CASE;
2967 context.stepCallback = quitCallback;
2968 context.currentPage = 0;
2969 context.nbPages = 1;
2970
2972 NULL,
2973 message,
2974 NULL,
2975 statusButtonCallback,
2976 false,
2977 NO_FORCED_TYPE,
2978 false);
2979}
2980
2988 nbgl_callback_t quitCallback)
2989{
2990 const char *msg;
2991 bool isSuccess;
2992 switch (reviewStatusType) {
2994 msg = "Operation signed";
2995 isSuccess = true;
2996 break;
2998 msg = "Operation rejected";
2999 isSuccess = false;
3000 break;
3002 msg = "Transaction signed";
3003 isSuccess = true;
3004 break;
3006 msg = "Transaction rejected";
3007 isSuccess = false;
3008 break;
3010 msg = "Message signed";
3011 isSuccess = true;
3012 break;
3014 msg = "Message rejected";
3015 isSuccess = false;
3016 break;
3018 msg = "Address verified";
3019 isSuccess = true;
3020 break;
3022 msg = "Address verification cancelled";
3023 isSuccess = false;
3024 break;
3025 default:
3026 return;
3027 }
3028 nbgl_useCaseStatus(msg, isSuccess, quitCallback);
3029}
3030
3044 const nbgl_icon_details_t *icon,
3045 const char *reviewTitle,
3046 const char *reviewSubTitle,
3047 nbgl_choiceCallback_t choiceCallback)
3048{
3049 // memorize streaming operation type for future API calls
3050 streamingOpType = operationType;
3051
3052 memset(&context, 0, sizeof(UseCaseContext_t));
3053 context.type = STREAMING_START_REVIEW_USE_CASE;
3054 context.operationType = operationType;
3055 context.review.reviewTitle = reviewTitle;
3056 context.review.reviewSubTitle = reviewSubTitle;
3057 context.review.icon = icon;
3058 context.review.onChoice = choiceCallback;
3059 context.currentPage = 0;
3060 context.nbPages = reviewSubTitle ? 3 : 2; // Start page(s) + trick for review continue
3061
3062 displayStreamingReviewPage(FORWARD_DIRECTION);
3063}
3064
3079 const nbgl_icon_details_t *icon,
3080 const char *reviewTitle,
3081 const char *reviewSubTitle,
3082 nbgl_choiceCallback_t choiceCallback)
3083{
3085 operationType, icon, reviewTitle, reviewSubTitle, &blindSigningWarning, choiceCallback);
3086}
3087
3104 const nbgl_icon_details_t *icon,
3105 const char *reviewTitle,
3106 const char *reviewSubTitle,
3107 const nbgl_warning_t *warning,
3108 nbgl_choiceCallback_t choiceCallback)
3109{
3110 memset(&context, 0, sizeof(UseCaseContext_t));
3111 context.type = STREAMING_START_REVIEW_USE_CASE;
3112 context.operationType = operationType;
3113 context.review.reviewTitle = reviewTitle;
3114 context.review.reviewSubTitle = reviewSubTitle;
3115 context.review.icon = icon;
3116 context.review.onChoice = choiceCallback;
3117 context.currentPage = 0;
3118 context.nbPages = reviewSubTitle ? 3 : 2; // Start page(s) + trick for review continue
3119
3120 // memorize streaming operation type for future API calls
3121 streamingOpType = operationType;
3122
3123 // if no warning at all, it's a simple review
3124 if ((warning == NULL)
3125 || ((warning->predefinedSet == 0) && (warning->info == NULL)
3126 && (warning->reviewDetails == NULL) && (warning->prelude == NULL))) {
3127 displayStreamingReviewPage(FORWARD_DIRECTION);
3128 return;
3129 }
3130 if (warning->predefinedSet == (1 << W3C_NO_THREAT_WARN)) {
3131 operationType |= NO_THREAT_OPERATION;
3132 }
3133 else if (warning->predefinedSet != 0) {
3134 operationType |= RISKY_OPERATION;
3135 }
3136 memset(&reviewWithWarnCtx, 0, sizeof(reviewWithWarnCtx));
3137
3138 reviewWithWarnCtx.type = context.type;
3139 reviewWithWarnCtx.operationType = operationType;
3140 reviewWithWarnCtx.icon = icon;
3141 reviewWithWarnCtx.reviewTitle = reviewTitle;
3142 reviewWithWarnCtx.reviewSubTitle = reviewSubTitle;
3143 reviewWithWarnCtx.choiceCallback = choiceCallback;
3144 reviewWithWarnCtx.warning = warning;
3145
3146 // if the warning contains a prelude, display it first
3147 if (reviewWithWarnCtx.warning->prelude) {
3148 displayPrelude();
3149 }
3150 // display the initial warning only of a risk/threat or blind signing or prelude
3151 else if (HAS_INITIAL_WARNING(warning)) {
3152 displayInitialWarning();
3153 }
3154 else {
3155 displayStreamingReviewPage(FORWARD_DIRECTION);
3156 }
3157}
3158
3173 nbgl_choiceCallback_t choiceCallback,
3174 nbgl_callback_t skipCallback)
3175{
3176 uint8_t curNbDataSets = context.review.nbDataSets;
3177
3178 memset(&context, 0, sizeof(UseCaseContext_t));
3179 context.type = STREAMING_CONTINUE_REVIEW_USE_CASE;
3180 context.operationType = streamingOpType;
3181 context.review.tagValueList = tagValueList;
3182 context.review.onChoice = choiceCallback;
3183 context.currentPage = 0;
3184 context.nbPages = tagValueList->nbPairs + 1; // data + trick for review continue
3185 context.review.skipCallback = skipCallback;
3186 context.review.nbDataSets = curNbDataSets + 1;
3187
3188 displayStreamingReviewPage(FORWARD_DIRECTION);
3189}
3190
3202 nbgl_choiceCallback_t choiceCallback)
3203{
3204 nbgl_useCaseReviewStreamingContinueExt(tagValueList, choiceCallback, NULL);
3205}
3206
3207void nbgl_useCaseReviewStreamingFinish(const char *finishTitle,
3208 nbgl_choiceCallback_t choiceCallback)
3209{
3210 memset(&context, 0, sizeof(UseCaseContext_t));
3211 context.type = STREAMING_FINISH_REVIEW_USE_CASE;
3212 context.operationType = streamingOpType;
3213 context.review.onChoice = choiceCallback;
3214 context.review.finishTitle = finishTitle;
3215 context.currentPage = 0;
3216 context.nbPages = 2; // 2 pages at the end for accept/reject
3217
3218 displayStreamingReviewPage(FORWARD_DIRECTION);
3219}
3220
3226void nbgl_useCaseSpinner(const char *text)
3227{
3228 memset(&context, 0, sizeof(UseCaseContext_t));
3229 context.type = SPINNER_USE_CASE;
3230 context.currentPage = 0;
3231 context.nbPages = 1;
3232
3233 displaySpinner(text);
3234}
3235
3249 const char *message,
3250 const char *subMessage,
3251 const char *confirmText,
3252 const char *cancelText,
3253 nbgl_choiceCallback_t callback)
3254{
3256 icon, message, subMessage, confirmText, cancelText, NULL, callback);
3257};
3258
3259// Common implementation of the choice flows. title is NULL for the basic ones, in which case
3260// message and subMessage share the same page.
3261static void startChoice(const nbgl_icon_details_t *icon,
3262 const char *title,
3263 const char *message,
3264 const char *subMessage,
3265 const char *confirmText,
3266 const char *cancelText,
3267 nbgl_genericDetails_t *details,
3268 nbgl_choiceCallback_t callback)
3269{
3270 memset(&context, 0, sizeof(UseCaseContext_t));
3271 context.type = CHOICE_USE_CASE;
3272 context.choice.icon = icon;
3273 context.choice.title = title;
3274 context.choice.message = message;
3275 context.choice.subMessage = subMessage;
3276 context.choice.confirmText = confirmText;
3277 context.choice.cancelText = cancelText;
3278 context.choice.onChoice = callback;
3279 context.choice.details = details;
3280 context.currentPage = 0;
3281 context.nbPages = 2 + getChoiceNbIntroPages(); // 2 pages for confirm/cancel
3282 if (details != NULL) {
3283 // only the first level of details and BAR_LIST type are supported
3284 if (details->type == BAR_LIST_WARNING) {
3285 context.nbPages += details->barList.nbBars;
3286 }
3287 }
3288
3289 displayChoicePage(FORWARD_DIRECTION);
3290}
3291
3307 const char *message,
3308 const char *subMessage,
3309 const char *confirmText,
3310 const char *cancelText,
3311 nbgl_genericDetails_t *details,
3312 nbgl_choiceCallback_t callback)
3313{
3314 startChoice(icon, NULL, message, subMessage, confirmText, cancelText, details, callback);
3315};
3316
3317// On Nano, the three texts of the advanced variant are spread over one page each, the title
3318// being kept as the first line of all of them. headerIcon is ignored (no top-right button).
3320 const nbgl_icon_details_t *headerIcon,
3321 const char *title,
3322 const char *message,
3323 const char *subMessage,
3324 const char *confirmText,
3325 const char *cancelText,
3326 nbgl_genericDetails_t *details,
3327 nbgl_choiceCallback_t callback)
3328{
3329 UNUSED(headerIcon);
3330 startChoice(centerIcon, title, message, subMessage, confirmText, cancelText, details, callback);
3331}
3332
3346void nbgl_useCaseConfirm(const char *message,
3347 const char *subMessage,
3348 const char *confirmText,
3349 const char *cancelText,
3350 nbgl_callback_t callback)
3351{
3352 memset(&context, 0, sizeof(UseCaseContext_t));
3353 context.type = CONFIRM_USE_CASE;
3354 context.confirm.message = message;
3355 context.confirm.subMessage = subMessage;
3356 context.confirm.confirmText = confirmText;
3357 context.confirm.cancelText = cancelText;
3358 context.confirm.onConfirm = callback;
3359 context.currentPage = 0;
3360 context.nbPages = 1 + 2; // 2 pages at the end for confirm/cancel
3361
3362 displayConfirm(FORWARD_DIRECTION);
3363}
3364
3375 const char *message,
3376 const char *actionText,
3377 nbgl_callback_t callback)
3378{
3379 nbgl_layoutCenteredInfo_t centeredInfo = {0};
3380
3381 UNUSED(actionText);
3382
3383 // memorize callback in context
3384 memset(&context, 0, sizeof(UseCaseContext_t));
3385 context.type = ACTION_USE_CASE;
3386 context.action.actionCallback = callback;
3387
3388 centeredInfo.icon = icon;
3389 centeredInfo.text1 = message;
3390 centeredInfo.style = BOLD_TEXT1_INFO;
3391 nbgl_stepDrawCenteredInfo(0, useCaseActionCallback, NULL, &centeredInfo, false);
3392}
3393
3394#ifdef NBGL_KEYPAD
3413void nbgl_useCaseKeypad(const char *title,
3414 uint8_t minDigits,
3415 uint8_t maxDigits,
3416 bool shuffled,
3417 bool hidden,
3418 nbgl_pinValidCallback_t validatePinCallback,
3419 nbgl_callback_t backCallback)
3420{
3421 nbgl_layoutDescription_t layoutDescription = {0};
3422 int status = -1;
3423
3424 // reset the keypad context
3425 memset(&context, 0, sizeof(KeypadContext_t));
3426 context.type = KEYPAD_USE_CASE;
3427 context.currentPage = 0;
3428 context.nbPages = 1;
3429 context.keypad.validatePin = validatePinCallback;
3430 context.keypad.backCallback = backCallback;
3431 context.keypad.pinMinDigits = minDigits;
3432 context.keypad.pinMaxDigits = maxDigits;
3433 context.keypad.hidden = hidden;
3434 context.keypad.layoutCtx = nbgl_layoutGet(&layoutDescription);
3435
3436 // add keypad
3437 status = nbgl_layoutAddKeypad(context.keypad.layoutCtx, keypadCallback, title, shuffled);
3438 if (status < 0) {
3439 LOG_WARN(
3440 USE_CASE_LOGGER, "nbgl_useCaseKeypad(): layout setup failed (status=%d)\n", status);
3441 return;
3442 }
3443 context.keypad.keypadIndex = status;
3444 // add digits
3445 status = nbgl_layoutAddKeypadContent(context.keypad.layoutCtx, hidden, maxDigits, "");
3446 if (status < 0) {
3447 LOG_WARN(
3448 USE_CASE_LOGGER, "nbgl_useCaseKeypad(): layout setup failed (status=%d)\n", status);
3449 return;
3450 }
3451
3452 nbgl_layoutDraw(context.keypad.layoutCtx);
3453 if (context.keypad.backCallback != NULL) {
3454 // force backspace to be visible at first digit, to be used as quit
3455 nbgl_layoutUpdateKeypad(context.keypad.layoutCtx, context.keypad.keypadIndex, false, true);
3456 }
3457 nbgl_refresh();
3458}
3459#endif // NBGL_KEYPAD
3460
3461#ifdef NBGL_KEYBOARD
3485void nbgl_useCaseKeyboard(const nbgl_keyboardParams_t *params, nbgl_callback_t backCallback)
3486{
3487 // clang-format off
3488 nbgl_layoutDescription_t layoutDescription = {0};
3489 nbgl_layoutCenteredInfo_t centeredInfo = {.text1 = params->title,
3490 .onTop = true};
3492 .indication = LEFT_ARROW | RIGHT_ARROW};
3493 nbgl_layoutKbd_t kbdInfo = {.callback = &keyboardCallback,
3494 .mode = params->mode,
3495 .lettersOnly = params->lettersOnly};
3496 int status = -1;
3497 // clang-format on
3498
3499 // reset the keypad context
3500 memset(&context, 0, sizeof(UseCaseContext_t));
3501 context.type = KEYBOARD_USE_CASE;
3502 context.currentPage = 0;
3503 context.nbPages = 1;
3504 context.keyboard.entryBuffer = PIC(params->entryBuffer);
3505 context.keyboard.entryMaxLen = params->entryMaxLen;
3506 context.keyboard.entryBuffer[0] = '\0';
3507
3508 context.keyboard.content.type = params->type;
3509
3510 // memorize context
3511 context.keyboard.backCallback = PIC(backCallback);
3512
3513 switch (params->type) {
3515 context.keyboard.actionCallback = PIC(params->confirmationParams.onButtonCallback);
3516 break;
3518 // No need for 'Validate' or 'Case switch' buttons
3519 kbdInfo.keyMask = (1 << VALIDATE_INDEX) | (1 << SHIFT_KEY_INDEX);
3520 context.keyboard.getSuggestButtons
3522 context.keyboard.onButtonCallback = PIC(params->suggestionParams.onButtonCallback);
3523 context.keyboard.content.suggestionButtons = (nbgl_layoutSuggestionButtons_t){
3524 .buttons = PIC(params->suggestionParams.buttons),
3525 .firstButtonToken = params->suggestionParams.firstButtonToken,
3526 };
3527 break;
3528 default:
3529 return;
3530 }
3531
3532 // get a layout
3533 context.keyboard.layoutCtx = nbgl_layoutGet(&layoutDescription);
3534
3535 // add description
3536 nbgl_layoutAddCenteredInfo(context.keyboard.layoutCtx, &centeredInfo);
3537
3538 // Add keyboard
3539 status = nbgl_layoutAddKeyboard(context.keyboard.layoutCtx, &kbdInfo);
3540 if (status < 0) {
3541 LOG_WARN(
3542 USE_CASE_LOGGER, "nbgl_useCaseKeyboard(): layout setup failed (status=%d)\n", status);
3543 return;
3544 }
3545 context.keyboard.keyboardIndex = status;
3546
3547 // add empty entered text
3548 status = nbgl_layoutAddEnteredText(context.keyboard.layoutCtx, "", true);
3549 if (status < 0) {
3550 LOG_WARN(
3551 USE_CASE_LOGGER, "nbgl_useCaseKeyboard(): layout setup failed (status=%d)\n", status);
3552 return;
3553 }
3554 context.keyboard.textIndex = status;
3555
3556 nbgl_layoutAddNavigation(context.keyboard.layoutCtx, &navInfo);
3557
3558 nbgl_layoutDraw(context.keyboard.layoutCtx);
3559 nbgl_refresh();
3560}
3561#endif // NBGL_KEYBOARD
3562
3563#endif // HAVE_SE_TOUCH
3564#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
#define LOG_DEBUG(__logger,...)
Definition nbgl_debug.h:86
@ USE_CASE_LOGGER
Definition nbgl_debug.h:35
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
@ 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_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
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