Преглед изворни кода

Added timezone configuration UI

alex.kopachov пре 3 година
родитељ
комит
e9cc0309c5

+ 1 - 1
README.md

@@ -23,7 +23,7 @@ Detailed description of file format can be found [here](.github/conf-file_descri
 
 
 ## Is it secure?
 ## Is it secure?
 
 
-Flipper Authenticator stores token secrets in config file in encrypted form. Encryption is done using standard Flipepr Zero API, which states that it is using AES encryption with built-in into flipper secret key and initialization vector (IV) generated by the app at initial setup XOR-ed by user's PIN.
+Flipper Authenticator stores token secrets in config file in encrypted form. Encryption is done using standard Flipper Zero API, which states that it is using AES encryption with built-in into flipper secret key and initialization vector (IV) generated by the app at initial setup XOR-ed by user's PIN.
 
 
 So in theory to get plain token secret it is necessary to have original Flipper Zero device where config file was generated and know user's PIN.
 So in theory to get plain token secret it is necessary to have original Flipper Zero device where config file was generated and know user's PIN.
 
 

+ 3 - 10
totp/scenes/add_new_token/totp_scene_add_new_token.c

@@ -100,8 +100,8 @@ void totp_scene_add_new_token_render(Canvas* const canvas, PluginState* plugin_s
 
 
     ui_control_text_box_render(canvas, 10 - scene_state->screen_y_offset, scene_state->token_name, scene_state->selected_control == TokenNameTextBox);
     ui_control_text_box_render(canvas, 10 - scene_state->screen_y_offset, scene_state->token_name, scene_state->selected_control == TokenNameTextBox);
     ui_control_text_box_render(canvas, 27 - scene_state->screen_y_offset, scene_state->token_secret, scene_state->selected_control == TokenSecretTextBox);
     ui_control_text_box_render(canvas, 27 - scene_state->screen_y_offset, scene_state->token_secret, scene_state->selected_control == TokenSecretTextBox);
-    ui_control_select_render(canvas, 44 - scene_state->screen_y_offset, TOKEN_ALGO_LIST[scene_state->algo], scene_state->selected_control == TokenAlgoSelect);
-    ui_control_select_render(canvas, 63 - scene_state->screen_y_offset, TOKEN_DIGITS_LIST[scene_state->digits_count], scene_state->selected_control == TokenLengthSelect);
+    ui_control_select_render(canvas, 0, 44 - scene_state->screen_y_offset, SCREEN_WIDTH, TOKEN_ALGO_LIST[scene_state->algo], scene_state->selected_control == TokenAlgoSelect);
+    ui_control_select_render(canvas, 0, 63 - scene_state->screen_y_offset, SCREEN_WIDTH, TOKEN_DIGITS_LIST[scene_state->digits_count], scene_state->selected_control == TokenLengthSelect);
     ui_control_button_render(canvas, SCREEN_WIDTH_CENTER - 24, 85 - scene_state->screen_y_offset, 48, 13, "Confirm", scene_state->selected_control == ConfirmButton);
     ui_control_button_render(canvas, SCREEN_WIDTH_CENTER - 24, 85 - scene_state->screen_y_offset, 48, 13, "Confirm", scene_state->selected_control == ConfirmButton);
 
 
     canvas_set_color(canvas, ColorWhite);
     canvas_set_color(canvas, ColorWhite);
@@ -212,14 +212,7 @@ bool totp_scene_add_new_token_handle_event(PluginEvent* const event, PluginState
                             }
                             }
                             plugin_state->tokens_count++;
                             plugin_state->tokens_count++;
 
 
-                            Storage* cfg_storage = totp_open_storage();
-                            FlipperFormat* cfg_file = totp_open_config_file(cfg_storage);
-
-                            flipper_format_seek_to_end(cfg_file);
-                            totp_config_file_save_new_token(cfg_file, tokenInfo);
-
-                            totp_close_config_file(cfg_file);
-                            totp_close_storage();
+                            totp_config_file_save_new_token(tokenInfo);
 
 
                             GenerateTokenSceneContext generate_scene_context = { .current_token_index = plugin_state->tokens_count - 1 };
                             GenerateTokenSceneContext generate_scene_context = { .current_token_index = plugin_state->tokens_count - 1 };
                             totp_scene_director_activate_scene(plugin_state, TotpSceneGenerateToken, &generate_scene_context);
                             totp_scene_director_activate_scene(plugin_state, TotpSceneGenerateToken, &generate_scene_context);

+ 154 - 0
totp/scenes/app_settings/totp_app_settings.c

@@ -0,0 +1,154 @@
+#include "totp_app_settings.h"
+#include "../../services/ui/ui_controls.h"
+#include "../scene_director.h"
+#include "../token_menu/totp_scene_token_menu.h"
+#include "../../services/ui/constants.h"
+#include "../../services/config/config.h"
+
+#define DIGIT_TO_CHAR(digit) ((digit) + '0')
+
+typedef enum {
+    HoursInput,
+    MinutesInput,
+    ConfirmButton
+} Control;
+
+typedef struct {
+    int8_t tz_offset_hours;
+    uint8_t tz_offset_minutes;
+    int16_t current_token_index;
+    Control selected_control;
+} SceneState;
+
+void totp_scene_app_settings_init(PluginState* plugin_state) {
+    UNUSED(plugin_state);
+}
+
+void totp_scene_app_settings_activate(PluginState* plugin_state, const AppSettingsSceneContext* context) {
+    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;
+    } else {
+        scene_state->current_token_index = -1;
+    }
+
+    float off_int;
+    float off_dec = modff(plugin_state->timezone_offset, &off_int);
+    scene_state->tz_offset_hours = off_int;
+    scene_state->tz_offset_minutes = 60.0f * off_dec;
+}
+
+static void two_digit_to_str(int8_t num, char* str) {
+    uint8_t index = 0;
+    if (num < 0) {
+        str[0] = '-';
+        index++;
+        num = -num;
+    }
+
+    uint8_t d1 = (num / 10) % 10;
+    uint8_t d2 = num % 10;
+    str[index] = DIGIT_TO_CHAR(d1);
+    str[index + 1] = DIGIT_TO_CHAR(d2);
+    str[index + 2] = '\0';
+}
+
+void totp_scene_app_settings_render(Canvas* const canvas, PluginState* plugin_state) {
+    SceneState* scene_state = (SceneState *)plugin_state->current_scene_state;
+
+    canvas_set_font(canvas, FontPrimary);
+    canvas_draw_str_aligned(canvas, 0, 0, AlignLeft, AlignTop, "Timezone offset");
+    canvas_set_font(canvas, FontSecondary);
+
+    char tmp_str[4];
+    two_digit_to_str(scene_state->tz_offset_hours, &tmp_str[0]);
+    canvas_draw_str_aligned(canvas, 0, 16, AlignLeft, AlignTop, "Hours:");
+    ui_control_select_render(canvas, 36, 10, SCREEN_WIDTH - 36, &tmp_str[0], scene_state->selected_control == HoursInput);
+
+    two_digit_to_str(scene_state->tz_offset_minutes, &tmp_str[0]);
+    canvas_draw_str_aligned(canvas, 0, 34, AlignLeft, AlignTop, "Minutes:");
+    ui_control_select_render(canvas, 36, 28, SCREEN_WIDTH - 36, &tmp_str[0], scene_state->selected_control == MinutesInput);
+
+    ui_control_button_render(canvas, SCREEN_WIDTH_CENTER - 24, 50, 48, 13, "Confirm", scene_state->selected_control == ConfirmButton);
+}
+
+bool totp_scene_app_settings_handle_event(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;
+                }
+            }
+        }
+    }
+    return true;
+}
+
+void totp_scene_app_settings_deactivate(PluginState* plugin_state) {
+    if (plugin_state->current_scene_state == NULL) return;
+    
+    free(plugin_state->current_scene_state);
+    plugin_state->current_scene_state = NULL;
+}
+
+void totp_scene_app_settings_free(PluginState* plugin_state) {
+    UNUSED(plugin_state);
+}

+ 18 - 0
totp/scenes/app_settings/totp_app_settings.h

@@ -0,0 +1,18 @@
+#pragma once
+
+#include <gui/gui.h>
+#include <furi.h>
+#include <furi_hal.h>
+#include "../../types/plugin_state.h"
+#include "../../types/plugin_event.h"
+
+typedef struct {
+    uint8_t current_token_index;
+} AppSettingsSceneContext;
+
+void totp_scene_app_settings_init(PluginState* plugin_state);
+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(PluginEvent* const event, PluginState* plugin_state);
+void totp_scene_app_settings_deactivate(PluginState* plugin_state);
+void totp_scene_app_settings_free(PluginState* plugin_state);

+ 1 - 1
totp/scenes/generate_token/totp_scene_generate_token.c

@@ -211,7 +211,7 @@ bool totp_scene_generate_token_handle_event(PluginEvent* const event, PluginStat
                     break;
                     break;
                 case InputKeyOk:
                 case InputKeyOk:
                     if (plugin_state->tokens_count == 0) {
                     if (plugin_state->tokens_count == 0) {
-                        totp_scene_director_activate_scene(plugin_state, TotpSceneAddNewToken, NULL);
+                        totp_scene_director_activate_scene(plugin_state, TotpSceneTokenMenu, NULL);
                     } else {
                     } else {
                         TokenMenuSceneContext ctx = { .current_token_index = scene_state->current_token_index };
                         TokenMenuSceneContext ctx = { .current_token_index = scene_state->current_token_index };
                         totp_scene_director_activate_scene(plugin_state, TotpSceneTokenMenu, &ctx);
                         totp_scene_director_activate_scene(plugin_state, TotpSceneTokenMenu, &ctx);

+ 15 - 0
totp/scenes/scene_director.c

@@ -4,6 +4,7 @@
 #include "generate_token/totp_scene_generate_token.h"
 #include "generate_token/totp_scene_generate_token.h"
 #include "add_new_token/totp_scene_add_new_token.h"
 #include "add_new_token/totp_scene_add_new_token.h"
 #include "token_menu/totp_scene_token_menu.h"
 #include "token_menu/totp_scene_token_menu.h"
+#include "app_settings/totp_app_settings.h"
 
 
 void totp_scene_director_activate_scene(PluginState* const plugin_state, Scene scene, const void* context) {   
 void totp_scene_director_activate_scene(PluginState* const plugin_state, Scene scene, const void* context) {   
     plugin_state->changing_scene = true;
     plugin_state->changing_scene = true;
@@ -21,6 +22,9 @@ void totp_scene_director_activate_scene(PluginState* const plugin_state, Scene s
         case TotpSceneTokenMenu:
         case TotpSceneTokenMenu:
             totp_scene_token_menu_activate(plugin_state, context);
             totp_scene_token_menu_activate(plugin_state, context);
             break;
             break;
+        case TotpSceneAppSettings:
+            totp_scene_app_settings_activate(plugin_state, context);
+            break;
     }
     }
 
 
     plugin_state->current_scene = scene;
     plugin_state->current_scene = scene;
@@ -41,6 +45,9 @@ void totp_scene_director_deactivate_active_scene(PluginState* const plugin_state
         case TotpSceneTokenMenu:
         case TotpSceneTokenMenu:
             totp_scene_token_menu_deactivate(plugin_state);
             totp_scene_token_menu_deactivate(plugin_state);
             break;
             break;
+        case TotpSceneAppSettings:
+            totp_scene_app_settings_deactivate(plugin_state);
+            break;
     }
     }
 }
 }
 
 
@@ -49,6 +56,7 @@ void totp_scene_director_init_scenes(PluginState* const plugin_state) {
     totp_scene_generate_token_init(plugin_state);
     totp_scene_generate_token_init(plugin_state);
     totp_scene_add_new_token_init(plugin_state);
     totp_scene_add_new_token_init(plugin_state);
     totp_scene_token_menu_init(plugin_state);
     totp_scene_token_menu_init(plugin_state);
+    totp_scene_app_settings_init(plugin_state);
 }
 }
 
 
 void totp_scene_director_render(Canvas* const canvas, PluginState* const plugin_state) {
 void totp_scene_director_render(Canvas* const canvas, PluginState* const plugin_state) {
@@ -65,6 +73,9 @@ void totp_scene_director_render(Canvas* const canvas, PluginState* const plugin_
         case TotpSceneTokenMenu: 
         case TotpSceneTokenMenu: 
             totp_scene_token_menu_render(canvas, plugin_state);
             totp_scene_token_menu_render(canvas, plugin_state);
             break;
             break;
+        case TotpSceneAppSettings: 
+            totp_scene_app_settings_render(canvas, plugin_state);
+            break;
     }
     }
 }
 }
 
 
@@ -73,6 +84,7 @@ void totp_scene_director_dispose(PluginState* const plugin_state) {
     totp_scene_authenticate_free(plugin_state);
     totp_scene_authenticate_free(plugin_state);
     totp_scene_add_new_token_free(plugin_state);
     totp_scene_add_new_token_free(plugin_state);
     totp_scene_token_menu_free(plugin_state);
     totp_scene_token_menu_free(plugin_state);
+    totp_scene_app_settings_free(plugin_state);
 }
 }
 
 
 bool totp_scene_director_handle_event(PluginEvent* const event, PluginState* const plugin_state) {
 bool totp_scene_director_handle_event(PluginEvent* const event, PluginState* const plugin_state) {
@@ -90,6 +102,9 @@ bool totp_scene_director_handle_event(PluginEvent* const event, PluginState* con
         case TotpSceneTokenMenu:
         case TotpSceneTokenMenu:
             processing = totp_scene_token_menu_handle_event(event, plugin_state);
             processing = totp_scene_token_menu_handle_event(event, plugin_state);
             break;
             break;
+        case TotpSceneAppSettings:
+            processing = totp_scene_app_settings_handle_event(event, plugin_state);
+            break;
     }
     }
 
 
     return processing;
     return processing;

+ 46 - 8
totp/scenes/token_menu/totp_scene_token_menu.c

@@ -9,15 +9,20 @@
 #include "../../types/token_info.h"
 #include "../../types/token_info.h"
 #include "../generate_token/totp_scene_generate_token.h"
 #include "../generate_token/totp_scene_generate_token.h"
 #include "../add_new_token/totp_scene_add_new_token.h"
 #include "../add_new_token/totp_scene_add_new_token.h"
+#include "../app_settings/totp_app_settings.h"
+
+#define SCREEN_HEIGHT_THIRD (SCREEN_HEIGHT / 3)
+#define SCREEN_HEIGHT_THIRD_CENTER (SCREEN_HEIGHT_THIRD >> 1)
 
 
 typedef enum {
 typedef enum {
     AddNewToken,
     AddNewToken,
-    DeleteToken
+    DeleteToken,
+    AppSettings
 } Control;
 } Control;
 
 
 typedef struct {
 typedef struct {
     Control selected_control;
     Control selected_control;
-    uint8_t current_token_index;
+    int16_t current_token_index;
 } SceneState;
 } SceneState;
 
 
 void totp_scene_token_menu_init(PluginState* plugin_state) {
 void totp_scene_token_menu_init(PluginState* plugin_state) {
@@ -27,13 +32,23 @@ void totp_scene_token_menu_init(PluginState* plugin_state) {
 void totp_scene_token_menu_activate(PluginState* plugin_state, const TokenMenuSceneContext* context) {
 void totp_scene_token_menu_activate(PluginState* plugin_state, const TokenMenuSceneContext* context) {
     SceneState* scene_state = malloc(sizeof(SceneState));
     SceneState* scene_state = malloc(sizeof(SceneState));
     plugin_state->current_scene_state = scene_state;
     plugin_state->current_scene_state = scene_state;
-    scene_state->current_token_index = context->current_token_index;
+    if (context != NULL) {
+        scene_state->current_token_index = context->current_token_index;
+    } else {
+        scene_state->current_token_index = -1;
+    }
 }
 }
 
 
 void totp_scene_token_menu_render(Canvas* const canvas, PluginState* plugin_state) {
 void totp_scene_token_menu_render(Canvas* const canvas, PluginState* plugin_state) {
     SceneState* scene_state = (SceneState *)plugin_state->current_scene_state;
     SceneState* scene_state = (SceneState *)plugin_state->current_scene_state;
-    ui_control_button_render(canvas, SCREEN_WIDTH_CENTER - 36, 5, 72, 21, "Add new token", scene_state->selected_control == AddNewToken);
-    ui_control_button_render(canvas, SCREEN_WIDTH_CENTER - 36, 39, 72, 21, "Delete token", scene_state->selected_control == DeleteToken);
+    if (scene_state->current_token_index < 0) {
+        ui_control_button_render(canvas, SCREEN_WIDTH_CENTER - 36, 5, 72, 21, "Add new token", scene_state->selected_control == AddNewToken);
+        ui_control_button_render(canvas, SCREEN_WIDTH_CENTER - 36, 39, 72, 21, "Settings", scene_state->selected_control == AppSettings);
+    } else {
+        ui_control_button_render(canvas, SCREEN_WIDTH_CENTER - 36, SCREEN_HEIGHT_THIRD_CENTER - 8, 72, 16, "Add new token", scene_state->selected_control == AddNewToken);
+        ui_control_button_render(canvas, SCREEN_WIDTH_CENTER - 36, SCREEN_HEIGHT_THIRD + SCREEN_HEIGHT_THIRD_CENTER - 8, 72, 16, "Delete token", scene_state->selected_control == DeleteToken);
+        ui_control_button_render(canvas, SCREEN_WIDTH_CENTER - 36, SCREEN_HEIGHT_THIRD + SCREEN_HEIGHT_THIRD + SCREEN_HEIGHT_THIRD_CENTER - 8, 72, 16, "Settings", scene_state->selected_control == AppSettings);
+    }
 }
 }
 
 
 bool totp_scene_token_menu_handle_event(PluginEvent* const event, PluginState* plugin_state) {
 bool totp_scene_token_menu_handle_event(PluginEvent* const event, PluginState* plugin_state) {
@@ -44,11 +59,21 @@ bool totp_scene_token_menu_handle_event(PluginEvent* const event, PluginState* p
                 case InputKeyUp:
                 case InputKeyUp:
                     if (scene_state->selected_control > AddNewToken) {
                     if (scene_state->selected_control > AddNewToken) {
                         scene_state->selected_control--;
                         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;
                     break;
                 case InputKeyDown:
                 case InputKeyDown:
-                    if (scene_state->selected_control < DeleteToken) {
+                    if (scene_state->selected_control < AppSettings) {
                         scene_state->selected_control++;
                         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;
                     break;
                 case InputKeyRight:
                 case InputKeyRight:
@@ -88,11 +113,24 @@ bool totp_scene_token_menu_handle_event(PluginEvent* const event, PluginState* p
                             }
                             }
                             break;
                             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;
                     break;
                 case InputKeyBack: {
                 case InputKeyBack: {
-                    GenerateTokenSceneContext generate_scene_context = { .current_token_index = scene_state->current_token_index };
-                    totp_scene_director_activate_scene(plugin_state, TotpSceneGenerateToken, &generate_scene_context);
+                    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;
                     break;
                 }
                 }
             }
             }

+ 2 - 1
totp/scenes/totp_scenes_enum.h

@@ -4,5 +4,6 @@ typedef enum {
     TotpSceneAuthentication,
     TotpSceneAuthentication,
     TotpSceneGenerateToken,
     TotpSceneGenerateToken,
     TotpSceneAddNewToken,
     TotpSceneAddNewToken,
-    TotpSceneTokenMenu
+    TotpSceneTokenMenu,
+    TotpSceneAppSettings
 } Scene;
 } Scene;

+ 23 - 2
totp/services/config/config.c

@@ -129,7 +129,8 @@ FlipperFormat* totp_open_config_file(Storage* storage) {
     return fff_data_file;
     return fff_data_file;
 }
 }
 
 
-void totp_config_file_save_new_token(FlipperFormat* file, TokenInfo* token_info) {
+void totp_config_file_save_new_token_i(FlipperFormat* file, TokenInfo* token_info) {
+    flipper_format_seek_to_end(file);
     flipper_format_write_string_cstr(file, TOTP_CONFIG_KEY_TOKEN_NAME, token_info->name);
     flipper_format_write_string_cstr(file, TOTP_CONFIG_KEY_TOKEN_NAME, token_info->name);
     flipper_format_write_hex(file, TOTP_CONFIG_KEY_TOKEN_SECRET, token_info->token, token_info->token_length);
     flipper_format_write_hex(file, TOTP_CONFIG_KEY_TOKEN_SECRET, token_info->token, token_info->token_length);
     flipper_format_write_string_cstr(file, TOTP_CONFIG_KEY_TOKEN_ALGO, token_info_get_algo_as_cstr(token_info));
     flipper_format_write_string_cstr(file, TOTP_CONFIG_KEY_TOKEN_ALGO, token_info_get_algo_as_cstr(token_info));
@@ -137,6 +138,26 @@ void totp_config_file_save_new_token(FlipperFormat* file, TokenInfo* token_info)
     flipper_format_write_uint32(file, TOTP_CONFIG_KEY_TOKEN_DIGITS, &digits_count_as_uint32, 1);
     flipper_format_write_uint32(file, TOTP_CONFIG_KEY_TOKEN_DIGITS, &digits_count_as_uint32, 1);
 }
 }
 
 
+void totp_config_file_save_new_token(TokenInfo* token_info) {
+    Storage* cfg_storage = totp_open_storage();
+    FlipperFormat* file = totp_open_config_file(cfg_storage);
+
+    totp_config_file_save_new_token_i(file, token_info);
+
+    totp_close_config_file(file);
+    totp_close_storage();
+}
+
+void totp_config_file_update_timezone_offset(float new_timezone_offset) {
+    Storage* cfg_storage = totp_open_storage();
+    FlipperFormat* file = totp_open_config_file(cfg_storage);
+
+    flipper_format_insert_or_update_float(file, TOTP_CONFIG_KEY_TIMEZONE, &new_timezone_offset, 1);
+
+    totp_close_config_file(file);
+    totp_close_storage();
+}
+
 void totp_full_save_config_file(PluginState* const plugin_state) {
 void totp_full_save_config_file(PluginState* const plugin_state) {
     Storage* storage = totp_open_storage();
     Storage* storage = totp_open_storage();
     FlipperFormat* fff_data_file = flipper_format_file_alloc(storage);
     FlipperFormat* fff_data_file = flipper_format_file_alloc(storage);
@@ -149,7 +170,7 @@ void totp_full_save_config_file(PluginState* const plugin_state) {
     ListNode* node = plugin_state->tokens_list;
     ListNode* node = plugin_state->tokens_list;
     while (node != NULL) {
     while (node != NULL) {
         TokenInfo* token_info = node->data;
         TokenInfo* token_info = node->data;
-        totp_config_file_save_new_token(fff_data_file, token_info);
+        totp_config_file_save_new_token_i(fff_data_file, token_info);
         node = node->next;
         node = node->next;
     } 
     } 
 
 

+ 2 - 1
totp/services/config/config.h

@@ -13,4 +13,5 @@ void totp_close_config_file(FlipperFormat* file);
 void totp_full_save_config_file(PluginState* const plugin_state);
 void totp_full_save_config_file(PluginState* const plugin_state);
 void totp_config_file_load_base(PluginState* const plugin_state);
 void totp_config_file_load_base(PluginState* const plugin_state);
 void totp_config_file_load_tokens(PluginState* const plugin_state);
 void totp_config_file_load_tokens(PluginState* const plugin_state);
-void totp_config_file_save_new_token(FlipperFormat* file, TokenInfo* token_info);
+void totp_config_file_save_new_token(TokenInfo* token_info);
+void totp_config_file_update_timezone_offset(float new_timezone_offset);

+ 8 - 8
totp/services/ui/ui_controls.c

@@ -20,24 +20,24 @@ void ui_control_text_box_render(Canvas* const canvas, int8_t y, char* text, bool
     canvas_draw_str_aligned(canvas, TEXT_BOX_MARGIN + 2, TEXT_BOX_MARGIN + 3 + y, AlignLeft, AlignTop, text);
     canvas_draw_str_aligned(canvas, TEXT_BOX_MARGIN + 2, TEXT_BOX_MARGIN + 3 + y, AlignLeft, AlignTop, text);
 }
 }
 
 
-void ui_control_select_render(Canvas* const canvas, int8_t y, char* text, bool is_selected) {
+void ui_control_select_render(Canvas* const canvas, int16_t x, int16_t y, uint8_t width, char* text, bool is_selected) {
     if (y < -TEXT_BOX_HEIGHT) {
     if (y < -TEXT_BOX_HEIGHT) {
         return;
         return;
     }
     }
 
 
     if (is_selected) {
     if (is_selected) {
-        canvas_draw_rframe(canvas, TEXT_BOX_MARGIN, TEXT_BOX_MARGIN + y, SCREEN_WIDTH - TEXT_BOX_MARGIN - TEXT_BOX_MARGIN, TEXT_BOX_HEIGHT, 0);
-        canvas_draw_rframe(canvas, TEXT_BOX_MARGIN - 1, TEXT_BOX_MARGIN + y - 1, SCREEN_WIDTH - TEXT_BOX_MARGIN - TEXT_BOX_MARGIN + 2, TEXT_BOX_HEIGHT + 2, 1);
+        canvas_draw_rframe(canvas, x + TEXT_BOX_MARGIN, TEXT_BOX_MARGIN + y, width - TEXT_BOX_MARGIN - TEXT_BOX_MARGIN, TEXT_BOX_HEIGHT, 0);
+        canvas_draw_rframe(canvas, x + TEXT_BOX_MARGIN - 1, TEXT_BOX_MARGIN + y - 1, width - TEXT_BOX_MARGIN - TEXT_BOX_MARGIN + 2, TEXT_BOX_HEIGHT + 2, 1);
     } else {
     } else {
-        canvas_draw_rframe(canvas, TEXT_BOX_MARGIN, TEXT_BOX_MARGIN + y, SCREEN_WIDTH - TEXT_BOX_MARGIN - TEXT_BOX_MARGIN, TEXT_BOX_HEIGHT, 1);
+        canvas_draw_rframe(canvas, x + TEXT_BOX_MARGIN, TEXT_BOX_MARGIN + y, width - TEXT_BOX_MARGIN - TEXT_BOX_MARGIN, TEXT_BOX_HEIGHT, 1);
     }
     }
 
 
-    canvas_draw_str_aligned(canvas, SCREEN_WIDTH_CENTER, TEXT_BOX_MARGIN + 3 + y, AlignCenter, AlignTop, text);
-    canvas_draw_xbm(canvas, 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, SCREEN_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_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]);
 }
 }
 
 
-void ui_control_button_render(Canvas* const canvas, uint8_t x, int8_t y, uint8_t width, uint8_t height, char* text, bool is_selected) {
+void ui_control_button_render(Canvas* const canvas, int16_t x, int16_t y, uint8_t width, uint8_t height, char* text, bool is_selected) {
     if (y < -height) {
     if (y < -height) {
         return;
         return;
     }
     }

+ 2 - 2
totp/services/ui/ui_controls.h

@@ -4,5 +4,5 @@
 #include <gui/gui.h>
 #include <gui/gui.h>
 
 
 void ui_control_text_box_render(Canvas* const canvas, int8_t y, char* text, bool is_selected);
 void ui_control_text_box_render(Canvas* const canvas, int8_t y, char* text, bool is_selected);
-void ui_control_button_render(Canvas* const canvas, uint8_t x, int8_t y, uint8_t width, uint8_t height, char* text, bool is_selected);
-void ui_control_select_render(Canvas* const canvas, int8_t y, char* text, bool is_selected);
+void ui_control_button_render(Canvas* const canvas, int16_t x, int16_t y, uint8_t width, uint8_t height, char* text, bool is_selected);
+void ui_control_select_render(Canvas* const canvas, int16_t x, int16_t y, uint8_t width, char* text, bool is_selected);