From fae8d8f23ceb971cb08119d0dc8de48a06091a13 Mon Sep 17 00:00:00 2001 From: its your bedtime <23366927+itsyourbedtime@users.noreply.github.com> Date: Tue, 26 Oct 2021 21:34:31 +0300 Subject: [PATCH] [FL-1968] Pin code locking (#788) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Gui: code input module * Gui: fix size to fit frame * Desktop: PIN config and lock option * Gui: code input: cleanup, offset input fields if no header present * Desktop: move code unlock to desktop_locked scene * Desktop: fix unlock with back key * Desktop: bump settings version * Desktop: correct scene usage. Co-authored-by: あく --- applications/desktop/desktop.c | 6 +- applications/desktop/desktop_i.h | 5 + .../desktop_settings/desktop_settings.h | 11 +- .../desktop_settings/desktop_settings_app.c | 12 +- .../desktop_settings/desktop_settings_app.h | 16 +- .../scenes/desktop_settings_scene_config.h | 2 + .../scenes/desktop_settings_scene_favorite.c | 2 +- .../desktop_settings_scene_pincode_input.c | 62 +++ .../desktop_settings_scene_pincode_menu.c | 78 +++ .../scenes/desktop_settings_scene_start.c | 8 +- .../desktop/scenes/desktop_scene_config.h | 1 + .../desktop/scenes/desktop_scene_lock_menu.c | 18 + .../desktop/scenes/desktop_scene_locked.c | 37 ++ .../desktop/scenes/desktop_scene_main.c | 2 + .../desktop/scenes/desktop_scene_pinsetup.c | 50 ++ .../desktop/views/desktop_lock_menu.c | 28 +- .../desktop/views/desktop_lock_menu.h | 3 + applications/desktop/views/desktop_locked.c | 58 ++- applications/desktop/views/desktop_locked.h | 14 +- applications/desktop/views/desktop_main.c | 1 + applications/desktop/views/desktop_main.h | 11 +- applications/gui/modules/code_input.c | 475 ++++++++++++++++++ applications/gui/modules/code_input.h | 91 ++++ 23 files changed, 948 insertions(+), 43 deletions(-) create mode 100644 applications/desktop/desktop_settings/scenes/desktop_settings_scene_pincode_input.c create mode 100644 applications/desktop/desktop_settings/scenes/desktop_settings_scene_pincode_menu.c create mode 100644 applications/desktop/scenes/desktop_scene_pinsetup.c create mode 100644 applications/gui/modules/code_input.c create mode 100644 applications/gui/modules/code_input.h diff --git a/applications/desktop/desktop.c b/applications/desktop/desktop.c index 4aa21453..aa9b4ed5 100644 --- a/applications/desktop/desktop.c +++ b/applications/desktop/desktop.c @@ -41,6 +41,7 @@ Desktop* desktop_alloc() { desktop->debug_view = desktop_debug_alloc(); desktop->first_start_view = desktop_first_start_alloc(); desktop->hw_mismatch_popup = popup_alloc(); + desktop->code_input = code_input_alloc(); view_dispatcher_add_view( desktop->view_dispatcher, DesktopViewMain, desktop_main_get_view(desktop->main_view)); @@ -62,7 +63,8 @@ Desktop* desktop_alloc() { desktop->view_dispatcher, DesktopViewHwMismatch, popup_get_view(desktop->hw_mismatch_popup)); - + view_dispatcher_add_view( + desktop->view_dispatcher, DesktopViewPinSetup, code_input_get_view(desktop->code_input)); // Lock icon desktop->lock_viewport = view_port_alloc(); view_port_set_width(desktop->lock_viewport, icon_get_width(&I_Lock_8x8)); @@ -82,6 +84,7 @@ void desktop_free(Desktop* desktop) { view_dispatcher_remove_view(desktop->view_dispatcher, DesktopViewDebug); view_dispatcher_remove_view(desktop->view_dispatcher, DesktopViewFirstStart); view_dispatcher_remove_view(desktop->view_dispatcher, DesktopViewHwMismatch); + view_dispatcher_remove_view(desktop->view_dispatcher, DesktopViewPinSetup); view_dispatcher_free(desktop->view_dispatcher); scene_manager_free(desktop->scene_manager); @@ -92,6 +95,7 @@ void desktop_free(Desktop* desktop) { desktop_debug_free(desktop->debug_view); desktop_first_start_free(desktop->first_start_view); popup_free(desktop->hw_mismatch_popup); + code_input_free(desktop->code_input); furi_record_close("gui"); desktop->gui = NULL; diff --git a/applications/desktop/desktop_i.h b/applications/desktop/desktop_i.h index 62caff0f..0bf5f3da 100644 --- a/applications/desktop/desktop_i.h +++ b/applications/desktop/desktop_i.h @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -29,6 +30,7 @@ typedef enum { DesktopViewDebug, DesktopViewFirstStart, DesktopViewHwMismatch, + DesktopViewPinSetup, DesktopViewTotal, } DesktopViewEnum; @@ -46,7 +48,10 @@ struct Desktop { DesktopLockMenuView* lock_menu; DesktopLockedView* locked_view; DesktopDebugView* debug_view; + CodeInput* code_input; + DesktopSettings settings; + PinCode pincode_buffer; ViewPort* lock_viewport; }; diff --git a/applications/desktop/desktop_settings/desktop_settings.h b/applications/desktop/desktop_settings/desktop_settings.h index a2aead82..c8f324a1 100644 --- a/applications/desktop/desktop_settings/desktop_settings.h +++ b/applications/desktop/desktop_settings/desktop_settings.h @@ -3,11 +3,20 @@ #include #include -#define DESKTOP_SETTINGS_VER (0) +#define DESKTOP_SETTINGS_VER (1) +#define PIN_MAX_LENGTH 12 + +typedef struct { + uint8_t length; + uint8_t data[PIN_MAX_LENGTH]; +} PinCode; typedef struct { uint8_t version; uint16_t favorite; + + PinCode pincode; + bool locked; } DesktopSettings; bool desktop_settings_load(DesktopSettings* desktop_settings); diff --git a/applications/desktop/desktop_settings/desktop_settings_app.c b/applications/desktop/desktop_settings/desktop_settings_app.c index 3c7610f5..e284ff80 100644 --- a/applications/desktop/desktop_settings/desktop_settings_app.c +++ b/applications/desktop/desktop_settings/desktop_settings_app.c @@ -33,10 +33,13 @@ DesktopSettingsApp* desktop_settings_app_alloc() { app->submenu = submenu_alloc(); view_dispatcher_add_view( - app->view_dispatcher, DesktopSettingsAppViewMain, submenu_get_view(app->submenu)); + app->view_dispatcher, DesktopSettingsAppViewMenu, submenu_get_view(app->submenu)); + app->code_input = code_input_alloc(); view_dispatcher_add_view( - app->view_dispatcher, DesktopSettingsAppViewFavorite, submenu_get_view(app->submenu)); + app->view_dispatcher, + DesktopSettingsAppViewPincodeInput, + code_input_get_view(app->code_input)); scene_manager_next_scene(app->scene_manager, DesktopSettingsAppSceneStart); return app; @@ -45,9 +48,10 @@ DesktopSettingsApp* desktop_settings_app_alloc() { void desktop_settings_app_free(DesktopSettingsApp* app) { furi_assert(app); // Variable item list - view_dispatcher_remove_view(app->view_dispatcher, DesktopSettingsAppViewMain); - view_dispatcher_remove_view(app->view_dispatcher, DesktopSettingsAppViewFavorite); + view_dispatcher_remove_view(app->view_dispatcher, DesktopSettingsAppViewMenu); submenu_free(app->submenu); + view_dispatcher_remove_view(app->view_dispatcher, DesktopSettingsAppViewPincodeInput); + code_input_free(app->code_input); // View dispatcher view_dispatcher_free(app->view_dispatcher); scene_manager_free(app->scene_manager); diff --git a/applications/desktop/desktop_settings/desktop_settings_app.h b/applications/desktop/desktop_settings/desktop_settings_app.h index ba381cde..8ec8e892 100644 --- a/applications/desktop/desktop_settings/desktop_settings_app.h +++ b/applications/desktop/desktop_settings/desktop_settings_app.h @@ -6,20 +6,32 @@ #include #include #include +#include #include "desktop_settings.h" + #include "scenes/desktop_settings_scene.h" typedef enum { - DesktopSettingsAppViewMain, - DesktopSettingsAppViewFavorite, + CodeEventsSetPin, + CodeEventsChangePin, + CodeEventsDisablePin, +} CodeEventsEnum; + +typedef enum { + DesktopSettingsAppViewMenu, + DesktopSettingsAppViewPincodeInput, } DesktopSettingsAppView; typedef struct { DesktopSettings settings; + Gui* gui; SceneManager* scene_manager; ViewDispatcher* view_dispatcher; Submenu* submenu; + CodeInput* code_input; + + uint8_t menu_idx; } DesktopSettingsApp; diff --git a/applications/desktop/desktop_settings/scenes/desktop_settings_scene_config.h b/applications/desktop/desktop_settings/scenes/desktop_settings_scene_config.h index a2abec0a..126873db 100644 --- a/applications/desktop/desktop_settings/scenes/desktop_settings_scene_config.h +++ b/applications/desktop/desktop_settings/scenes/desktop_settings_scene_config.h @@ -1,2 +1,4 @@ ADD_SCENE(desktop_settings, start, Start) ADD_SCENE(desktop_settings, favorite, Favorite) +ADD_SCENE(desktop_settings, pincode_menu, PinCodeMenu) +ADD_SCENE(desktop_settings, pincode_input, PinCodeInput) diff --git a/applications/desktop/desktop_settings/scenes/desktop_settings_scene_favorite.c b/applications/desktop/desktop_settings/scenes/desktop_settings_scene_favorite.c index e8559017..1fead1f7 100644 --- a/applications/desktop/desktop_settings/scenes/desktop_settings_scene_favorite.c +++ b/applications/desktop/desktop_settings/scenes/desktop_settings_scene_favorite.c @@ -22,7 +22,7 @@ void desktop_settings_scene_favorite_on_enter(void* context) { submenu_set_header(app->submenu, "Quick access app:"); submenu_set_selected_item(app->submenu, app->settings.favorite); - view_dispatcher_switch_to_view(app->view_dispatcher, DesktopSettingsAppViewFavorite); + view_dispatcher_switch_to_view(app->view_dispatcher, DesktopSettingsAppViewMenu); } bool desktop_settings_scene_favorite_on_event(void* context, SceneManagerEvent event) { diff --git a/applications/desktop/desktop_settings/scenes/desktop_settings_scene_pincode_input.c b/applications/desktop/desktop_settings/scenes/desktop_settings_scene_pincode_input.c new file mode 100644 index 00000000..d809f999 --- /dev/null +++ b/applications/desktop/desktop_settings/scenes/desktop_settings_scene_pincode_input.c @@ -0,0 +1,62 @@ +#include "../desktop_settings_app.h" + +#define SCENE_EXIT_EVENT (0U) + +void desktop_settings_scene_ok_callback(void* context) { + DesktopSettingsApp* app = context; + uint32_t state = + scene_manager_get_scene_state(app->scene_manager, DesktopSettingsAppScenePinCodeInput); + + if(state == CodeEventsDisablePin) { + memset(app->settings.pincode.data, 0, app->settings.pincode.length * sizeof(uint8_t)); + app->settings.pincode.length = 0; + } + + view_dispatcher_send_custom_event(app->view_dispatcher, SCENE_EXIT_EVENT); +} + +void desktop_settings_scene_pincode_input_on_enter(void* context) { + DesktopSettingsApp* app = context; + CodeInput* code_input = app->code_input; + + uint32_t state = + scene_manager_get_scene_state(app->scene_manager, DesktopSettingsAppScenePinCodeInput); + bool update = state != CodeEventsDisablePin; + + code_input_set_header_text(code_input, "PIN Code Setup"); + code_input_set_result_callback( + code_input, + desktop_settings_scene_ok_callback, + NULL, + app, + app->settings.pincode.data, + &app->settings.pincode.length, + update); + + view_dispatcher_switch_to_view(app->view_dispatcher, DesktopSettingsAppViewPincodeInput); +} + +bool desktop_settings_scene_pincode_input_on_event(void* context, SceneManagerEvent event) { + DesktopSettingsApp* app = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + switch(event.event) { + case SCENE_EXIT_EVENT: + scene_manager_previous_scene(app->scene_manager); + consumed = true; + break; + + default: + consumed = true; + break; + } + } + return consumed; +} + +void desktop_settings_scene_pincode_input_on_exit(void* context) { + DesktopSettingsApp* app = context; + code_input_set_result_callback(app->code_input, NULL, NULL, NULL, NULL, NULL, 0); + code_input_set_header_text(app->code_input, ""); +} diff --git a/applications/desktop/desktop_settings/scenes/desktop_settings_scene_pincode_menu.c b/applications/desktop/desktop_settings/scenes/desktop_settings_scene_pincode_menu.c new file mode 100644 index 00000000..78c2eee9 --- /dev/null +++ b/applications/desktop/desktop_settings/scenes/desktop_settings_scene_pincode_menu.c @@ -0,0 +1,78 @@ +#include "../desktop_settings_app.h" +#include "applications.h" + +static void desktop_settings_scene_pincode_menu_submenu_callback(void* context, uint32_t index) { + DesktopSettingsApp* app = context; + view_dispatcher_send_custom_event(app->view_dispatcher, index); +} + +void desktop_settings_scene_pincode_menu_on_enter(void* context) { + DesktopSettingsApp* app = context; + Submenu* submenu = app->submenu; + submenu_clean(submenu); + + if(!app->settings.pincode.length) { + submenu_add_item( + submenu, + "Set Pin", + CodeEventsSetPin, + desktop_settings_scene_pincode_menu_submenu_callback, + app); + + } else { + submenu_add_item( + submenu, + "Change Pin", + CodeEventsChangePin, + desktop_settings_scene_pincode_menu_submenu_callback, + app); + + submenu_add_item( + submenu, + "Disable", + CodeEventsDisablePin, + desktop_settings_scene_pincode_menu_submenu_callback, + app); + } + + submenu_set_header(app->submenu, "Pin code settings:"); + submenu_set_selected_item(app->submenu, app->menu_idx); + view_dispatcher_switch_to_view(app->view_dispatcher, DesktopSettingsAppViewMenu); +} + +bool desktop_settings_scene_pincode_menu_on_event(void* context, SceneManagerEvent event) { + DesktopSettingsApp* app = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + switch(event.event) { + case CodeEventsSetPin: + scene_manager_set_scene_state( + app->scene_manager, DesktopSettingsAppScenePinCodeInput, event.event); + scene_manager_next_scene(app->scene_manager, DesktopSettingsAppScenePinCodeInput); + consumed = true; + break; + case CodeEventsChangePin: + scene_manager_set_scene_state( + app->scene_manager, DesktopSettingsAppScenePinCodeInput, event.event); + scene_manager_next_scene(app->scene_manager, DesktopSettingsAppScenePinCodeInput); + consumed = true; + break; + case CodeEventsDisablePin: + scene_manager_set_scene_state( + app->scene_manager, DesktopSettingsAppScenePinCodeInput, event.event); + scene_manager_next_scene(app->scene_manager, DesktopSettingsAppScenePinCodeInput); + consumed = true; + break; + default: + consumed = true; + break; + } + } + return consumed; +} + +void desktop_settings_scene_pincode_menu_on_exit(void* context) { + DesktopSettingsApp* app = context; + submenu_clean(app->submenu); +} diff --git a/applications/desktop/desktop_settings/scenes/desktop_settings_scene_start.c b/applications/desktop/desktop_settings/scenes/desktop_settings_scene_start.c index 33d66d0c..43b541ac 100755 --- a/applications/desktop/desktop_settings/scenes/desktop_settings_scene_start.c +++ b/applications/desktop/desktop_settings/scenes/desktop_settings_scene_start.c @@ -29,7 +29,7 @@ void desktop_settings_scene_start_on_enter(void* context) { desktop_settings_scene_start_submenu_callback, app); - view_dispatcher_switch_to_view(app->view_dispatcher, DesktopSettingsAppViewMain); + view_dispatcher_switch_to_view(app->view_dispatcher, DesktopSettingsAppViewMenu); } bool desktop_settings_scene_start_on_event(void* context, SceneManagerEvent event) { @@ -39,7 +39,11 @@ bool desktop_settings_scene_start_on_event(void* context, SceneManagerEvent even if(event.type == SceneManagerEventTypeCustom) { switch(event.event) { case DesktopSettingsStartSubmenuIndexFavorite: - scene_manager_next_scene(app->scene_manager, DesktopSettingsAppViewFavorite); + scene_manager_next_scene(app->scene_manager, DesktopSettingsAppSceneFavorite); + consumed = true; + break; + case DesktopSettingsStartSubmenuIndexPinSetup: + scene_manager_next_scene(app->scene_manager, DesktopSettingsAppScenePinCodeMenu); consumed = true; break; } diff --git a/applications/desktop/scenes/desktop_scene_config.h b/applications/desktop/scenes/desktop_scene_config.h index 067de7c4..e84db67d 100644 --- a/applications/desktop/scenes/desktop_scene_config.h +++ b/applications/desktop/scenes/desktop_scene_config.h @@ -4,3 +4,4 @@ ADD_SCENE(desktop, locked, Locked) ADD_SCENE(desktop, debug, Debug) ADD_SCENE(desktop, first_start, FirstStart) ADD_SCENE(desktop, hw_mismatch, HwMismatch) +ADD_SCENE(desktop, pinsetup, PinSetup) diff --git a/applications/desktop/scenes/desktop_scene_lock_menu.c b/applications/desktop/scenes/desktop_scene_lock_menu.c index 537469b5..46561e47 100644 --- a/applications/desktop/scenes/desktop_scene_lock_menu.c +++ b/applications/desktop/scenes/desktop_scene_lock_menu.c @@ -9,7 +9,10 @@ void desktop_scene_lock_menu_callback(DesktopLockMenuEvent event, void* context) void desktop_scene_lock_menu_on_enter(void* context) { Desktop* desktop = (Desktop*)context; + desktop_settings_load(&desktop->settings); + desktop_lock_menu_set_callback(desktop->lock_menu, desktop_scene_lock_menu_callback, desktop); + desktop_lock_menu_pin_set(desktop->lock_menu, desktop->settings.pincode.length > 0); view_dispatcher_switch_to_view(desktop->view_dispatcher, DesktopViewLockMenu); } @@ -20,10 +23,25 @@ bool desktop_scene_lock_menu_on_event(void* context, SceneManagerEvent event) { if(event.type == SceneManagerEventTypeCustom) { switch(event.event) { case DesktopLockMenuEventLock: + scene_manager_set_scene_state( + desktop->scene_manager, DesktopSceneLocked, DesktopLockedNoPin); scene_manager_next_scene(desktop->scene_manager, DesktopSceneLocked); consumed = true; break; + case DesktopLockMenuEventPinLock: + if(desktop->settings.pincode.length > 0) { + desktop->settings.locked = true; + desktop_settings_save(&desktop->settings); + scene_manager_set_scene_state( + desktop->scene_manager, DesktopSceneLocked, DesktopLockedWithPin); + scene_manager_next_scene(desktop->scene_manager, DesktopSceneLocked); + } else { + scene_manager_next_scene(desktop->scene_manager, DesktopScenePinSetup); + } + + consumed = true; + break; case DesktopLockMenuEventExit: scene_manager_next_scene(desktop->scene_manager, DesktopSceneMain); consumed = true; diff --git a/applications/desktop/scenes/desktop_scene_locked.c b/applications/desktop/scenes/desktop_scene_locked.c index 6e7064ad..ad436212 100644 --- a/applications/desktop/scenes/desktop_scene_locked.c +++ b/applications/desktop/scenes/desktop_scene_locked.c @@ -15,12 +15,39 @@ void desktop_scene_locked_on_enter(void* context) { desktop_locked_update_hint_timeout(locked_view); desktop_locked_set_dolphin_animation(locked_view); + uint32_t state = scene_manager_get_scene_state(desktop->scene_manager, DesktopViewLocked); + + desktop_locked_with_pin(desktop->locked_view, state == DesktopLockedWithPin); + view_port_enabled_set(desktop->lock_viewport, true); osTimerStart(locked_view->timer, 63); view_dispatcher_switch_to_view(desktop->view_dispatcher, DesktopViewLocked); } +static bool desktop_scene_locked_check_pin(Desktop* desktop, DesktopMainEvent event) { + bool match = false; + + size_t length = desktop->pincode_buffer.length; + length = code_input_push(desktop->pincode_buffer.data, length, event); + desktop->pincode_buffer.length = length; + + match = code_input_compare( + desktop->pincode_buffer.data, + length, + desktop->settings.pincode.data, + desktop->settings.pincode.length); + + if(match) { + desktop->pincode_buffer.length = 0; + desktop->settings.locked = false; + desktop_settings_save(&desktop->settings); + desktop_main_unlocked(desktop->main_view); + } + + return match; +} + bool desktop_scene_locked_on_event(void* context, SceneManagerEvent event) { Desktop* desktop = (Desktop*)context; @@ -36,7 +63,17 @@ bool desktop_scene_locked_on_event(void* context, SceneManagerEvent event) { case DesktopLockedEventUpdate: desktop_locked_manage_redraw(desktop->locked_view); consumed = true; + break; + case DesktopLockedEventInputReset: + desktop->pincode_buffer.length = 0; + break; default: + if(desktop_scene_locked_check_pin(desktop, event.event)) { + scene_manager_set_scene_state( + desktop->scene_manager, DesktopSceneMain, DesktopMainEventUnlocked); + scene_manager_next_scene(desktop->scene_manager, DesktopSceneMain); + consumed = true; + } break; } } diff --git a/applications/desktop/scenes/desktop_scene_main.c b/applications/desktop/scenes/desktop_scene_main.c index 4826c2cd..4065b51c 100644 --- a/applications/desktop/scenes/desktop_scene_main.c +++ b/applications/desktop/scenes/desktop_scene_main.c @@ -34,6 +34,8 @@ void desktop_scene_main_on_enter(void* context) { desktop_main_set_callback(main_view, desktop_scene_main_callback, desktop); view_port_enabled_set(desktop->lock_viewport, false); + desktop_settings_load(&desktop->settings); + if(scene_manager_get_scene_state(desktop->scene_manager, DesktopSceneMain) == DesktopMainEventUnlocked) { desktop_main_unlocked(desktop->main_view); diff --git a/applications/desktop/scenes/desktop_scene_pinsetup.c b/applications/desktop/scenes/desktop_scene_pinsetup.c new file mode 100644 index 00000000..78515d7e --- /dev/null +++ b/applications/desktop/scenes/desktop_scene_pinsetup.c @@ -0,0 +1,50 @@ +#include "../desktop_i.h" + +#define SCENE_EXIT_EVENT (0U) + +void desktop_scene_ok_callback(void* context) { + Desktop* app = context; + desktop_settings_save(&app->settings); + view_dispatcher_send_custom_event(app->view_dispatcher, SCENE_EXIT_EVENT); +} + +void desktop_scene_pinsetup_on_enter(void* context) { + Desktop* app = context; + CodeInput* code_input = app->code_input; + + code_input_set_result_callback( + code_input, + desktop_scene_ok_callback, + NULL, + app, + app->settings.pincode.data, + &app->settings.pincode.length, + true); + + view_dispatcher_switch_to_view(app->view_dispatcher, DesktopViewPinSetup); +} + +bool desktop_scene_pinsetup_on_event(void* context, SceneManagerEvent event) { + Desktop* app = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + switch(event.event) { + case SCENE_EXIT_EVENT: + scene_manager_previous_scene(app->scene_manager); + consumed = true; + break; + + default: + consumed = true; + break; + } + } + return consumed; +} + +void desktop_scene_pinsetup_on_exit(void* context) { + Desktop* app = context; + code_input_set_result_callback(app->code_input, NULL, NULL, NULL, NULL, NULL, 0); + code_input_set_header_text(app->code_input, ""); +} diff --git a/applications/desktop/views/desktop_lock_menu.c b/applications/desktop/views/desktop_lock_menu.c index 11392cd8..52a8df56 100644 --- a/applications/desktop/views/desktop_lock_menu.c +++ b/applications/desktop/views/desktop_lock_menu.c @@ -12,6 +12,14 @@ void desktop_lock_menu_set_callback( lock_menu->context = context; } +void desktop_lock_menu_pin_set(DesktopLockMenuView* lock_menu, bool pin_is_set) { + with_view_model( + lock_menu->view, (DesktopLockMenuViewModel * model) { + model->pin_set = pin_is_set; + return true; + }); +} + void desktop_lock_menu_reset_idx(DesktopLockMenuView* lock_menu) { with_view_model( lock_menu->view, (DesktopLockMenuViewModel * model) { @@ -26,6 +34,10 @@ static void lock_menu_callback(void* context, uint8_t index) { switch(index) { case 0: // lock lock_menu->callback(DesktopLockMenuEventLock, lock_menu->context); + break; + case 1: // lock + lock_menu->callback(DesktopLockMenuEventPinLock, lock_menu->context); + break; default: // wip message with_view_model( lock_menu->view, (DesktopLockMenuViewModel * model) { @@ -37,7 +49,7 @@ static void lock_menu_callback(void* context, uint8_t index) { } void desktop_lock_menu_render(Canvas* canvas, void* model) { - const char* Lockmenu_Items[3] = {"Lock", "Set PIN", "DUMB mode"}; + const char* Lockmenu_Items[3] = {"Lock", "Lock with PIN", "DUMB mode"}; DesktopLockMenuViewModel* m = model; canvas_clear(canvas); @@ -47,13 +59,13 @@ void desktop_lock_menu_render(Canvas* canvas, void* model) { canvas_set_font(canvas, FontSecondary); for(uint8_t i = 0; i < 3; ++i) { - canvas_draw_str_aligned( - canvas, - 64, - 13 + (i * 17), - AlignCenter, - AlignCenter, - (m->hint_timeout && m->idx == i && m->idx) ? "Not implemented" : Lockmenu_Items[i]); + const char* str = Lockmenu_Items[i]; + + if(i == 1 && !m->pin_set) str = "Set PIN"; + if(m->hint_timeout && m->idx == 2 && m->idx == i) str = "Not implemented"; + + canvas_draw_str_aligned(canvas, 64, 13 + (i * 17), AlignCenter, AlignCenter, str); + if(m->idx == i) elements_frame(canvas, 15, 5 + (i * 17), 98, 15); } } diff --git a/applications/desktop/views/desktop_lock_menu.h b/applications/desktop/views/desktop_lock_menu.h index 714069cb..f86b5d74 100644 --- a/applications/desktop/views/desktop_lock_menu.h +++ b/applications/desktop/views/desktop_lock_menu.h @@ -11,6 +11,7 @@ typedef enum { DesktopLockMenuEventLock, DesktopLockMenuEventUnlock, + DesktopLockMenuEventPinLock, DesktopLockMenuEventExit, } DesktopLockMenuEvent; @@ -27,6 +28,7 @@ struct DesktopLockMenuView { typedef struct { uint8_t idx; uint8_t hint_timeout; + bool pin_set; } DesktopLockMenuViewModel; void desktop_lock_menu_set_callback( @@ -35,6 +37,7 @@ void desktop_lock_menu_set_callback( void* context); View* desktop_lock_menu_get_view(DesktopLockMenuView* lock_menu); +void desktop_lock_menu_pin_set(DesktopLockMenuView* lock_menu, bool pin_is_set); void desktop_lock_menu_reset_idx(DesktopLockMenuView* lock_menu); DesktopLockMenuView* desktop_lock_menu_alloc(); void desktop_lock_menu_free(DesktopLockMenuView* lock_menu); diff --git a/applications/desktop/views/desktop_locked.c b/applications/desktop/views/desktop_locked.c index b4942506..f4373dfb 100644 --- a/applications/desktop/views/desktop_locked.c +++ b/applications/desktop/views/desktop_locked.c @@ -80,6 +80,14 @@ void desktop_locked_reset_counter(DesktopLockedView* locked_view) { }); } +void desktop_locked_with_pin(DesktopLockedView* locked_view, bool locked) { + with_view_model( + locked_view->view, (DesktopLockedViewModel * model) { + model->pin_lock = locked; + return true; + }); +} + void desktop_locked_render(Canvas* canvas, void* model) { DesktopLockedViewModel* m = model; uint32_t now = osKernelGetTickCount(); @@ -100,7 +108,7 @@ void desktop_locked_render(Canvas* canvas, void* model) { canvas_set_font(canvas, FontPrimary); elements_multiline_text_framed(canvas, 42, 30, "Locked"); - } else { + } else if(!m->pin_lock) { canvas_set_font(canvas, FontSecondary); canvas_draw_icon(canvas, 13, 5, &I_LockPopup_100x49); elements_multiline_text(canvas, 65, 20, "To unlock\npress:"); @@ -116,27 +124,49 @@ View* desktop_locked_get_view(DesktopLockedView* locked_view) { bool desktop_locked_input(InputEvent* event, void* context) { furi_assert(event); furi_assert(context); - DesktopLockedView* locked_view = context; + + uint32_t press_time = 0; + bool locked_with_pin = false; + + with_view_model( + locked_view->view, (DesktopLockedViewModel * model) { + locked_with_pin = model->pin_lock; + return false; + }); + if(event->type == InputTypeShort) { - desktop_locked_update_hint_timeout(locked_view); + if(locked_with_pin) { + press_time = osKernelGetTickCount(); - if(event->key == InputKeyBack) { - uint32_t press_time = osKernelGetTickCount(); - // check if pressed sequentially - if(press_time - locked_view->lock_lastpress > UNLOCK_RST_TIMEOUT) { + if(press_time - locked_view->lock_lastpress > UNLOCK_RST_TIMEOUT * 3) { locked_view->lock_lastpress = press_time; - locked_view->lock_count = 0; - } else if(press_time - locked_view->lock_lastpress < UNLOCK_RST_TIMEOUT) { - locked_view->lock_lastpress = press_time; - locked_view->lock_count++; + locked_view->callback(DesktopLockedEventInputReset, locked_view->context); } - if(locked_view->lock_count == UNLOCK_CNT) { - locked_view->lock_count = 0; - locked_view->callback(DesktopLockedEventUnlock, locked_view->context); + locked_view->callback(event->key, locked_view->context); + } else { + desktop_locked_update_hint_timeout(locked_view); + + if(event->key == InputKeyBack) { + press_time = osKernelGetTickCount(); + // check if pressed sequentially + if(press_time - locked_view->lock_lastpress < UNLOCK_RST_TIMEOUT) { + locked_view->lock_lastpress = press_time; + locked_view->lock_count++; + } + + if(locked_view->lock_count == UNLOCK_CNT) { + locked_view->lock_count = 0; + locked_view->callback(DesktopLockedEventUnlock, locked_view->context); + } } } + + if(press_time - locked_view->lock_lastpress > UNLOCK_RST_TIMEOUT) { + locked_view->lock_lastpress = press_time; + locked_view->lock_count = 0; + } } // All events consumed return true; diff --git a/applications/desktop/views/desktop_locked.h b/applications/desktop/views/desktop_locked.h index c85705a8..f7d635b3 100644 --- a/applications/desktop/views/desktop_locked.h +++ b/applications/desktop/views/desktop_locked.h @@ -15,10 +15,16 @@ #define DOOR_R_POS_MIN 60 typedef enum { - DesktopLockedEventUnlock, - DesktopLockedEventUpdate, + DesktopLockedEventUnlock = 10U, + DesktopLockedEventUpdate = 11U, + DesktopLockedEventInputReset = 12U, } DesktopLockedEvent; +typedef enum { + DesktopLockedWithPin, + DesktopLockedNoPin, +} DesktopLockedSceneState; + typedef struct DesktopLockedView DesktopLockedView; typedef void (*DesktopLockedViewCallback)(DesktopLockedEvent event, void* context); @@ -42,6 +48,7 @@ typedef struct { int8_t door_right_x; bool animation_seq_end; + bool pin_lock; } DesktopLockedViewModel; void desktop_locked_set_callback( @@ -58,5 +65,4 @@ void desktop_locked_manage_redraw(DesktopLockedView* locked_view); View* desktop_locked_get_view(DesktopLockedView* locked_view); DesktopLockedView* desktop_locked_alloc(); void desktop_locked_free(DesktopLockedView* locked_view); -void desktop_main_unlocked(DesktopMainView* main_view); -void desktop_main_reset_hint(DesktopMainView* main_view); \ No newline at end of file +void desktop_locked_with_pin(DesktopLockedView* lock_menu, bool locked); \ No newline at end of file diff --git a/applications/desktop/views/desktop_main.c b/applications/desktop/views/desktop_main.c index a35ba008..00a606e8 100644 --- a/applications/desktop/views/desktop_main.c +++ b/applications/desktop/views/desktop_main.c @@ -67,6 +67,7 @@ bool desktop_main_input(InputEvent* event, void* context) { } else if(event->key == InputKeyLeft && event->type == InputTypeShort) { main_view->callback(DesktopMainEventOpenFavorite, main_view->context); } + desktop_main_reset_hint(main_view); return true; diff --git a/applications/desktop/views/desktop_main.h b/applications/desktop/views/desktop_main.h index 78678b8c..1b631f6f 100644 --- a/applications/desktop/views/desktop_main.h +++ b/applications/desktop/views/desktop_main.h @@ -7,12 +7,12 @@ #include typedef enum { - DesktopMainEventOpenMenu, DesktopMainEventOpenLockMenu, - DesktopMainEventOpenDebug, - DesktopMainEventUnlocked, DesktopMainEventOpenArchive, DesktopMainEventOpenFavorite, + DesktopMainEventOpenMenu, + DesktopMainEventOpenDebug, + DesktopMainEventUnlocked, } DesktopMainEvent; typedef struct DesktopMainView DesktopMainView; @@ -37,9 +37,8 @@ void desktop_main_set_callback( void* context); View* desktop_main_get_view(DesktopMainView* main_view); - DesktopMainView* desktop_main_alloc(); - void desktop_main_free(DesktopMainView* main_view); - void desktop_main_switch_dolphin_animation(DesktopMainView* main_view); +void desktop_main_unlocked(DesktopMainView* main_view); +void desktop_main_reset_hint(DesktopMainView* main_view); diff --git a/applications/gui/modules/code_input.c b/applications/gui/modules/code_input.c new file mode 100644 index 00000000..62f5118d --- /dev/null +++ b/applications/gui/modules/code_input.c @@ -0,0 +1,475 @@ +#include "code_input.h" +#include +#include + +#define MAX_CODE_LEN 10 + +struct CodeInput { + View* view; +}; + +typedef enum { + CodeInputStateVerify, + CodeInputStateUpdate, + CodeInputStateTotal, +} CodeInputStateEnum; + +typedef enum { + CodeInputFirst, + CodeInputSecond, + CodeInputTotal, +} CodeInputsEnum; + +typedef struct { + uint8_t state; + uint8_t current; + bool ext_update; + + uint8_t input_length[CodeInputTotal]; + uint8_t local_buffer[CodeInputTotal][MAX_CODE_LEN]; + + CodeInputOkCallback ok_callback; + CodeInputFailCallback fail_callback; + void* callback_context; + + const char* header; + + uint8_t* ext_buffer; + uint8_t* ext_buffer_length; +} CodeInputModel; + +static const Icon* keys_assets[] = { + [InputKeyUp] = &I_ButtonUp_7x4, + [InputKeyDown] = &I_ButtonDown_7x4, + [InputKeyRight] = &I_ButtonRight_4x7, + [InputKeyLeft] = &I_ButtonLeft_4x7, +}; + +/** + * @brief Compare buffers + * + * @param in Input buffer pointer + * @param len_in Input array length + * @param src Source buffer pointer + * @param len_src Source array length + */ + +bool code_input_compare(uint8_t* in, size_t len_in, uint8_t* src, size_t len_src) { + bool result = false; + do { + result = (len_in && len_src); + if(!result) { + break; + } + result = (len_in == len_src); + if(!result) { + break; + } + for(size_t i = 0; i < len_in; i++) { + result = (in[i] == src[i]); + if(!result) { + break; + } + } + } while(false); + + return result; +} + +/** + * @brief Compare local buffers + * + * @param model + */ +static bool code_input_compare_local(CodeInputModel* model) { + uint8_t* source = model->local_buffer[CodeInputFirst]; + size_t source_length = model->input_length[CodeInputFirst]; + + uint8_t* input = model->local_buffer[CodeInputSecond]; + size_t input_length = model->input_length[CodeInputSecond]; + + return code_input_compare(input, input_length, source, source_length); +} + +/** + * @brief Compare ext with local + * + * @param model + */ +static bool code_input_compare_ext(CodeInputModel* model) { + uint8_t* input = model->local_buffer[CodeInputFirst]; + size_t input_length = model->input_length[CodeInputFirst]; + + uint8_t* source = model->ext_buffer; + size_t source_length = *model->ext_buffer_length; + + return code_input_compare(input, input_length, source, source_length); +} + +/** + * @brief Set ext buffer + * + * @param model + */ +static void code_input_set_ext(CodeInputModel* model) { + *model->ext_buffer_length = model->input_length[CodeInputFirst]; + for(size_t i = 0; i <= model->input_length[CodeInputFirst]; i++) { + model->ext_buffer[i] = model->local_buffer[CodeInputFirst][i]; + } +} + +/** + * @brief Draw input sequence + * + * @param canvas + * @param buffer + * @param length + * @param x + * @param y + * @param active + */ +static void code_input_draw_sequence( + Canvas* canvas, + uint8_t* buffer, + uint8_t length, + uint8_t x, + uint8_t y, + bool active) { + uint8_t pos_x = x + 6; + uint8_t pos_y = y + 3; + + if(active) canvas_draw_icon(canvas, x - 4, y + 5, &I_ButtonRightSmall_3x5); + + elements_slightly_rounded_frame(canvas, x, y, 116, 15); + + for(size_t i = 0; i < length; i++) { + // maybe symmetrical assets? :-/ + uint8_t offset_y = buffer[i] < 2 ? 2 + (buffer[i] * 2) : 1; + canvas_draw_icon(canvas, pos_x, pos_y + offset_y, keys_assets[buffer[i]]); + pos_x += buffer[i] > 1 ? 9 : 11; + } +} + +/** + * @brief Reset input count + * + * @param model + */ +static void code_input_reset_count(CodeInputModel* model) { + model->input_length[model->current] = 0; +} + +/** + * @brief Call input callback + * + * @param model + */ +static void code_input_call_ok_callback(CodeInputModel* model) { + if(model->ok_callback != NULL) { + model->ok_callback(model->callback_context); + } +} + +/** + * @brief Call changed callback + * + * @param model + */ +static void code_input_call_fail_callback(CodeInputModel* model) { + if(model->fail_callback != NULL) { + model->fail_callback(model->callback_context); + } +} + +/** + * @brief Handle Back button + * + * @param model + */ +static bool code_input_handle_back(CodeInputModel* model) { + if(model->current && !model->input_length[model->current]) { + --model->current; + return true; + } + + if(model->input_length[model->current]) { + code_input_reset_count(model); + return true; + } + + code_input_call_fail_callback(model); + return false; +} + +/** + * @brief Handle OK button + * + * @param model + */ +static void code_input_handle_ok(CodeInputModel* model) { + switch(model->state) { + case CodeInputStateVerify: + + if(code_input_compare_ext(model)) { + if(model->ext_update) { + model->state = CodeInputStateUpdate; + } else { + code_input_call_ok_callback(model); + } + } + code_input_reset_count(model); + break; + + case CodeInputStateUpdate: + + if(!model->current && model->input_length[model->current]) { + model->current++; + } else { + if(code_input_compare_local(model)) { + if(model->ext_update) { + code_input_set_ext(model); + } + code_input_call_ok_callback(model); + } else { + code_input_reset_count(model); + } + } + + break; + default: + break; + } +} + +/** + * @brief Handle input + * + * @param model + * @param key + */ + +size_t code_input_push(uint8_t* buffer, size_t length, InputKey key) { + buffer[length] = key; + length = CLAMP(length + 1, MAX_CODE_LEN, 0); + return length; +} + +/** + * @brief Handle D-pad keys + * + * @param model + * @param key + */ +static void code_input_handle_dpad(CodeInputModel* model, InputKey key) { + uint8_t at = model->current; + size_t new_length = code_input_push(model->local_buffer[at], model->input_length[at], key); + model->input_length[at] = new_length; +} + +/** + * @brief Draw callback + * + * @param canvas + * @param _model + */ +static void code_input_view_draw_callback(Canvas* canvas, void* _model) { + CodeInputModel* model = _model; + uint8_t y_offset = 0; + if(!strlen(model->header)) y_offset = 5; + canvas_clear(canvas); + canvas_set_color(canvas, ColorBlack); + + canvas_draw_str(canvas, 2, 9, model->header); + + canvas_set_font(canvas, FontSecondary); + + switch(model->state) { + case CodeInputStateVerify: + code_input_draw_sequence( + canvas, + model->local_buffer[CodeInputFirst], + model->input_length[CodeInputFirst], + 6, + 30 - y_offset, + true); + break; + case CodeInputStateUpdate: + code_input_draw_sequence( + canvas, + model->local_buffer[CodeInputFirst], + model->input_length[CodeInputFirst], + 6, + 14 - y_offset, + !model->current); + code_input_draw_sequence( + canvas, + model->local_buffer[CodeInputSecond], + model->input_length[CodeInputSecond], + 6, + 44 - y_offset, + model->current); + + if(model->current) canvas_draw_str(canvas, 2, 39 - y_offset, "Repeat code"); + + break; + default: + break; + } +} + +/** + * @brief Input callback + * + * @param event + * @param context + * @return true + * @return false + */ +static bool code_input_view_input_callback(InputEvent* event, void* context) { + CodeInput* code_input = context; + furi_assert(code_input); + bool consumed = false; + + if(event->type == InputTypeShort || event->type == InputTypeRepeat) { + switch(event->key) { + case InputKeyBack: + with_view_model( + code_input->view, (CodeInputModel * model) { + consumed = code_input_handle_back(model); + return true; + }); + break; + + case InputKeyOk: + with_view_model( + code_input->view, (CodeInputModel * model) { + code_input_handle_ok(model); + return true; + }); + consumed = true; + break; + default: + + with_view_model( + code_input->view, (CodeInputModel * model) { + code_input_handle_dpad(model, event->key); + return true; + }); + consumed = true; + break; + } + } + + return consumed; +} + +/** + * @brief Reset all input-related data in model + * + * @param model CodeInputModel + */ +static void code_input_reset_model_input_data(CodeInputModel* model) { + model->current = 0; + model->input_length[CodeInputFirst] = 0; + model->input_length[CodeInputSecond] = 0; + model->ext_buffer = NULL; + model->ext_update = false; + model->state = 0; +} + +/** + * @brief Allocate and initialize code input. This code input is used to enter codes. + * + * @return CodeInput instance pointer + */ +CodeInput* code_input_alloc() { + CodeInput* code_input = furi_alloc(sizeof(CodeInput)); + code_input->view = view_alloc(); + view_set_context(code_input->view, code_input); + view_allocate_model(code_input->view, ViewModelTypeLocking, sizeof(CodeInputModel)); + view_set_draw_callback(code_input->view, code_input_view_draw_callback); + view_set_input_callback(code_input->view, code_input_view_input_callback); + + with_view_model( + code_input->view, (CodeInputModel * model) { + model->header = ""; + model->ok_callback = NULL; + model->fail_callback = NULL; + model->callback_context = NULL; + code_input_reset_model_input_data(model); + return true; + }); + + return code_input; +} + +/** + * @brief Deinitialize and free code input + * + * @param code_input Code input instance + */ +void code_input_free(CodeInput* code_input) { + furi_assert(code_input); + view_free(code_input->view); + free(code_input); +} + +/** + * @brief Get code input view + * + * @param code_input code input instance + * @return View instance that can be used for embedding + */ +View* code_input_get_view(CodeInput* code_input) { + furi_assert(code_input); + return code_input->view; +} + +/** + * @brief Set code input callbacks + * + * @param code_input code input instance + * @param ok_callback input callback fn + * @param fail_callback code match callback fn + * @param callback_context callback context + * @param buffer buffer + * @param buffer_length ptr to buffer length uint + * @param ext_update true to update buffer + */ +void code_input_set_result_callback( + CodeInput* code_input, + CodeInputOkCallback ok_callback, + CodeInputFailCallback fail_callback, + void* callback_context, + uint8_t* buffer, + uint8_t* buffer_length, + bool ext_update) { + with_view_model( + code_input->view, (CodeInputModel * model) { + code_input_reset_model_input_data(model); + model->ok_callback = ok_callback; + model->fail_callback = fail_callback; + model->callback_context = callback_context; + + model->ext_buffer = buffer; + model->ext_buffer_length = buffer_length; + model->state = (*buffer_length == 0) ? 1 : 0; + model->ext_update = ext_update; + + return true; + }); +} + +/** + * @brief Set code input header text + * + * @param code_input code input instance + * @param text text to be shown + */ +void code_input_set_header_text(CodeInput* code_input, const char* text) { + with_view_model( + code_input->view, (CodeInputModel * model) { + model->header = text; + return true; + }); +} diff --git a/applications/gui/modules/code_input.h b/applications/gui/modules/code_input.h new file mode 100644 index 00000000..d6a43fc1 --- /dev/null +++ b/applications/gui/modules/code_input.h @@ -0,0 +1,91 @@ +/** + * @file code_input.h + * GUI: CodeInput keyboard view module API + */ + +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** Code input anonymous structure */ +typedef struct CodeInput CodeInput; + +/** callback that is executed when entered code matches ext buffer */ +typedef void (*CodeInputOkCallback)(void* context); + +/** callback that is executed when entered code does not matches ext buffer */ +typedef void (*CodeInputFailCallback)(void* context); + +/** Allocate and initialize code input. This code input is used to enter codes. + * + * @return CodeInput instance pointer + */ +CodeInput* code_input_alloc(); + +/** Deinitialize and free code input + * + * @param code_input Code input instance + */ +void code_input_free(CodeInput* code_input); + +/** Get code input view + * + * @param code_input code input instance + * + * @return View instance that can be used for embedding + */ +View* code_input_get_view(CodeInput* code_input); + +/** Set code input result callback + * + * @param code_input code input instance + * @param ok_callback ok callback fn + * @param fail_callback fail callback fn + * @param callback_context callback context + * @param buffer buffer to use + * @param buffer_length buffer length + * @param update set true to update buffer + */ +void code_input_set_result_callback( + CodeInput* code_input, + CodeInputOkCallback ok_callback, + CodeInputFailCallback fail_callback, + void* callback_context, + uint8_t* buffer, + uint8_t* buffer_length, + bool update); + +/** Set code input header text + * + * @param code_input code input instance + * @param text text to be shown + */ +void code_input_set_header_text(CodeInput* code_input, const char* text); + +/** Compare two buffers + * + * @param in buffer to compare to source + * @param len_in length of input buffer + * @param src source buffer + * @param len_src length of insourceput buffer + * @return true if buffers match + */ + +bool code_input_compare(uint8_t* in, size_t len_in, uint8_t* src, size_t len_src); + +/** Push input into the end of array + * + * @param buffer buffer + * @param length length of buffer + * @param key input key + * @return new length of input buffer + */ +size_t code_input_push(uint8_t* buffer, size_t length, InputKey key); + +#ifdef __cplusplus +} +#endif \ No newline at end of file