Alexander Kopachov 3 лет назад
Родитель
Сommit
e8bcd906fa

+ 1 - 0
application.fam

@@ -15,5 +15,6 @@ App(
     stack_size=2 * 1024,
     order=20,
     fap_category="Misc",
+    fap_icon_assets="images",
     fap_icon="totp_10px.png"
 )

BIN
images/DolphinCommon_56x48.png


BIN
images/totp_arrow_left_8x9.png


BIN
images/totp_arrow_right_8x9.png


+ 142 - 134
scenes/add_new_token/totp_scene_add_new_token.c

@@ -8,6 +8,8 @@
 #include "../../services/base32/base32.h"
 #include "../../services/config/config.h"
 #include "../../services/ui/ui_controls.h"
+#include "../../services/roll_value/roll_value.h"
+#include "../../services/nullable/nullable.h"
 #include "../generate_token/totp_scene_generate_token.h"
 
 #define TOKEN_ALGO_LIST_LENGTH 3
@@ -34,7 +36,7 @@ typedef struct {
     InputTextSceneContext* token_secret_input_context;
     InputTextSceneState* input_state;
     uint32_t input_started_at;
-    int16_t current_token_index;
+    TotpNullable_uint16_t current_token_index;
     int16_t screen_y_offset;
     TokenHashAlgo algo;
     TokenDigitsCount digits_count;
@@ -87,9 +89,9 @@ void totp_scene_add_new_token_activate(
     scene_state->input_state = NULL;
 
     if(context == NULL) {
-        scene_state->current_token_index = -1;
+        TOTP_NULLABLE_NULL(scene_state->current_token_index);
     } else {
-        scene_state->current_token_index = context->current_token_index;
+        TOTP_NULLABLE_VALUE(scene_state->current_token_index, context->current_token_index);
     }
 }
 
@@ -150,144 +152,150 @@ void update_screen_y_offset(SceneState* scene_state) {
 }
 
 bool totp_scene_add_new_token_handle_event(PluginEvent* const event, PluginState* plugin_state) {
-    if(event->type == EventTypeKey) {
-        SceneState* scene_state = (SceneState*)plugin_state->current_scene_state;
-        if(scene_state->input_started_at > 0 &&
-           furi_get_tick() - scene_state->input_started_at > 300) {
-            return totp_input_text_handle_event(event, scene_state->input_state);
+    if(event->type != EventTypeKey) {
+        return true;
+    }
+
+    SceneState* scene_state = (SceneState*)plugin_state->current_scene_state;
+    if(scene_state->input_started_at > 0 &&
+       furi_get_tick() - scene_state->input_started_at > 300) {
+        return totp_input_text_handle_event(event, scene_state->input_state);
+    }
+
+    if(event->input.type == InputTypeLong && event->input.key == InputKeyBack) {
+        return false;
+    }
+
+    if(event->input.type != InputTypePress) {
+        return true;
+    }
+
+    switch(event->input.key) {
+    case InputKeyUp:
+        totp_roll_value_uint8_t(
+            &scene_state->selected_control,
+            -1,
+            TokenNameTextBox,
+            ConfirmButton,
+            RollOverflowBehaviorStop);
+        update_screen_y_offset(scene_state);
+        break;
+    case InputKeyDown:
+        totp_roll_value_uint8_t(
+            &scene_state->selected_control,
+            1,
+            TokenNameTextBox,
+            ConfirmButton,
+            RollOverflowBehaviorStop);
+        update_screen_y_offset(scene_state);
+        break;
+    case InputKeyRight:
+        if(scene_state->selected_control == TokenAlgoSelect) {
+            totp_roll_value_uint8_t(&scene_state->algo, 1, SHA1, SHA512, RollOverflowBehaviorRoll);
+        } else if(scene_state->selected_control == TokenLengthSelect) {
+            totp_roll_value_uint8_t(
+                &scene_state->digits_count,
+                1,
+                TOTP_6_DIGITS,
+                TOTP_8_DIGITS,
+                RollOverflowBehaviorRoll);
         }
+        break;
+    case InputKeyLeft:
+        if(scene_state->selected_control == TokenAlgoSelect) {
+            totp_roll_value_uint8_t(
+                &scene_state->algo, -1, SHA1, SHA512, RollOverflowBehaviorRoll);
+        } else if(scene_state->selected_control == TokenLengthSelect) {
+            totp_roll_value_uint8_t(
+                &scene_state->digits_count,
+                -1,
+                TOTP_6_DIGITS,
+                TOTP_8_DIGITS,
+                RollOverflowBehaviorRoll);
+        }
+        break;
+    case InputKeyOk:
+        switch(scene_state->selected_control) {
+        case TokenNameTextBox:
+            if(scene_state->input_state != NULL) {
+                totp_input_text_free(scene_state->input_state);
+            }
+            scene_state->input_state =
+                totp_input_text_activate(scene_state->token_name_input_context);
+            scene_state->input_started_at = furi_get_tick();
+            break;
+        case TokenSecretTextBox:
+            if(scene_state->input_state != NULL) {
+                totp_input_text_free(scene_state->input_state);
+            }
+            scene_state->input_state =
+                totp_input_text_activate(scene_state->token_secret_input_context);
+            scene_state->input_started_at = furi_get_tick();
+            break;
+        case TokenAlgoSelect:
+            break;
+        case TokenLengthSelect:
+            break;
+        case ConfirmButton: {
+            TokenInfo* tokenInfo = token_info_alloc();
+            bool token_secret_set = token_info_set_secret(
+                tokenInfo,
+                scene_state->token_secret,
+                scene_state->token_secret_length,
+                &plugin_state->iv[0]);
 
-        if(event->input.type == InputTypeLong && event->input.key == InputKeyBack) {
-            return false;
-        } else if(event->input.type == InputTypePress) {
-            switch(event->input.key) {
-            case InputKeyUp:
-                if(scene_state->selected_control > TokenNameTextBox) {
-                    scene_state->selected_control--;
-                    update_screen_y_offset(scene_state);
-                }
-                break;
-            case InputKeyDown:
-                if(scene_state->selected_control < ConfirmButton) {
-                    scene_state->selected_control++;
-                    update_screen_y_offset(scene_state);
-                }
-                break;
-            case InputKeyRight:
-                if(scene_state->selected_control == TokenAlgoSelect) {
-                    if(scene_state->algo < SHA512) {
-                        scene_state->algo++;
-                    } else {
-                        scene_state->algo = SHA1;
-                    }
-                } else if(scene_state->selected_control == TokenLengthSelect) {
-                    if(scene_state->digits_count < TOTP_8_DIGITS) {
-                        scene_state->digits_count++;
-                    } else {
-                        scene_state->digits_count = TOTP_6_DIGITS;
-                    }
-                }
-                break;
-            case InputKeyLeft:
-                if(scene_state->selected_control == TokenAlgoSelect) {
-                    if(scene_state->algo > SHA1) {
-                        scene_state->algo--;
-                    } else {
-                        scene_state->algo = SHA512;
-                    }
-                } else if(scene_state->selected_control == TokenLengthSelect) {
-                    if(scene_state->digits_count > TOTP_6_DIGITS) {
-                        scene_state->digits_count--;
-                    } else {
-                        scene_state->digits_count = TOTP_8_DIGITS;
-                    }
-                }
-                break;
-            case InputKeyOk:
-                switch(scene_state->selected_control) {
-                case TokenNameTextBox:
-                    if(scene_state->input_state != NULL) {
-                        totp_input_text_free(scene_state->input_state);
-                    }
-                    scene_state->input_state =
-                        totp_input_text_activate(scene_state->token_name_input_context);
-                    scene_state->input_started_at = furi_get_tick();
-                    break;
-                case TokenSecretTextBox:
-                    if(scene_state->input_state != NULL) {
-                        totp_input_text_free(scene_state->input_state);
-                    }
-                    scene_state->input_state =
-                        totp_input_text_activate(scene_state->token_secret_input_context);
-                    scene_state->input_started_at = furi_get_tick();
-                    break;
-                case TokenAlgoSelect:
-                    break;
-                case TokenLengthSelect:
-                    break;
-                case ConfirmButton: {
-                    TokenInfo* tokenInfo = token_info_alloc();
-                    bool token_secret_set = token_info_set_secret(
-                        tokenInfo,
-                        scene_state->token_secret,
-                        scene_state->token_secret_length,
-                        &plugin_state->iv[0]);
-
-                    if(token_secret_set) {
-                        tokenInfo->name = malloc(scene_state->token_name_length + 1);
-                        strlcpy(
-                            tokenInfo->name,
-                            scene_state->token_name,
-                            scene_state->token_name_length + 1);
-                        tokenInfo->algo = scene_state->algo;
-                        tokenInfo->digits = scene_state->digits_count;
-
-                        if(plugin_state->tokens_list == NULL) {
-                            plugin_state->tokens_list = list_init_head(tokenInfo);
-                        } else {
-                            list_add(plugin_state->tokens_list, tokenInfo);
-                        }
-                        plugin_state->tokens_count++;
-
-                        totp_config_file_save_new_token(tokenInfo);
-
-                        GenerateTokenSceneContext generate_scene_context = {
-                            .current_token_index = plugin_state->tokens_count - 1};
-                        totp_scene_director_activate_scene(
-                            plugin_state, TotpSceneGenerateToken, &generate_scene_context);
-                    } else {
-                        token_info_free(tokenInfo);
-                        DialogMessage* message = dialog_message_alloc();
-                        dialog_message_set_buttons(message, "Back", NULL, NULL);
-                        dialog_message_set_text(
-                            message,
-                            "Token secret is invalid",
-                            SCREEN_WIDTH_CENTER,
-                            SCREEN_HEIGHT_CENTER,
-                            AlignCenter,
-                            AlignCenter);
-                        dialog_message_show(plugin_state->dialogs, message);
-                        dialog_message_free(message);
-                        scene_state->selected_control = TokenSecretTextBox;
-                        update_screen_y_offset(scene_state);
-                    }
-                    break;
-                }
-                }
-                break;
-            case InputKeyBack:
-                if(scene_state->current_token_index >= 0) {
-                    GenerateTokenSceneContext generate_scene_context = {
-                        .current_token_index = scene_state->current_token_index};
-                    totp_scene_director_activate_scene(
-                        plugin_state, TotpSceneGenerateToken, &generate_scene_context);
+            if(token_secret_set) {
+                tokenInfo->name = malloc(scene_state->token_name_length + 1);
+                strlcpy(
+                    tokenInfo->name, scene_state->token_name, scene_state->token_name_length + 1);
+                tokenInfo->algo = scene_state->algo;
+                tokenInfo->digits = scene_state->digits_count;
+
+                if(plugin_state->tokens_list == NULL) {
+                    plugin_state->tokens_list = list_init_head(tokenInfo);
                 } else {
-                    totp_scene_director_activate_scene(plugin_state, TotpSceneGenerateToken, NULL);
+                    list_add(plugin_state->tokens_list, tokenInfo);
                 }
-                break;
+                plugin_state->tokens_count++;
+
+                totp_config_file_save_new_token(tokenInfo);
+
+                GenerateTokenSceneContext generate_scene_context = {
+                    .current_token_index = plugin_state->tokens_count - 1};
+                totp_scene_director_activate_scene(
+                    plugin_state, TotpSceneGenerateToken, &generate_scene_context);
+            } else {
+                token_info_free(tokenInfo);
+                DialogMessage* message = dialog_message_alloc();
+                dialog_message_set_buttons(message, "Back", NULL, NULL);
+                dialog_message_set_text(
+                    message,
+                    "Token secret is invalid",
+                    SCREEN_WIDTH_CENTER,
+                    SCREEN_HEIGHT_CENTER,
+                    AlignCenter,
+                    AlignCenter);
+                dialog_message_show(plugin_state->dialogs, message);
+                dialog_message_free(message);
+                scene_state->selected_control = TokenSecretTextBox;
+                update_screen_y_offset(scene_state);
             }
+            break;
         }
+        }
+        break;
+    case InputKeyBack:
+        if(!scene_state->current_token_index.is_null) {
+            GenerateTokenSceneContext generate_scene_context = {
+                .current_token_index = scene_state->current_token_index.value};
+            totp_scene_director_activate_scene(
+                plugin_state, TotpSceneGenerateToken, &generate_scene_context);
+        } else {
+            totp_scene_director_activate_scene(plugin_state, TotpSceneGenerateToken, NULL);
+        }
+        break;
     }
+
     return true;
 }
 

+ 1 - 1
scenes/add_new_token/totp_scene_add_new_token.h

@@ -7,7 +7,7 @@
 #include "../../types/plugin_event.h"
 
 typedef struct {
-    uint8_t current_token_index;
+    uint16_t current_token_index;
 } TokenAddEditSceneContext;
 
 void totp_scene_add_new_token_init(const PluginState* plugin_state);

+ 74 - 71
scenes/app_settings/totp_app_settings.c

@@ -4,6 +4,8 @@
 #include "../token_menu/totp_scene_token_menu.h"
 #include "../../services/ui/constants.h"
 #include "../../services/config/config.h"
+#include "../../services/roll_value/roll_value.h"
+#include "../../services/nullable/nullable.h"
 
 #define DIGIT_TO_CHAR(digit) ((digit) + '0')
 
@@ -12,7 +14,7 @@ typedef enum { HoursInput, MinutesInput, ConfirmButton } Control;
 typedef struct {
     int8_t tz_offset_hours;
     uint8_t tz_offset_minutes;
-    int16_t current_token_index;
+    TotpNullable_uint16_t current_token_index;
     Control selected_control;
 } SceneState;
 
@@ -26,9 +28,9 @@ void totp_scene_app_settings_activate(
     SceneState* scene_state = malloc(sizeof(SceneState));
     plugin_state->current_scene_state = scene_state;
     if(context != NULL) {
-        scene_state->current_token_index = context->current_token_index;
+        TOTP_NULLABLE_VALUE(scene_state->current_token_index, context->current_token_index);
     } else {
-        scene_state->current_token_index = -1;
+        TOTP_NULLABLE_NULL(scene_state->current_token_index);
     }
 
     float off_int;
@@ -90,77 +92,78 @@ void totp_scene_app_settings_render(Canvas* const canvas, PluginState* plugin_st
         scene_state->selected_control == ConfirmButton);
 }
 
-bool totp_scene_app_settings_handle_event(const PluginEvent* const event, PluginState* plugin_state) {
-    if(event->type == EventTypeKey) {
-        SceneState* scene_state = (SceneState*)plugin_state->current_scene_state;
-        if(event->input.type == InputTypePress) {
-            switch(event->input.key) {
-            case InputKeyUp:
-                if(scene_state->selected_control > HoursInput) {
-                    scene_state->selected_control--;
-                }
-                break;
-            case InputKeyDown:
-                if(scene_state->selected_control < ConfirmButton) {
-                    scene_state->selected_control++;
-                }
-                break;
-            case InputKeyRight:
-                if(scene_state->selected_control == HoursInput) {
-                    if(scene_state->tz_offset_hours < 12) {
-                        scene_state->tz_offset_hours++;
-                    }
-                } else if(scene_state->selected_control == MinutesInput) {
-                    if(scene_state->tz_offset_minutes < 45) {
-                        scene_state->tz_offset_minutes += 15;
-                    } else {
-                        scene_state->tz_offset_minutes = 0;
-                    }
-                }
-                break;
-            case InputKeyLeft:
-                if(scene_state->selected_control == HoursInput) {
-                    if(scene_state->tz_offset_hours > -12) {
-                        scene_state->tz_offset_hours--;
-                    }
-                } else if(scene_state->selected_control == MinutesInput) {
-                    if(scene_state->tz_offset_minutes >= 15) {
-                        scene_state->tz_offset_minutes -= 15;
-                    } else {
-                        scene_state->tz_offset_minutes = 45;
-                    }
-                }
-                break;
-            case InputKeyOk:
-                if(scene_state->selected_control == ConfirmButton) {
-                    plugin_state->timezone_offset = (float)scene_state->tz_offset_hours +
-                                                    (float)scene_state->tz_offset_minutes / 60.0f;
-                    totp_config_file_update_timezone_offset(plugin_state->timezone_offset);
-
-                    if(scene_state->current_token_index >= 0) {
-                        TokenMenuSceneContext generate_scene_context = {
-                            .current_token_index = scene_state->current_token_index};
-                        totp_scene_director_activate_scene(
-                            plugin_state, TotpSceneTokenMenu, &generate_scene_context);
-                    } else {
-                        totp_scene_director_activate_scene(plugin_state, TotpSceneTokenMenu, NULL);
-                    }
-                }
-                break;
-            case InputKeyBack: {
-                if(scene_state->current_token_index >= 0) {
-                    TokenMenuSceneContext generate_scene_context = {
-                        .current_token_index = scene_state->current_token_index};
-                    totp_scene_director_activate_scene(
-                        plugin_state, TotpSceneTokenMenu, &generate_scene_context);
-                } else {
-                    totp_scene_director_activate_scene(plugin_state, TotpSceneTokenMenu, NULL);
-                }
-                break;
-            }
+bool totp_scene_app_settings_handle_event(
+    const PluginEvent* const event,
+    PluginState* plugin_state) {
+    if(event->type != EventTypeKey) {
+        return true;
+    }
+
+    SceneState* scene_state = (SceneState*)plugin_state->current_scene_state;
+    if(event->input.type != InputTypePress) {
+        return true;
+    }
+
+    switch(event->input.key) {
+    case InputKeyUp:
+        totp_roll_value_uint8_t(
+            &scene_state->selected_control,
+            -1,
+            HoursInput,
+            ConfirmButton,
+            RollOverflowBehaviorStop);
+        break;
+    case InputKeyDown:
+        totp_roll_value_uint8_t(
+            &scene_state->selected_control, 1, HoursInput, ConfirmButton, RollOverflowBehaviorStop);
+        break;
+    case InputKeyRight:
+        if(scene_state->selected_control == HoursInput) {
+            totp_roll_value_int8_t(
+                &scene_state->tz_offset_hours, 1, -12, 12, RollOverflowBehaviorStop);
+        } else if(scene_state->selected_control == MinutesInput) {
+            totp_roll_value_uint8_t(
+                &scene_state->tz_offset_minutes, 15, 0, 45, RollOverflowBehaviorRoll);
+        }
+        break;
+    case InputKeyLeft:
+        if(scene_state->selected_control == HoursInput) {
+            totp_roll_value_int8_t(
+                &scene_state->tz_offset_hours, -1, -12, 12, RollOverflowBehaviorStop);
+        } else if(scene_state->selected_control == MinutesInput) {
+            totp_roll_value_uint8_t(
+                &scene_state->tz_offset_minutes, -15, 0, 45, RollOverflowBehaviorRoll);
+        }
+        break;
+    case InputKeyOk:
+        if(scene_state->selected_control == ConfirmButton) {
+            plugin_state->timezone_offset = (float)scene_state->tz_offset_hours +
+                                            (float)scene_state->tz_offset_minutes / 60.0f;
+            totp_config_file_update_timezone_offset(plugin_state->timezone_offset);
+
+            if(!scene_state->current_token_index.is_null) {
+                TokenMenuSceneContext generate_scene_context = {
+                    .current_token_index = scene_state->current_token_index.value};
+                totp_scene_director_activate_scene(
+                    plugin_state, TotpSceneTokenMenu, &generate_scene_context);
+            } else {
+                totp_scene_director_activate_scene(plugin_state, TotpSceneTokenMenu, NULL);
             }
         }
+        break;
+    case InputKeyBack: {
+        if(!scene_state->current_token_index.is_null) {
+            TokenMenuSceneContext generate_scene_context = {
+                .current_token_index = scene_state->current_token_index.value};
+            totp_scene_director_activate_scene(
+                plugin_state, TotpSceneTokenMenu, &generate_scene_context);
+        } else {
+            totp_scene_director_activate_scene(plugin_state, TotpSceneTokenMenu, NULL);
+        }
+        break;
     }
+    }
+
     return true;
 }
 

+ 4 - 2
scenes/app_settings/totp_app_settings.h

@@ -7,7 +7,7 @@
 #include "../../types/plugin_event.h"
 
 typedef struct {
-    uint8_t current_token_index;
+    uint16_t current_token_index;
 } AppSettingsSceneContext;
 
 void totp_scene_app_settings_init(const PluginState* plugin_state);
@@ -15,6 +15,8 @@ void totp_scene_app_settings_activate(
     PluginState* plugin_state,
     const AppSettingsSceneContext* context);
 void totp_scene_app_settings_render(Canvas* const canvas, PluginState* plugin_state);
-bool totp_scene_app_settings_handle_event(const PluginEvent* const event, PluginState* plugin_state);
+bool totp_scene_app_settings_handle_event(
+    const PluginEvent* const event,
+    PluginState* plugin_state);
 void totp_scene_app_settings_deactivate(PluginState* plugin_state);
 void totp_scene_app_settings_free(const PluginState* plugin_state);

+ 78 - 72
scenes/authenticate/totp_scene_authenticate.c

@@ -1,7 +1,7 @@
 #include "totp_scene_authenticate.h"
 #include <dialogs/dialogs.h>
+#include <totp_icons.h>
 #include "../../types/common.h"
-#include "../../services/ui/icons.h"
 #include "../../services/ui/constants.h"
 #include "../../services/config/config.h"
 #include "../scene_director.h"
@@ -9,6 +9,10 @@
 #include "../../services/crypto/crypto.h"
 
 #define MAX_CODE_LENGTH TOTP_IV_SIZE
+#define ARROW_UP_CODE 2
+#define ARROW_RIGHT_CODE 8
+#define ARROW_DOWN_CODE 11
+#define ARROW_LEFT_CODE 5
 
 typedef struct {
     uint8_t code_input[MAX_CODE_LENGTH];
@@ -73,78 +77,80 @@ void totp_scene_authenticate_render(Canvas* const canvas, PluginState* plugin_st
     }
 }
 
-bool totp_scene_authenticate_handle_event(const PluginEvent* const event, PluginState* plugin_state) {
-    if(event->type == EventTypeKey) {
-        if(event->input.type == InputTypeLong && event->input.key == InputKeyBack) {
-            return false;
-        } else if(event->input.type == InputTypePress) {
-            SceneState* scene_state = (SceneState*)plugin_state->current_scene_state;
-
-            const uint8_t ARROW_UP_CODE = 2;
-            const uint8_t ARROW_RIGHT_CODE = 8;
-            const uint8_t ARROW_DOWN_CODE = 11;
-            const uint8_t ARROW_LEFT_CODE = 5;
-
-            switch(event->input.key) {
-            case InputKeyUp:
-                if(scene_state->code_length < MAX_CODE_LENGTH) {
-                    scene_state->code_input[scene_state->code_length] = ARROW_UP_CODE;
-                    scene_state->code_length++;
-                }
-                break;
-            case InputKeyDown:
-                if(scene_state->code_length < MAX_CODE_LENGTH) {
-                    scene_state->code_input[scene_state->code_length] = ARROW_DOWN_CODE;
-                    scene_state->code_length++;
-                }
-                break;
-            case InputKeyRight:
-                if(scene_state->code_length < MAX_CODE_LENGTH) {
-                    scene_state->code_input[scene_state->code_length] = ARROW_RIGHT_CODE;
-                    scene_state->code_length++;
-                }
-                break;
-            case InputKeyLeft:
-                if(scene_state->code_length < MAX_CODE_LENGTH) {
-                    scene_state->code_input[scene_state->code_length] = ARROW_LEFT_CODE;
-                    scene_state->code_length++;
-                }
-                break;
-            case InputKeyOk:
-                totp_crypto_seed_iv(
-                    plugin_state, &scene_state->code_input[0], scene_state->code_length);
-
-                if(totp_crypto_verify_key(plugin_state)) {
-                    FURI_LOG_D(LOGGING_TAG, "PIN is valid");
-                    totp_scene_director_activate_scene(plugin_state, TotpSceneGenerateToken, NULL);
-                } else {
-                    FURI_LOG_D(LOGGING_TAG, "PIN is NOT valid");
-                    memset(&scene_state->code_input[0], 0, MAX_CODE_LENGTH);
-                    memset(&plugin_state->iv[0], 0, TOTP_IV_SIZE);
-                    scene_state->code_length = 0;
-
-                    DialogMessage* message = dialog_message_alloc();
-                    dialog_message_set_buttons(message, "Try again", NULL, NULL);
-                    dialog_message_set_header(
-                        message,
-                        "You entered\ninvalid PIN",
-                        SCREEN_WIDTH_CENTER - 25,
-                        SCREEN_HEIGHT_CENTER - 5,
-                        AlignCenter,
-                        AlignCenter);
-                    dialog_message_set_icon(message, &I_DolphinCommon_56x48, 72, 17);
-                    dialog_message_show(plugin_state->dialogs, message);
-                    dialog_message_free(message);
-                }
-                break;
-            case InputKeyBack:
-                if(scene_state->code_length > 0) {
-                    scene_state->code_input[scene_state->code_length - 1] = 0;
-                    scene_state->code_length--;
-                }
-                break;
-            }
+bool totp_scene_authenticate_handle_event(
+    const PluginEvent* const event,
+    PluginState* plugin_state) {
+    if(event->type != EventTypeKey) {
+        return true;
+    }
+
+    if(event->input.type == InputTypeLong && event->input.key == InputKeyBack) {
+        return false;
+    }
+
+    if(event->input.type != InputTypePress) {
+        return true;
+    }
+
+    SceneState* scene_state = (SceneState*)plugin_state->current_scene_state;
+
+    switch(event->input.key) {
+    case InputKeyUp:
+        if(scene_state->code_length < MAX_CODE_LENGTH) {
+            scene_state->code_input[scene_state->code_length] = ARROW_UP_CODE;
+            scene_state->code_length++;
+        }
+        break;
+    case InputKeyDown:
+        if(scene_state->code_length < MAX_CODE_LENGTH) {
+            scene_state->code_input[scene_state->code_length] = ARROW_DOWN_CODE;
+            scene_state->code_length++;
+        }
+        break;
+    case InputKeyRight:
+        if(scene_state->code_length < MAX_CODE_LENGTH) {
+            scene_state->code_input[scene_state->code_length] = ARROW_RIGHT_CODE;
+            scene_state->code_length++;
+        }
+        break;
+    case InputKeyLeft:
+        if(scene_state->code_length < MAX_CODE_LENGTH) {
+            scene_state->code_input[scene_state->code_length] = ARROW_LEFT_CODE;
+            scene_state->code_length++;
+        }
+        break;
+    case InputKeyOk:
+        totp_crypto_seed_iv(plugin_state, &scene_state->code_input[0], scene_state->code_length);
+
+        if(totp_crypto_verify_key(plugin_state)) {
+            FURI_LOG_D(LOGGING_TAG, "PIN is valid");
+            totp_scene_director_activate_scene(plugin_state, TotpSceneGenerateToken, NULL);
+        } else {
+            FURI_LOG_D(LOGGING_TAG, "PIN is NOT valid");
+            memset(&scene_state->code_input[0], 0, MAX_CODE_LENGTH);
+            memset(&plugin_state->iv[0], 0, TOTP_IV_SIZE);
+            scene_state->code_length = 0;
+
+            DialogMessage* message = dialog_message_alloc();
+            dialog_message_set_buttons(message, "Try again", NULL, NULL);
+            dialog_message_set_header(
+                message,
+                "You entered\ninvalid PIN",
+                SCREEN_WIDTH_CENTER - 25,
+                SCREEN_HEIGHT_CENTER - 5,
+                AlignCenter,
+                AlignCenter);
+            dialog_message_set_icon(message, &I_DolphinCommon_56x48, 72, 17);
+            dialog_message_show(plugin_state->dialogs, message);
+            dialog_message_free(message);
+        }
+        break;
+    case InputKeyBack:
+        if(scene_state->code_length > 0) {
+            scene_state->code_input[scene_state->code_length - 1] = 0;
+            scene_state->code_length--;
         }
+        break;
     }
 
     return true;

+ 3 - 1
scenes/authenticate/totp_scene_authenticate.h

@@ -9,6 +9,8 @@
 void totp_scene_authenticate_init(PluginState* plugin_state);
 void totp_scene_authenticate_activate(PluginState* plugin_state);
 void totp_scene_authenticate_render(Canvas* const canvas, PluginState* plugin_state);
-bool totp_scene_authenticate_handle_event(const PluginEvent* const event, PluginState* plugin_state);
+bool totp_scene_authenticate_handle_event(
+    const PluginEvent* const event,
+    PluginState* plugin_state);
 void totp_scene_authenticate_deactivate(PluginState* plugin_state);
 void totp_scene_authenticate_free(const PluginState* plugin_state);

+ 51 - 54
scenes/generate_token/totp_scene_generate_token.c

@@ -1,15 +1,16 @@
 #include <gui/gui.h>
 #include <notification/notification.h>
 #include <notification/notification_messages.h>
+#include <totp_icons.h>
 #include "totp_scene_generate_token.h"
 #include "../../types/token_info.h"
 #include "../../types/common.h"
-#include "../../services/ui/icons.h"
 #include "../../services/ui/constants.h"
 #include "../../services/totp/totp.h"
 #include "../../services/config/config.h"
 #include "../../services/crypto/crypto.h"
 #include "../../services/crypto/memset_s.h"
+#include "../../services/roll_value/roll_value.h"
 #include "../scene_director.h"
 #include "../token_menu/totp_scene_token_menu.h"
 
@@ -17,7 +18,7 @@
 #define DIGIT_TO_CHAR(digit) ((digit) + '0')
 
 typedef struct {
-    uint8_t current_token_index;
+    uint16_t current_token_index;
     char last_code[9];
     char* last_code_name;
     bool need_token_update;
@@ -249,65 +250,61 @@ void totp_scene_generate_token_render(Canvas* const canvas, PluginState* plugin_
     canvas_draw_box(canvas, barX, SCREEN_HEIGHT - BAR_MARGIN - BAR_HEIGHT, barWidth, BAR_HEIGHT);
 
     if(plugin_state->tokens_count > 1) {
-        canvas_draw_xbm(
-            canvas,
-            0,
-            SCREEN_HEIGHT_CENTER - 24,
-            ICON_ARROW_LEFT_8x9_WIDTH,
-            ICON_ARROW_LEFT_8x9_HEIGHT,
-            &ICON_ARROW_LEFT_8x9[0]);
-        canvas_draw_xbm(
-            canvas,
-            SCREEN_WIDTH - 9,
-            SCREEN_HEIGHT_CENTER - 24,
-            ICON_ARROW_RIGHT_8x9_WIDTH,
-            ICON_ARROW_RIGHT_8x9_HEIGHT,
-            &ICON_ARROW_RIGHT_8x9[0]);
+        canvas_draw_icon(canvas, 0, SCREEN_HEIGHT_CENTER - 24, &I_totp_arrow_left_8x9);
+        canvas_draw_icon(
+            canvas, SCREEN_WIDTH - 9, SCREEN_HEIGHT_CENTER - 24, &I_totp_arrow_right_8x9);
     }
 }
 
 bool totp_scene_generate_token_handle_event(
     const PluginEvent* const event,
     PluginState* plugin_state) {
-    if(event->type == EventTypeKey) {
-        if(event->input.type == InputTypeLong && event->input.key == InputKeyBack) {
-            return false;
-        } else if(event->input.type == InputTypePress) {
-            SceneState* scene_state = (SceneState*)plugin_state->current_scene_state;
-            switch(event->input.key) {
-            case InputKeyUp:
-                break;
-            case InputKeyDown:
-                break;
-            case InputKeyRight:
-                if(scene_state->current_token_index < plugin_state->tokens_count - 1) {
-                    scene_state->current_token_index++;
-                } else {
-                    scene_state->current_token_index = 0;
-                }
-                update_totp_params(plugin_state);
-                break;
-            case InputKeyLeft:
-                if(scene_state->current_token_index > 0) {
-                    scene_state->current_token_index--;
-                } else {
-                    scene_state->current_token_index = plugin_state->tokens_count - 1;
-                }
-                update_totp_params(plugin_state);
-                break;
-            case InputKeyOk:
-                if(plugin_state->tokens_count == 0) {
-                    totp_scene_director_activate_scene(plugin_state, TotpSceneTokenMenu, NULL);
-                } else {
-                    TokenMenuSceneContext ctx = {
-                        .current_token_index = scene_state->current_token_index};
-                    totp_scene_director_activate_scene(plugin_state, TotpSceneTokenMenu, &ctx);
-                }
-                break;
-            case InputKeyBack:
-                break;
-            }
+    if(event->type != EventTypeKey) {
+        return true;
+    }
+
+    if(event->input.type == InputTypeLong && event->input.key == InputKeyBack) {
+        return false;
+    }
+
+    if(event->input.type != InputTypePress) {
+        return true;
+    }
+
+    SceneState* scene_state = (SceneState*)plugin_state->current_scene_state;
+    switch(event->input.key) {
+    case InputKeyUp:
+        break;
+    case InputKeyDown:
+        break;
+    case InputKeyRight:
+        totp_roll_value_uint16_t(
+            &scene_state->current_token_index,
+            1,
+            0,
+            plugin_state->tokens_count - 1,
+            RollOverflowBehaviorRoll);
+        update_totp_params(plugin_state);
+        break;
+    case InputKeyLeft:
+        totp_roll_value_uint16_t(
+            &scene_state->current_token_index,
+            -1,
+            0,
+            plugin_state->tokens_count - 1,
+            RollOverflowBehaviorRoll);
+        update_totp_params(plugin_state);
+        break;
+    case InputKeyOk:
+        if(plugin_state->tokens_count == 0) {
+            totp_scene_director_activate_scene(plugin_state, TotpSceneTokenMenu, NULL);
+        } else {
+            TokenMenuSceneContext ctx = {.current_token_index = scene_state->current_token_index};
+            totp_scene_director_activate_scene(plugin_state, TotpSceneTokenMenu, &ctx);
         }
+        break;
+    case InputKeyBack:
+        break;
     }
 
     return true;

+ 1 - 1
scenes/generate_token/totp_scene_generate_token.h

@@ -7,7 +7,7 @@
 #include "../../types/plugin_event.h"
 
 typedef struct {
-    uint8_t current_token_index;
+    uint16_t current_token_index;
 } GenerateTokenSceneContext;
 
 void totp_scene_generate_token_init(const PluginState* plugin_state);

+ 98 - 95
scenes/token_menu/totp_scene_token_menu.c

@@ -10,6 +10,8 @@
 #include "../generate_token/totp_scene_generate_token.h"
 #include "../add_new_token/totp_scene_add_new_token.h"
 #include "../app_settings/totp_app_settings.h"
+#include "../../services/nullable/nullable.h"
+#include "../../services/roll_value/roll_value.h"
 
 #define SCREEN_HEIGHT_THIRD (SCREEN_HEIGHT / 3)
 #define SCREEN_HEIGHT_THIRD_CENTER (SCREEN_HEIGHT_THIRD >> 1)
@@ -18,7 +20,7 @@ typedef enum { AddNewToken, DeleteToken, AppSettings } Control;
 
 typedef struct {
     Control selected_control;
-    int16_t current_token_index;
+    TotpNullable_uint16_t current_token_index;
 } SceneState;
 
 void totp_scene_token_menu_init(const PluginState* plugin_state) {
@@ -31,15 +33,15 @@ void totp_scene_token_menu_activate(
     SceneState* scene_state = malloc(sizeof(SceneState));
     plugin_state->current_scene_state = scene_state;
     if(context != NULL) {
-        scene_state->current_token_index = context->current_token_index;
+        TOTP_NULLABLE_VALUE(scene_state->current_token_index, context->current_token_index);
     } else {
-        scene_state->current_token_index = -1;
+        TOTP_NULLABLE_NULL(scene_state->current_token_index);
     }
 }
 
 void totp_scene_token_menu_render(Canvas* const canvas, PluginState* plugin_state) {
     const SceneState* scene_state = (SceneState*)plugin_state->current_scene_state;
-    if(scene_state->current_token_index < 0) {
+    if(scene_state->current_token_index.is_null) {
         ui_control_button_render(
             canvas,
             SCREEN_WIDTH_CENTER - 36,
@@ -85,103 +87,104 @@ void totp_scene_token_menu_render(Canvas* const canvas, PluginState* plugin_stat
 }
 
 bool totp_scene_token_menu_handle_event(const PluginEvent* const event, PluginState* plugin_state) {
-    if(event->type == EventTypeKey) {
-        SceneState* scene_state = (SceneState*)plugin_state->current_scene_state;
-        if(event->input.type == InputTypePress) {
-            switch(event->input.key) {
-            case InputKeyUp:
-                if(scene_state->selected_control > AddNewToken) {
-                    scene_state->selected_control--;
-                    if(scene_state->selected_control == DeleteToken &&
-                       scene_state->current_token_index < 0) {
-                        scene_state->selected_control--;
-                    }
-                } else {
-                    scene_state->selected_control = AppSettings;
-                }
-                break;
-            case InputKeyDown:
-                if(scene_state->selected_control < AppSettings) {
-                    scene_state->selected_control++;
-                    if(scene_state->selected_control == DeleteToken &&
-                       scene_state->current_token_index < 0) {
-                        scene_state->selected_control++;
-                    }
-                } else {
-                    scene_state->selected_control = AddNewToken;
-                }
-                break;
-            case InputKeyRight:
-                break;
-            case InputKeyLeft:
-                break;
-            case InputKeyOk:
-                switch(scene_state->selected_control) {
-                case AddNewToken: {
-                    TokenAddEditSceneContext add_new_token_scene_context = {
-                        .current_token_index = scene_state->current_token_index};
-                    totp_scene_director_activate_scene(
-                        plugin_state, TotpSceneAddNewToken, &add_new_token_scene_context);
-                    break;
-                }
-                case DeleteToken: {
-                    DialogMessage* message = dialog_message_alloc();
-                    dialog_message_set_buttons(message, "No", NULL, "Yes");
-                    dialog_message_set_header(message, "Confirmation", 0, 0, AlignLeft, AlignTop);
-                    dialog_message_set_text(
-                        message,
-                        "Are you sure want to delete?",
-                        SCREEN_WIDTH_CENTER,
-                        SCREEN_HEIGHT_CENTER,
-                        AlignCenter,
-                        AlignCenter);
-                    DialogMessageButton dialog_result =
-                        dialog_message_show(plugin_state->dialogs, message);
-                    dialog_message_free(message);
-                    if(dialog_result == DialogMessageButtonRight) {
-                        ListNode* list_node = list_element_at(
-                            plugin_state->tokens_list, scene_state->current_token_index);
+    if(event->type != EventTypeKey) {
+        return true;
+    }
 
-                        TokenInfo* tokenInfo = list_node->data;
-                        token_info_free(tokenInfo);
-                        plugin_state->tokens_list =
-                            list_remove(plugin_state->tokens_list, list_node);
-                        plugin_state->tokens_count--;
+    SceneState* scene_state = (SceneState*)plugin_state->current_scene_state;
+    if(event->input.type != InputTypePress) {
+        return true;
+    }
 
-                        totp_full_save_config_file(plugin_state);
-                        totp_scene_director_activate_scene(
-                            plugin_state, TotpSceneGenerateToken, NULL);
-                    }
-                    break;
-                }
-                case AppSettings: {
-                    if(scene_state->current_token_index >= 0) {
-                        AppSettingsSceneContext app_settings_context = {
-                            .current_token_index = scene_state->current_token_index};
-                        totp_scene_director_activate_scene(
-                            plugin_state, TotpSceneAppSettings, &app_settings_context);
-                    } else {
-                        totp_scene_director_activate_scene(
-                            plugin_state, TotpSceneAppSettings, NULL);
-                    }
-                    break;
-                }
-                }
-                break;
-            case InputKeyBack: {
-                if(scene_state->current_token_index >= 0) {
-                    GenerateTokenSceneContext generate_scene_context = {
-                        .current_token_index = scene_state->current_token_index};
-                    totp_scene_director_activate_scene(
-                        plugin_state, TotpSceneGenerateToken, &generate_scene_context);
-                } else {
-                    totp_scene_director_activate_scene(plugin_state, TotpSceneGenerateToken, NULL);
-                }
-                break;
+    switch(event->input.key) {
+    case InputKeyUp:
+        totp_roll_value_uint8_t(
+            &scene_state->selected_control, -1, AddNewToken, AppSettings, RollOverflowBehaviorRoll);
+        if(scene_state->selected_control == DeleteToken &&
+           scene_state->current_token_index.is_null) {
+            scene_state->selected_control--;
+        }
+        break;
+    case InputKeyDown:
+        totp_roll_value_uint8_t(
+            &scene_state->selected_control, 1, AddNewToken, AppSettings, RollOverflowBehaviorRoll);
+        if(scene_state->selected_control == DeleteToken &&
+           scene_state->current_token_index.is_null) {
+            scene_state->selected_control++;
+        }
+        break;
+    case InputKeyRight:
+        break;
+    case InputKeyLeft:
+        break;
+    case InputKeyOk:
+        switch(scene_state->selected_control) {
+        case AddNewToken: {
+            if(scene_state->current_token_index.is_null) {
+                totp_scene_director_activate_scene(plugin_state, TotpSceneAddNewToken, NULL);
+            } else {
+                TokenAddEditSceneContext add_new_token_scene_context = {
+                    .current_token_index = scene_state->current_token_index.value};
+                totp_scene_director_activate_scene(
+                    plugin_state, TotpSceneAddNewToken, &add_new_token_scene_context);
             }
+            break;
+        }
+        case DeleteToken: {
+            DialogMessage* message = dialog_message_alloc();
+            dialog_message_set_buttons(message, "No", NULL, "Yes");
+            dialog_message_set_header(message, "Confirmation", 0, 0, AlignLeft, AlignTop);
+            dialog_message_set_text(
+                message,
+                "Are you sure want to delete?",
+                SCREEN_WIDTH_CENTER,
+                SCREEN_HEIGHT_CENTER,
+                AlignCenter,
+                AlignCenter);
+            DialogMessageButton dialog_result =
+                dialog_message_show(plugin_state->dialogs, message);
+            dialog_message_free(message);
+            if(dialog_result == DialogMessageButtonRight &&
+               !scene_state->current_token_index.is_null) {
+                ListNode* list_node = list_element_at(
+                    plugin_state->tokens_list, scene_state->current_token_index.value);
+
+                TokenInfo* tokenInfo = list_node->data;
+                token_info_free(tokenInfo);
+                plugin_state->tokens_list = list_remove(plugin_state->tokens_list, list_node);
+                plugin_state->tokens_count--;
+
+                totp_full_save_config_file(plugin_state);
+                totp_scene_director_activate_scene(plugin_state, TotpSceneGenerateToken, NULL);
             }
+            break;
         }
+        case AppSettings: {
+            if(!scene_state->current_token_index.is_null) {
+                AppSettingsSceneContext app_settings_context = {
+                    .current_token_index = scene_state->current_token_index.value};
+                totp_scene_director_activate_scene(
+                    plugin_state, TotpSceneAppSettings, &app_settings_context);
+            } else {
+                totp_scene_director_activate_scene(plugin_state, TotpSceneAppSettings, NULL);
+            }
+            break;
+        }
+        }
+        break;
+    case InputKeyBack: {
+        if(!scene_state->current_token_index.is_null) {
+            GenerateTokenSceneContext generate_scene_context = {
+                .current_token_index = scene_state->current_token_index.value};
+            totp_scene_director_activate_scene(
+                plugin_state, TotpSceneGenerateToken, &generate_scene_context);
+        } else {
+            totp_scene_director_activate_scene(plugin_state, TotpSceneGenerateToken, NULL);
+        }
+        break;
+    }
     }
+
     return true;
 }
 

+ 1 - 1
scenes/token_menu/totp_scene_token_menu.h

@@ -7,7 +7,7 @@
 #include "../../types/plugin_event.h"
 
 typedef struct {
-    uint8_t current_token_index;
+    uint16_t current_token_index;
 } TokenMenuSceneContext;
 
 void totp_scene_token_menu_init(const PluginState* plugin_state);

+ 2 - 2
services/config/config.c

@@ -333,7 +333,7 @@ TokenLoadingResult totp_config_file_load_tokens(PluginState* const plugin_state)
     }
 
     TokenLoadingResult result = TokenLoadingResultSuccess;
-    uint8_t index = 0;
+    uint16_t index = 0;
     bool has_any_plain_secret = false;
 
     while(true) {
@@ -421,7 +421,7 @@ TokenLoadingResult totp_config_file_load_tokens(PluginState* const plugin_state)
     plugin_state->tokens_count = index;
     plugin_state->token_list_loaded = true;
 
-    FURI_LOG_D(LOGGING_TAG, "Found %" PRIu8 " tokens", index);
+    FURI_LOG_D(LOGGING_TAG, "Found %" PRIu16 " tokens", index);
 
     furi_string_free(temp_str);
     totp_close_config_file(fff_data_file);

+ 17 - 0
services/nullable/nullable.h

@@ -0,0 +1,17 @@
+#pragma once
+
+#include <stdint.h>
+#include <stdbool.h>
+
+#define TOTP_NULLABLE_STRUCT(value_type)       \
+    typedef struct TotpNullable_##value_type { \
+        bool is_null;                          \
+        value_type value;                      \
+    } TotpNullable_##value_type
+
+#define TOTP_NULLABLE_NULL(s) s.is_null = true
+#define TOTP_NULLABLE_VALUE(s, v) \
+    s.is_null = false;            \
+    s.value = v
+
+TOTP_NULLABLE_STRUCT(uint16_t);

+ 28 - 0
services/roll_value/roll_value.c

@@ -0,0 +1,28 @@
+#include "roll_value.h"
+
+#define TOTP_ROLL_VALUE_FN(type, step_type)                            \
+    TOTP_ROLL_VALUE_FN_HEADER(type, step_type) {                       \
+        type v = *value;                                               \
+        if(step > 0 && v > max - step) {                               \
+            if(overflow_behavior == RollOverflowBehaviorRoll) {        \
+                v = min;                                               \
+            } else if(overflow_behavior == RollOverflowBehaviorStop) { \
+                v = max;                                               \
+            }                                                          \
+        } else if(step < 0 && v < min - step) {                        \
+            if(overflow_behavior == RollOverflowBehaviorRoll) {        \
+                v = max;                                               \
+            } else if(overflow_behavior == RollOverflowBehaviorStop) { \
+                v = min;                                               \
+            }                                                          \
+        } else {                                                       \
+            v += step;                                                 \
+        }                                                              \
+        *value = v;                                                    \
+    }
+
+TOTP_ROLL_VALUE_FN(int8_t, int8_t)
+
+TOTP_ROLL_VALUE_FN(uint8_t, int8_t)
+
+TOTP_ROLL_VALUE_FN(uint16_t, int16_t);

+ 17 - 0
services/roll_value/roll_value.h

@@ -0,0 +1,17 @@
+#pragma once
+
+#include <stdint.h>
+
+typedef enum { RollOverflowBehaviorStop, RollOverflowBehaviorRoll } TotpRollValueOverflowBehavior;
+
+#define TOTP_ROLL_VALUE_FN_HEADER(type, step_type) \
+    void totp_roll_value_##type(                   \
+        type* value,                               \
+        step_type step,                            \
+        type min,                                  \
+        type max,                                  \
+        TotpRollValueOverflowBehavior overflow_behavior)
+
+TOTP_ROLL_VALUE_FN_HEADER(int8_t, int8_t);
+TOTP_ROLL_VALUE_FN_HEADER(uint8_t, int8_t);
+TOTP_ROLL_VALUE_FN_HEADER(uint16_t, int16_t);

+ 0 - 12
services/ui/icons.h

@@ -1,12 +0,0 @@
-#pragma once
-
-#include <inttypes.h>
-
-#define ICON_ARROW_LEFT_8x9_WIDTH 8
-#define ICON_ARROW_LEFT_8x9_HEIGHT 9
-static const uint8_t ICON_ARROW_LEFT_8x9[] = {0x80, 0xe0, 0xf8, 0xfe, 0xff, 0xfe, 0xf8, 0xe0, 0x80};
-
-#define ICON_ARROW_RIGHT_8x9_WIDTH 8
-#define ICON_ARROW_RIGHT_8x9_HEIGHT 9
-static const uint8_t ICON_ARROW_RIGHT_8x9[] =
-    {0x01, 0x07, 0x1f, 0x7f, 0xff, 0x7f, 0x1f, 0x07, 0x01};

+ 10 - 16
services/ui/ui_controls.c

@@ -1,11 +1,15 @@
 #include "ui_controls.h"
+#include <totp_icons.h>
 #include "constants.h"
-#include "icons.h"
 
 #define TEXT_BOX_HEIGHT 13
 #define TEXT_BOX_MARGIN 4
 
-void ui_control_text_box_render(Canvas* const canvas, int16_t y, const char* text, bool is_selected) {
+void ui_control_text_box_render(
+    Canvas* const canvas,
+    int16_t y,
+    const char* text,
+    bool is_selected) {
     if(y < -TEXT_BOX_HEIGHT) {
         return;
     }
@@ -77,20 +81,10 @@ void ui_control_select_render(
 
     canvas_draw_str_aligned(
         canvas, x + (width >> 1), TEXT_BOX_MARGIN + 3 + y, AlignCenter, AlignTop, text);
-    canvas_draw_xbm(
-        canvas,
-        x + TEXT_BOX_MARGIN + 2,
-        TEXT_BOX_MARGIN + 2 + y,
-        ICON_ARROW_LEFT_8x9_WIDTH,
-        ICON_ARROW_LEFT_8x9_HEIGHT,
-        &ICON_ARROW_LEFT_8x9[0]);
-    canvas_draw_xbm(
-        canvas,
-        x + width - TEXT_BOX_MARGIN - 10,
-        TEXT_BOX_MARGIN + 2 + y,
-        ICON_ARROW_RIGHT_8x9_WIDTH,
-        ICON_ARROW_RIGHT_8x9_HEIGHT,
-        &ICON_ARROW_RIGHT_8x9[0]);
+    canvas_draw_icon(
+        canvas, x + TEXT_BOX_MARGIN + 2, TEXT_BOX_MARGIN + 2 + y, &I_totp_arrow_left_8x9);
+    canvas_draw_icon(
+        canvas, x + width - TEXT_BOX_MARGIN - 10, TEXT_BOX_MARGIN + 2 + y, &I_totp_arrow_right_8x9);
 }
 
 void ui_control_button_render(

+ 5 - 1
services/ui/ui_controls.h

@@ -3,7 +3,11 @@
 #include <inttypes.h>
 #include <gui/gui.h>
 
-void ui_control_text_box_render(Canvas* const canvas, int16_t y, const char* text, bool is_selected);
+void ui_control_text_box_render(
+    Canvas* const canvas,
+    int16_t y,
+    const char* text,
+    bool is_selected);
 void ui_control_button_render(
     Canvas* const canvas,
     int16_t x,

+ 1 - 1
types/plugin_state.h

@@ -19,7 +19,7 @@ typedef struct {
     float timezone_offset;
     ListNode* tokens_list;
     bool token_list_loaded;
-    uint8_t tokens_count;
+    uint16_t tokens_count;
 
     uint8_t* crypto_verify_data;
     size_t crypto_verify_data_length;