From 21ac37a6f696da8ec26407d2f025c24825fcfec8 Mon Sep 17 00:00:00 2001 From: Albert Kharisov Date: Thu, 10 Feb 2022 22:17:41 +0400 Subject: [PATCH] [FL-2152] New PIN lock (#989) * [Fl-2152] New PIN Lock, part 1 * Fix errors & leaks, renaming * Add support to f6 * Fix error, remove duplicate code * Fix drawing corners of Lock Popup * FuriHal: insomnia if usb connected * Applications: cleanup timers use Co-authored-by: Aleksandr Kutuzov --- .../views/one_shot_animation_view.c | 2 +- applications/desktop/desktop.c | 106 ++-- applications/desktop/desktop_helpers.c | 82 +++ applications/desktop/desktop_helpers.h | 9 + applications/desktop/desktop_i.h | 39 +- .../desktop_settings/desktop_settings.h | 22 +- .../desktop_settings/desktop_settings_app.c | 45 +- .../desktop_settings/desktop_settings_app.h | 24 +- .../scenes/desktop_settings_scene_config.h | 11 +- .../scenes/desktop_settings_scene_i.h | 7 + .../scenes/desktop_settings_scene_pin_auth.c | 95 ++++ .../desktop_settings_scene_pin_disable.c | 56 ++ .../scenes/desktop_settings_scene_pin_error.c | 76 +++ ...nu.c => desktop_settings_scene_pin_menu.c} | 55 +- .../scenes/desktop_settings_scene_pin_setup.c | 107 ++++ .../desktop_settings_scene_pin_setup_done.c | 77 +++ .../desktop_settings_scene_pin_setup_howto.c | 44 ++ .../desktop_settings_scene_pin_setup_howto2.c | 67 +++ .../desktop_settings_scene_pincode_input.c | 64 --- .../scenes/desktop_settings_scene_start.c | 19 +- .../desktop_settings_view_pin_setup_howto.c | 78 +++ .../desktop_settings_view_pin_setup_howto.h | 15 + .../desktop_settings_view_pin_setup_howto2.c | 101 ++++ .../desktop_settings_view_pin_setup_howto2.h | 20 + .../desktop/scenes/desktop_scene_config.h | 4 +- .../desktop/scenes/desktop_scene_debug.c | 4 +- .../desktop/scenes/desktop_scene_fault.c | 2 +- .../scenes/desktop_scene_first_start.c | 4 +- .../scenes/desktop_scene_hw_mismatch.c | 4 +- applications/desktop/scenes/desktop_scene_i.h | 7 +- .../desktop/scenes/desktop_scene_lock_menu.c | 47 +- .../desktop/scenes/desktop_scene_locked.c | 109 ++++ .../desktop/scenes/desktop_scene_main.c | 55 +- .../desktop/scenes/desktop_scene_pin_input.c | 162 ++++++ .../scenes/desktop_scene_pin_timeout.c | 46 ++ .../desktop/scenes/desktop_scene_pinsetup.c | 50 -- applications/desktop/views/desktop_events.h | 27 +- applications/desktop/views/desktop_locked.c | 247 --------- applications/desktop/views/desktop_locked.h | 36 -- .../{desktop_debug.c => desktop_view_debug.c} | 6 +- .../{desktop_debug.h => desktop_view_debug.h} | 0 ...rst_start.c => desktop_view_first_start.c} | 3 +- ...rst_start.h => desktop_view_first_start.h} | 0 ...p_lock_menu.c => desktop_view_lock_menu.c} | 2 +- ...p_lock_menu.h => desktop_view_lock_menu.h} | 0 .../desktop/views/desktop_view_locked.c | 233 +++++++++ .../desktop/views/desktop_view_locked.h | 21 + .../{desktop_main.c => desktop_view_main.c} | 2 +- .../{desktop_main.h => desktop_view_main.h} | 0 .../desktop/views/desktop_view_pin_input.c | 340 +++++++++++++ .../desktop/views/desktop_view_pin_input.h | 40 ++ .../views/desktop_view_pin_setup_done.c | 80 +++ .../views/desktop_view_pin_setup_done.h | 15 + .../desktop/views/desktop_view_pin_timeout.c | 109 ++++ .../desktop/views/desktop_view_pin_timeout.h | 16 + applications/dolphin/dolphin.c | 10 +- applications/gui/elements.c | 44 ++ applications/gui/elements.h | 13 + applications/gui/modules/code_input.c | 478 ------------------ applications/gui/modules/code_input.h | 91 ---- applications/loader/loader.c | 7 + assets/compiled/assets_icons.c | 44 +- assets/compiled/assets_icons.h | 11 +- assets/icons/Interface/LockPopup_100x49.png | Bin 577 -> 0 bytes assets/icons/PIN/Pin_arrow_down_7x9.png | Bin 0 -> 3607 bytes assets/icons/PIN/Pin_arrow_left_9x7.png | Bin 0 -> 3603 bytes assets/icons/PIN/Pin_arrow_right_9x7.png | Bin 0 -> 3602 bytes assets/icons/PIN/Pin_arrow_up7x9.png | Bin 0 -> 3603 bytes assets/icons/PIN/Pin_attention_dpad_29x29.png | Bin 0 -> 3688 bytes assets/icons/PIN/Pin_back_arrow_10x8.png | Bin 0 -> 3606 bytes assets/icons/PIN/Pin_back_full_40x8.png | Bin 0 -> 3641 bytes assets/icons/PIN/Pin_cell_13x13.png | Bin 0 -> 3593 bytes assets/icons/PIN/Pin_pointer_5x3.png | Bin 0 -> 3592 bytes assets/icons/PIN/Pin_star_7x7.png | Bin 0 -> 3600 bytes firmware/targets/f6/furi_hal/furi_hal_power.c | 8 +- firmware/targets/f6/furi_hal/furi_hal_rtc.c | 8 + firmware/targets/f6/furi_hal/furi_hal_usb.c | 5 + firmware/targets/f7/furi_hal/furi_hal_power.c | 8 +- firmware/targets/f7/furi_hal/furi_hal_rtc.c | 8 + firmware/targets/f7/furi_hal/furi_hal_usb.c | 5 + .../targets/furi_hal_include/furi_hal_rtc.h | 5 + 81 files changed, 2461 insertions(+), 1176 deletions(-) create mode 100644 applications/desktop/desktop_helpers.c create mode 100644 applications/desktop/desktop_helpers.h create mode 100644 applications/desktop/desktop_settings/scenes/desktop_settings_scene_i.h create mode 100644 applications/desktop/desktop_settings/scenes/desktop_settings_scene_pin_auth.c create mode 100644 applications/desktop/desktop_settings/scenes/desktop_settings_scene_pin_disable.c create mode 100644 applications/desktop/desktop_settings/scenes/desktop_settings_scene_pin_error.c rename applications/desktop/desktop_settings/scenes/{desktop_settings_scene_pincode_menu.c => desktop_settings_scene_pin_menu.c} (54%) create mode 100644 applications/desktop/desktop_settings/scenes/desktop_settings_scene_pin_setup.c create mode 100644 applications/desktop/desktop_settings/scenes/desktop_settings_scene_pin_setup_done.c create mode 100644 applications/desktop/desktop_settings/scenes/desktop_settings_scene_pin_setup_howto.c create mode 100644 applications/desktop/desktop_settings/scenes/desktop_settings_scene_pin_setup_howto2.c delete mode 100644 applications/desktop/desktop_settings/scenes/desktop_settings_scene_pincode_input.c create mode 100644 applications/desktop/desktop_settings/views/desktop_settings_view_pin_setup_howto.c create mode 100644 applications/desktop/desktop_settings/views/desktop_settings_view_pin_setup_howto.h create mode 100644 applications/desktop/desktop_settings/views/desktop_settings_view_pin_setup_howto2.c create mode 100644 applications/desktop/desktop_settings/views/desktop_settings_view_pin_setup_howto2.h create mode 100644 applications/desktop/scenes/desktop_scene_locked.c create mode 100644 applications/desktop/scenes/desktop_scene_pin_input.c create mode 100644 applications/desktop/scenes/desktop_scene_pin_timeout.c delete mode 100644 applications/desktop/scenes/desktop_scene_pinsetup.c delete mode 100644 applications/desktop/views/desktop_locked.c delete mode 100644 applications/desktop/views/desktop_locked.h rename applications/desktop/views/{desktop_debug.c => desktop_view_debug.c} (98%) rename applications/desktop/views/{desktop_debug.h => desktop_view_debug.h} (100%) rename applications/desktop/views/{desktop_first_start.c => desktop_view_first_start.c} (99%) rename applications/desktop/views/{desktop_first_start.h => desktop_view_first_start.h} (100%) rename applications/desktop/views/{desktop_lock_menu.c => desktop_view_lock_menu.c} (99%) rename applications/desktop/views/{desktop_lock_menu.h => desktop_view_lock_menu.h} (100%) create mode 100644 applications/desktop/views/desktop_view_locked.c create mode 100644 applications/desktop/views/desktop_view_locked.h rename applications/desktop/views/{desktop_main.c => desktop_view_main.c} (98%) rename applications/desktop/views/{desktop_main.h => desktop_view_main.h} (100%) create mode 100644 applications/desktop/views/desktop_view_pin_input.c create mode 100644 applications/desktop/views/desktop_view_pin_input.h create mode 100644 applications/desktop/views/desktop_view_pin_setup_done.c create mode 100644 applications/desktop/views/desktop_view_pin_setup_done.h create mode 100644 applications/desktop/views/desktop_view_pin_timeout.c create mode 100644 applications/desktop/views/desktop_view_pin_timeout.h delete mode 100644 applications/gui/modules/code_input.c delete mode 100644 applications/gui/modules/code_input.h delete mode 100644 assets/icons/Interface/LockPopup_100x49.png create mode 100644 assets/icons/PIN/Pin_arrow_down_7x9.png create mode 100644 assets/icons/PIN/Pin_arrow_left_9x7.png create mode 100644 assets/icons/PIN/Pin_arrow_right_9x7.png create mode 100644 assets/icons/PIN/Pin_arrow_up7x9.png create mode 100644 assets/icons/PIN/Pin_attention_dpad_29x29.png create mode 100644 assets/icons/PIN/Pin_back_arrow_10x8.png create mode 100644 assets/icons/PIN/Pin_back_full_40x8.png create mode 100644 assets/icons/PIN/Pin_cell_13x13.png create mode 100644 assets/icons/PIN/Pin_pointer_5x3.png create mode 100644 assets/icons/PIN/Pin_star_7x7.png diff --git a/applications/desktop/animations/views/one_shot_animation_view.c b/applications/desktop/animations/views/one_shot_animation_view.c index 106cef55..d7e9915a 100644 --- a/applications/desktop/animations/views/one_shot_animation_view.c +++ b/applications/desktop/animations/views/one_shot_animation_view.c @@ -81,7 +81,7 @@ OneShotView* one_shot_view_alloc(void) { OneShotView* view = furi_alloc(sizeof(OneShotView)); view->view = view_alloc(); view->update_timer = - xTimerCreate("Update timer", 1000, pdTRUE, view, one_shot_view_update_timer_callback); + xTimerCreate(NULL, 1000, pdTRUE, view, one_shot_view_update_timer_callback); view_allocate_model(view->view, ViewModelTypeLocking, sizeof(OneShotViewModel)); view_set_context(view->view, view); diff --git a/applications/desktop/desktop.c b/applications/desktop/desktop.c index 5d9b7b80..29e17a88 100644 --- a/applications/desktop/desktop.c +++ b/applications/desktop/desktop.c @@ -1,9 +1,3 @@ -#include "animations/animation_manager.h" -#include "desktop/scenes/desktop_scene.h" -#include "desktop/scenes/desktop_scene_i.h" -#include "desktop/views/desktop_locked.h" -#include "desktop_i.h" - #include #include #include @@ -12,23 +6,38 @@ #include #include +#include "animations/animation_manager.h" +#include "desktop/scenes/desktop_scene.h" +#include "desktop/scenes/desktop_scene_i.h" +#include "desktop/views/desktop_view_locked.h" +#include "desktop/views/desktop_view_pin_input.h" +#include "desktop/views/desktop_view_pin_timeout.h" +#include "desktop_i.h" +#include "desktop_helpers.h" + static void desktop_lock_icon_callback(Canvas* canvas, void* context) { furi_assert(canvas); canvas_draw_icon(canvas, 0, 0, &I_Lock_8x8); } -bool desktop_custom_event_callback(void* context, uint32_t event) { +static bool desktop_custom_event_callback(void* context, uint32_t event) { furi_assert(context); Desktop* desktop = (Desktop*)context; return scene_manager_handle_custom_event(desktop->scene_manager, event); } -bool desktop_back_event_callback(void* context) { +static bool desktop_back_event_callback(void* context) { furi_assert(context); Desktop* desktop = (Desktop*)context; return scene_manager_handle_back_event(desktop->scene_manager); } +static void desktop_tick_event_callback(void* context) { + furi_assert(context); + Desktop* app = context; + scene_manager_handle_tick_event(app->scene_manager); +} + Desktop* desktop_alloc() { Desktop* desktop = furi_alloc(sizeof(Desktop)); @@ -42,6 +51,8 @@ Desktop* desktop_alloc() { view_dispatcher_enable_queue(desktop->view_dispatcher); view_dispatcher_attach_to_gui( desktop->view_dispatcher, desktop->gui, ViewDispatcherTypeDesktop); + view_dispatcher_set_tick_event_callback( + desktop->view_dispatcher, desktop_tick_event_callback, 500); view_dispatcher_set_event_callback_context(desktop->view_dispatcher, desktop); view_dispatcher_set_custom_event_callback( @@ -49,37 +60,60 @@ Desktop* desktop_alloc() { view_dispatcher_set_navigation_event_callback( desktop->view_dispatcher, desktop_back_event_callback); - desktop->locked_view = desktop_locked_alloc(); desktop->lock_menu = desktop_lock_menu_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(); + desktop->locked_view = desktop_view_locked_alloc(); + desktop->pin_input_view = desktop_view_pin_input_alloc(); + desktop->pin_timeout_view = desktop_view_pin_timeout_alloc(); + desktop->main_view_stack = view_stack_alloc(); desktop->main_view = desktop_main_alloc(); View* dolphin_view = animation_manager_get_animation_view(desktop->animation_manager); view_stack_add_view(desktop->main_view_stack, desktop_main_get_view(desktop->main_view)); view_stack_add_view(desktop->main_view_stack, dolphin_view); - view_stack_add_view(desktop->main_view_stack, desktop_locked_get_view(desktop->locked_view)); + view_stack_add_view( + desktop->main_view_stack, desktop_view_locked_get_view(desktop->locked_view)); + + /* locked view (as animation view) attends in 2 scenes: main & locked, + * because it has to draw "Unlocked" label on main scene */ + desktop->locked_view_stack = view_stack_alloc(); + view_stack_add_view(desktop->locked_view_stack, dolphin_view); + view_stack_add_view( + desktop->locked_view_stack, desktop_view_locked_get_view(desktop->locked_view)); view_dispatcher_add_view( - desktop->view_dispatcher, DesktopViewMain, view_stack_get_view(desktop->main_view_stack)); + desktop->view_dispatcher, + DesktopViewIdMain, + view_stack_get_view(desktop->main_view_stack)); view_dispatcher_add_view( desktop->view_dispatcher, - DesktopViewLockMenu, + DesktopViewIdLocked, + view_stack_get_view(desktop->locked_view_stack)); + view_dispatcher_add_view( + desktop->view_dispatcher, + DesktopViewIdLockMenu, desktop_lock_menu_get_view(desktop->lock_menu)); view_dispatcher_add_view( - desktop->view_dispatcher, DesktopViewDebug, desktop_debug_get_view(desktop->debug_view)); + desktop->view_dispatcher, DesktopViewIdDebug, desktop_debug_get_view(desktop->debug_view)); view_dispatcher_add_view( desktop->view_dispatcher, - DesktopViewFirstStart, + DesktopViewIdFirstStart, desktop_first_start_get_view(desktop->first_start_view)); view_dispatcher_add_view( desktop->view_dispatcher, - DesktopViewHwMismatch, + DesktopViewIdHwMismatch, popup_get_view(desktop->hw_mismatch_popup)); view_dispatcher_add_view( - desktop->view_dispatcher, DesktopViewPinSetup, code_input_get_view(desktop->code_input)); + desktop->view_dispatcher, + DesktopViewIdPinTimeout, + desktop_view_pin_timeout_get_view(desktop->pin_timeout_view)); + view_dispatcher_add_view( + desktop->view_dispatcher, + DesktopViewIdPinInput, + desktop_view_pin_input_get_view(desktop->pin_input_view)); + // Lock icon desktop->lock_viewport = view_port_alloc(); view_port_set_width(desktop->lock_viewport, icon_get_width(&I_Lock_8x8)); @@ -93,27 +127,29 @@ Desktop* desktop_alloc() { void desktop_free(Desktop* desktop) { furi_assert(desktop); - view_dispatcher_remove_view(desktop->view_dispatcher, DesktopViewMain); - view_dispatcher_remove_view(desktop->view_dispatcher, DesktopViewLockMenu); - view_dispatcher_remove_view(desktop->view_dispatcher, DesktopViewLocked); - 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_remove_view(desktop->view_dispatcher, DesktopViewIdMain); + view_dispatcher_remove_view(desktop->view_dispatcher, DesktopViewIdLockMenu); + view_dispatcher_remove_view(desktop->view_dispatcher, DesktopViewIdLocked); + view_dispatcher_remove_view(desktop->view_dispatcher, DesktopViewIdDebug); + view_dispatcher_remove_view(desktop->view_dispatcher, DesktopViewIdFirstStart); + view_dispatcher_remove_view(desktop->view_dispatcher, DesktopViewIdHwMismatch); + view_dispatcher_remove_view(desktop->view_dispatcher, DesktopViewIdPinInput); + view_dispatcher_remove_view(desktop->view_dispatcher, DesktopViewIdPinTimeout); view_dispatcher_free(desktop->view_dispatcher); scene_manager_free(desktop->scene_manager); animation_manager_free(desktop->animation_manager); view_stack_free(desktop->main_view_stack); - view_stack_free(desktop->locked_view_stack); desktop_main_free(desktop->main_view); + view_stack_free(desktop->locked_view_stack); + desktop_view_locked_free(desktop->locked_view); desktop_lock_menu_free(desktop->lock_menu); - desktop_locked_free(desktop->locked_view); + desktop_view_locked_free(desktop->locked_view); 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); + desktop_view_pin_timeout_free(desktop->pin_timeout_view); osSemaphoreDelete(desktop->unload_animation_semaphore); @@ -145,14 +181,18 @@ int32_t desktop_srv(void* p) { SAVE_DESKTOP_SETTINGS(&desktop->settings); } - if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagLock)) { - furi_hal_usb_disable(); - scene_manager_set_scene_state( - desktop->scene_manager, DesktopSceneMain, DesktopMainSceneStateLockedWithPin); - } - scene_manager_next_scene(desktop->scene_manager, DesktopSceneMain); + if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagLock)) { + if(desktop->settings.pin_code.length > 0) { + scene_manager_set_scene_state( + desktop->scene_manager, DesktopSceneLocked, SCENE_LOCKED_FIRST_ENTER); + scene_manager_next_scene(desktop->scene_manager, DesktopSceneLocked); + } else { + furi_hal_rtc_reset_flag(FuriHalRtcFlagLock); + } + } + if(desktop_is_first_start()) { scene_manager_next_scene(desktop->scene_manager, DesktopSceneFirstStart); } diff --git a/applications/desktop/desktop_helpers.c b/applications/desktop/desktop_helpers.c new file mode 100644 index 00000000..c3025ae1 --- /dev/null +++ b/applications/desktop/desktop_helpers.c @@ -0,0 +1,82 @@ +#include +#include +#include +#include +#include +#include + +#include "desktop_helpers.h" +#include "desktop_i.h" + +static const NotificationSequence sequence_pin_fail = { + &message_display_on, + + &message_red_255, + &message_vibro_on, + &message_delay_100, + &message_vibro_off, + &message_red_0, + + &message_delay_250, + + &message_red_255, + &message_vibro_on, + &message_delay_100, + &message_vibro_off, + &message_red_0, + NULL, +}; + +static const uint8_t desktop_helpers_fails_timeout[] = { + 0, + 0, + 0, + 0, + 30, + 60, + 90, + 120, + 150, + 180, + /* +60 for every next fail */ +}; + +void desktop_helpers_emit_error_notification() { + NotificationApp* notification = furi_record_open("notification"); + notification_message(notification, &sequence_pin_fail); + furi_record_close("notification"); +} + +void desktop_helpers_lock_system(Desktop* desktop, bool hard_lock) { + view_port_enabled_set(desktop->lock_viewport, true); + if(hard_lock) { + furi_hal_rtc_set_flag(FuriHalRtcFlagLock); + furi_hal_usb_disable(); + } + + Gui* gui = furi_record_open("gui"); + gui_set_lockdown(gui, true); + furi_record_close("gui"); +} + +void desktop_helpers_unlock_system(Desktop* desktop) { + furi_hal_rtc_reset_flag(FuriHalRtcFlagLock); + furi_hal_usb_enable(); + view_port_enabled_set(desktop->lock_viewport, false); + + Gui* gui = furi_record_open("gui"); + gui_set_lockdown(gui, false); + furi_record_close("gui"); +} + +uint32_t desktop_helpers_get_pin_fail_timeout(uint32_t pin_fails) { + uint32_t pin_timeout = 0; + uint32_t max_index = COUNT_OF(desktop_helpers_fails_timeout) - 1; + if(pin_fails <= max_index) { + pin_timeout = desktop_helpers_fails_timeout[pin_fails]; + } else { + pin_timeout = desktop_helpers_fails_timeout[max_index] + (pin_fails - max_index) * 60; + } + + return pin_timeout; +} diff --git a/applications/desktop/desktop_helpers.h b/applications/desktop/desktop_helpers.h new file mode 100644 index 00000000..f8393df8 --- /dev/null +++ b/applications/desktop/desktop_helpers.h @@ -0,0 +1,9 @@ +#pragma once +#include +#include +#include "desktop.h" + +void desktop_helpers_emit_error_notification(); +void desktop_helpers_lock_system(Desktop* desktop, bool hard_lock); +void desktop_helpers_unlock_system(Desktop* desktop); +uint32_t desktop_helpers_get_pin_fail_timeout(uint32_t pin_fails); diff --git a/applications/desktop/desktop_i.h b/applications/desktop/desktop_i.h index b6d0d923..fb15dbd7 100644 --- a/applications/desktop/desktop_i.h +++ b/applications/desktop/desktop_i.h @@ -2,11 +2,13 @@ #include "desktop.h" #include "animations/animation_manager.h" -#include "views/desktop_main.h" -#include "views/desktop_first_start.h" -#include "views/desktop_lock_menu.h" -#include "views/desktop_locked.h" -#include "views/desktop_debug.h" +#include "views/desktop_view_pin_timeout.h" +#include "views/desktop_view_pin_input.h" +#include "views/desktop_view_locked.h" +#include "views/desktop_view_main.h" +#include "views/desktop_view_first_start.h" +#include "views/desktop_view_lock_menu.h" +#include "views/desktop_view_debug.h" #include "desktop/desktop_settings/desktop_settings.h" #include @@ -14,21 +16,21 @@ #include #include #include -#include #include #define STATUS_BAR_Y_SHIFT 13 typedef enum { - DesktopViewMain, - DesktopViewLockMenu, - DesktopViewLocked, - DesktopViewDebug, - DesktopViewFirstStart, - DesktopViewHwMismatch, - DesktopViewPinSetup, - DesktopViewTotal, -} DesktopViewEnum; + DesktopViewIdMain, + DesktopViewIdLockMenu, + DesktopViewIdLocked, + DesktopViewIdDebug, + DesktopViewIdFirstStart, + DesktopViewIdHwMismatch, + DesktopViewIdPinInput, + DesktopViewIdPinTimeout, + DesktopViewIdTotal, +} DesktopViewId; struct Desktop { // Scene @@ -42,16 +44,15 @@ struct Desktop { Popup* hw_mismatch_popup; DesktopLockMenuView* lock_menu; DesktopDebugView* debug_view; - CodeInput* code_input; - + DesktopViewLocked* locked_view; DesktopMainView* main_view; - DesktopLockedView* locked_view; + DesktopViewPinTimeout* pin_timeout_view; ViewStack* main_view_stack; ViewStack* locked_view_stack; DesktopSettings settings; - PinCode pincode_buffer; + DesktopViewPinInput* pin_input_view; ViewPort* lock_viewport; diff --git a/applications/desktop/desktop_settings/desktop_settings.h b/applications/desktop/desktop_settings/desktop_settings.h index 27ded715..24165fe2 100644 --- a/applications/desktop/desktop_settings/desktop_settings.h +++ b/applications/desktop/desktop_settings/desktop_settings.h @@ -1,5 +1,6 @@ #pragma once +#include #include #include #include @@ -9,6 +10,8 @@ #define DESKTOP_SETTINGS_MAGIC (0x17) #define PIN_MAX_LENGTH 12 +#define DESKTOP_SETTINGS_RUN_PIN_SETUP_ARG "run_pin_setup" + #define SAVE_DESKTOP_SETTINGS(x) \ saved_struct_save( \ DESKTOP_SETTINGS_PATH, \ @@ -25,12 +28,27 @@ DESKTOP_SETTINGS_MAGIC, \ DESKTOP_SETTINGS_VER) +#define MAX_PIN_SIZE 10 +#define MIN_PIN_SIZE 4 + typedef struct { + InputKey data[MAX_PIN_SIZE]; uint8_t length; - uint8_t data[PIN_MAX_LENGTH]; } PinCode; typedef struct { uint16_t favorite; - PinCode pincode; + PinCode pin_code; } DesktopSettings; + +static inline bool pins_are_equal(const PinCode* pin_code1, const PinCode* pin_code2) { + furi_assert(pin_code1); + furi_assert(pin_code2); + bool result = false; + + if(pin_code1->length == pin_code2->length) { + result = !memcmp(pin_code1->data, pin_code2->data, pin_code1->length); + } + + return result; +} diff --git a/applications/desktop/desktop_settings/desktop_settings_app.c b/applications/desktop/desktop_settings/desktop_settings_app.c index 1c65f58b..ca078ccd 100644 --- a/applications/desktop/desktop_settings/desktop_settings_app.c +++ b/applications/desktop/desktop_settings/desktop_settings_app.c @@ -1,6 +1,10 @@ -#include "desktop_settings_app.h" #include +#include +#include + +#include "desktop_settings_app.h" #include "scenes/desktop_settings_scene.h" +#include "../views/desktop_view_pin_input.h" static bool desktop_settings_custom_event_callback(void* context, uint32_t event) { furi_assert(context); @@ -30,17 +34,28 @@ DesktopSettingsApp* desktop_settings_app_alloc() { view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen); + app->popup = popup_alloc(); app->submenu = submenu_alloc(); + app->pin_input_view = desktop_view_pin_input_alloc(); + app->pin_setup_howto_view = desktop_settings_view_pin_setup_howto_alloc(); + app->pin_setup_howto2_view = desktop_settings_view_pin_setup_howto2_alloc(); + view_dispatcher_add_view( app->view_dispatcher, DesktopSettingsAppViewMenu, submenu_get_view(app->submenu)); - - app->code_input = code_input_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, DesktopSettingsAppViewIdPopup, popup_get_view(app->popup)); view_dispatcher_add_view( app->view_dispatcher, - DesktopSettingsAppViewPincodeInput, - code_input_get_view(app->code_input)); - - scene_manager_next_scene(app->scene_manager, DesktopSettingsAppSceneStart); + DesktopSettingsAppViewIdPinInput, + desktop_view_pin_input_get_view(app->pin_input_view)); + view_dispatcher_add_view( + app->view_dispatcher, + DesktopSettingsAppViewIdPinSetupHowto, + desktop_settings_view_pin_setup_howto_get_view(app->pin_setup_howto_view)); + view_dispatcher_add_view( + app->view_dispatcher, + DesktopSettingsAppViewIdPinSetupHowto2, + desktop_settings_view_pin_setup_howto2_get_view(app->pin_setup_howto2_view)); return app; } @@ -48,9 +63,15 @@ void desktop_settings_app_free(DesktopSettingsApp* app) { furi_assert(app); // Variable item list view_dispatcher_remove_view(app->view_dispatcher, DesktopSettingsAppViewMenu); + view_dispatcher_remove_view(app->view_dispatcher, DesktopSettingsAppViewIdPopup); + view_dispatcher_remove_view(app->view_dispatcher, DesktopSettingsAppViewIdPinInput); + view_dispatcher_remove_view(app->view_dispatcher, DesktopSettingsAppViewIdPinSetupHowto); + view_dispatcher_remove_view(app->view_dispatcher, DesktopSettingsAppViewIdPinSetupHowto2); submenu_free(app->submenu); - view_dispatcher_remove_view(app->view_dispatcher, DesktopSettingsAppViewPincodeInput); - code_input_free(app->code_input); + popup_free(app->popup); + desktop_view_pin_input_free(app->pin_input_view); + desktop_settings_view_pin_setup_howto_free(app->pin_setup_howto_view); + desktop_settings_view_pin_setup_howto2_free(app->pin_setup_howto2_view); // View dispatcher view_dispatcher_free(app->view_dispatcher); scene_manager_free(app->scene_manager); @@ -62,6 +83,12 @@ void desktop_settings_app_free(DesktopSettingsApp* app) { extern int32_t desktop_settings_app(void* p) { DesktopSettingsApp* app = desktop_settings_app_alloc(); LOAD_DESKTOP_SETTINGS(&app->settings); + if(!strcmp(p, DESKTOP_SETTINGS_RUN_PIN_SETUP_ARG)) { + scene_manager_next_scene(app->scene_manager, DesktopSettingsAppScenePinSetupHowto); + } else { + scene_manager_next_scene(app->scene_manager, DesktopSettingsAppSceneStart); + } + view_dispatcher_run(app->view_dispatcher); desktop_settings_app_free(app); return 0; diff --git a/applications/desktop/desktop_settings/desktop_settings_app.h b/applications/desktop/desktop_settings/desktop_settings_app.h index 9c37a154..6297e42d 100644 --- a/applications/desktop/desktop_settings/desktop_settings_app.h +++ b/applications/desktop/desktop_settings/desktop_settings_app.h @@ -1,22 +1,22 @@ #pragma once #include +#include #include #include #include -#include #include "desktop_settings.h" - -typedef enum { - CodeEventsSetPin, - CodeEventsChangePin, - CodeEventsDisablePin, -} CodeEventsEnum; +#include "desktop/views/desktop_view_pin_input.h" +#include "views/desktop_settings_view_pin_setup_howto.h" +#include "views/desktop_settings_view_pin_setup_howto2.h" typedef enum { DesktopSettingsAppViewMenu, - DesktopSettingsAppViewPincodeInput, + DesktopSettingsAppViewIdPopup, + DesktopSettingsAppViewIdPinInput, + DesktopSettingsAppViewIdPinSetupHowto, + DesktopSettingsAppViewIdPinSetupHowto2, } DesktopSettingsAppView; typedef struct { @@ -26,7 +26,13 @@ typedef struct { SceneManager* scene_manager; ViewDispatcher* view_dispatcher; Submenu* submenu; - CodeInput* code_input; + Popup* popup; + DesktopViewPinInput* pin_input_view; + DesktopSettingsViewPinSetupHowto* pin_setup_howto_view; + DesktopSettingsViewPinSetupHowto2* pin_setup_howto2_view; + + PinCode pincode_buffer; + bool pincode_buffer_filled; uint8_t menu_idx; 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 126873db..5bc52172 100644 --- a/applications/desktop/desktop_settings/scenes/desktop_settings_scene_config.h +++ b/applications/desktop/desktop_settings/scenes/desktop_settings_scene_config.h @@ -1,4 +1,11 @@ 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) +ADD_SCENE(desktop_settings, pin_menu, PinMenu) + +ADD_SCENE(desktop_settings, pin_auth, PinAuth) +ADD_SCENE(desktop_settings, pin_error, PinError) +ADD_SCENE(desktop_settings, pin_disable, PinDisable) +ADD_SCENE(desktop_settings, pin_setup, PinSetup) +ADD_SCENE(desktop_settings, pin_setup_howto, PinSetupHowto) +ADD_SCENE(desktop_settings, pin_setup_howto2, PinSetupHowto2) +ADD_SCENE(desktop_settings, pin_setup_done, PinSetupDone) diff --git a/applications/desktop/desktop_settings/scenes/desktop_settings_scene_i.h b/applications/desktop/desktop_settings/scenes/desktop_settings_scene_i.h new file mode 100644 index 00000000..230fec87 --- /dev/null +++ b/applications/desktop/desktop_settings/scenes/desktop_settings_scene_i.h @@ -0,0 +1,7 @@ +#pragma once + +#define SCENE_STATE_PIN_AUTH_DISABLE (0) +#define SCENE_STATE_PIN_AUTH_CHANGE_PIN (1) + +#define SCENE_STATE_PIN_ERROR_MISMATCH (0) +#define SCENE_STATE_PIN_ERROR_WRONG (1) diff --git a/applications/desktop/desktop_settings/scenes/desktop_settings_scene_pin_auth.c b/applications/desktop/desktop_settings/scenes/desktop_settings_scene_pin_auth.c new file mode 100644 index 00000000..c57506f9 --- /dev/null +++ b/applications/desktop/desktop_settings/scenes/desktop_settings_scene_pin_auth.c @@ -0,0 +1,95 @@ +#include +#include +#include + +#include "../desktop_settings_app.h" +#include "desktop/desktop_settings/desktop_settings.h" +#include "desktop/views/desktop_view_pin_input.h" +#include "desktop_settings_scene.h" +#include "desktop_settings_scene_i.h" + +#define SCENE_EVENT_EXIT (0U) +#define SCENE_EVENT_PINS_EQUAL (1U) +#define SCENE_EVENT_PINS_DIFFERENT (2U) + +static void pin_auth_done_callback(const PinCode* pin_code, void* context) { + furi_assert(pin_code); + furi_assert(context); + DesktopSettingsApp* app = context; + + app->pincode_buffer = *pin_code; + if(pins_are_equal(&app->settings.pin_code, pin_code)) { + view_dispatcher_send_custom_event(app->view_dispatcher, SCENE_EVENT_PINS_EQUAL); + } else { + view_dispatcher_send_custom_event(app->view_dispatcher, SCENE_EVENT_PINS_DIFFERENT); + } +} + +static void pin_auth_back_callback(void* context) { + DesktopSettingsApp* app = context; + view_dispatcher_send_custom_event(app->view_dispatcher, SCENE_EVENT_EXIT); +} + +void desktop_settings_scene_pin_auth_on_enter(void* context) { + DesktopSettingsApp* app = context; + + LOAD_DESKTOP_SETTINGS(&app->settings); + furi_assert(app->settings.pin_code.length > 0); + + desktop_view_pin_input_set_context(app->pin_input_view, app); + desktop_view_pin_input_set_back_callback(app->pin_input_view, pin_auth_back_callback); + desktop_view_pin_input_set_done_callback(app->pin_input_view, pin_auth_done_callback); + desktop_view_pin_input_set_label_button(app->pin_input_view, "OK"); + desktop_view_pin_input_set_label_primary(app->pin_input_view, 0, 0, NULL); + desktop_view_pin_input_set_label_secondary( + app->pin_input_view, 0, 8, "Enter your current PIN:"); + desktop_view_pin_input_reset_pin(app->pin_input_view); + desktop_view_pin_input_unlock_input(app->pin_input_view); + view_dispatcher_switch_to_view(app->view_dispatcher, DesktopSettingsAppViewIdPinInput); +} + +bool desktop_settings_scene_pin_auth_on_event(void* context, SceneManagerEvent event) { + DesktopSettingsApp* app = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + switch(event.event) { + case SCENE_EVENT_PINS_DIFFERENT: + scene_manager_set_scene_state( + app->scene_manager, DesktopSettingsAppScenePinError, SCENE_STATE_PIN_ERROR_WRONG); + scene_manager_next_scene(app->scene_manager, DesktopSettingsAppScenePinError); + consumed = true; + break; + case SCENE_EVENT_PINS_EQUAL: { + uint32_t state = + scene_manager_get_scene_state(app->scene_manager, DesktopSettingsAppScenePinAuth); + if(state == SCENE_STATE_PIN_AUTH_CHANGE_PIN) { + scene_manager_next_scene(app->scene_manager, DesktopSettingsAppScenePinSetupHowto); + } else if(state == SCENE_STATE_PIN_AUTH_DISABLE) { + scene_manager_next_scene(app->scene_manager, DesktopSettingsAppScenePinDisable); + } else { + furi_assert(0); + } + consumed = true; + break; + } + case SCENE_EVENT_EXIT: + scene_manager_search_and_switch_to_previous_scene( + app->scene_manager, DesktopSettingsAppScenePinMenu); + consumed = true; + break; + + default: + consumed = true; + break; + } + } + return consumed; +} + +void desktop_settings_scene_pin_auth_on_exit(void* context) { + furi_assert(context); + DesktopSettingsApp* app = context; + desktop_view_pin_input_set_back_callback(app->pin_input_view, NULL); + desktop_view_pin_input_set_done_callback(app->pin_input_view, NULL); +} diff --git a/applications/desktop/desktop_settings/scenes/desktop_settings_scene_pin_disable.c b/applications/desktop/desktop_settings/scenes/desktop_settings_scene_pin_disable.c new file mode 100644 index 00000000..7486d4aa --- /dev/null +++ b/applications/desktop/desktop_settings/scenes/desktop_settings_scene_pin_disable.c @@ -0,0 +1,56 @@ +#include +#include +#include +#include + +#include "../desktop_settings_app.h" +#include "../desktop_settings.h" +#include "desktop/desktop_settings/desktop_settings.h" +#include "desktop_settings_scene.h" + +#define SCENE_EVENT_EXIT (0U) + +static void pin_disable_back_callback(void* context) { + furi_assert(context); + DesktopSettingsApp* app = context; + view_dispatcher_send_custom_event(app->view_dispatcher, SCENE_EVENT_EXIT); +} + +void desktop_settings_scene_pin_disable_on_enter(void* context) { + furi_assert(context); + DesktopSettingsApp* app = context; + app->settings.pin_code.length = 0; + memset(app->settings.pin_code.data, '0', sizeof(app->settings.pin_code.data)); + SAVE_DESKTOP_SETTINGS(&app->settings); + + popup_set_context(app->popup, app); + popup_set_callback(app->popup, pin_disable_back_callback); + popup_set_icon(app->popup, 0, 2, &I_DolphinMafia_115x62); + popup_set_header(app->popup, "PIN\ndeleted!", 95, 9, AlignCenter, AlignCenter); + popup_set_timeout(app->popup, 1500); + popup_enable_timeout(app->popup); + view_dispatcher_switch_to_view(app->view_dispatcher, DesktopSettingsAppViewIdPopup); +} + +bool desktop_settings_scene_pin_disable_on_event(void* context, SceneManagerEvent event) { + DesktopSettingsApp* app = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + switch(event.event) { + case SCENE_EVENT_EXIT: + scene_manager_search_and_switch_to_previous_scene( + app->scene_manager, DesktopSettingsAppScenePinMenu); + consumed = true; + break; + + default: + consumed = true; + break; + } + } + return consumed; +} + +void desktop_settings_scene_pin_disable_on_exit(void* context) { +} diff --git a/applications/desktop/desktop_settings/scenes/desktop_settings_scene_pin_error.c b/applications/desktop/desktop_settings/scenes/desktop_settings_scene_pin_error.c new file mode 100644 index 00000000..07bba4a8 --- /dev/null +++ b/applications/desktop/desktop_settings/scenes/desktop_settings_scene_pin_error.c @@ -0,0 +1,76 @@ +#include +#include +#include + +#include "desktop/desktop_settings/desktop_settings.h" +#include "desktop/views/desktop_view_pin_input.h" +#include "desktop_settings_scene.h" +#include "desktop_settings_scene_i.h" +#include "../../desktop_helpers.h" +#include "../desktop_settings_app.h" + +#define SCENE_EVENT_EXIT (0U) + +static void pin_error_back_callback(void* context) { + furi_assert(context); + DesktopSettingsApp* app = context; + view_dispatcher_send_custom_event(app->view_dispatcher, SCENE_EVENT_EXIT); +} + +static void pin_error_done_callback(const PinCode* pin_code, void* context) { + furi_assert(context); + DesktopSettingsApp* app = context; + view_dispatcher_send_custom_event(app->view_dispatcher, SCENE_EVENT_EXIT); +} + +void desktop_settings_scene_pin_error_on_enter(void* context) { + DesktopSettingsApp* app = context; + desktop_helpers_emit_error_notification(); + + desktop_view_pin_input_set_context(app->pin_input_view, app); + desktop_view_pin_input_set_back_callback(app->pin_input_view, pin_error_back_callback); + desktop_view_pin_input_set_done_callback(app->pin_input_view, pin_error_done_callback); + + uint32_t state = + scene_manager_get_scene_state(app->scene_manager, DesktopSettingsAppScenePinError); + if(state == SCENE_STATE_PIN_ERROR_MISMATCH) { + desktop_view_pin_input_set_label_primary(app->pin_input_view, 29, 8, "PIN mismatch!"); + } else if(state == SCENE_STATE_PIN_ERROR_WRONG) { + desktop_view_pin_input_set_label_primary(app->pin_input_view, 35, 8, "Wrong PIN!"); + } else { + furi_assert(0); + } + desktop_view_pin_input_set_label_secondary(app->pin_input_view, 0, 8, NULL); + desktop_view_pin_input_set_label_button(app->pin_input_view, "Retry"); + desktop_view_pin_input_lock_input(app->pin_input_view); + desktop_view_pin_input_set_pin(app->pin_input_view, &app->pincode_buffer); + + view_dispatcher_switch_to_view(app->view_dispatcher, DesktopSettingsAppViewIdPinInput); +} + +bool desktop_settings_scene_pin_error_on_event(void* context, SceneManagerEvent event) { + DesktopSettingsApp* app = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + switch(event.event) { + case SCENE_EVENT_EXIT: + scene_manager_previous_scene(app->scene_manager); + consumed = true; + break; + + default: + consumed = true; + break; + } + } + return consumed; +} + +void desktop_settings_scene_pin_error_on_exit(void* context) { + furi_assert(context); + DesktopSettingsApp* app = context; + desktop_view_pin_input_unlock_input(app->pin_input_view); + desktop_view_pin_input_set_back_callback(app->pin_input_view, NULL); + desktop_view_pin_input_set_done_callback(app->pin_input_view, NULL); +} diff --git a/applications/desktop/desktop_settings/scenes/desktop_settings_scene_pincode_menu.c b/applications/desktop/desktop_settings/scenes/desktop_settings_scene_pin_menu.c similarity index 54% rename from applications/desktop/desktop_settings/scenes/desktop_settings_scene_pincode_menu.c rename to applications/desktop/desktop_settings/scenes/desktop_settings_scene_pin_menu.c index 9e72f52a..d226181d 100644 --- a/applications/desktop/desktop_settings/scenes/desktop_settings_scene_pincode_menu.c +++ b/applications/desktop/desktop_settings/scenes/desktop_settings_scene_pin_menu.c @@ -1,38 +1,45 @@ -#include "../desktop_settings_app.h" -#include "applications.h" -#include "desktop_settings_scene.h" +#include +#include -static void desktop_settings_scene_pincode_menu_submenu_callback(void* context, uint32_t index) { +#include "../desktop_settings_app.h" +#include "desktop_settings_scene.h" +#include "desktop_settings_scene_i.h" + +#define SCENE_EVENT_SET_PIN 0 +#define SCENE_EVENT_CHANGE_PIN 1 +#define SCENE_EVENT_DISABLE_PIN 2 + +static void desktop_settings_scene_pin_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) { +void desktop_settings_scene_pin_menu_on_enter(void* context) { DesktopSettingsApp* app = context; Submenu* submenu = app->submenu; submenu_reset(submenu); - if(!app->settings.pincode.length) { + if(!app->settings.pin_code.length) { submenu_add_item( submenu, "Set Pin", - CodeEventsSetPin, - desktop_settings_scene_pincode_menu_submenu_callback, + SCENE_EVENT_SET_PIN, + desktop_settings_scene_pin_menu_submenu_callback, app); } else { submenu_add_item( submenu, "Change Pin", - CodeEventsChangePin, - desktop_settings_scene_pincode_menu_submenu_callback, + SCENE_EVENT_CHANGE_PIN, + desktop_settings_scene_pin_menu_submenu_callback, app); submenu_add_item( submenu, "Disable", - CodeEventsDisablePin, - desktop_settings_scene_pincode_menu_submenu_callback, + SCENE_EVENT_DISABLE_PIN, + desktop_settings_scene_pin_menu_submenu_callback, app); } @@ -41,28 +48,28 @@ void desktop_settings_scene_pincode_menu_on_enter(void* context) { view_dispatcher_switch_to_view(app->view_dispatcher, DesktopSettingsAppViewMenu); } -bool desktop_settings_scene_pincode_menu_on_event(void* context, SceneManagerEvent event) { +bool desktop_settings_scene_pin_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); + case SCENE_EVENT_SET_PIN: + scene_manager_next_scene(app->scene_manager, DesktopSettingsAppScenePinSetupHowto); consumed = true; break; - case CodeEventsChangePin: + case SCENE_EVENT_CHANGE_PIN: scene_manager_set_scene_state( - app->scene_manager, DesktopSettingsAppScenePinCodeInput, event.event); - scene_manager_next_scene(app->scene_manager, DesktopSettingsAppScenePinCodeInput); + app->scene_manager, + DesktopSettingsAppScenePinAuth, + SCENE_STATE_PIN_AUTH_CHANGE_PIN); + scene_manager_next_scene(app->scene_manager, DesktopSettingsAppScenePinAuth); consumed = true; break; - case CodeEventsDisablePin: + case SCENE_EVENT_DISABLE_PIN: scene_manager_set_scene_state( - app->scene_manager, DesktopSettingsAppScenePinCodeInput, event.event); - scene_manager_next_scene(app->scene_manager, DesktopSettingsAppScenePinCodeInput); + app->scene_manager, DesktopSettingsAppScenePinAuth, SCENE_STATE_PIN_AUTH_DISABLE); + scene_manager_next_scene(app->scene_manager, DesktopSettingsAppScenePinAuth); consumed = true; break; default: @@ -73,7 +80,7 @@ bool desktop_settings_scene_pincode_menu_on_event(void* context, SceneManagerEve return consumed; } -void desktop_settings_scene_pincode_menu_on_exit(void* context) { +void desktop_settings_scene_pin_menu_on_exit(void* context) { DesktopSettingsApp* app = context; submenu_reset(app->submenu); } diff --git a/applications/desktop/desktop_settings/scenes/desktop_settings_scene_pin_setup.c b/applications/desktop/desktop_settings/scenes/desktop_settings_scene_pin_setup.c new file mode 100644 index 00000000..5659684f --- /dev/null +++ b/applications/desktop/desktop_settings/scenes/desktop_settings_scene_pin_setup.c @@ -0,0 +1,107 @@ +#include +#include +#include + +#include "../desktop_settings_app.h" +#include "desktop/desktop_settings/desktop_settings.h" +#include "desktop/views/desktop_view_pin_input.h" +#include "desktop_settings_scene.h" +#include "desktop_settings_scene_i.h" + +#define SCENE_EVENT_EXIT (0U) +#define SCENE_EVENT_1ST_PIN_ENTERED (1U) +#define SCENE_EVENT_PINS_EQUAL (2U) +#define SCENE_EVENT_PINS_DIFFERENT (3U) + +static void pin_setup_done_callback(const PinCode* pin_code, void* context) { + furi_assert(pin_code); + furi_assert(context); + DesktopSettingsApp* app = context; + + if(!app->pincode_buffer_filled) { + app->pincode_buffer = *pin_code; + app->pincode_buffer_filled = true; + view_dispatcher_send_custom_event(app->view_dispatcher, SCENE_EVENT_1ST_PIN_ENTERED); + } else { + app->pincode_buffer_filled = false; + if(pins_are_equal(&app->pincode_buffer, pin_code)) { + view_dispatcher_send_custom_event(app->view_dispatcher, SCENE_EVENT_PINS_EQUAL); + } else { + view_dispatcher_send_custom_event(app->view_dispatcher, SCENE_EVENT_PINS_DIFFERENT); + } + } +} + +static void pin_setup_back_callback(void* context) { + DesktopSettingsApp* app = context; + view_dispatcher_send_custom_event(app->view_dispatcher, SCENE_EVENT_EXIT); +} + +void desktop_settings_scene_pin_setup_on_enter(void* context) { + DesktopSettingsApp* app = context; + + app->pincode_buffer_filled = false; + desktop_view_pin_input_set_context(app->pin_input_view, app); + desktop_view_pin_input_set_back_callback(app->pin_input_view, pin_setup_back_callback); + desktop_view_pin_input_set_done_callback(app->pin_input_view, pin_setup_done_callback); + desktop_view_pin_input_set_label_button(app->pin_input_view, "OK"); + desktop_view_pin_input_set_label_primary(app->pin_input_view, 0, 0, NULL); + desktop_view_pin_input_set_label_secondary( + app->pin_input_view, 0, 8, "Enter from 4 to 10 arrows:"); + desktop_view_pin_input_reset_pin(app->pin_input_view); + desktop_view_pin_input_unlock_input(app->pin_input_view); + view_dispatcher_switch_to_view(app->view_dispatcher, DesktopSettingsAppViewIdPinInput); +} + +bool desktop_settings_scene_pin_setup_on_event(void* context, SceneManagerEvent event) { + DesktopSettingsApp* app = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + switch(event.event) { + case SCENE_EVENT_1ST_PIN_ENTERED: + desktop_view_pin_input_set_label_button(app->pin_input_view, "OK"); + desktop_view_pin_input_set_label_primary(app->pin_input_view, 0, 0, NULL); + desktop_view_pin_input_set_label_secondary( + app->pin_input_view, 0, 8, "Confirm your PIN:"); + desktop_view_pin_input_reset_pin(app->pin_input_view); + desktop_view_pin_input_unlock_input(app->pin_input_view); + consumed = true; + break; + case SCENE_EVENT_PINS_DIFFERENT: + scene_manager_set_scene_state( + app->scene_manager, + DesktopSettingsAppScenePinError, + SCENE_STATE_PIN_ERROR_MISMATCH); + scene_manager_next_scene(app->scene_manager, DesktopSettingsAppScenePinError); + consumed = true; + break; + case SCENE_EVENT_PINS_EQUAL: + scene_manager_next_scene(app->scene_manager, DesktopSettingsAppScenePinSetupHowto2); + consumed = true; + break; + case SCENE_EVENT_EXIT: { + uint32_t scene_found; + scene_found = scene_manager_search_and_switch_to_previous_scene( + app->scene_manager, DesktopSettingsAppScenePinMenu); + if(!scene_found) { + view_dispatcher_stop(app->view_dispatcher); + } + consumed = true; + break; + } + + default: + consumed = true; + break; + } + } + return consumed; +} + +void desktop_settings_scene_pin_setup_on_exit(void* context) { + furi_assert(context); + DesktopSettingsApp* app = context; + desktop_view_pin_input_set_back_callback(app->pin_input_view, NULL); + desktop_view_pin_input_set_done_callback(app->pin_input_view, NULL); +} diff --git a/applications/desktop/desktop_settings/scenes/desktop_settings_scene_pin_setup_done.c b/applications/desktop/desktop_settings/scenes/desktop_settings_scene_pin_setup_done.c new file mode 100644 index 00000000..7be0e51c --- /dev/null +++ b/applications/desktop/desktop_settings/scenes/desktop_settings_scene_pin_setup_done.c @@ -0,0 +1,77 @@ +#include +#include +#include +#include +#include +#include + +#include "../desktop_settings_app.h" +#include "desktop/desktop_settings/desktop_settings.h" +#include "desktop/views/desktop_view_pin_input.h" +#include "desktop_settings_scene.h" + +#define SCENE_EVENT_DONE (0U) + +static void pin_setup_done_callback(const PinCode* pin_code, void* context) { + furi_assert(pin_code); + furi_assert(context); + DesktopSettingsApp* app = context; + + view_dispatcher_send_custom_event(app->view_dispatcher, SCENE_EVENT_DONE); +} + +void desktop_settings_scene_pin_setup_done_on_enter(void* context) { + DesktopSettingsApp* app = context; + + app->settings.pin_code = app->pincode_buffer; + SAVE_DESKTOP_SETTINGS(&app->settings); + NotificationApp* notification = furi_record_open("notification"); + notification_message(notification, &sequence_single_vibro); + furi_record_close("notification"); + + desktop_view_pin_input_set_context(app->pin_input_view, app); + desktop_view_pin_input_set_back_callback(app->pin_input_view, NULL); + desktop_view_pin_input_set_done_callback(app->pin_input_view, pin_setup_done_callback); + desktop_view_pin_input_set_pin(app->pin_input_view, &app->settings.pin_code); + desktop_view_pin_input_set_label_button(app->pin_input_view, "Done"); + desktop_view_pin_input_set_label_primary(app->pin_input_view, 29, 8, "PIN activated!"); + desktop_view_pin_input_set_label_secondary( + app->pin_input_view, 7, 45, "Remember or write it down"); + desktop_view_pin_input_lock_input(app->pin_input_view); + desktop_view_pin_input_set_pin_position(app->pin_input_view, 64, 24); + view_dispatcher_switch_to_view(app->view_dispatcher, DesktopSettingsAppViewIdPinInput); +} + +bool desktop_settings_scene_pin_setup_done_on_event(void* context, SceneManagerEvent event) { + DesktopSettingsApp* app = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + switch(event.event) { + case SCENE_EVENT_DONE: { + bool scene_found = false; + scene_found = scene_manager_search_and_switch_to_previous_scene( + app->scene_manager, DesktopSettingsAppScenePinMenu); + if(!scene_found) { + view_dispatcher_stop(app->view_dispatcher); + } + consumed = true; + break; + } + default: + consumed = true; + break; + } + } else if(event.type == SceneManagerEventTypeBack) { + consumed = true; + } + return consumed; +} + +void desktop_settings_scene_pin_setup_done_on_exit(void* context) { + furi_assert(context); + DesktopSettingsApp* app = context; + desktop_view_pin_input_set_pin_position(app->pin_input_view, 64, 32); + desktop_view_pin_input_set_back_callback(app->pin_input_view, NULL); + desktop_view_pin_input_set_done_callback(app->pin_input_view, NULL); +} diff --git a/applications/desktop/desktop_settings/scenes/desktop_settings_scene_pin_setup_howto.c b/applications/desktop/desktop_settings/scenes/desktop_settings_scene_pin_setup_howto.c new file mode 100644 index 00000000..22727a7a --- /dev/null +++ b/applications/desktop/desktop_settings/scenes/desktop_settings_scene_pin_setup_howto.c @@ -0,0 +1,44 @@ +#include +#include +#include + +#include "desktop_settings_scene.h" +#include "../desktop_settings_app.h" +#include "../views/desktop_settings_view_pin_setup_howto.h" + +#define SCENE_EXIT_EVENT (0U) + +static void desktop_settings_scene_pin_lock_done_callback(void* context) { + DesktopSettingsApp* app = context; + view_dispatcher_send_custom_event(app->view_dispatcher, SCENE_EXIT_EVENT); +} + +void desktop_settings_scene_pin_setup_howto_on_enter(void* context) { + DesktopSettingsApp* app = context; + + desktop_settings_view_pin_setup_howto_set_callback( + app->pin_setup_howto_view, desktop_settings_scene_pin_lock_done_callback, app); + view_dispatcher_switch_to_view(app->view_dispatcher, DesktopSettingsAppViewIdPinSetupHowto); +} + +bool desktop_settings_scene_pin_setup_howto_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_next_scene(app->scene_manager, DesktopSettingsAppScenePinSetup); + consumed = true; + break; + default: + furi_assert(0); + consumed = true; + break; + } + } + return consumed; +} + +void desktop_settings_scene_pin_setup_howto_on_exit(void* context) { +} diff --git a/applications/desktop/desktop_settings/scenes/desktop_settings_scene_pin_setup_howto2.c b/applications/desktop/desktop_settings/scenes/desktop_settings_scene_pin_setup_howto2.c new file mode 100644 index 00000000..477d1f27 --- /dev/null +++ b/applications/desktop/desktop_settings/scenes/desktop_settings_scene_pin_setup_howto2.c @@ -0,0 +1,67 @@ +#include +#include +#include + +#include "desktop_settings_scene.h" +#include "../desktop_settings_app.h" +#include "../views/desktop_settings_view_pin_setup_howto2.h" + +#define SCENE_EXIT_EVENT (0U) +#define SCENE_DONE_EVENT (1U) + +static void desktop_settings_scene_pin_setup_howto2_done_callback(void* context) { + DesktopSettingsApp* app = context; + view_dispatcher_send_custom_event(app->view_dispatcher, SCENE_DONE_EVENT); +} + +static void desktop_settings_scene_pin_setup_howto2_exit_callback(void* context) { + DesktopSettingsApp* app = context; + view_dispatcher_send_custom_event(app->view_dispatcher, SCENE_EXIT_EVENT); +} + +void desktop_settings_scene_pin_setup_howto2_on_enter(void* context) { + DesktopSettingsApp* app = context; + + desktop_settings_view_pin_setup_howto2_set_context(app->pin_setup_howto2_view, app); + desktop_settings_view_pin_setup_howto2_set_ok_callback( + app->pin_setup_howto2_view, desktop_settings_scene_pin_setup_howto2_done_callback); + desktop_settings_view_pin_setup_howto2_set_cancel_callback( + app->pin_setup_howto2_view, desktop_settings_scene_pin_setup_howto2_exit_callback); + view_dispatcher_switch_to_view(app->view_dispatcher, DesktopSettingsAppViewIdPinSetupHowto2); +} + +bool desktop_settings_scene_pin_setup_howto2_on_event(void* context, SceneManagerEvent event) { + DesktopSettingsApp* app = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + switch(event.event) { + case SCENE_DONE_EVENT: { + scene_manager_next_scene(app->scene_manager, DesktopSettingsAppScenePinSetupDone); + consumed = true; + break; + } + case SCENE_EXIT_EVENT: { + bool scene_found = false; + scene_found = scene_manager_search_and_switch_to_previous_scene( + app->scene_manager, DesktopSettingsAppScenePinMenu); + if(!scene_found) { + view_dispatcher_stop(app->view_dispatcher); + } + consumed = true; + break; + } + default: + furi_assert(0); + consumed = true; + break; + } + } + return consumed; +} + +void desktop_settings_scene_pin_setup_howto2_on_exit(void* context) { + DesktopSettingsApp* app = context; + desktop_settings_view_pin_setup_howto2_set_ok_callback(app->pin_setup_howto2_view, NULL); + desktop_settings_view_pin_setup_howto2_set_cancel_callback(app->pin_setup_howto2_view, NULL); +} 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 deleted file mode 100644 index a24551c4..00000000 --- a/applications/desktop/desktop_settings/scenes/desktop_settings_scene_pincode_input.c +++ /dev/null @@ -1,64 +0,0 @@ -#include "../desktop_settings_app.h" -#include "desktop_settings_scene.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; - SAVE_DESKTOP_SETTINGS(&app->settings); - 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_start.c b/applications/desktop/desktop_settings/scenes/desktop_settings_scene_start.c index 4ea7e0cc..8f856b6a 100644 --- a/applications/desktop/desktop_settings/scenes/desktop_settings_scene_start.c +++ b/applications/desktop/desktop_settings/scenes/desktop_settings_scene_start.c @@ -1,11 +1,10 @@ +#include + #include "../desktop_settings_app.h" -#include "applications.h" #include "desktop_settings_scene.h" -enum DesktopSettingsStartSubmenuIndex { - DesktopSettingsStartSubmenuIndexFavorite, - DesktopSettingsStartSubmenuIndexPinSetup, -}; +#define SCENE_EVENT_SELECT_FAVORITE 0 +#define SCENE_EVENT_SELECT_PIN_SETUP 1 static void desktop_settings_scene_start_submenu_callback(void* context, uint32_t index) { DesktopSettingsApp* app = context; @@ -19,14 +18,14 @@ void desktop_settings_scene_start_on_enter(void* context) { submenu_add_item( submenu, "Favorite App", - DesktopSettingsStartSubmenuIndexFavorite, + SCENE_EVENT_SELECT_FAVORITE, desktop_settings_scene_start_submenu_callback, app); submenu_add_item( submenu, "PIN Setup", - DesktopSettingsStartSubmenuIndexPinSetup, + SCENE_EVENT_SELECT_PIN_SETUP, desktop_settings_scene_start_submenu_callback, app); @@ -39,12 +38,12 @@ bool desktop_settings_scene_start_on_event(void* context, SceneManagerEvent even if(event.type == SceneManagerEventTypeCustom) { switch(event.event) { - case DesktopSettingsStartSubmenuIndexFavorite: + case SCENE_EVENT_SELECT_FAVORITE: scene_manager_next_scene(app->scene_manager, DesktopSettingsAppSceneFavorite); consumed = true; break; - case DesktopSettingsStartSubmenuIndexPinSetup: - scene_manager_next_scene(app->scene_manager, DesktopSettingsAppScenePinCodeMenu); + case SCENE_EVENT_SELECT_PIN_SETUP: + scene_manager_next_scene(app->scene_manager, DesktopSettingsAppScenePinMenu); consumed = true; break; } diff --git a/applications/desktop/desktop_settings/views/desktop_settings_view_pin_setup_howto.c b/applications/desktop/desktop_settings/views/desktop_settings_view_pin_setup_howto.c new file mode 100644 index 00000000..c87de756 --- /dev/null +++ b/applications/desktop/desktop_settings/views/desktop_settings_view_pin_setup_howto.c @@ -0,0 +1,78 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#include "desktop_settings_view_pin_setup_howto.h" + +struct DesktopSettingsViewPinSetupHowto { + View* view; + DesktopSettingsViewPinSetupHowtoDoneCallback callback; + void* context; +}; + +static void desktop_settings_view_pin_setup_howto_draw(Canvas* canvas, void* model) { + furi_assert(canvas); + furi_assert(model); + + canvas_draw_icon(canvas, 16, 18, &I_Pin_attention_dpad_29x29); + elements_button_right(canvas, "Next"); + + canvas_set_font(canvas, FontPrimary); + elements_multiline_text_aligned(canvas, 64, 0, AlignCenter, AlignTop, "Setting up PIN"); + + canvas_set_font(canvas, FontSecondary); + elements_multiline_text(canvas, 58, 24, "Prepare to use\narrows as\nPIN symbols"); +} + +static bool desktop_settings_view_pin_setup_howto_input(InputEvent* event, void* context) { + furi_assert(event); + furi_assert(context); + + DesktopSettingsViewPinSetupHowto* instance = context; + bool consumed = false; + + if((event->key == InputKeyRight) && (event->type == InputTypeShort)) { + instance->callback(instance->context); + consumed = true; + } + + return consumed; +} + +void desktop_settings_view_pin_setup_howto_set_callback( + DesktopSettingsViewPinSetupHowto* instance, + DesktopSettingsViewPinSetupHowtoDoneCallback callback, + void* context) { + furi_assert(instance); + furi_assert(callback); + instance->callback = callback; + instance->context = context; +} + +DesktopSettingsViewPinSetupHowto* desktop_settings_view_pin_setup_howto_alloc() { + DesktopSettingsViewPinSetupHowto* view = furi_alloc(sizeof(DesktopSettingsViewPinSetupHowto)); + view->view = view_alloc(); + view_allocate_model(view->view, ViewModelTypeLockFree, 1); + view_set_context(view->view, view); + view_set_draw_callback(view->view, desktop_settings_view_pin_setup_howto_draw); + view_set_input_callback(view->view, desktop_settings_view_pin_setup_howto_input); + + return view; +} + +void desktop_settings_view_pin_setup_howto_free(DesktopSettingsViewPinSetupHowto* instance) { + furi_assert(instance); + + view_free(instance->view); + free(instance); +} + +View* desktop_settings_view_pin_setup_howto_get_view(DesktopSettingsViewPinSetupHowto* instance) { + furi_assert(instance); + return instance->view; +} diff --git a/applications/desktop/desktop_settings/views/desktop_settings_view_pin_setup_howto.h b/applications/desktop/desktop_settings/views/desktop_settings_view_pin_setup_howto.h new file mode 100644 index 00000000..0f62cb43 --- /dev/null +++ b/applications/desktop/desktop_settings/views/desktop_settings_view_pin_setup_howto.h @@ -0,0 +1,15 @@ +#pragma once + +#include + +typedef struct DesktopSettingsViewPinSetupHowto DesktopSettingsViewPinSetupHowto; + +typedef void (*DesktopSettingsViewPinSetupHowtoDoneCallback)(void*); + +void desktop_settings_view_pin_setup_howto_set_callback( + DesktopSettingsViewPinSetupHowto* instance, + DesktopSettingsViewPinSetupHowtoDoneCallback callback, + void* context); +DesktopSettingsViewPinSetupHowto* desktop_settings_view_pin_setup_howto_alloc(); +void desktop_settings_view_pin_setup_howto_free(DesktopSettingsViewPinSetupHowto* instance); +View* desktop_settings_view_pin_setup_howto_get_view(DesktopSettingsViewPinSetupHowto* instance); diff --git a/applications/desktop/desktop_settings/views/desktop_settings_view_pin_setup_howto2.c b/applications/desktop/desktop_settings/views/desktop_settings_view_pin_setup_howto2.c new file mode 100644 index 00000000..3ef22b46 --- /dev/null +++ b/applications/desktop/desktop_settings/views/desktop_settings_view_pin_setup_howto2.c @@ -0,0 +1,101 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#include "desktop_settings_view_pin_setup_howto2.h" + +struct DesktopSettingsViewPinSetupHowto2 { + View* view; + DesktopSettingsViewPinSetupHowto2Callback cancel_callback; + DesktopSettingsViewPinSetupHowto2Callback ok_callback; + void* context; +}; + +static void desktop_settings_view_pin_setup_howto2_draw(Canvas* canvas, void* model) { + furi_assert(canvas); + furi_assert(model); + + canvas_set_font(canvas, FontSecondary); + elements_multiline_text_aligned( + canvas, + 64, + 24, + AlignCenter, + AlignCenter, + "Forgotten PIN can only be\n" + "reset with entire device.\n" + "Read docs How to reset PIN."); + + elements_button_right(canvas, "OK"); + elements_button_left(canvas, "Cancel"); +} + +static bool desktop_settings_view_pin_setup_howto2_input(InputEvent* event, void* context) { + furi_assert(event); + furi_assert(context); + + DesktopSettingsViewPinSetupHowto2* instance = context; + bool consumed = false; + + if(event->type == InputTypeShort) { + if(event->key == InputKeyRight) { + instance->ok_callback(instance->context); + consumed = true; + } else if(event->key == InputKeyLeft) { + instance->cancel_callback(instance->context); + consumed = true; + } + } + + return consumed; +} + +void desktop_settings_view_pin_setup_howto2_set_context( + DesktopSettingsViewPinSetupHowto2* instance, + void* context) { + furi_assert(instance); + instance->context = context; +} + +void desktop_settings_view_pin_setup_howto2_set_cancel_callback( + DesktopSettingsViewPinSetupHowto2* instance, + DesktopSettingsViewPinSetupHowto2Callback callback) { + furi_assert(instance); + instance->cancel_callback = callback; +} + +void desktop_settings_view_pin_setup_howto2_set_ok_callback( + DesktopSettingsViewPinSetupHowto2* instance, + DesktopSettingsViewPinSetupHowto2Callback callback) { + furi_assert(instance); + instance->ok_callback = callback; +} + +DesktopSettingsViewPinSetupHowto2* desktop_settings_view_pin_setup_howto2_alloc() { + DesktopSettingsViewPinSetupHowto2* view = + furi_alloc(sizeof(DesktopSettingsViewPinSetupHowto2)); + view->view = view_alloc(); + view_allocate_model(view->view, ViewModelTypeLockFree, 1); + view_set_context(view->view, view); + view_set_draw_callback(view->view, desktop_settings_view_pin_setup_howto2_draw); + view_set_input_callback(view->view, desktop_settings_view_pin_setup_howto2_input); + + return view; +} + +void desktop_settings_view_pin_setup_howto2_free(DesktopSettingsViewPinSetupHowto2* instance) { + furi_assert(instance); + + view_free(instance->view); + free(instance); +} + +View* desktop_settings_view_pin_setup_howto2_get_view(DesktopSettingsViewPinSetupHowto2* instance) { + furi_assert(instance); + return instance->view; +} diff --git a/applications/desktop/desktop_settings/views/desktop_settings_view_pin_setup_howto2.h b/applications/desktop/desktop_settings/views/desktop_settings_view_pin_setup_howto2.h new file mode 100644 index 00000000..8e3018d9 --- /dev/null +++ b/applications/desktop/desktop_settings/views/desktop_settings_view_pin_setup_howto2.h @@ -0,0 +1,20 @@ +#pragma once + +#include + +typedef struct DesktopSettingsViewPinSetupHowto2 DesktopSettingsViewPinSetupHowto2; + +typedef void (*DesktopSettingsViewPinSetupHowto2Callback)(void*); + +DesktopSettingsViewPinSetupHowto2* desktop_settings_view_pin_setup_howto2_alloc(); +void desktop_settings_view_pin_setup_howto2_free(DesktopSettingsViewPinSetupHowto2* instance); +View* desktop_settings_view_pin_setup_howto2_get_view(DesktopSettingsViewPinSetupHowto2* instance); +void desktop_settings_view_pin_setup_howto2_set_context( + DesktopSettingsViewPinSetupHowto2* instance, + void* context); +void desktop_settings_view_pin_setup_howto2_set_cancel_callback( + DesktopSettingsViewPinSetupHowto2* instance, + DesktopSettingsViewPinSetupHowto2Callback callback); +void desktop_settings_view_pin_setup_howto2_set_ok_callback( + DesktopSettingsViewPinSetupHowto2* instance, + DesktopSettingsViewPinSetupHowto2Callback callback); diff --git a/applications/desktop/scenes/desktop_scene_config.h b/applications/desktop/scenes/desktop_scene_config.h index b4e39dd3..c84d6ff8 100644 --- a/applications/desktop/scenes/desktop_scene_config.h +++ b/applications/desktop/scenes/desktop_scene_config.h @@ -3,5 +3,7 @@ ADD_SCENE(desktop, lock_menu, LockMenu) ADD_SCENE(desktop, debug, Debug) ADD_SCENE(desktop, first_start, FirstStart) ADD_SCENE(desktop, hw_mismatch, HwMismatch) -ADD_SCENE(desktop, pinsetup, PinSetup) ADD_SCENE(desktop, fault, Fault) +ADD_SCENE(desktop, locked, Locked) +ADD_SCENE(desktop, pin_input, PinInput) +ADD_SCENE(desktop, pin_timeout, PinTimeout) diff --git a/applications/desktop/scenes/desktop_scene_debug.c b/applications/desktop/scenes/desktop_scene_debug.c index bf8607f8..99fcad87 100644 --- a/applications/desktop/scenes/desktop_scene_debug.c +++ b/applications/desktop/scenes/desktop_scene_debug.c @@ -3,7 +3,7 @@ #include #include "../desktop_i.h" -#include "../views/desktop_debug.h" +#include "../views/desktop_view_debug.h" #include "desktop_scene.h" void desktop_scene_debug_callback(DesktopEvent event, void* context) { @@ -17,7 +17,7 @@ void desktop_scene_debug_on_enter(void* context) { desktop_debug_get_dolphin_data(desktop->debug_view); desktop_debug_set_callback(desktop->debug_view, desktop_scene_debug_callback, desktop); - view_dispatcher_switch_to_view(desktop->view_dispatcher, DesktopViewDebug); + view_dispatcher_switch_to_view(desktop->view_dispatcher, DesktopViewIdDebug); } bool desktop_scene_debug_on_event(void* context, SceneManagerEvent event) { diff --git a/applications/desktop/scenes/desktop_scene_fault.c b/applications/desktop/scenes/desktop_scene_fault.c index a2555e64..b75b32aa 100644 --- a/applications/desktop/scenes/desktop_scene_fault.c +++ b/applications/desktop/scenes/desktop_scene_fault.c @@ -25,7 +25,7 @@ void desktop_scene_fault_on_enter(void* context) { char* message = (char*)furi_hal_rtc_get_fault_data(); popup_set_text(popup, message, 60, 37 + STATUS_BAR_Y_SHIFT, AlignCenter, AlignCenter); popup_set_callback(popup, desktop_scene_fault_callback); - view_dispatcher_switch_to_view(desktop->view_dispatcher, DesktopViewHwMismatch); + view_dispatcher_switch_to_view(desktop->view_dispatcher, DesktopViewIdHwMismatch); } bool desktop_scene_fault_on_event(void* context, SceneManagerEvent event) { diff --git a/applications/desktop/scenes/desktop_scene_first_start.c b/applications/desktop/scenes/desktop_scene_first_start.c index 8dfc96dc..9563094c 100644 --- a/applications/desktop/scenes/desktop_scene_first_start.c +++ b/applications/desktop/scenes/desktop_scene_first_start.c @@ -2,7 +2,7 @@ #include #include "../desktop_i.h" -#include "../views/desktop_first_start.h" +#include "../views/desktop_view_first_start.h" #include "../views/desktop_events.h" void desktop_scene_first_start_callback(DesktopEvent event, void* context) { @@ -17,7 +17,7 @@ void desktop_scene_first_start_on_enter(void* context) { desktop_first_start_set_callback( first_start_view, desktop_scene_first_start_callback, desktop); - view_dispatcher_switch_to_view(desktop->view_dispatcher, DesktopViewFirstStart); + view_dispatcher_switch_to_view(desktop->view_dispatcher, DesktopViewIdFirstStart); } bool desktop_scene_first_start_on_event(void* context, SceneManagerEvent event) { diff --git a/applications/desktop/scenes/desktop_scene_hw_mismatch.c b/applications/desktop/scenes/desktop_scene_hw_mismatch.c index 7ca090b9..05a6fd68 100644 --- a/applications/desktop/scenes/desktop_scene_hw_mismatch.c +++ b/applications/desktop/scenes/desktop_scene_hw_mismatch.c @@ -1,5 +1,5 @@ #include -#include +#include #include "desktop_scene.h" #include "../desktop_i.h" @@ -31,7 +31,7 @@ void desktop_scene_hw_mismatch_on_enter(void* context) { popup, "!!!! HW Mismatch !!!!", 60, 14 + STATUS_BAR_Y_SHIFT, AlignCenter, AlignCenter); popup_set_text(popup, text_buffer, 60, 37 + STATUS_BAR_Y_SHIFT, AlignCenter, AlignCenter); popup_set_callback(popup, desktop_scene_hw_mismatch_callback); - view_dispatcher_switch_to_view(desktop->view_dispatcher, DesktopViewHwMismatch); + view_dispatcher_switch_to_view(desktop->view_dispatcher, DesktopViewIdHwMismatch); } bool desktop_scene_hw_mismatch_on_event(void* context, SceneManagerEvent event) { diff --git a/applications/desktop/scenes/desktop_scene_i.h b/applications/desktop/scenes/desktop_scene_i.h index f459eb4a..953f8c83 100644 --- a/applications/desktop/scenes/desktop_scene_i.h +++ b/applications/desktop/scenes/desktop_scene_i.h @@ -1,7 +1,4 @@ #pragma once -typedef enum { - DesktopMainSceneStateUnlocked, - DesktopMainSceneStateLockedWithPin, - DesktopMainSceneStateLockedNoPin, -} DesktopMainSceneState; +#define SCENE_LOCKED_FIRST_ENTER 0 +#define SCENE_LOCKED_REPEAT_ENTER 1 diff --git a/applications/desktop/scenes/desktop_scene_lock_menu.c b/applications/desktop/scenes/desktop_scene_lock_menu.c index 7a3d51bb..c329af59 100644 --- a/applications/desktop/scenes/desktop_scene_lock_menu.c +++ b/applications/desktop/scenes/desktop_scene_lock_menu.c @@ -1,8 +1,13 @@ +#include +#include +#include #include #include +#include #include "../desktop_i.h" -#include "../views/desktop_lock_menu.h" +#include "../desktop_settings/desktop_settings.h" +#include "../views/desktop_view_lock_menu.h" #include "desktop_scene_i.h" #include "desktop_scene.h" @@ -15,36 +20,50 @@ void desktop_scene_lock_menu_on_enter(void* context) { Desktop* desktop = (Desktop*)context; LOAD_DESKTOP_SETTINGS(&desktop->settings); - + scene_manager_set_scene_state(desktop->scene_manager, DesktopSceneLockMenu, 0); 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); + desktop_lock_menu_pin_set(desktop->lock_menu, desktop->settings.pin_code.length > 0); + desktop_lock_menu_set_idx(desktop->lock_menu, 0); - uint8_t idx = scene_manager_get_scene_state(desktop->scene_manager, DesktopSceneLockMenu); - desktop_lock_menu_set_idx(desktop->lock_menu, idx); - view_dispatcher_switch_to_view(desktop->view_dispatcher, DesktopViewLockMenu); + view_dispatcher_switch_to_view(desktop->view_dispatcher, DesktopViewIdLockMenu); } bool desktop_scene_lock_menu_on_event(void* context, SceneManagerEvent event) { Desktop* desktop = (Desktop*)context; bool consumed = false; - if(event.type == SceneManagerEventTypeCustom) { + if(event.type == SceneManagerEventTypeTick) { + bool check_pin_changed = + scene_manager_get_scene_state(desktop->scene_manager, DesktopSceneLockMenu); + if(check_pin_changed) { + LOAD_DESKTOP_SETTINGS(&desktop->settings); + if(desktop->settings.pin_code.length > 0) { + desktop_lock_menu_pin_set(desktop->lock_menu, 1); + scene_manager_set_scene_state(desktop->scene_manager, DesktopSceneLockMenu, 0); + } + } + } else if(event.type == SceneManagerEventTypeCustom) { switch(event.event) { case DesktopLockMenuEventLock: - scene_manager_set_scene_state( - desktop->scene_manager, DesktopSceneMain, DesktopMainSceneStateLockedNoPin); scene_manager_set_scene_state(desktop->scene_manager, DesktopSceneLockMenu, 0); - scene_manager_next_scene(desktop->scene_manager, DesktopSceneMain); + scene_manager_set_scene_state( + desktop->scene_manager, DesktopSceneLocked, SCENE_LOCKED_FIRST_ENTER); + scene_manager_next_scene(desktop->scene_manager, DesktopSceneLocked); consumed = true; break; case DesktopLockMenuEventPinLock: - if(desktop->settings.pincode.length > 0) { + if(desktop->settings.pin_code.length > 0) { + furi_hal_rtc_set_flag(FuriHalRtcFlagLock); scene_manager_set_scene_state( - desktop->scene_manager, DesktopSceneMain, DesktopMainSceneStateLockedWithPin); - scene_manager_next_scene(desktop->scene_manager, DesktopSceneMain); + desktop->scene_manager, DesktopSceneLocked, SCENE_LOCKED_FIRST_ENTER); + scene_manager_next_scene(desktop->scene_manager, DesktopSceneLocked); } else { scene_manager_set_scene_state(desktop->scene_manager, DesktopSceneLockMenu, 1); - scene_manager_next_scene(desktop->scene_manager, DesktopScenePinSetup); + Loader* loader = furi_record_open("loader"); + LoaderStatus status = + loader_start(loader, "Desktop", DESKTOP_SETTINGS_RUN_PIN_SETUP_ARG); + furi_check(status == LoaderStatusOk); + furi_record_close("loader"); } consumed = true; diff --git a/applications/desktop/scenes/desktop_scene_locked.c b/applications/desktop/scenes/desktop_scene_locked.c new file mode 100644 index 00000000..98d5fa0a --- /dev/null +++ b/applications/desktop/scenes/desktop_scene_locked.c @@ -0,0 +1,109 @@ +#include +#include +#include +#include +#include +#include + +#include "../desktop.h" +#include "../desktop_i.h" +#include "../desktop_helpers.h" +#include "../animations/animation_manager.h" +#include "../views/desktop_events.h" +#include "../views/desktop_view_pin_input.h" +#include "../views/desktop_view_locked.h" +#include "desktop_scene.h" +#include "desktop_scene_i.h" + +#define WRONG_PIN_HEADER_TIMEOUT 3000 +#define INPUT_PIN_VIEW_TIMEOUT 15000 + +static void desktop_scene_locked_callback(DesktopEvent event, void* context) { + Desktop* desktop = (Desktop*)context; + view_dispatcher_send_custom_event(desktop->view_dispatcher, event); +} + +static void desktop_scene_locked_new_idle_animation_callback(void* context) { + furi_assert(context); + Desktop* desktop = context; + view_dispatcher_send_custom_event( + desktop->view_dispatcher, DesktopAnimationEventNewIdleAnimation); +} + +void desktop_scene_locked_on_enter(void* context) { + Desktop* desktop = (Desktop*)context; + + // callbacks for 1-st layer + animation_manager_set_new_idle_callback( + desktop->animation_manager, desktop_scene_locked_new_idle_animation_callback); + animation_manager_set_check_callback(desktop->animation_manager, NULL); + animation_manager_set_interact_callback(desktop->animation_manager, NULL); + + // callbacks for 2-nd layer + desktop_view_locked_set_callback(desktop->locked_view, desktop_scene_locked_callback, desktop); + + bool switch_to_timeout_scene = false; + uint32_t state = scene_manager_get_scene_state(desktop->scene_manager, DesktopSceneLocked); + if(state == SCENE_LOCKED_FIRST_ENTER) { + bool pin_locked = furi_hal_rtc_is_flag_set(FuriHalRtcFlagLock); + desktop_helpers_lock_system(desktop, pin_locked); + if(pin_locked) { + LOAD_DESKTOP_SETTINGS(&desktop->settings); + desktop_view_locked_lock(desktop->locked_view, true); + uint32_t pin_fails = furi_hal_rtc_get_pin_fails(); + uint32_t pin_timeout = desktop_helpers_get_pin_fail_timeout(pin_fails); + if(pin_timeout) { + scene_manager_set_scene_state( + desktop->scene_manager, DesktopScenePinTimeout, pin_timeout); + switch_to_timeout_scene = true; + } else { + desktop_view_locked_close_doors(desktop->locked_view); + } + } else { + desktop_view_locked_lock(desktop->locked_view, false); + desktop_view_locked_close_doors(desktop->locked_view); + } + scene_manager_set_scene_state( + desktop->scene_manager, DesktopSceneLocked, SCENE_LOCKED_REPEAT_ENTER); + } + + if(switch_to_timeout_scene) { + scene_manager_next_scene(desktop->scene_manager, DesktopScenePinTimeout); + } else { + view_dispatcher_switch_to_view(desktop->view_dispatcher, DesktopViewIdLocked); + } +} + +bool desktop_scene_locked_on_event(void* context, SceneManagerEvent event) { + Desktop* desktop = (Desktop*)context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + switch(event.event) { + case DesktopLockedEventUnlocked: + furi_hal_rtc_set_pin_fails(0); + desktop_helpers_unlock_system(desktop); + scene_manager_search_and_switch_to_previous_scene( + desktop->scene_manager, DesktopSceneMain); + consumed = true; + break; + case DesktopLockedEventUpdate: + desktop_view_locked_update(desktop->locked_view); + consumed = true; + break; + case DesktopLockedEventShowPinInput: + scene_manager_next_scene(desktop->scene_manager, DesktopScenePinInput); + consumed = true; + break; + case DesktopAnimationEventNewIdleAnimation: + animation_manager_new_idle_process(desktop->animation_manager); + consumed = true; + break; + } + } + + return consumed; +} + +void desktop_scene_locked_on_exit(void* context) { +} diff --git a/applications/desktop/scenes/desktop_scene_main.c b/applications/desktop/scenes/desktop_scene_main.c index 0db7508d..8131d0c3 100644 --- a/applications/desktop/scenes/desktop_scene_main.c +++ b/applications/desktop/scenes/desktop_scene_main.c @@ -4,15 +4,14 @@ #include #include -#include "desktop/desktop_i.h" -#include "desktop/views/desktop_main.h" +#include "../desktop_i.h" +#include "../views/desktop_events.h" +#include "../views/desktop_view_main.h" #include "desktop_scene.h" #include "desktop_scene_i.h" #define TAG "DesktopSrv" -#define MAIN_VIEW_DEFAULT (0UL) - static void desktop_scene_main_app_started_callback(const void* message, void* context) { furi_assert(context); Desktop* desktop = context; @@ -31,19 +30,22 @@ static void desktop_scene_main_app_started_callback(const void* message, void* c static void desktop_scene_main_new_idle_animation_callback(void* context) { furi_assert(context); Desktop* desktop = context; - view_dispatcher_send_custom_event(desktop->view_dispatcher, DesktopMainEventNewIdleAnimation); + view_dispatcher_send_custom_event( + desktop->view_dispatcher, DesktopAnimationEventNewIdleAnimation); } static void desktop_scene_main_check_animation_callback(void* context) { furi_assert(context); Desktop* desktop = context; - view_dispatcher_send_custom_event(desktop->view_dispatcher, DesktopMainEventCheckAnimation); + view_dispatcher_send_custom_event( + desktop->view_dispatcher, DesktopAnimationEventCheckAnimation); } static void desktop_scene_main_interact_animation_callback(void* context) { furi_assert(context); Desktop* desktop = context; - view_dispatcher_send_custom_event(desktop->view_dispatcher, DesktopMainEventInteractAnimation); + view_dispatcher_send_custom_event( + desktop->view_dispatcher, DesktopAnimationEventInteractAnimation); } static void desktop_switch_to_app(Desktop* desktop, const FlipperApplication* flipper_app) { @@ -80,7 +82,6 @@ void desktop_scene_main_on_enter(void* context) { desktop->animation_manager, desktop_scene_main_check_animation_callback); animation_manager_set_interact_callback( desktop->animation_manager, desktop_scene_main_interact_animation_callback); - desktop_locked_set_callback(desktop->locked_view, desktop_scene_main_callback, desktop); furi_assert(osSemaphoreGetCount(desktop->unload_animation_semaphore) == 0); Loader* loader = furi_record_open("loader"); @@ -90,24 +91,7 @@ void desktop_scene_main_on_enter(void* context) { desktop_main_set_callback(main_view, desktop_scene_main_callback, desktop); - DesktopMainSceneState state = - scene_manager_get_scene_state(desktop->scene_manager, DesktopSceneMain); - if(state == DesktopMainSceneStateLockedNoPin) { - desktop_locked_lock(desktop->locked_view); - view_port_enabled_set(desktop->lock_viewport, true); - } else if(state == DesktopMainSceneStateLockedWithPin) { - LOAD_DESKTOP_SETTINGS(&desktop->settings); - furi_assert(desktop->settings.pincode.length > 0); - desktop_locked_lock_pincode(desktop->locked_view, desktop->settings.pincode); - view_port_enabled_set(desktop->lock_viewport, true); - furi_hal_rtc_set_flag(FuriHalRtcFlagLock); - furi_hal_usb_disable(); - } else { - furi_assert(state == DesktopMainSceneStateUnlocked); - view_port_enabled_set(desktop->lock_viewport, false); - } - - view_dispatcher_switch_to_view(desktop->view_dispatcher, DesktopViewMain); + view_dispatcher_switch_to_view(desktop->view_dispatcher, DesktopViewIdMain); } bool desktop_scene_main_on_event(void* context, SceneManagerEvent event) { @@ -154,15 +138,15 @@ bool desktop_scene_main_on_event(void* context, SceneManagerEvent event) { consumed = true; break; - case DesktopMainEventCheckAnimation: + case DesktopAnimationEventCheckAnimation: animation_manager_check_blocking_process(desktop->animation_manager); consumed = true; break; - case DesktopMainEventNewIdleAnimation: + case DesktopAnimationEventNewIdleAnimation: animation_manager_new_idle_process(desktop->animation_manager); consumed = true; break; - case DesktopMainEventInteractAnimation: + case DesktopAnimationEventInteractAnimation: animation_manager_interact_process(desktop->animation_manager); consumed = true; break; @@ -175,16 +159,8 @@ bool desktop_scene_main_on_event(void* context, SceneManagerEvent event) { animation_manager_load_and_continue_animation(desktop->animation_manager); consumed = true; break; - case DesktopMainEventUnlocked: - consumed = true; - furi_hal_rtc_reset_flag(FuriHalRtcFlagLock); - furi_hal_usb_enable(); - view_port_enabled_set(desktop->lock_viewport, false); - scene_manager_set_scene_state( - desktop->scene_manager, DesktopSceneMain, DesktopMainSceneStateUnlocked); - break; - case DesktopMainEventUpdate: - desktop_locked_update(desktop->locked_view); + case DesktopLockedEventUpdate: + desktop_view_locked_update(desktop->locked_view); consumed = true; break; @@ -213,5 +189,4 @@ void desktop_scene_main_on_exit(void* context) { animation_manager_set_check_callback(desktop->animation_manager, NULL); animation_manager_set_interact_callback(desktop->animation_manager, NULL); animation_manager_set_context(desktop->animation_manager, desktop); - scene_manager_set_scene_state(desktop->scene_manager, DesktopSceneMain, MAIN_VIEW_DEFAULT); } diff --git a/applications/desktop/scenes/desktop_scene_pin_input.c b/applications/desktop/scenes/desktop_scene_pin_input.c new file mode 100644 index 00000000..8d714ff9 --- /dev/null +++ b/applications/desktop/scenes/desktop_scene_pin_input.c @@ -0,0 +1,162 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../desktop.h" +#include "../desktop_i.h" +#include "../animations/animation_manager.h" +#include "../views/desktop_events.h" +#include "../views/desktop_view_pin_input.h" +#include "../desktop_helpers.h" +#include "desktop_scene.h" +#include "desktop_scene_i.h" + +#define WRONG_PIN_HEADER_TIMEOUT 3000 +#define INPUT_PIN_VIEW_TIMEOUT 15000 + +typedef struct { + TimerHandle_t timer; +} DesktopScenePinInputState; + +static void desktop_scene_locked_light_red(bool value) { + NotificationApp* app = furi_record_open("notification"); + if(value) { + notification_message(app, &sequence_set_only_red_255); + } else { + notification_message(app, &sequence_reset_red); + } + furi_record_close("notification"); +} + +static void + desktop_scene_pin_input_set_timer(Desktop* desktop, bool enable, TickType_t new_period) { + furi_assert(desktop); + + DesktopScenePinInputState* state = (DesktopScenePinInputState*)scene_manager_get_scene_state( + desktop->scene_manager, DesktopScenePinInput); + furi_assert(state); + if(enable) { + xTimerChangePeriod(state->timer, new_period, portMAX_DELAY); + } else { + xTimerStop(state->timer, portMAX_DELAY); + } +} + +static void desktop_scene_pin_input_back_callback(void* context) { + Desktop* desktop = (Desktop*)context; + view_dispatcher_send_custom_event(desktop->view_dispatcher, DesktopPinInputEventBack); +} + +static void desktop_scene_pin_input_done_callback(const PinCode* pin_code, void* context) { + Desktop* desktop = (Desktop*)context; + if(pins_are_equal(&desktop->settings.pin_code, pin_code)) { + view_dispatcher_send_custom_event(desktop->view_dispatcher, DesktopPinInputEventUnlocked); + } else { + view_dispatcher_send_custom_event( + desktop->view_dispatcher, DesktopPinInputEventUnlockFailed); + } +} + +static void desktop_scene_pin_input_timer_callback(TimerHandle_t timer) { + Desktop* desktop = pvTimerGetTimerID(timer); + + view_dispatcher_send_custom_event( + desktop->view_dispatcher, DesktopPinInputEventResetWrongPinLabel); +} + +void desktop_scene_pin_input_on_enter(void* context) { + Desktop* desktop = (Desktop*)context; + + desktop_view_pin_input_set_context(desktop->pin_input_view, desktop); + desktop_view_pin_input_set_back_callback( + desktop->pin_input_view, desktop_scene_pin_input_back_callback); + desktop_view_pin_input_set_timeout_callback( + desktop->pin_input_view, desktop_scene_pin_input_back_callback); + desktop_view_pin_input_set_done_callback( + desktop->pin_input_view, desktop_scene_pin_input_done_callback); + + DesktopScenePinInputState* state = furi_alloc(sizeof(DesktopScenePinInputState)); + state->timer = + xTimerCreate(NULL, 10000, pdFALSE, desktop, desktop_scene_pin_input_timer_callback); + scene_manager_set_scene_state(desktop->scene_manager, DesktopScenePinInput, (uint32_t)state); + + desktop_view_pin_input_hide_pin(desktop->pin_input_view, true); + desktop_view_pin_input_set_label_button(desktop->pin_input_view, "OK"); + desktop_view_pin_input_set_label_secondary(desktop->pin_input_view, 44, 25, "Enter PIN:"); + desktop_view_pin_input_set_pin_position(desktop->pin_input_view, 64, 37); + desktop_view_pin_input_reset_pin(desktop->pin_input_view); + + view_dispatcher_switch_to_view(desktop->view_dispatcher, DesktopViewIdPinInput); +} + +bool desktop_scene_pin_input_on_event(void* context, SceneManagerEvent event) { + Desktop* desktop = (Desktop*)context; + bool consumed = false; + uint32_t pin_fails = 0; + + if(event.type == SceneManagerEventTypeCustom) { + switch(event.event) { + case DesktopPinInputEventUnlockFailed: + pin_fails = furi_hal_rtc_get_pin_fails(); + pin_fails++; + furi_hal_rtc_set_pin_fails(pin_fails); + uint32_t pin_timeout = desktop_helpers_get_pin_fail_timeout(pin_fails); + if(pin_timeout > 0) { + desktop_helpers_emit_error_notification(); + scene_manager_set_scene_state( + desktop->scene_manager, DesktopScenePinTimeout, pin_timeout); + scene_manager_next_scene(desktop->scene_manager, DesktopScenePinTimeout); + } else { + desktop_scene_locked_light_red(true); + desktop_view_pin_input_set_label_primary(desktop->pin_input_view, 0, 0, NULL); + desktop_view_pin_input_set_label_secondary( + desktop->pin_input_view, 25, 25, "Wrong PIN try again:"); + desktop_scene_pin_input_set_timer(desktop, true, WRONG_PIN_HEADER_TIMEOUT); + desktop_view_pin_input_reset_pin(desktop->pin_input_view); + } + consumed = true; + break; + case DesktopPinInputEventResetWrongPinLabel: + desktop_scene_locked_light_red(false); + desktop_view_pin_input_set_label_primary(desktop->pin_input_view, 0, 0, NULL); + desktop_view_pin_input_set_label_secondary( + desktop->pin_input_view, 44, 25, "Enter PIN:"); + consumed = true; + break; + case DesktopPinInputEventUnlocked: + desktop_view_locked_unlock(desktop->locked_view); + furi_hal_rtc_set_pin_fails(0); + desktop_helpers_unlock_system(desktop); + scene_manager_search_and_switch_to_previous_scene( + desktop->scene_manager, DesktopSceneMain); + consumed = true; + break; + case DesktopPinInputEventBack: + scene_manager_search_and_switch_to_previous_scene( + desktop->scene_manager, DesktopSceneLocked); + consumed = true; + break; + } + } + + return consumed; +} + +void desktop_scene_pin_input_on_exit(void* context) { + Desktop* desktop = (Desktop*)context; + desktop_scene_locked_light_red(false); + + DesktopScenePinInputState* state = (DesktopScenePinInputState*)scene_manager_get_scene_state( + desktop->scene_manager, DesktopScenePinInput); + xTimerStop(state->timer, portMAX_DELAY); + while(xTimerIsTimerActive(state->timer)) { + delay(1); + } + xTimerDelete(state->timer, portMAX_DELAY); + free(state); +} diff --git a/applications/desktop/scenes/desktop_scene_pin_timeout.c b/applications/desktop/scenes/desktop_scene_pin_timeout.c new file mode 100644 index 00000000..3c267a0d --- /dev/null +++ b/applications/desktop/scenes/desktop_scene_pin_timeout.c @@ -0,0 +1,46 @@ +#include +#include +#include +#include +#include + +#include "../desktop_i.h" +#include "../views/desktop_view_pin_timeout.h" +#include "desktop_scene.h" +#include "desktop_scene_i.h" + +static void desktop_scene_pin_timeout_callback(void* context) { + Desktop* desktop = (Desktop*)context; + view_dispatcher_send_custom_event(desktop->view_dispatcher, DesktopPinTimeoutExit); +} + +void desktop_scene_pin_timeout_on_enter(void* context) { + Desktop* desktop = (Desktop*)context; + + uint32_t timeout = + scene_manager_get_scene_state(desktop->scene_manager, DesktopScenePinTimeout); + desktop_view_pin_timeout_start(desktop->pin_timeout_view, timeout); + desktop_view_pin_timeout_set_callback( + desktop->pin_timeout_view, desktop_scene_pin_timeout_callback, desktop); + + view_dispatcher_switch_to_view(desktop->view_dispatcher, DesktopViewIdPinTimeout); +} + +bool desktop_scene_pin_timeout_on_event(void* context, SceneManagerEvent event) { + Desktop* desktop = (Desktop*)context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + switch(event.event) { + case DesktopPinTimeoutExit: + scene_manager_previous_scene(desktop->scene_manager); + consumed = true; + break; + } + } + + return consumed; +} + +void desktop_scene_pin_timeout_on_exit(void* context) { +} diff --git a/applications/desktop/scenes/desktop_scene_pinsetup.c b/applications/desktop/scenes/desktop_scene_pinsetup.c deleted file mode 100644 index 6b1c9686..00000000 --- a/applications/desktop/scenes/desktop_scene_pinsetup.c +++ /dev/null @@ -1,50 +0,0 @@ -#include "../desktop_i.h" - -#define SCENE_EXIT_EVENT (0U) - -void desktop_scene_ok_callback(void* context) { - Desktop* app = context; - 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; - SAVE_DESKTOP_SETTINGS(&app->settings); - 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_events.h b/applications/desktop/views/desktop_events.h index 56d9729f..49d6f81e 100644 --- a/applications/desktop/views/desktop_events.h +++ b/applications/desktop/views/desktop_events.h @@ -6,24 +6,35 @@ typedef enum { DesktopMainEventOpenFavorite, DesktopMainEventOpenMenu, DesktopMainEventOpenDebug, - DesktopMainEventUpdate, - DesktopMainEventUnlocked, DesktopMainEventRightShort, - DesktopMainEventCheckAnimation, - DesktopMainEventNewIdleAnimation, - DesktopMainEventInteractAnimation, DesktopMainEventBeforeAppStarted, DesktopMainEventAfterAppFinished, - DesktopLockedEventUnlock, - DesktopLockedEventCheckAnimation, - DesktopLockedEventMax, + + DesktopLockedEventUnlocked, + DesktopLockedEventUpdate, + DesktopLockedEventShowPinInput, + + DesktopPinInputEventResetWrongPinLabel, + DesktopPinInputEventUnlocked, + DesktopPinInputEventUnlockFailed, + DesktopPinInputEventBack, + + DesktopPinTimeoutExit, + DesktopDebugEventDeed, DesktopDebugEventWrongDeed, DesktopDebugEventSaveState, DesktopDebugEventExit, + DesktopFirstStartCompleted, DesktopFirstStartPoweroff, + DesktopLockMenuEventLock, DesktopLockMenuEventPinLock, DesktopLockMenuEventExit, + + DesktopAnimationEventCheckAnimation, + DesktopAnimationEventNewIdleAnimation, + DesktopAnimationEventInteractAnimation, + } DesktopEvent; diff --git a/applications/desktop/views/desktop_locked.c b/applications/desktop/views/desktop_locked.c deleted file mode 100644 index d8102f57..00000000 --- a/applications/desktop/views/desktop_locked.c +++ /dev/null @@ -1,247 +0,0 @@ -#include "desktop/desktop_settings/desktop_settings.h" -#include "furi/check.h" -#include "gui/view.h" -#include "portmacro.h" -#include -#include -#include -#include "../desktop_i.h" -#include "desktop_locked.h" -#include - -#define DOOR_MOVING_INTERVAL_MS (1000 / 16) -#define UNLOCKED_HINT_TIMEOUT_MS (2000) - -struct DesktopLockedView { - View* view; - DesktopLockedViewCallback callback; - void* context; - - TimerHandle_t timer; - uint8_t lock_count; - uint32_t lock_lastpress; - - PinCode pincode; - PinCode pincode_input; -}; - -typedef struct { - uint32_t hint_icon_expire_at; - bool unlocked_hint; - bool locked; - bool pin_locked; - - int8_t door_left_x; - int8_t door_right_x; - bool animation_seq_end; -} DesktopLockedViewModel; - -static void desktop_locked_unlock(DesktopLockedView* locked_view); - -void desktop_locked_set_callback( - DesktopLockedView* locked_view, - DesktopLockedViewCallback callback, - void* context) { - furi_assert(locked_view); - furi_assert(callback); - locked_view->callback = callback; - locked_view->context = context; -} - -void locked_view_timer_callback(TimerHandle_t timer) { - DesktopLockedView* locked_view = pvTimerGetTimerID(timer); - locked_view->callback(DesktopMainEventUpdate, locked_view->context); -} - -static void desktop_locked_update_hint_icon_timeout(DesktopLockedView* locked_view) { - DesktopLockedViewModel* model = view_get_model(locked_view->view); - model->hint_icon_expire_at = osKernelGetTickCount() + osKernelGetTickFreq(); - view_commit_model(locked_view->view, true); -} - -static void desktop_locked_reset_door_pos(DesktopLockedView* locked_view) { - DesktopLockedViewModel* model = view_get_model(locked_view->view); - model->animation_seq_end = false; - model->door_left_x = DOOR_L_POS; - model->door_right_x = DOOR_R_POS; - view_commit_model(locked_view->view, true); -} - -void desktop_locked_update(DesktopLockedView* locked_view) { - bool stop_timer = false; - - DesktopLockedViewModel* model = view_get_model(locked_view->view); - if(model->locked) { - if(model->door_left_x != DOOR_L_POS_MAX) { - model->door_left_x = CLAMP(model->door_left_x + 5, DOOR_L_POS_MAX, DOOR_L_POS); - model->door_right_x = CLAMP(model->door_right_x - 5, DOOR_R_POS, DOOR_R_POS_MIN); - } else { - model->animation_seq_end = true; - } - stop_timer = model->animation_seq_end; - } else { - model->unlocked_hint = false; - stop_timer = true; - } - view_commit_model(locked_view->view, true); - - if(stop_timer) { - xTimerStop(locked_view->timer, portMAX_DELAY); - } -} - -void desktop_locked_draw(Canvas* canvas, void* model) { - DesktopLockedViewModel* m = model; - uint32_t now = osKernelGetTickCount(); - canvas_set_color(canvas, ColorBlack); - - if(m->locked) { - if(!m->animation_seq_end) { - canvas_draw_icon(canvas, m->door_left_x, 0 + STATUS_BAR_Y_SHIFT, &I_DoorLeft_70x55); - canvas_draw_icon(canvas, m->door_right_x, 0 + STATUS_BAR_Y_SHIFT, &I_DoorRight_70x55); - canvas_set_font(canvas, FontPrimary); - elements_multiline_text_framed(canvas, 42, 30 + STATUS_BAR_Y_SHIFT, "Locked"); - } else if((now < m->hint_icon_expire_at) && !m->pin_locked) { - canvas_set_font(canvas, FontSecondary); - canvas_draw_icon(canvas, 13, 2 + STATUS_BAR_Y_SHIFT, &I_LockPopup_100x49); - elements_multiline_text(canvas, 65, 20 + STATUS_BAR_Y_SHIFT, "To unlock\npress:"); - } - } else { - if(m->unlocked_hint) { - canvas_set_font(canvas, FontPrimary); - elements_multiline_text_framed(canvas, 42, 30 + STATUS_BAR_Y_SHIFT, "Unlocked"); - } - } -} - -View* desktop_locked_get_view(DesktopLockedView* locked_view) { - furi_assert(locked_view); - return locked_view->view; -} - -bool desktop_locked_input(InputEvent* event, void* context) { - furi_assert(event); - furi_assert(context); - DesktopLockedView* locked_view = context; - bool locked = false; - bool locked_with_pin = false; - uint32_t press_time = xTaskGetTickCount(); - - { - DesktopLockedViewModel* model = view_get_model(locked_view->view); - bool changed = false; - locked = model->locked; - locked_with_pin = model->pin_locked; - if(!locked && model->unlocked_hint && event->type == InputTypePress) { - model->unlocked_hint = false; - changed = true; - } - view_commit_model(locked_view->view, changed); - } - - if(!locked || (event->type != InputTypeShort)) { - return locked; - } - - if(press_time - locked_view->lock_lastpress > UNLOCK_RST_TIMEOUT) { - locked_view->lock_lastpress = press_time; - locked_view->lock_count = 0; - locked_view->pincode_input.length = 0; - } - - if(locked_with_pin) { - locked_view->pincode_input.length = code_input_push( - locked_view->pincode_input.data, locked_view->pincode_input.length, event->key); - bool match = code_input_compare( - locked_view->pincode_input.data, - locked_view->pincode_input.length, - locked_view->pincode.data, - locked_view->pincode.length); - - if(match) { - desktop_locked_unlock(locked_view); - } - } else { - if(event->key == InputKeyBack) { - locked_view->lock_lastpress = press_time; - locked_view->lock_count++; - if(locked_view->lock_count == UNLOCK_CNT) { - desktop_locked_unlock(locked_view); - } - } else { - desktop_locked_update_hint_icon_timeout(locked_view); - locked_view->lock_count = 0; - } - } - - locked_view->lock_lastpress = press_time; - - return locked; -} - -DesktopLockedView* desktop_locked_alloc() { - DesktopLockedView* locked_view = furi_alloc(sizeof(DesktopLockedView)); - locked_view->view = view_alloc(); - locked_view->timer = - xTimerCreate("Locked view", 1000 / 16, pdTRUE, locked_view, locked_view_timer_callback); - - view_allocate_model(locked_view->view, ViewModelTypeLocking, sizeof(DesktopLockedViewModel)); - view_set_context(locked_view->view, locked_view); - view_set_draw_callback(locked_view->view, (ViewDrawCallback)desktop_locked_draw); - view_set_input_callback(locked_view->view, desktop_locked_input); - - return locked_view; -} - -void desktop_locked_free(DesktopLockedView* locked_view) { - furi_assert(locked_view); - osTimerDelete(locked_view->timer); - view_free(locked_view->view); - free(locked_view); -} - -void desktop_locked_lock(DesktopLockedView* locked_view) { - locked_view->pincode.length = 0; - DesktopLockedViewModel* model = view_get_model(locked_view->view); - model->locked = true; - model->pin_locked = false; - view_commit_model(locked_view->view, true); - desktop_locked_reset_door_pos(locked_view); - xTimerChangePeriod(locked_view->timer, DOOR_MOVING_INTERVAL_MS, portMAX_DELAY); - - Gui* gui = furi_record_open("gui"); - gui_set_lockdown(gui, true); - furi_record_close("gui"); -} - -void desktop_locked_lock_pincode(DesktopLockedView* locked_view, PinCode pincode) { - locked_view->pincode = pincode; - locked_view->pincode_input.length = 0; - DesktopLockedViewModel* model = view_get_model(locked_view->view); - model->locked = true; - model->pin_locked = true; - view_commit_model(locked_view->view, true); - desktop_locked_reset_door_pos(locked_view); - xTimerChangePeriod(locked_view->timer, DOOR_MOVING_INTERVAL_MS, portMAX_DELAY); - - Gui* gui = furi_record_open("gui"); - gui_set_lockdown(gui, true); - furi_record_close("gui"); -} - -static void desktop_locked_unlock(DesktopLockedView* locked_view) { - furi_assert(locked_view); - - locked_view->lock_count = 0; - DesktopLockedViewModel* model = view_get_model(locked_view->view); - model->locked = false; - model->pin_locked = false; - model->unlocked_hint = true; - view_commit_model(locked_view->view, true); - locked_view->callback(DesktopMainEventUnlocked, locked_view->context); - xTimerChangePeriod(locked_view->timer, UNLOCKED_HINT_TIMEOUT_MS, portMAX_DELAY); - - Gui* gui = furi_record_open("gui"); - gui_set_lockdown(gui, false); - furi_record_close("gui"); -} diff --git a/applications/desktop/views/desktop_locked.h b/applications/desktop/views/desktop_locked.h deleted file mode 100644 index 61e91e4e..00000000 --- a/applications/desktop/views/desktop_locked.h +++ /dev/null @@ -1,36 +0,0 @@ -#pragma once - -#include -#include -#include "desktop_events.h" - -#define UNLOCK_RST_TIMEOUT 300 -#define UNLOCK_CNT 3 - -#define DOOR_L_POS -57 -#define DOOR_L_POS_MAX 0 -#define DOOR_R_POS 115 -#define DOOR_R_POS_MIN 60 - -typedef enum { - DesktopLockedWithPin, - DesktopLockedNoPin, -} DesktopLockedSceneState; - -typedef struct DesktopLockedView DesktopLockedView; - -typedef void (*DesktopLockedViewCallback)(DesktopEvent event, void* context); - -void desktop_locked_set_callback( - DesktopLockedView* locked_view, - DesktopLockedViewCallback callback, - void* context); - -void desktop_locked_update(DesktopLockedView* locked_view); - -View* desktop_locked_get_view(DesktopLockedView* locked_view); -DesktopLockedView* desktop_locked_alloc(); -void desktop_locked_free(DesktopLockedView* locked_view); - -void desktop_locked_lock_pincode(DesktopLockedView* locked_view, PinCode pincode); -void desktop_locked_lock(DesktopLockedView* locked_view); diff --git a/applications/desktop/views/desktop_debug.c b/applications/desktop/views/desktop_view_debug.c similarity index 98% rename from applications/desktop/views/desktop_debug.c rename to applications/desktop/views/desktop_view_debug.c index 0d793dca..0ffd8d17 100644 --- a/applications/desktop/views/desktop_debug.c +++ b/applications/desktop/views/desktop_view_debug.c @@ -1,11 +1,11 @@ #include #include #include +#include +#include #include "../desktop_i.h" -#include "desktop_debug.h" -#include "dolphin/helpers/dolphin_state.h" -#include "dolphin/dolphin.h" +#include "desktop_view_debug.h" void desktop_debug_set_callback( DesktopDebugView* debug_view, diff --git a/applications/desktop/views/desktop_debug.h b/applications/desktop/views/desktop_view_debug.h similarity index 100% rename from applications/desktop/views/desktop_debug.h rename to applications/desktop/views/desktop_view_debug.h diff --git a/applications/desktop/views/desktop_first_start.c b/applications/desktop/views/desktop_view_first_start.c similarity index 99% rename from applications/desktop/views/desktop_first_start.c rename to applications/desktop/views/desktop_view_first_start.c index c0ef3a5f..5408dac0 100644 --- a/applications/desktop/views/desktop_first_start.c +++ b/applications/desktop/views/desktop_view_first_start.c @@ -1,8 +1,9 @@ #include #include #include + #include "../desktop_i.h" -#include "desktop_first_start.h" +#include "desktop_view_first_start.h" #define DESKTOP_FIRST_START_POWEROFF_SHORT 5000 #define DESKTOP_FIRST_START_POWEROFF_LONG (60 * 60 * 1000) diff --git a/applications/desktop/views/desktop_first_start.h b/applications/desktop/views/desktop_view_first_start.h similarity index 100% rename from applications/desktop/views/desktop_first_start.h rename to applications/desktop/views/desktop_view_first_start.h diff --git a/applications/desktop/views/desktop_lock_menu.c b/applications/desktop/views/desktop_view_lock_menu.c similarity index 99% rename from applications/desktop/views/desktop_lock_menu.c rename to applications/desktop/views/desktop_view_lock_menu.c index d65aa30d..005512cf 100644 --- a/applications/desktop/views/desktop_lock_menu.c +++ b/applications/desktop/views/desktop_view_lock_menu.c @@ -2,7 +2,7 @@ #include #include "../desktop_i.h" -#include "desktop_lock_menu.h" +#include "desktop_view_lock_menu.h" #define LOCK_MENU_ITEMS_NB 3 diff --git a/applications/desktop/views/desktop_lock_menu.h b/applications/desktop/views/desktop_view_lock_menu.h similarity index 100% rename from applications/desktop/views/desktop_lock_menu.h rename to applications/desktop/views/desktop_view_lock_menu.h diff --git a/applications/desktop/views/desktop_view_locked.c b/applications/desktop/views/desktop_view_locked.c new file mode 100644 index 00000000..8e3f22ee --- /dev/null +++ b/applications/desktop/views/desktop_view_locked.c @@ -0,0 +1,233 @@ +#include +#include +#include +#include +#include +#include +#include + +#include "../desktop_settings/desktop_settings.h" +#include "../desktop_i.h" +#include "desktop_view_locked.h" + +#define DOOR_MOVING_INTERVAL_MS (1000 / 16) +#define UNLOCKED_HINT_TIMEOUT_MS (2000) + +#define DOOR_OFFSET_START -55 +#define DOOR_OFFSET_END 0 + +#define DOOR_L_FINAL_POS 0 +#define DOOR_R_FINAL_POS 60 + +#define UNLOCK_CNT 3 +#define UNLOCK_RST_TIMEOUT 600 + +struct DesktopViewLocked { + View* view; + DesktopViewLockedCallback callback; + void* context; + + TimerHandle_t timer; + uint8_t lock_count; + uint32_t lock_lastpress; +}; + +typedef struct { + uint32_t hint_icon_expire_at; + bool unlocked_hint; + bool locked; + bool pin_locked; + + int8_t door_offset; + bool doors_closing; +} DesktopViewLockedModel; + +void desktop_view_locked_set_callback( + DesktopViewLocked* locked_view, + DesktopViewLockedCallback callback, + void* context) { + furi_assert(locked_view); + furi_assert(callback); + locked_view->callback = callback; + locked_view->context = context; +} + +static void locked_view_timer_callback(TimerHandle_t timer) { + DesktopViewLocked* locked_view = pvTimerGetTimerID(timer); + locked_view->callback(DesktopLockedEventUpdate, locked_view->context); +} + +static void desktop_view_locked_doors_draw(Canvas* canvas, DesktopViewLockedModel* model) { + int8_t offset = model->door_offset; + uint8_t door_left_x = DOOR_L_FINAL_POS + offset; + uint8_t door_right_x = DOOR_R_FINAL_POS - offset; + uint8_t height = icon_get_height(&I_DoorLeft_70x55); + canvas_draw_icon(canvas, door_left_x, canvas_height(canvas) - height, &I_DoorLeft_70x55); + canvas_draw_icon(canvas, door_right_x, canvas_height(canvas) - height, &I_DoorRight_70x55); +} + +static bool desktop_view_locked_doors_move(DesktopViewLockedModel* model) { + bool stop = false; + if(model->door_offset < DOOR_OFFSET_END) { + model->door_offset = CLAMP(model->door_offset + 5, DOOR_OFFSET_END, DOOR_OFFSET_START); + stop = true; + } + + return stop; +} + +static void desktop_view_locked_update_hint_icon_timeout(DesktopViewLocked* locked_view) { + DesktopViewLockedModel* model = view_get_model(locked_view->view); + model->hint_icon_expire_at = osKernelGetTickCount() + osKernelGetTickFreq(); + view_commit_model(locked_view->view, true); +} + +void desktop_view_locked_update(DesktopViewLocked* locked_view) { + bool stop_timer = false; + + DesktopViewLockedModel* model = view_get_model(locked_view->view); + if(model->locked) { + model->doors_closing = desktop_view_locked_doors_move(model); + stop_timer = !model->doors_closing; + } else { + model->unlocked_hint = false; + stop_timer = true; + } + view_commit_model(locked_view->view, true); + + if(stop_timer) { + xTimerStop(locked_view->timer, portMAX_DELAY); + } +} + +static void desktop_view_locked_draw(Canvas* canvas, void* model) { + DesktopViewLockedModel* m = model; + uint32_t now = osKernelGetTickCount(); + canvas_set_color(canvas, ColorBlack); + + if(m->locked) { + if(m->doors_closing) { + desktop_view_locked_doors_draw(canvas, m); + canvas_set_font(canvas, FontPrimary); + elements_multiline_text_framed(canvas, 42, 30 + STATUS_BAR_Y_SHIFT, "Locked"); + } else if((now < m->hint_icon_expire_at) && !m->pin_locked) { + canvas_set_font(canvas, FontSecondary); + elements_bold_rounded_frame(canvas, 14, 2 + STATUS_BAR_Y_SHIFT, 99, 48); + elements_multiline_text(canvas, 65, 20 + STATUS_BAR_Y_SHIFT, "To unlock\npress:"); + canvas_draw_icon(canvas, 65, 36 + STATUS_BAR_Y_SHIFT, &I_Back3_45x8); + canvas_draw_icon(canvas, 16, 7 + STATUS_BAR_Y_SHIFT, &I_WarningDolphin_45x42); + canvas_draw_dot(canvas, 17, 61); + } + } else { + if(m->unlocked_hint) { + canvas_set_font(canvas, FontPrimary); + elements_multiline_text_framed(canvas, 42, 30 + STATUS_BAR_Y_SHIFT, "Unlocked"); + } + } +} + +View* desktop_view_locked_get_view(DesktopViewLocked* locked_view) { + furi_assert(locked_view); + return locked_view->view; +} + +static bool desktop_view_locked_input(InputEvent* event, void* context) { + furi_assert(event); + furi_assert(context); + DesktopViewLocked* locked_view = context; + bool locked = false; + bool locked_with_pin = false; + bool doors_closing = false; + uint32_t press_time = xTaskGetTickCount(); + + { + DesktopViewLockedModel* model = view_get_model(locked_view->view); + bool changed = false; + locked = model->locked; + locked_with_pin = model->pin_locked; + doors_closing = model->doors_closing; + if(!locked && model->unlocked_hint && event->type == InputTypePress) { + model->unlocked_hint = false; + changed = true; + } + view_commit_model(locked_view->view, changed); + } + + if(!locked || doors_closing || (event->type != InputTypeShort)) { + return locked; + } + + if(locked_with_pin) { + locked_view->callback(DesktopLockedEventShowPinInput, locked_view->context); + } else { + if(press_time - locked_view->lock_lastpress > UNLOCK_RST_TIMEOUT) { + locked_view->lock_lastpress = press_time; + locked_view->lock_count = 0; + } + + desktop_view_locked_update_hint_icon_timeout(locked_view); + if(event->key == InputKeyBack) { + locked_view->lock_lastpress = press_time; + locked_view->lock_count++; + if(locked_view->lock_count == UNLOCK_CNT) { + desktop_view_locked_unlock(locked_view); + locked_view->callback(DesktopLockedEventUnlocked, locked_view->context); + } + } else { + locked_view->lock_count = 0; + } + + locked_view->lock_lastpress = press_time; + } + + return locked; +} + +DesktopViewLocked* desktop_view_locked_alloc() { + DesktopViewLocked* locked_view = furi_alloc(sizeof(DesktopViewLocked)); + locked_view->view = view_alloc(); + locked_view->timer = + xTimerCreate(NULL, 1000 / 16, pdTRUE, locked_view, locked_view_timer_callback); + + locked_view->view = view_alloc(); + view_allocate_model(locked_view->view, ViewModelTypeLocking, sizeof(DesktopViewLockedModel)); + view_set_context(locked_view->view, locked_view); + view_set_draw_callback(locked_view->view, desktop_view_locked_draw); + view_set_input_callback(locked_view->view, desktop_view_locked_input); + + return locked_view; +} + +void desktop_view_locked_free(DesktopViewLocked* locked_view) { + furi_assert(locked_view); + osTimerDelete(locked_view->timer); + view_free(locked_view->view); + free(locked_view); +} + +void desktop_view_locked_close_doors(DesktopViewLocked* locked_view) { + DesktopViewLockedModel* model = view_get_model(locked_view->view); + model->doors_closing = true; + model->door_offset = DOOR_OFFSET_START; + view_commit_model(locked_view->view, true); + xTimerChangePeriod(locked_view->timer, pdMS_TO_TICKS(DOOR_MOVING_INTERVAL_MS), portMAX_DELAY); +} + +void desktop_view_locked_lock(DesktopViewLocked* locked_view, bool pin_locked) { + DesktopViewLockedModel* model = view_get_model(locked_view->view); + model->locked = true; + model->pin_locked = pin_locked; + view_commit_model(locked_view->view, true); +} + +void desktop_view_locked_unlock(DesktopViewLocked* locked_view) { + furi_assert(locked_view); + + locked_view->lock_count = 0; + DesktopViewLockedModel* model = view_get_model(locked_view->view); + model->locked = false; + model->pin_locked = false; + model->unlocked_hint = true; + view_commit_model(locked_view->view, true); + xTimerChangePeriod(locked_view->timer, pdMS_TO_TICKS(UNLOCKED_HINT_TIMEOUT_MS), portMAX_DELAY); +} diff --git a/applications/desktop/views/desktop_view_locked.h b/applications/desktop/views/desktop_view_locked.h new file mode 100644 index 00000000..60fe791b --- /dev/null +++ b/applications/desktop/views/desktop_view_locked.h @@ -0,0 +1,21 @@ +#pragma once + +#include "../desktop_settings/desktop_settings.h" +#include "../views/desktop_events.h" +#include + +typedef struct DesktopViewLocked DesktopViewLocked; + +typedef void (*DesktopViewLockedCallback)(DesktopEvent event, void* context); + +void desktop_view_locked_set_callback( + DesktopViewLocked* locked_view, + DesktopViewLockedCallback callback, + void* context); +void desktop_view_locked_update(DesktopViewLocked* locked_view); +View* desktop_view_locked_get_view(DesktopViewLocked* locked_view); +DesktopViewLocked* desktop_view_locked_alloc(); +void desktop_view_locked_free(DesktopViewLocked* locked_view); +void desktop_view_locked_lock(DesktopViewLocked* locked_view, bool pin_locked); +void desktop_view_locked_unlock(DesktopViewLocked* locked_view); +void desktop_view_locked_close_doors(DesktopViewLocked* locked_view); diff --git a/applications/desktop/views/desktop_main.c b/applications/desktop/views/desktop_view_main.c similarity index 98% rename from applications/desktop/views/desktop_main.c rename to applications/desktop/views/desktop_view_main.c index 83d561df..51520863 100644 --- a/applications/desktop/views/desktop_main.c +++ b/applications/desktop/views/desktop_view_main.c @@ -7,7 +7,7 @@ #include #include "../desktop_i.h" -#include "desktop_main.h" +#include "desktop_view_main.h" struct DesktopMainView { View* view; diff --git a/applications/desktop/views/desktop_main.h b/applications/desktop/views/desktop_view_main.h similarity index 100% rename from applications/desktop/views/desktop_main.h rename to applications/desktop/views/desktop_view_main.h diff --git a/applications/desktop/views/desktop_view_pin_input.c b/applications/desktop/views/desktop_view_pin_input.c new file mode 100644 index 00000000..b8976ca2 --- /dev/null +++ b/applications/desktop/views/desktop_view_pin_input.c @@ -0,0 +1,340 @@ +#include +#include +#include +#include +#include +#include + +#include "desktop_view_pin_input.h" +#include "../desktop_settings/desktop_settings.h" + +#define NO_ACTIVITY_TIMEOUT 15000 + +#define PIN_CELL_WIDTH 13 +#define DEFAULT_PIN_X 64 +#define DEFAULT_PIN_Y 32 + +struct DesktopViewPinInput { + View* view; + DesktopViewPinInputCallback back_callback; + DesktopViewPinInputCallback timeout_callback; + DesktopViewPinInputDoneCallback done_callback; + void* context; + TimerHandle_t timer; +}; + +typedef struct { + PinCode pin; + bool pin_hidden; + bool locked_input; + uint8_t pin_x; + uint8_t pin_y; + const char* primary_str; + uint8_t primary_str_x; + uint8_t primary_str_y; + const char* secondary_str; + uint8_t secondary_str_x; + uint8_t secondary_str_y; + const char* button_label; +} DesktopViewPinInputModel; + +static bool desktop_view_pin_input_input(InputEvent* event, void* context) { + furi_assert(event); + furi_assert(context); + + DesktopViewPinInput* pin_input = context; + DesktopViewPinInputModel* model = view_get_model(pin_input->view); + + bool call_back_callback = false; + bool call_done_callback = false; + PinCode pin_code = {0}; + + if(event->type == InputTypeShort) { + switch(event->key) { + case InputKeyRight: + case InputKeyLeft: + case InputKeyDown: + case InputKeyUp: + if(!model->locked_input) { + if(model->pin.length < MAX_PIN_SIZE) { + model->pin.data[model->pin.length++] = event->key; + } + } + break; + case InputKeyOk: + if(model->pin.length >= MIN_PIN_SIZE) { + call_done_callback = true; + pin_code = model->pin; + } + break; + case InputKeyBack: + if(!model->locked_input) { + if(model->pin.length > 0) { + model->pin.length = 0; + } else { + call_back_callback = true; + } + } + break; + default: + furi_assert(0); + break; + } + } + view_commit_model(pin_input->view, true); + + if(call_done_callback && pin_input->done_callback) { + pin_input->done_callback(&pin_code, pin_input->context); + } else if(call_back_callback && pin_input->back_callback) { + pin_input->back_callback(pin_input->context); + } + + xTimerStart(pin_input->timer, 0); + + return true; +} + +static void desktop_view_pin_input_draw_cells(Canvas* canvas, DesktopViewPinInputModel* model) { + furi_assert(canvas); + furi_assert(model); + + uint8_t draw_pin_size = MAX(4, model->pin.length + 1); + if(model->locked_input || (model->pin.length == MAX_PIN_SIZE)) { + draw_pin_size = model->pin.length; + } + + uint8_t x = model->pin_x - (draw_pin_size * (PIN_CELL_WIDTH - 1)) / 2; + uint8_t y = model->pin_y - (PIN_CELL_WIDTH / 2); + + for(int i = 0; i < draw_pin_size; ++i) { + canvas_draw_frame(canvas, x, y, PIN_CELL_WIDTH, PIN_CELL_WIDTH); + if(i < model->pin.length) { + if(model->pin_hidden) { + canvas_draw_icon(canvas, x + 3, y + 3, &I_Pin_star_7x7); + } else { + switch(model->pin.data[i]) { + case InputKeyDown: + canvas_draw_icon(canvas, x + 3, y + 2, &I_Pin_arrow_down_7x9); + break; + case InputKeyUp: + canvas_draw_icon(canvas, x + 3, y + 2, &I_Pin_arrow_up7x9); + break; + case InputKeyLeft: + canvas_draw_icon(canvas, x + 2, y + 3, &I_Pin_arrow_left_9x7); + break; + case InputKeyRight: + canvas_draw_icon(canvas, x + 2, y + 3, &I_Pin_arrow_right_9x7); + break; + default: + furi_assert(0); + break; + } + } + } else if(i == model->pin.length) { + canvas_draw_icon(canvas, x + 4, y + PIN_CELL_WIDTH + 1, &I_Pin_pointer_5x3); + } + x += PIN_CELL_WIDTH - 1; + } +} + +static void desktop_view_pin_input_draw(Canvas* canvas, void* context) { + furi_assert(canvas); + furi_assert(context); + + canvas_set_font(canvas, FontSecondary); + DesktopViewPinInputModel* model = context; + desktop_view_pin_input_draw_cells(canvas, model); + + if((model->pin.length > 0) && !model->locked_input) { + canvas_draw_icon(canvas, 4, 53, &I_Pin_back_full_40x8); + } + + if(model->button_label && ((model->pin.length >= MIN_PIN_SIZE) || model->locked_input)) { + elements_button_center(canvas, model->button_label); + } + + if(model->primary_str) { + canvas_set_font(canvas, FontPrimary); + canvas_draw_str(canvas, model->primary_str_x, model->primary_str_y, model->primary_str); + canvas_set_font(canvas, FontSecondary); + } + + if(model->secondary_str) { + canvas_set_font(canvas, FontSecondary); + canvas_draw_str( + canvas, model->secondary_str_x, model->secondary_str_y, model->secondary_str); + } +} + +void desktop_view_pin_input_timer_callback(TimerHandle_t timer) { + DesktopViewPinInput* pin_input = pvTimerGetTimerID(timer); + + if(pin_input->timeout_callback) { + pin_input->timeout_callback(pin_input->context); + } +} + +static void desktop_view_pin_input_enter(void* context) { + DesktopViewPinInput* pin_input = context; + xTimerStart(pin_input->timer, portMAX_DELAY); +} + +static void desktop_view_pin_input_exit(void* context) { + DesktopViewPinInput* pin_input = context; + xTimerStop(pin_input->timer, portMAX_DELAY); +} + +DesktopViewPinInput* desktop_view_pin_input_alloc(void) { + DesktopViewPinInput* pin_input = furi_alloc(sizeof(DesktopViewPinInput)); + pin_input->view = view_alloc(); + view_allocate_model(pin_input->view, ViewModelTypeLocking, sizeof(DesktopViewPinInputModel)); + view_set_context(pin_input->view, pin_input); + view_set_draw_callback(pin_input->view, desktop_view_pin_input_draw); + view_set_input_callback(pin_input->view, desktop_view_pin_input_input); + pin_input->timer = xTimerCreate( + NULL, + pdMS_TO_TICKS(NO_ACTIVITY_TIMEOUT), + pdFALSE, + pin_input, + desktop_view_pin_input_timer_callback); + view_set_enter_callback(pin_input->view, desktop_view_pin_input_enter); + view_set_exit_callback(pin_input->view, desktop_view_pin_input_exit); + + DesktopViewPinInputModel* model = view_get_model(pin_input->view); + model->pin_x = DEFAULT_PIN_X; + model->pin_y = DEFAULT_PIN_Y; + model->pin.length = 0; + view_commit_model(pin_input->view, false); + + return pin_input; +} + +void desktop_view_pin_input_free(DesktopViewPinInput* pin_input) { + furi_assert(pin_input); + + xTimerStop(pin_input->timer, portMAX_DELAY); + while(xTimerIsTimerActive(pin_input->timer)) { + delay(1); + } + xTimerDelete(pin_input->timer, portMAX_DELAY); + + view_free(pin_input->view); + free(pin_input); +} + +void desktop_view_pin_input_lock_input(DesktopViewPinInput* pin_input) { + furi_assert(pin_input); + + DesktopViewPinInputModel* model = view_get_model(pin_input->view); + model->locked_input = true; + view_commit_model(pin_input->view, true); +} + +void desktop_view_pin_input_unlock_input(DesktopViewPinInput* pin_input) { + furi_assert(pin_input); + + DesktopViewPinInputModel* model = view_get_model(pin_input->view); + model->locked_input = false; + view_commit_model(pin_input->view, true); +} + +void desktop_view_pin_input_set_pin(DesktopViewPinInput* pin_input, const PinCode* pin) { + furi_assert(pin_input); + furi_assert(pin); + + DesktopViewPinInputModel* model = view_get_model(pin_input->view); + model->pin = *pin; + view_commit_model(pin_input->view, true); +} + +void desktop_view_pin_input_reset_pin(DesktopViewPinInput* pin_input) { + furi_assert(pin_input); + + DesktopViewPinInputModel* model = view_get_model(pin_input->view); + model->pin.length = 0; + view_commit_model(pin_input->view, true); +} + +void desktop_view_pin_input_hide_pin(DesktopViewPinInput* pin_input, bool pin_hidden) { + furi_assert(pin_input); + + DesktopViewPinInputModel* model = view_get_model(pin_input->view); + model->pin_hidden = pin_hidden; + view_commit_model(pin_input->view, true); +} + +void desktop_view_pin_input_set_label_button(DesktopViewPinInput* pin_input, const char* label) { + furi_assert(pin_input); + + DesktopViewPinInputModel* model = view_get_model(pin_input->view); + model->button_label = label; + view_commit_model(pin_input->view, true); +} + +void desktop_view_pin_input_set_label_primary( + DesktopViewPinInput* pin_input, + uint8_t x, + uint8_t y, + const char* label) { + furi_assert(pin_input); + + DesktopViewPinInputModel* model = view_get_model(pin_input->view); + model->primary_str = label; + model->primary_str_x = x; + model->primary_str_y = y; + view_commit_model(pin_input->view, true); +} + +void desktop_view_pin_input_set_label_secondary( + DesktopViewPinInput* pin_input, + uint8_t x, + uint8_t y, + const char* label) { + furi_assert(pin_input); + + DesktopViewPinInputModel* model = view_get_model(pin_input->view); + model->secondary_str = label; + model->secondary_str_x = x; + model->secondary_str_y = y; + view_commit_model(pin_input->view, true); +} + +void desktop_view_pin_input_set_pin_position(DesktopViewPinInput* pin_input, uint8_t x, uint8_t y) { + furi_assert(pin_input); + + DesktopViewPinInputModel* model = view_get_model(pin_input->view); + model->pin_x = x; + model->pin_y = y; + view_commit_model(pin_input->view, true); +} + +void desktop_view_pin_input_set_context(DesktopViewPinInput* pin_input, void* context) { + furi_assert(pin_input); + pin_input->context = context; +} + +void desktop_view_pin_input_set_timeout_callback( + DesktopViewPinInput* pin_input, + DesktopViewPinInputCallback callback) { + furi_assert(pin_input); + pin_input->timeout_callback = callback; +} + +void desktop_view_pin_input_set_back_callback( + DesktopViewPinInput* pin_input, + DesktopViewPinInputCallback callback) { + furi_assert(pin_input); + pin_input->back_callback = callback; +} + +void desktop_view_pin_input_set_done_callback( + DesktopViewPinInput* pin_input, + DesktopViewPinInputDoneCallback callback) { + furi_assert(pin_input); + pin_input->done_callback = callback; +} + +View* desktop_view_pin_input_get_view(DesktopViewPinInput* pin_input) { + furi_assert(pin_input); + return pin_input->view; +} diff --git a/applications/desktop/views/desktop_view_pin_input.h b/applications/desktop/views/desktop_view_pin_input.h new file mode 100644 index 00000000..3e39fd20 --- /dev/null +++ b/applications/desktop/views/desktop_view_pin_input.h @@ -0,0 +1,40 @@ +#pragma once + +#include +#include "desktop/desktop_settings/desktop_settings.h" + +typedef void (*DesktopViewPinInputCallback)(void*); +typedef void (*DesktopViewPinInputDoneCallback)(const PinCode* pin_code, void*); +typedef struct DesktopViewPinInput DesktopViewPinInput; + +DesktopViewPinInput* desktop_view_pin_input_alloc(void); +void desktop_view_pin_input_free(DesktopViewPinInput*); + +void desktop_view_pin_input_set_pin(DesktopViewPinInput* pin_input, const PinCode* pin); +void desktop_view_pin_input_reset_pin(DesktopViewPinInput* pin_input); +void desktop_view_pin_input_hide_pin(DesktopViewPinInput* pin_input, bool pin_hidden); +void desktop_view_pin_input_set_label_button(DesktopViewPinInput* pin_input, const char* label); +void desktop_view_pin_input_set_label_primary( + DesktopViewPinInput* pin_input, + uint8_t x, + uint8_t y, + const char* label); +void desktop_view_pin_input_set_label_secondary( + DesktopViewPinInput* pin_input, + uint8_t x, + uint8_t y, + const char* label); +void desktop_view_pin_input_set_pin_position(DesktopViewPinInput* pin_input, uint8_t x, uint8_t y); +View* desktop_view_pin_input_get_view(DesktopViewPinInput*); +void desktop_view_pin_input_set_done_callback( + DesktopViewPinInput* pin_input, + DesktopViewPinInputDoneCallback callback); +void desktop_view_pin_input_set_back_callback( + DesktopViewPinInput* pin_input, + DesktopViewPinInputCallback callback); +void desktop_view_pin_input_set_timeout_callback( + DesktopViewPinInput* pin_input, + DesktopViewPinInputCallback callback); +void desktop_view_pin_input_set_context(DesktopViewPinInput* pin_input, void* context); +void desktop_view_pin_input_lock_input(DesktopViewPinInput* pin_input); +void desktop_view_pin_input_unlock_input(DesktopViewPinInput* pin_input); diff --git a/applications/desktop/views/desktop_view_pin_setup_done.c b/applications/desktop/views/desktop_view_pin_setup_done.c new file mode 100644 index 00000000..a90903e9 --- /dev/null +++ b/applications/desktop/views/desktop_view_pin_setup_done.c @@ -0,0 +1,80 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../desktop_i.h" +#include "desktop_view_pin_setup_done.h" + +struct DesktopViewPinSetupDone { + View* view; + DesktopViewPinSetupDoneDoneCallback callback; + void* context; +}; + +static void desktop_view_pin_done_draw(Canvas* canvas, void* model) { + furi_assert(canvas); + furi_assert(model); + + canvas_set_font(canvas, FontPrimary); + elements_multiline_text_aligned( + canvas, 64, 0, AlignCenter, AlignTop, "Prepare to use\narrows as\nPIN symbols"); + + canvas_set_font(canvas, FontSecondary); + elements_multiline_text(canvas, 58, 24, "Prepare to use\narrows as\nPIN symbols"); + + canvas_draw_icon(canvas, 16, 18, &I_Pin_attention_dpad_29x29); + elements_button_right(canvas, "Next"); +} + +static bool desktop_view_pin_done_input(InputEvent* event, void* context) { + furi_assert(event); + furi_assert(context); + + DesktopViewPinSetupDone* instance = context; + bool consumed = false; + + if((event->key == InputKeyRight) && (event->type == InputTypeShort)) { + instance->callback(instance->context); + consumed = true; + } + + return consumed; +} + +void desktop_view_pin_done_set_callback( + DesktopViewPinSetupDone* instance, + DesktopViewPinSetupDoneDoneCallback callback, + void* context) { + furi_assert(instance); + furi_assert(callback); + instance->callback = callback; + instance->context = context; +} + +DesktopViewPinSetupDone* desktop_view_pin_done_alloc() { + DesktopViewPinSetupDone* view = furi_alloc(sizeof(DesktopViewPinSetupDone)); + view->view = view_alloc(); + view_allocate_model(view->view, ViewModelTypeLockFree, 1); + view_set_context(view->view, view); + view_set_draw_callback(view->view, desktop_view_pin_done_draw); + view_set_input_callback(view->view, desktop_view_pin_done_input); + + return view; +} + +void desktop_view_pin_done_free(DesktopViewPinSetupDone* instance) { + furi_assert(instance); + + view_free(instance->view); + free(instance); +} + +View* desktop_view_pin_done_get_view(DesktopViewPinSetupDone* instance) { + furi_assert(instance); + return instance->view; +} diff --git a/applications/desktop/views/desktop_view_pin_setup_done.h b/applications/desktop/views/desktop_view_pin_setup_done.h new file mode 100644 index 00000000..b55677dc --- /dev/null +++ b/applications/desktop/views/desktop_view_pin_setup_done.h @@ -0,0 +1,15 @@ +#pragma once + +#include + +typedef struct DesktopViewPinSetupDone DesktopViewPinSetupDone; + +typedef void (*DesktopViewPinSetupDoneDoneCallback)(void*); + +void desktop_view_pin_done_set_callback( + DesktopViewPinSetupDone* instance, + DesktopViewPinSetupDoneDoneCallback callback, + void* context); +DesktopViewPinSetupDone* desktop_view_pin_done_alloc(); +void desktop_view_pin_done_free(DesktopViewPinSetupDone* instance); +View* desktop_view_pin_done_get_view(DesktopViewPinSetupDone* instance); diff --git a/applications/desktop/views/desktop_view_pin_timeout.c b/applications/desktop/views/desktop_view_pin_timeout.c new file mode 100644 index 00000000..7cc0860f --- /dev/null +++ b/applications/desktop/views/desktop_view_pin_timeout.c @@ -0,0 +1,109 @@ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "desktop_view_pin_timeout.h" + +struct DesktopViewPinTimeout { + View* view; + TimerHandle_t timer; + DesktopViewPinTimeoutDoneCallback callback; + void* context; +}; + +typedef struct { + uint32_t time_left; +} DesktopViewPinTimeoutModel; + +void desktop_view_pin_timeout_set_callback( + DesktopViewPinTimeout* instance, + DesktopViewPinTimeoutDoneCallback callback, + void* context) { + furi_assert(instance); + + instance->callback = callback; + instance->context = context; +} + +static void desktop_view_pin_timeout_timer_callback(TimerHandle_t timer) { + DesktopViewPinTimeout* instance = pvTimerGetTimerID(timer); + bool stop = false; + + DesktopViewPinTimeoutModel* model = view_get_model(instance->view); + if(model->time_left > 0) { + --model->time_left; + } else { + stop = true; + } + view_commit_model(instance->view, true); + + if(stop) { + xTimerStop(instance->timer, portMAX_DELAY); + instance->callback(instance->context); + } +} + +static bool desktop_view_pin_timeout_input(InputEvent* event, void* context) { + return true; +} + +static void desktop_view_pin_timeout_draw(Canvas* canvas, void* _model) { + furi_assert(canvas); + furi_assert(_model); + + DesktopViewPinTimeoutModel* model = _model; + + canvas_set_font(canvas, FontPrimary); + canvas_draw_str(canvas, 36, 31, "Wrong PIN!"); + + canvas_set_font(canvas, FontSecondary); + char str[30] = {0}; + snprintf(str, sizeof(str), "Timeout: %lds", model->time_left); + canvas_draw_str_aligned(canvas, 64, 38, AlignCenter, AlignCenter, str); +} + +void desktop_view_pin_timeout_free(DesktopViewPinTimeout* instance) { + view_free(instance->view); + xTimerDelete(instance->timer, portMAX_DELAY); + + free(instance); +} + +DesktopViewPinTimeout* desktop_view_pin_timeout_alloc(void) { + DesktopViewPinTimeout* instance = furi_alloc(sizeof(DesktopViewPinTimeout)); + instance->timer = xTimerCreate( + NULL, pdMS_TO_TICKS(1000), pdTRUE, instance, desktop_view_pin_timeout_timer_callback); + + instance->view = view_alloc(); + view_allocate_model(instance->view, ViewModelTypeLockFree, sizeof(DesktopViewPinTimeoutModel)); + + view_set_context(instance->view, instance); + view_set_draw_callback(instance->view, desktop_view_pin_timeout_draw); + view_set_input_callback(instance->view, desktop_view_pin_timeout_input); + + return instance; +} + +void desktop_view_pin_timeout_start(DesktopViewPinTimeout* instance, uint32_t time_left) { + furi_assert(instance); + + DesktopViewPinTimeoutModel* model = view_get_model(instance->view); + // no race - always called when timer is stopped + model->time_left = time_left; + view_commit_model(instance->view, true); + + xTimerStart(instance->timer, portMAX_DELAY); +} + +View* desktop_view_pin_timeout_get_view(DesktopViewPinTimeout* instance) { + furi_assert(instance); + + return instance->view; +} diff --git a/applications/desktop/views/desktop_view_pin_timeout.h b/applications/desktop/views/desktop_view_pin_timeout.h new file mode 100644 index 00000000..76d84ba5 --- /dev/null +++ b/applications/desktop/views/desktop_view_pin_timeout.h @@ -0,0 +1,16 @@ +#pragma once + +#include +#include + +typedef void (*DesktopViewPinTimeoutDoneCallback)(void*); +typedef struct DesktopViewPinTimeout DesktopViewPinTimeout; + +void desktop_view_pin_timeout_set_callback( + DesktopViewPinTimeout* instance, + DesktopViewPinTimeoutDoneCallback callback, + void* context); +DesktopViewPinTimeout* desktop_view_pin_timeout_alloc(void); +void desktop_view_pin_timeout_free(DesktopViewPinTimeout*); +void desktop_view_pin_timeout_start(DesktopViewPinTimeout* instance, uint32_t time_left); +View* desktop_view_pin_timeout_get_view(DesktopViewPinTimeout* instance); diff --git a/applications/dolphin/dolphin.c b/applications/dolphin/dolphin.c index 2213ebb9..92c6d7a7 100644 --- a/applications/dolphin/dolphin.c +++ b/applications/dolphin/dolphin.c @@ -80,15 +80,11 @@ Dolphin* dolphin_alloc() { dolphin->event_queue = osMessageQueueNew(8, sizeof(DolphinEvent), NULL); dolphin->pubsub = furi_pubsub_alloc(); dolphin->butthurt_timer = xTimerCreate( - "Butthurt timer", HOURS_IN_TICKS(2 * 24), pdTRUE, dolphin, dolphin_butthurt_timer_callback); + NULL, HOURS_IN_TICKS(2 * 24), pdTRUE, dolphin, dolphin_butthurt_timer_callback); dolphin->flush_timer = - xTimerCreate("Flush timer", 30 * 1000, pdFALSE, dolphin, dolphin_flush_timer_callback); + xTimerCreate(NULL, 30 * 1000, pdFALSE, dolphin, dolphin_flush_timer_callback); dolphin->clear_limits_timer = xTimerCreate( - "Clear limits timer", - HOURS_IN_TICKS(24), - pdTRUE, - dolphin, - dolphin_clear_limits_timer_callback); + NULL, HOURS_IN_TICKS(24), pdTRUE, dolphin, dolphin_clear_limits_timer_callback); return dolphin; } diff --git a/applications/gui/elements.c b/applications/gui/elements.c index 0cd341b3..2b874ec1 100644 --- a/applications/gui/elements.c +++ b/applications/gui/elements.c @@ -1,4 +1,7 @@ #include "elements.h" +#include +#include "furi_hal_resources.h" +#include #include "gui/canvas.h" #include @@ -337,6 +340,47 @@ void elements_slightly_rounded_box( canvas_draw_rbox(canvas, x, y, width, height, 1); } +void elements_bold_rounded_frame( + Canvas* canvas, + uint8_t x, + uint8_t y, + uint8_t width, + uint8_t height) { + furi_assert(canvas); + + canvas_set_color(canvas, ColorWhite); + canvas_draw_box(canvas, x + 2, y + 2, width - 3, height - 3); + canvas_set_color(canvas, ColorBlack); + + canvas_draw_line(canvas, x + 3, y, x + width - 3, y); + canvas_draw_line(canvas, x + 2, y + 1, x + width - 2, y + 1); + + canvas_draw_line(canvas, x, y + 3, x, y + height - 3); + canvas_draw_line(canvas, x + 1, y + 2, x + 1, y + height - 2); + + canvas_draw_line(canvas, x + width, y + 3, x + width, y + height - 3); + canvas_draw_line(canvas, x + width - 1, y + 2, x + width - 1, y + height - 2); + + canvas_draw_line(canvas, x + 3, y + height, x + width - 3, y + height); + canvas_draw_line(canvas, x + 2, y + height - 1, x + width - 2, y + height - 1); + + canvas_draw_dot(canvas, x + 2, y + 2); + canvas_draw_dot(canvas, x + 3, y + 2); + canvas_draw_dot(canvas, x + 2, y + 3); + + canvas_draw_dot(canvas, x + width - 2, y + 2); + canvas_draw_dot(canvas, x + width - 3, y + 2); + canvas_draw_dot(canvas, x + width - 2, y + 3); + + canvas_draw_dot(canvas, x + 2, y + height - 2); + canvas_draw_dot(canvas, x + 3, y + height - 2); + canvas_draw_dot(canvas, x + 2, y + height - 3); + + canvas_draw_dot(canvas, x + width - 2, y + height - 2); + canvas_draw_dot(canvas, x + width - 3, y + height - 2); + canvas_draw_dot(canvas, x + width - 2, y + height - 3); +} + void elements_bubble(Canvas* canvas, uint8_t x, uint8_t y, uint8_t width, uint8_t height) { furi_assert(canvas); canvas_draw_rframe(canvas, x + 4, y, width, height, 3); diff --git a/applications/gui/elements.h b/applications/gui/elements.h index 2d576e5b..28010b36 100644 --- a/applications/gui/elements.h +++ b/applications/gui/elements.h @@ -150,6 +150,19 @@ void elements_slightly_rounded_box( uint8_t width, uint8_t height); +/** Draw bold rounded frame + * + * @param canvas Canvas instance + * @param x, y top left corner coordinates + * @param width, height size of frame + */ +void elements_bold_rounded_frame( + Canvas* canvas, + uint8_t x, + uint8_t y, + uint8_t width, + uint8_t height); + /** Draw bubble frame for text * * @param canvas Canvas instance diff --git a/applications/gui/modules/code_input.c b/applications/gui/modules/code_input.c deleted file mode 100644 index 988742f9..00000000 --- a/applications/gui/modules/code_input.c +++ /dev/null @@ -1,478 +0,0 @@ -#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; - canvas_clear(canvas); - canvas_set_color(canvas, ColorBlack); - - if(model->header && strlen(model->header)) { - canvas_draw_str(canvas, 2, 9, model->header); - } else { - y_offset = 4; - } - - 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 deleted file mode 100644 index d6a43fc1..00000000 --- a/applications/gui/modules/code_input.h +++ /dev/null @@ -1,91 +0,0 @@ -/** - * @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 diff --git a/applications/loader/loader.c b/applications/loader/loader.c index 95beab45..b8f67a59 100644 --- a/applications/loader/loader.c +++ b/applications/loader/loader.c @@ -1,3 +1,4 @@ +#include "applications.h" #include #include "loader/loader.h" #include "loader_i.h" @@ -79,6 +80,12 @@ const FlipperApplication* loader_find_application_by_name(const char* name) { } } + for(size_t i = 0; i < FLIPPER_SETTINGS_APPS_COUNT; i++) { + if(strcmp(name, FLIPPER_SETTINGS_APPS[i].name) == 0) { + application = &FLIPPER_SETTINGS_APPS[i]; + } + } + if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { for(size_t i = 0; i < FLIPPER_DEBUG_APPS_COUNT; i++) { if(strcmp(name, FLIPPER_DEBUG_APPS[i].name) == 0) { diff --git a/assets/compiled/assets_icons.c b/assets/compiled/assets_icons.c index 4a779743..f45b0719 100644 --- a/assets/compiled/assets_icons.c +++ b/assets/compiled/assets_icons.c @@ -205,9 +205,6 @@ const uint8_t *_I_DoorLocked_10x56[] = {_I_DoorLocked_10x56_0}; const uint8_t _I_DoorRight_70x55_0[] = {0x01,0x00,0x16,0x01,0x81,0xcc,0x01,0x0f,0x60,0x04,0x3f,0x00,0x10,0xf8,0x08,0x0c,0x02,0x05,0x01,0x84,0x02,0x06,0x26,0x0a,0x10,0x8a,0xcc,0xe0,0x1d,0x68,0xe0,0x18,0xab,0xd0,0x0b,0x18,0x10,0x46,0xe6,0x16,0x1e,0x18,0x10,0x46,0xe4,0x28,0x2c,0x98,0x14,0x68,0x00,0x21,0x1d,0x10,0x8c,0x40,0x02,0x0e,0x10,0xa1,0x08,0xc8,0x40,0x42,0x62,0x11,0x94,0x03,0xfd,0xff,0x00,0x0c,0xff,0x0c,0x08,0x28,0x60,0xe4,0xc0,0x85,0x00,0x83,0x00,0x87,0xf1,0x00,0x8c,0x02,0x0b,0x07,0x24,0x84,0xff,0x04,0xc7,0x80,0xa0,0xe4,0xa0,0x81,0x41,0x04,0x17,0x02,0x41,0x49,0x81,0x0e,0x10,0xb2,0xa0,0x82,0x0e,0x9f,0xfc,0x0a,0x62,0xf2,0xc0,0x03,0x92,0xf0,0x08,0x2d,0x78,0x20,0xff,0x02,0x01,0x08,0xae,0x60,0x64,0x38,0x0d,0xb0,0x8d,0x08,0x82,0x11,0x58,0xc4,0x13,0xc0,0x35,0x68,0x62,0x68,0x81,0x09,0x08,0x84,0x40,0x81,0x0d,0x18,0x69,0x10,0x47,0x44,0x66,0x5f,0x21,0xa9,0x29,0x94,0x10,0x2f,0x23,0x53,0x14,0x60,0x42,0x3c,0x08,0xfc,0x02,0x2c,0x62,0x23,0x58,0xd0,0x22,0x00,0x83,0x3e,0x98,0x44,0x43,0x46,0x22,0x30,0x89,0xce,0x01,0x0f,0x70,0x04,0x3f,0x81,0x8a,0x3c,0x21,0xaa,0x70,0x1a,0xe3,0x44,0x1a,0xa6,0x01,0xd2,0x38,0x90,0x8a,0x40,0x20,0xe5,0x96,0x80,0x43,0x81,0x06,0x6b,0x28,0x07,0xf3,0xfe,0x00,0x19,0xf9,0x34,0xc1,0x08,0x8f,0x20,0xf1,0x3e,0x16,0x00,0xa8,0x19,0x00,0x10,0x76,0x03,0xe2,0x3e,0x90,0x45,0x38,0x01,0x42,0x05,0x88,0x44,0x67,0x15,0x70,0x41,0x38,0x04,0x10,0x24,0x03,0x00,0x10,0x20,0x4a,0x46,0xe9,0x46,0xe1,0x04,0x50,0x66,0x40,0x85,0x19,0x98,0x00,0xc0,}; const uint8_t *_I_DoorRight_70x55[] = {_I_DoorRight_70x55_0}; -const uint8_t _I_LockPopup_100x49_0[] = {0x01,0x00,0x37,0x01,0xfc,0x7f,0xc0,0x13,0x01,0xfe,0x03,0x2a,0x07,0x06,0x12,0xd4,0x1a,0x06,0x0c,0xa8,0x60,0x33,0xe0,0x12,0x08,0x40,0x32,0x3f,0xd0,0x70,0x64,0xe0,0x20,0x31,0x8a,0x00,0x32,0x2c,0x10,0x0b,0x00,0x32,0x62,0x10,0x0c,0x06,0x00,0x19,0x00,0x82,0xc0,0x83,0x22,0x08,0x04,0x18,0x11,0x6a,0x01,0x25,0x02,0x84,0x83,0x1e,0x02,0x04,0x10,0xe1,0x03,0x1e,0x3c,0x0c,0x9c,0x1c,0x02,0x43,0x00,0x84,0x4f,0xc1,0x8f,0x80,0xaf,0x40,0x39,0x14,0x00,0x63,0xd0,0x36,0xf0,0x09,0xc6,0x00,0x18,0xd4,0x3a,0x06,0x9c,0x08,0x20,0xc9,0xdf,0xc0,0x20,0x7f,0x00,0x65,0x40,0x3f,0x80,0xc7,0xd0,0x10,0x06,0x01,0x7f,0x06,0x34,0x8e,0xa1,0x3d,0x80,0x70,0x0b,0x4f,0x23,0xd0,0x50,0xa0,0x1f,0x08,0x78,0x66,0x11,0xe3,0xfc,0x83,0x83,0x1e,0x40,0x0c,0x1f,0xfb,0xec,0x41,0x8c,0x03,0x1e,0x07,0x00,0x4d,0x10,0x0a,0x04,0xc0,0x9b,0x30,0x0c,0x1f,0xff,0xff,0x9f,0x06,0x3e,0x01,0x80,0x48,0xe7,0x99,0x83,0x0d,0x6a,0xe0,0xc4,0x90,0x03,0x1a,0x76,0x0c,0x38,0xe0,0x34,0x45,0x25,0x02,0x06,0x0d,0xe0,0x18,0x3c,0x08,0x19,0x40,0x78,0x00,0xc1,0x81,0xc3,0x27,0xf8,0x48,0x26,0x82,0x7d,0x00,0xfc,0x40,0xfc,0x10,0xfc,0x04,0xfc,0x18,0x30,0x28,0x7d,0x02,0x3f,0x00,0x98,0x41,0x38,0x31,0x08,0x25,0x0e,0x19,0x1f,0x81,0x42,0x70,0x11,0xa2,0x08,0xe2,0x30,0x72,0x08,0x76,0x0a,0x19,0x0f,0x85,0x42,0x60,0x11,0x51,0x78,0xc2,0x20,0x32,0x08,0x26,0x00,0x18,0x91,0x00,0x60,0x91,0x44,0x08,0x34,0x08,0x64,0x1f,0xe4,0x07,0x3f,0x84,0x0d,0x58,0x44,0x01,0x83,0xdc,0x60,0x43,0xe1,0x39,0xa9,0xd0,0x60,0x70,0x16,0x78,0xca,0x01,0x8f,0x83,0x3d,0x10,0x33,0x29,0x00,0xc7,0xa1,0x83,0x3f,0x10,0x0c,0x79,0x30,0x32,0xa0,0xdf,0xc7,0xa0,0x80,0x22,0x07,0xf8,0x06,0x54,0x04,}; -const uint8_t *_I_LockPopup_100x49[] = {_I_LockPopup_100x49_0}; - const uint8_t _I_PassportBottom_128x17_0[] = {0x01,0x00,0x5e,0x00,0x96,0x01,0x97,0xe1,0xff,0x00,0x2e,0x3e,0x68,0x0f,0x5a,0xc5,0x54,0x00,0xb9,0x50,0xfb,0x6a,0x35,0x40,0x05,0xcd,0x4e,0x03,0xfd,0x30,0x0f,0xf8,0x7f,0xa0,0x81,0xfe,0xf9,0x1b,0xfb,0xf3,0x01,0x47,0x66,0x02,0x1b,0x03,0x07,0xe7,0x02,0x0b,0x02,0x07,0xe5,0x82,0x0b,0xf2,0x1c,0xb0,0x01,0x67,0xf0,0x5f,0xd0,0x3f,0x23,0xf0,0x9b,0xc9,0xe5,0x80,0x03,0xd5,0xc0,0x00,0x86,0x01,0xf3,0xe6,0x1e,0x58,0x00,0x36,0xa8,0x06,0xac,0x04,0x30,0x6c,0x30,0xee,0x60,0x1f,0xe0,0x10,0xff,0x0d,0xfb,0x00,}; const uint8_t *_I_PassportBottom_128x17[] = {_I_PassportBottom_128x17_0}; @@ -447,6 +444,36 @@ const uint8_t *_I_Detailed_chip_17x13[] = {_I_Detailed_chip_17x13_0}; const uint8_t _I_Medium_chip_22x21_0[] = {0x01,0x00,0x35,0x00,0xfe,0x7f,0xe1,0xf0,0x28,0x04,0x43,0xf3,0xff,0x93,0xe1,0x6a,0x52,0x8e,0x2f,0xfe,0x51,0x25,0x80,0x4a,0x72,0xb6,0x79,0x55,0x76,0xc1,0x2e,0xaa,0xc0,0x25,0x51,0xdc,0x00,0x14,0x70,0x00,0x56,0xae,0x81,0x47,0x2b,0x7d,0x95,0x07,0x48,0x46,0x42,0x92,0x17,0x90,0xd4,0x87,0x64,}; const uint8_t *_I_Medium_chip_22x21[] = {_I_Medium_chip_22x21_0}; +const uint8_t _I_Pin_arrow_down_7x9_0[] = {0x00,0x1C,0x1C,0x1C,0x1C,0x1C,0x7F,0x3E,0x1C,0x08,}; +const uint8_t *_I_Pin_arrow_down_7x9[] = {_I_Pin_arrow_down_7x9_0}; + +const uint8_t _I_Pin_arrow_left_9x7_0[] = {0x00,0x08,0x00,0x0C,0x00,0xFE,0x01,0xFF,0x01,0xFE,0x01,0x0C,0x00,0x08,0x00,}; +const uint8_t *_I_Pin_arrow_left_9x7[] = {_I_Pin_arrow_left_9x7_0}; + +const uint8_t _I_Pin_arrow_right_9x7_0[] = {0x00,0x20,0x00,0x60,0x00,0xFF,0x00,0xFF,0x01,0xFF,0x00,0x60,0x00,0x20,0x00,}; +const uint8_t *_I_Pin_arrow_right_9x7[] = {_I_Pin_arrow_right_9x7_0}; + +const uint8_t _I_Pin_arrow_up7x9_0[] = {0x00,0x08,0x1C,0x3E,0x7F,0x1C,0x1C,0x1C,0x1C,0x1C,}; +const uint8_t *_I_Pin_arrow_up7x9[] = {_I_Pin_arrow_up7x9_0}; + +const uint8_t _I_Pin_attention_dpad_29x29_0[] = {0x01,0x00,0x56,0x00,0x80,0x7f,0x20,0xe0,0x31,0x81,0xc6,0x20,0x1c,0x08,0x05,0x82,0x01,0x20,0xa0,0x60,0x20,0x11,0x0f,0x04,0x02,0x03,0x08,0xf8,0x40,0x60,0x50,0x4f,0xc4,0x0e,0x09,0x04,0x05,0x8c,0x12,0x04,0x03,0x18,0x44,0x08,0x42,0x30,0x88,0x08,0x0c,0x62,0x14,0x18,0x05,0x02,0x21,0x61,0x14,0x8c,0x43,0xe3,0x01,0xf8,0x44,0x7f,0x20,0x31,0x89,0x81,0xcc,0x1e,0x61,0x73,0x0f,0x98,0x9c,0xc5,0xe6,0x37,0x31,0xf9,0x91,0xcc,0x9e,0x65,0x73,0x2f,0x99,0x9c,0xcd,0xe6,}; +const uint8_t *_I_Pin_attention_dpad_29x29[] = {_I_Pin_attention_dpad_29x29_0}; + +const uint8_t _I_Pin_back_arrow_10x8_0[] = {0x00,0x04,0x00,0x06,0x00,0xFF,0x00,0x06,0x01,0x04,0x02,0x00,0x02,0x00,0x01,0xF8,0x00,}; +const uint8_t *_I_Pin_back_arrow_10x8[] = {_I_Pin_back_arrow_10x8_0}; + +const uint8_t _I_Pin_back_full_40x8_0[] = {0x01,0x00,0x26,0x00,0x82,0x01,0x0e,0x0c,0x02,0x18,0x14,0x03,0xfe,0x04,0x38,0x37,0xc6,0xc3,0x32,0xf7,0x41,0x20,0x59,0x0a,0x54,0xa6,0x01,0xf2,0x88,0xde,0x80,0x83,0x01,0xc8,0x42,0xa5,0x3f,0x88,0x05,0x82,0x65,0x2e,}; +const uint8_t *_I_Pin_back_full_40x8[] = {_I_Pin_back_full_40x8_0}; + +const uint8_t _I_Pin_cell_13x13_0[] = {0x01,0x00,0x0a,0x00,0xff,0xc7,0xe0,0x31,0x00,0x0f,0x80,0x4c,0x2e,0x20,}; +const uint8_t *_I_Pin_cell_13x13[] = {_I_Pin_cell_13x13_0}; + +const uint8_t _I_Pin_pointer_5x3_0[] = {0x00,0x04,0x0E,0x1F,}; +const uint8_t *_I_Pin_pointer_5x3[] = {_I_Pin_pointer_5x3_0}; + +const uint8_t _I_Pin_star_7x7_0[] = {0x00,0x49,0x2A,0x1C,0x7F,0x1C,0x2A,0x49,}; +const uint8_t *_I_Pin_star_7x7[] = {_I_Pin_star_7x7_0}; + const uint8_t _I_passport_bad1_46x49_0[] = {0x01,0x00,0xd2,0x00,0xff,0x7f,0xc0,0x05,0x1f,0x02,0x1f,0xfe,0x7e,0x02,0x18,0x0f,0xe0,0x0a,0x57,0xff,0xf7,0x9c,0x0a,0x59,0xf8,0x0e,0x60,0x0a,0x56,0xf8,0x05,0x83,0xfc,0x05,0x18,0xbc,0x03,0x01,0xfd,0x02,0x8c,0x2c,0x5a,0x3f,0xa0,0x28,0xc1,0x40,0xa3,0xf4,0x02,0x8c,0x08,0x0a,0x77,0xf8,0x08,0x14,0x7d,0x13,0xfd,0xf9,0x14,0x80,0xab,0xd0,0x9f,0xd7,0xe0,0x10,0x60,0x2a,0x42,0x20,0x1a,0x09,0xfc,0xbe,0x01,0x10,0x02,0xa5,0x9c,0x0a,0x78,0x0e,0x74,0x04,0x0a,0x31,0x7a,0x06,0x7a,0x06,0x05,0x39,0xb0,0x44,0x80,0xa3,0x7e,0x02,0xa5,0xf0,0x0a,0x78,0x0a,0x00,0x14,0xf8,0x13,0xf0,0x29,0xc8,0x07,0x66,0x70,0x11,0xd8,0xea,0xa7,0xf1,0xb2,0x99,0x4c,0x00,0xa9,0xc0,0x9f,0x01,0x4e,0x01,0x3d,0x02,0x8c,0x38,0x0a,0x33,0xa8,0x6c,0x02,0x62,0x05,0x19,0xa0,0x14,0x78,0x00,0x51,0x94,0x01,0x46,0x01,0x03,0x02,0xa4,0x30,0x0a,0x2a,0x02,0x98,0x7c,0x25,0x60,0x52,0xe0,0x43,0xe5,0x80,0x51,0xc0,0x27,0x46,0x51,0x09,0x05,0x88,0xc0,0x66,0x80,0x52,0xfe,0x40,0x27,0x60,0x52,0xf8,0x7f,0xe7,0xa0,0x52,0xe0,0x5f,0xe7,0xc0,0x52,0x80,0x6f,0xe7,0xe0,0x53,0xde,0x01,0x50,0xe2,0x20,0x5f,0x02,0xbf,0xfb,0xfe,0x00,0x28,0xf8,}; const uint8_t *_I_passport_bad1_46x49[] = {_I_passport_bad1_46x49_0}; @@ -685,7 +712,6 @@ const Icon I_Back3_45x8 = {.width=45,.height=8,.frame_count=1,.frame_rate=0,.fra const Icon I_DoorLeft_70x55 = {.width=70,.height=55,.frame_count=1,.frame_rate=0,.frames=_I_DoorLeft_70x55}; const Icon I_DoorLocked_10x56 = {.width=10,.height=56,.frame_count=1,.frame_rate=0,.frames=_I_DoorLocked_10x56}; const Icon I_DoorRight_70x55 = {.width=70,.height=55,.frame_count=1,.frame_rate=0,.frames=_I_DoorRight_70x55}; -const Icon I_LockPopup_100x49 = {.width=100,.height=49,.frame_count=1,.frame_rate=0,.frames=_I_LockPopup_100x49}; const Icon I_PassportBottom_128x17 = {.width=128,.height=17,.frame_count=1,.frame_rate=0,.frames=_I_PassportBottom_128x17}; const Icon I_PassportLeft_6x47 = {.width=6,.height=47,.frame_count=1,.frame_rate=0,.frames=_I_PassportLeft_6x47}; const Icon I_WarningDolphin_45x42 = {.width=45,.height=42,.frame_count=1,.frame_rate=0,.frames=_I_WarningDolphin_45x42}; @@ -733,6 +759,16 @@ const Icon A_U2F_14 = {.width=14,.height=14,.frame_count=4,.frame_rate=3,.frames const Icon A_iButton_14 = {.width=14,.height=14,.frame_count=7,.frame_rate=3,.frames=_A_iButton_14}; const Icon I_Detailed_chip_17x13 = {.width=17,.height=13,.frame_count=1,.frame_rate=0,.frames=_I_Detailed_chip_17x13}; const Icon I_Medium_chip_22x21 = {.width=22,.height=21,.frame_count=1,.frame_rate=0,.frames=_I_Medium_chip_22x21}; +const Icon I_Pin_arrow_down_7x9 = {.width=7,.height=9,.frame_count=1,.frame_rate=0,.frames=_I_Pin_arrow_down_7x9}; +const Icon I_Pin_arrow_left_9x7 = {.width=9,.height=7,.frame_count=1,.frame_rate=0,.frames=_I_Pin_arrow_left_9x7}; +const Icon I_Pin_arrow_right_9x7 = {.width=9,.height=7,.frame_count=1,.frame_rate=0,.frames=_I_Pin_arrow_right_9x7}; +const Icon I_Pin_arrow_up7x9 = {.width=7,.height=9,.frame_count=1,.frame_rate=0,.frames=_I_Pin_arrow_up7x9}; +const Icon I_Pin_attention_dpad_29x29 = {.width=29,.height=29,.frame_count=1,.frame_rate=0,.frames=_I_Pin_attention_dpad_29x29}; +const Icon I_Pin_back_arrow_10x8 = {.width=10,.height=8,.frame_count=1,.frame_rate=0,.frames=_I_Pin_back_arrow_10x8}; +const Icon I_Pin_back_full_40x8 = {.width=40,.height=8,.frame_count=1,.frame_rate=0,.frames=_I_Pin_back_full_40x8}; +const Icon I_Pin_cell_13x13 = {.width=13,.height=13,.frame_count=1,.frame_rate=0,.frames=_I_Pin_cell_13x13}; +const Icon I_Pin_pointer_5x3 = {.width=5,.height=3,.frame_count=1,.frame_rate=0,.frames=_I_Pin_pointer_5x3}; +const Icon I_Pin_star_7x7 = {.width=7,.height=7,.frame_count=1,.frame_rate=0,.frames=_I_Pin_star_7x7}; const Icon I_passport_bad1_46x49 = {.width=46,.height=49,.frame_count=1,.frame_rate=0,.frames=_I_passport_bad1_46x49}; const Icon I_passport_bad2_46x49 = {.width=46,.height=49,.frame_count=1,.frame_rate=0,.frames=_I_passport_bad2_46x49}; const Icon I_passport_bad3_46x49 = {.width=46,.height=49,.frame_count=1,.frame_rate=0,.frames=_I_passport_bad3_46x49}; diff --git a/assets/compiled/assets_icons.h b/assets/compiled/assets_icons.h index 8f53bc36..26e26bef 100644 --- a/assets/compiled/assets_icons.h +++ b/assets/compiled/assets_icons.h @@ -62,7 +62,6 @@ extern const Icon I_Back3_45x8; extern const Icon I_DoorLeft_70x55; extern const Icon I_DoorLocked_10x56; extern const Icon I_DoorRight_70x55; -extern const Icon I_LockPopup_100x49; extern const Icon I_PassportBottom_128x17; extern const Icon I_PassportLeft_6x47; extern const Icon I_WarningDolphin_45x42; @@ -110,6 +109,16 @@ extern const Icon A_U2F_14; extern const Icon A_iButton_14; extern const Icon I_Detailed_chip_17x13; extern const Icon I_Medium_chip_22x21; +extern const Icon I_Pin_arrow_down_7x9; +extern const Icon I_Pin_arrow_left_9x7; +extern const Icon I_Pin_arrow_right_9x7; +extern const Icon I_Pin_arrow_up7x9; +extern const Icon I_Pin_attention_dpad_29x29; +extern const Icon I_Pin_back_arrow_10x8; +extern const Icon I_Pin_back_full_40x8; +extern const Icon I_Pin_cell_13x13; +extern const Icon I_Pin_pointer_5x3; +extern const Icon I_Pin_star_7x7; extern const Icon I_passport_bad1_46x49; extern const Icon I_passport_bad2_46x49; extern const Icon I_passport_bad3_46x49; diff --git a/assets/icons/Interface/LockPopup_100x49.png b/assets/icons/Interface/LockPopup_100x49.png deleted file mode 100644 index f30d88a9e3c6c60af669592f53fafd0fbb1c5118..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 577 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 QX&eA7&1_6djNPvM5BL~PlmGw# literal 0 HcmV?d00001 diff --git a/assets/icons/PIN/Pin_arrow_left_9x7.png b/assets/icons/PIN/Pin_arrow_left_9x7.png new file mode 100644 index 0000000000000000000000000000000000000000..fb4ded78fde8d1bf4f053ba0b353e7acf14f031a GIT binary patch literal 3603 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 literal 0 HcmV?d00001 diff --git a/assets/icons/PIN/Pin_arrow_up7x9.png b/assets/icons/PIN/Pin_arrow_up7x9.png new file mode 100644 index 0000000000000000000000000000000000000000..a91a6fd5e99a72112e28865cd8a004c7d1933fff GIT binary patch literal 3603 zcmaJ@c|4Te+rMpvvSba(81X2}EGQ;p8_TE>jcrt7jKN@*#$ZMzB}>VcEo(xFhBigA zRfKF&B$OpfLSqS8d&l#8dVcR8Z}0is_kGT}&h`CX>-l`{D|W}MM1o0W+qqCz&a@8xmO|M3uh;cln|6OUI z@X7fQ&dki(hqbDStcmq@R)<*FE(x{7@jPF^02^V5=v9ihMb|f1hw)0IhxkF_<1H_} z1sVWgmXE~@Wjrum=ebV>cmZ0s_CATm;a}mEc52Q5C=nO}OHAzGNx%Y4+73-pK+|sE zf&F7oVIUa*{8{JBz(BDGF#W^YNC4<9N*a&_dh_-a2?DV^K)SlsK3W zOCXnR0@miQE9D7uc?!4U4XYLag5q!qVkYiDSh|^JD*)2x1yFk>+xS2jzFcTm?NE^$ zEusR=1Jt#ow51*G(vhl2c`F}0KRYy{Jo3{2p&4FwzqpssC^#!EQ$-Rz!G~$z2>|jd zoi8@^jT0uuM~BC~Cj2=+8uB*%W~pE!<+;Jls%yObfcUWvPM_P@SPvhqk>^2RtzXee zpw9{L8C-GI=@-g9A^bLEC5ENHZn8J$mR*yf;vV50J7!cpZdF6S#2Ee38Kw@!gf4MU zH~T|ofioE<=_Pgf;Tvc0l%P^<+(Zk%8H}<#p|aT+abY8Ff9Htq!&92lSLbk7D(t{E zjjU(bM04fllo5%^3-CFm)D5AeU=e^FXGmfr{&k_>d3a+)aa}=xN$7&sHTfNh zfVj6VoV5%9Nwq8SCK^0ITUx;v0I2%9`_$cJSLF_4$)r9^g5d7-;)ha7k^2JBT`QGyenmoI!B!BgFZa^nPSIjjmHP5e8zHBct z>}g(M=h3f$4B-6LI6_z_Ow{YzNBpU4Q5No3aPn%6GK4Xlo>ROYK@oQ-NLryT2hS1Q z#~TwSIW2hlviM8?O9=^9I1CPTS9MyYOrlcISt$H6?B!qJq`S6dsv#09^-K@M!vvfq zTkX5@UgaFs(|?Idx+S6ai8fy!JtnNIngF-nVeN7Z`Pkld>>sQwike&!d8m z!q}j+#PS5O1l#Lt&96qwr4S9#BN(B)eb|Czi6eSM<1zl*H{oXKxy8rZigMly7Dpp) zp0Fn82H8REqlzST12a_HGG$OL1zP#tZ!<{Vq-7t-B%@O3Q}|wsw6|$peqXmwPE3aX z2;M0YDH7g@_E4AelRGO{xVu~ql8(6}@GdRA$pQKSu8{71L+l3C5qDtez&Yu}Hxem` z6sMHXl!;;o#{fs;ZdUOQhkK4<_f9*Vzhmk6*zQY_(0iGC-9?Iy&x;P0wqt{_@pc`@ z-STVPHZH9aL>@&(Sms8e^BoA~ujOKuWnROHb2zgex)a}&rr!-4kCTs9rZGVRYYIV- zvlx3+K(QCwE72=^{7f5<=%`? zl>Nr(;dCk;g6aw$Opx=3=@VvK69`}ZZjdTEXD<)m-PPh#nON_W-)WuySB2X5DDN+N zOj#o@Hg%5&TlX_@z|RoxL4x-e)E6|2*6eRf_RH|9>@0i7Xl-rM9ANjdo2TOpy0iRp z@HHQ+`qyJ4Zd+tE9Emv?)0oNb81R+irnMuZ>Qj# zxib@y+4A&mNoGlXP$qd$YD6l2f7kv+drBW{dVN}WI%9gX}>;*m9J4X{*B+`P?WbMg?R|_dOLt0YC zJHiM_Ty3A^GkR^rdo$!_RLz|l@F22ACA23r zJ#_ne&f4MCmW}wIwZp7=nYm*E?mRDe#(1hP%3plU=f|hSpU!`KyPiO-!1Ha8okr4T zJB37Cl;}y+I@x)J6@t!yw`NAC^c%r!=@Sa8&{j3f-kx1?ksX4A;-S<#E11dFr-IQ# zR{qfyN+h{-*_HEB`wzg2wZ9!NvuB)PENk|#M_tyutK;V4i>^I8-0%C89^}pT^~d@X zrZX$TDvB#EGNXQ4%%w>%B=-r;Tp6wJtw&z@62Lp*pP`dAn&FVjAe4>`?UC_VILOQnvfFm7kYb}KIe$4b!q%cDFE;P^!}5wFhS$flol=(c zKOH`gTJ?#vwG4c%BV>!!U?s|3f2Oiv<7D3Rncea6%ttMQ=SEEn7*BSKM z{I;U9VyY&6%QWwRxn-WhQPHJ&t+6%>}7+sVXoLpPbO)$>wJq(%cIl{yAd4L zao(3TFdv5v@49^(rE$qwH>D`KxrI{ti`zebVW|0ofEcHjRC^^ydT1 zit!QWV{YB&7Fp!JzRyR>-^@&*rwXPh>}8kQ`$wvMO}pPl&We;M%*Bo=xRH;1X50$# zU5slhYkSkir-#>@IobM@-9LZpVE$4__664#r;U<(Fif+aek4~_5ISPczF+n%G&YJPZd_dwhcM)XK$a~zGT6f@?}u{2kzI_J`y5h z5613ABWPopVbs3NnT+5kv=awJUz(1+_-pXaxwBvFzTRqoHSnr!F#SULqTm#orO}0` z4PcuJ1W{iBF zKEPVWtf%|A9(S$wMs?&E%QC)W%H5Wm7d}tKyUte8et?%f`c=!1mLN-!R-v?wVf6iz z)G6X}%Z#&ODdUID)ZtFfy9=wnb=?6Uetyt)y~(QPyq;Dlr>K3}Q=wY9_%mo}MmAXZ zJ7&N&B%XPHy{2#D+xAtlZx_lo9}?@xLqFZ?+&f;mh;c-PqH;Eqf4z$u?y_pN>Q=E- ziH*-zQc@6+ub%g8PZ}Rf89BiysN>^Vu*|b~eTqQIXzO`L8nmD()4q3juuoh;Z zx{Lc)DaWwDG3=>cj9@&S2$*_OJ%}J{GTxhrCE`61Z>_G%gwd42_vIJi(910C^C-NfacQ^Sl-eB6%Xg&U!Xb8ybq}LqdnpiS{AK90(zP z1Ord7u@T6SiQp2Di3~i5N%p4%Aecz--@FL!dP@uegZ@@w_#wgnaSCT+2SQQlM9?8^ zm=*yFg@O(lXcIm0a1R|XJV6r#hr(eH8234(1v`X*>mXnTpnnFKYmn~gg}|Cy{$q~2 zLxO!63>pFg2@Vd{4%X48(!C)t0|NsH6b^yIwYVBu0W1mw&(xv>sQhLyCk7DcBpQQ6 zrGT~=@gCGb1`^D5_CHaOY5&qv0{+PqH)jwgo(6$wL${*(t!QKO|ErS8|7r&?u*CoR z`+pJ#IIw6$2$mQ?4WtvewewQhGDSn6=tMk&N_U`A{eLIY&WFmN2KZ2EAh?b;45V&@ zCy*#xlKp=}Y-|wLlmG^vLLge3Bf(q}Z4${7VPJ`Z>caJO59#RW!C)3BeO)*VWoc## zg<9yK4D<|sW6i0AKr)fS_>J}aFIMl5*sX>j)3}z+iF8sB(bJMnC4>Hs8bSKAFYrI| z{e$)VvoAV-#6q~vK(=c8ziRzk#BHFh<-g6#-Td4BL<+a(>D=bN76lY@FUB@IjDy9m z(5*YN-4s*8oj}&+rVh+L4|neH1o$j1E!71)pl~xe=$Un0lQ15DzW@MKZGhDg}U`SZw?b6L%mi%}k>i0O|7rNX1<(d?0JS9PN(kfkHQG z1RKQz)HEfxq#ofv*@YVCvu@oE0CFqs zFE{j!`WYNKs2CGF-o$SPB`=SwY}7|wSdZP`KBW8jl!j!~9QGO;4YsLg zKF1`OD>o(&iBDXB%_*g<3nd1N0IVyvHOHTAecI$-6FS4g8!d|M2m(w(7c{TSZA$>e zymevBWdKO5UWqeN=K)$$^M?VT;;YQ5dr8vPZv+9rJm=zpN2Xi1zm#Crh-SW2sHl;^gC-&+PJmxG(DHc;&3qGFQ7ZGA zGzo*4Qzk#-dmBKosI{mHOK8%?k`sI%cmqwI-&2x{dXp2-PS@Sx8kUml*DhD8hzNU@ zThM;&Yakss9~FxaYln(@UH^nTb^WS^`!%ebTD=s06szYLr-xU-9tx6_ugS)71o!Yp zhwYjYJbhWL*8fs`yd~Bei@c{grBW(hF6bl_ei>T37o2#{wp2Ale5H=z0e>8iJ;G9( zQC3vWRz1B}beCHqYL95+jgjNR%Bt}&t8G?(s9uz2zFodihZr%_T5U3()c9pYuq2EH zN=+z>-w|(nW3}xt>Q0^W~ev%|u zUF3>Lcsilse&609z`?kad4HSz1BV?CPCK5zd&J=hdbm*NgVcl0LIsBxg$IjUF~OKPblVBH zyehZ#OF!`E!vfG2D`-RZwXNdD4!g}-T@?Vcs?Qco)OZO^oFMUsMZf?-%V|B9{r(z8{GySr0 zRi7$)*J2{>SY*CF8htRjKBXt+T$4bGP0BLsrlXM~({a)9Bg?Am)TG8FsmsY9E$@`E zJp-Q6^z2?yMp0c*2B}|SNFr{3$Noh-au0NBZAy45eQL|J!4hssb&0gprqH}wT%qeQ z{qQ*&J+L{*pIR}QKdAevu&(gcgp;?t_w9hJ1wHAz$KspBQ-e~a?MMYt1>{>HP06Ej zGvysm-!JB6yvXP)`b7Hhxf${L{DV~+tql1_*H;LR>G#=X?9iF!;jDs;g4%`nameYQ z)6Ss$sZo6fq!jSz&NtrL7kZ;W_vh1|MYb+-!KkEl`!?Fvl` z-MW^&diZD4dfbodLFT;F+;IVSo|inMJe3#aEAnBfVYntzeeqN%^1J&1%+)T{~m1vVd|Uf*Z5Aa9nhKgJ(F zmB9{U-)CQBT>W7&mnu%*wMS_B+DKh@9pc(>0Pnbex=!Xux_t^AUp(5E`&@c#qO~bI z*w9(eIsTk}#7*(k664WukQo{xV>?o$<)}ksmCileSGWJo!8)I3H$xHg5 z51&W=^tobtB=C)nkS#`?nIL=Pxypi#qj}dCR@I@ed7X}+ui8}c~r{8Pi z+ltDzOH$`5;~$~fkDhe18P0P9;A)E(QYz7Q(!p9#S4_DlgE-r9zR9PlBxM~vgvB__ z`!f6HV=>_!ckUq7FFENZ7bZ0}k&C~de2$mIC3`w>d{aAJ94{P@PIhBaS@(*6JUYT2 z8*)1C)_HD`N$TnOaE1GJsH#K!B43t4~W*9j=tR6fA`$kVf5GAt*`oCwe+W5C|eYqL^0Jn;MIXF z%{)>L;&vtH>-D@Wh0_X_Rb{_IInZHzV7*?PQ+7_)KE&M0$fd;L0?&f%tmDRr4{3eO zW$U#xKi3nkmEOpHG;Ny>vTCTU4m7F(I@?~VT&%4>G<%>UpmZqhHg=eh$ zTtng*57(-C*1v82x#Qb~(v8Q28q&ZI_Zs)MSI#>)qC!`*HqD!@+OTWgv)fjp+p3G1g$p#!gTjaK zdX;*midH6K?(SMqYwvjun^WFjU#XO~Z_Q)_z0m#>UtjRfcG+nB+{PMY`SQYQn1Sj} z?q8)W-2zLuqj=GSaWn#8>Phh+fUHP3ZvuvZ^JJcBBp3n!-#(%}mX5WthT|zDE!<{| z7K23Pq5;6rm_fzi{RniB2f>?2Mu0gls=*+lCj#uGXQORHMG}07s1O>#F66j9KEw}i z;0ZQ10vR&kTmlk-jsr1B{^S5S0|EYv7tXae-4HP7uMoN)0{m}LSQ|75Nud!yhqR!Y zcx`QM&>;gYf+r5V=||Dh!Z{9P$-%^(aM6#~`L-dxr1KpUI?A4($q6CFUu5dNe0 ze-a1SGpPg!h7dprq~W=H=cTfliV8>42sk>0W>2B`|1KojheD?W_)w@In2r_{w9f{I zCz3Zk`~QO2*ubsG0dyQ0Pp~pafVm`EM4~6$6l$P{Jff|0$kfan3bjC*nd+JwnCe?t zSiq3_2Te_XW6de}KoWsW|Bdzh4_5b&*v*C@QMsAT2{d95!Se`>LIVA@YB=%FTA+XA z`y1=|XDzTlVj)~Hkj=gQuf6{5;x^Ex`A_q52miD`fz0iB8n?Oc^xWm189)eaZEx`_ zbn}e-iUPLtf(5u1&C1-=p3#$;cucegBxo+nx$yOV%#~ld1XV*3?VJX^-HAR2WZ2uV z(u=0`@A`D~l*1KY>5k5u3YS}Tt=-W(r7?0)@^<^oozttsl&GYk_bxY5Kdf1bqO&9w v6Giu{-E1xE7b$V_+}miBSdCuGvkc<__M8EVOBBhHB}+m>LbCLY=Y4wK?~kwVKJMeb&hxs?-|t+n40QpA+b4G8*k_>A)gsvzul2%)`{+ zGXO-B3u=_{$d$PU5YEZSn%Bo%6nB$X*pi8HtvlN(j>)<>oU^ms-{SJc!?CVM_kGpq zD|mb=fG|Jac@dmEE>EYKyFP!dPw~V2q0~L3V4zJ7VgZs-lDyFoU9CnK9lA z{|)s3FeAcdMKT|ltq9$x0m1;iQ-6nS!_cqj3MXxM0Gt2}LS)A!gg7{$QQxIe9%xhs z9ymYp6$g?4Aeep95(3@bioPky5s{%vM(c>C~+;D?q3rCl<9Vk3~u)C^5I%(w`)RT2PH zm)f7N?K9(ykBtnC`Hctjzt`uk1dC{xK3DmG+T--QM)Dliz9M@cHh&jC)x2t{F@ZnKih0C+}OXW@w z`v&$?T!Pj1rsQGSiPMN#jg(cf#BeEqd)~3u;mM}Qyx`i%uR_AH()f-rz&vtJ?~1BK z0wCjWh+r=QKw`~Oyt$4L(2|<}2>>cTD<8d+q=bD10syO=GrJ#HY?6E~&#jfte6C(u zt0YX=Xk{+Bqt-;ma^pzUR`Hw4DHbX&wa9MK#}7nQbGD=p$&@~a?~@uIls$T8lCHGT zTRHoMa^-n3QHw^99AP{1;ufE{Zb&OgDJ@PELckbai^>O2T$Dcqsc&TD3l~}jCU{~r zzv(gLjjtXx|H*H&$^=ebjw433!=?SMd>|aXa>3gB5?)oiL6JC$H*$+NBC6x}hAF7kW)t|J z9m26ua#NsV=VV?4pXG3D@mM_ij@FcBscZ$vT`c+>{Ka38#5<0qS`o5Kbu1s`Lk`}C ztNnHRw(Z$k$NrL*^Gd|*kZ!s*;vl|Vi-WL}unWTUV)XKz^G!Qs$eCE}Ne-py;|QoE ziVIFnDC2DAI9^+BdO1=ikF38qj1|k>fy+;lJzzvK8x_5E17Vq#bN5h7VfH)F-HXT@ zhwUgiVNOuz3x#rqq3K#J8H#9LzFuDEn{={2c`*Pw!K@JLkKSgT`X;p_=<}wD@rmf~ z;gVA4rJ@@!K08%{R8FWAD3_@~)3CQUyiHAObb-A`sHOQ|-+Z0sir>Ak`=mm`YuRLE zvRiUw^7vgB*AQ2;PWD|1mwT?8?;UeHb=$`Ek<+I_v3H91It$fZpB3&YZpDS;;+@(K zdF54mt)Bf!lqxwNW0P|pljlM#d!=%9yW%SZX%=tU#c&gu)D60B?{lPNX$l**VOcE< zdIIZ=4!P^c^-J)}8av)1B>n2);EeHy%mc04Tcui0=!xi=={@WUEb=RgEZW->(No>y zGtHP*oSy9AhtjjmvvjlOkrd=&s943GibEAK6}_QtUrgT;C)pEX^RMTnC;HoM=PBRw z=9RwiyZG%Idtrv4Jsg!__&(xHGl%#&=sLN)edgTIoh`h8iiEm=ymq_1zsj}0Uhw~9 z#8NW#s4ujm8iU4JvG{?xr?d;JWxCeN2BzQy;MMf~vb=1*A#83ixqIOEV` zVaGg#~3WwEx!kV?Q+q$;Ioo@pT$VAd^FJUK|pMWk7 z+6G@N*C4B;DJ`9n-?bZYSO3eQQfKCI=Av#Fcf@1azbbAvzVOP^{k?%t7-9b0z+hZ3 zaVn!cs{C&G8PM z+2JN0Mjo7#`(m!krk0qEMuRP#pvsP;1yp-=xo_t(VjQijbFbzedRSI|z~tIkmRs_| zzW)8E&_4stJKBW4G7xjb>97-2u07S9vv;%V`p9kjaQuUwaZ+YdW*$z8oKmXu9#*!q z%+XIrCsAsIJw|!0mU!Xy;)v!_$Xu^Na16FRuM}78B&~>r-qB$lQ9i;d$5deszcU!{ zTl=!4DREZuWEJOuQ~85O-Q_Hg*+EE+^)p4ySZAeheYhvC!k0y!={Us;;FYATIt}A- zuHORLec$46(H*yLp>@u>8zvVfHSws$-w!_}DiD%=UHO5jok!eG?^a6o;?lWyihn$? zDIXhlckt>wInSo_^n5%}_Ii2}Gnqe0E+&@qiXwmuR{ESqQ+U(U)H80A6kIb79 zf%9=Kr7f>pM2rYV(?^=0aC^Vq+>^Huk#*XW=eAmOudMomc28GLfB11cI@{U7;B zQ-8QzAye z?YX)QgQSmUMA3ROrqjb8(+}^Keqk~C{I7xACr^BG`h2tXW#7w|fwa?Q^Pou#Tc-nA z6Ux=gqvW7&R`EYy$;(ndrfyqZ_A8PP|3nOJFp782&dJ(|nq3+>oA{}~w;(&q!3^~- zt&hEkT}cb_JmgvBk8aC0Q(}I_mU%5U&3zn?_nfJue}^pk^lFtIEJ78dY$NHbLzw$V zXp^Kx-n6?(G4s3qJ66M%C`$TCPDSu}Lmjrwww;{p%X+9*d9fjae!jTBR?Bh)&695p|Np`_A@%C6Gkw(!c ztlQ|bD0BfD08GqSbOJGm#02}0{K-@lg#WAt0w(*SAnr!?Fncs1cZ-)AAzU~M!*noC|vOF)r0RvA`FmlWAHx@MBtF&>xaZy+5F>9 zprIfEOeP%(g@%WR>xUcY(-{6xxUsP@6o!Bz5PAX&y%08)Nnq(wLo|OgSdl`A3^JWb zrcuG`j07KAC=&${1pA*XDD;16sUiPVN>DQ>i$I6M^|Nl)Xlz**5m^jjZ zpQ#thS=L9?WiG40+mRzvqC`xB>H5sFVffs4KqX-!S)&$7{TGz=zWF=INHY2 z0tT}-KpPtw|HfL;h@lh`mH8X%`(G^lkJ$BrpwI=Ltw;=V7|GX$L8E~G&KgPnV=RW& zf8_fI>-)!83~m01g$ja!uJ`tT_4@agV1U-ee}`9~{5$?6s$k|Bg5ln!QST+V7#p3i zF4n&y*YC(C3v7{K(X_L&aAEcMczb*MMhV&2h)M`^tW<_XOB8+kL0OWLfY3%j)E-d2 TFC+3}9cE|kU{!4CefEC<&8td2 literal 0 HcmV?d00001 diff --git a/assets/icons/PIN/Pin_back_full_40x8.png b/assets/icons/PIN/Pin_back_full_40x8.png new file mode 100644 index 0000000000000000000000000000000000000000..cd1301512db1c06700fec26d03231c63b9be75d3 GIT binary patch literal 3641 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 Cv0zgG literal 0 HcmV?d00001 diff --git a/assets/icons/PIN/Pin_cell_13x13.png b/assets/icons/PIN/Pin_cell_13x13.png new file mode 100644 index 0000000000000000000000000000000000000000..1b1ff0c2fbe6a1f30e974340f4b54760fe504677 GIT binary patch literal 3593 zcmaJ@c|26z|G&0EwyYr;Bc94KW-Kuo`!Z@Ujcrt7jKN@*#$ZNBNh#T~Wlbolp-qu& zl{H%w31ugtv4mvlJD%t1`TqWRzQ6mr_nvb;_x*l<-shapdA;rxw4JrEpqwB80Kzs% z3(R)A{qXZ}-j=$Ob^st~PBb@1+nAe!s5FWRrx z!J?<(q3pT2^$+V+Q`u7+9n4PA$lc;2p&F8~jx^B8sR zx>rCR%LJ^+TUW{z>G}+2%^g|I2L#7s6GcrtfXECp^)>*c&kdOGlW6Awp?LDNx@(7v z-Ko(PNG_nRHMKqcShu!hMe19*kj44oQKivW0gudZG6%)H1;)YI=~>DW$SEFFhY$eB zt#!TJ(l<_=nj9aQ^qvY}e{aa&@}H-Gjg%IKwyLgi^8#Xao$P-1iHTkwY7^JPpj!Xp zlR&>S;5)SDrad5#cS7)O=vpjOf5T*7?k#k)p~7ClUAyK~Ja1KNjl~-M(jK7<$40Dh zzHSYK&I4yMO)^UA3Zgd8;K;$HnE0tyUNb0pbxL`wDf--I{K2kKokyqCrLHbuuT-GH zwoT0Em?R6Omef)4>2t6J#k5U<Kzn-O7ywj#*>mb{iVUie9{?=!&L4Vcx>M+-B&$v&`=vrv zoeVc_hlPpI{yIZ3vmN7+dj)UpNi&sotb_OQK7Gg|m$y4}M6B#3R9|>%Sp3xa8LG?< zk3G4s_EcRG;5BXLm%u5(V|IJS_klb3WisMb#kh|E-FUbw5 zyr@BwG>AK8@-uOu83en!aka`CnsWZ}ah~_wK_<`dD#~4L%nR(I>xjBVrsey0$(8Lx zL_W(e>N@r%hz^8bjmJlJK}Ec;eZ-x*cG=S73RX_FNg6+a)pbtL#VcSB2TRG<<>J`< z`?+HyC1&|gUle;4a3L|#8jHf3-&L7aE)%chcM*uX2z~VjIQg!9nM$bmT0O%P{wNV^ z#ZvvIv`;Bl<@6sS67I>!{UR;b$L$1_R1#q}yKMZC14xZRheD%nF=94KbtaM2@_C&9 zaU=_ro>ZPFnrMH0z2)_Ixg@+HW)vlmzaLYWB7RhtU_8Nl`zFjRBk$hv_Tt?4{P$wu zH&57*@`BM2hs(thIzgE#?OD?1t%Vu|J#RCKKEzdD$TYoD;8WB-%k;PD-Tq&8PESoo zeGd^5z9bygg!DWh>o0p&wrEeeEF=SUhwoi_Mzf>V2bg?@&kfNV6esMVl|x}tNpHkc z;i=B45vf!69GwE4jC+{(b~)a661{)gIsA^5(-ZVqvA}!j`#r@9PA`h}N;@zim;`j^ zarc56_st7G@xqTUMO)=vLKZmU%Nu3ml%yMBgaxcwFU^@}M&190t>?+dYqO|ezIFLv z$XS$wdEh;7mUohO&g7YPE|JDZ!}A6ovyXNtbqIHy)!@-E)_BzGSK?g~QF6FHw7;g` zbB;DAJvYm|wtK=twSZHf3V{x^sfUGo=5?(S~&txT%-E$Ff-_@hGg+hw0I zU51R2H;b~@lcn>SFz9cH^CZFs3hN6S#%m6?r}$@jS9X=Xqqns+s}HjJSS_>h20hvS zxwx8-RRbGw(YGzL8;-{6#Wtn&r-ilhrP-#fvTisVIWwJ?oj*D(2*V8UO@;O6;BUNmvJB!T`eNt3~f!F zko#8I{q)^(LDq|`!IF=p_n+Dj4dM6KZ8fvxTijkF*rwm-SFxjK+QxE!oV zwhoA?P$bG`$gG7+9y|oQr}_1GnFIX{eO0}eHSW6ZQyssMP<-wAkpaJFv|t~WUjQZm zKbut%S#hu8Jmc~Y%Y}4ty2O5gxhv!Kef5YdV}aaL0h!v_-oUDw1g{pcIw>5q*kqCjS7$R7KNBC@T5#Nx%QXnV_={J8w%kIE~K8eX5waZX*) z|8ykW{HO0Fd#j*EZ2^0X8Z$}u`g7$aTW5>j&#camXFh5eq-3XL7hr^mX=Q33w8{^Z z+k302B@2%;CrNMQlP|wn9amlpTpExHh(>i4lwnHIBGM?xT{XtZJtr9z$ZF(?_u50= zTVL0dcU_PUt4@4~u6X#QuY%#aFbuA>d?BqI>mU=N33bC%dNGLe-Qlgit&h_-(W6+5 z)1n`9a4{Ye)qVT6x!MI6oz&u#mR54<_Y=?YQn*wvC$?XD&q?QVhh$RSSya~D(jO14 zDkeu=?A&|8mYJmf{?A9t-^|S*X9{P?tX0?A2S=;@Oncs5ninpSUx=HKcPAbFOurTC zw;bPI*8ZlQM;E6%ce3pnYhdw~UcpLe&N;VM=gpG)B&9!VE;HmQ^~52OSEds${}{Rxc6JQ?81X)1 zkhzN5$nbYN?pEz%-kEDGL;r>k0huQ(;>hkkyMz>yZX3 zyE%WAvUE!<-GSmw55dt0fT1NJfC!FKWRcq89?}qHC*VOEo9>5|N=afxKLY%hDXc9TWKN+GK!-J< z8h9-&Ezn^DO@bE==Be$C!>fZ}S}-UC%DE3~Ko7%V+Hj}==hG=V2Xg(0Afq?-;3kHF~G&l&2Kqi@vV`z{Am47Q(5CZWuB9%_0 zkU`suI8RCt9RcQ;{c9E^>OZpNz`s|Dvt|$mjtYTlYHiQzH_+Dh|A&%D|DXfu7{Y)3 z{;P1HBa=#iUSh0fZq#=_NCA%fxZ+f2&SzG1s$-( z;fdt!$iY7;wzhB^av&W?#uIET5MYjoCXwg`*VTsV=^4Ou4x5@;LZO!CW~Mq82B!L! zmXcl{>#<7uV}wy!_2I{hwS2#|&h9Z~xC;{|<2qXuJDQ@p1?|d{D+}$^aG_fmogF=ryT8)MaJ&NvI`G#e77mxqks*S=La{dukrE&;${5E7YhNZV|yf^2TeUpLW9^oB3W>r0J)j&sM41Z}4Q-)%~ zm$_dz`$Oh{Ga1I|CH47{8)tCjpi)fSWDCD3jJz_bve_7QZX@nM=ZMaOQyS7&=do8R z?ZGyUtf!bnGv(%#5s9gD@Oh=w4WXo95rBQkuHN>^_Qx#&_2J)mcwO4SOTEQp)RDaoh>Ta^E^=m-@Fv~rEu*_up&gW9>deN-s ziq-Y9ytr*<#yj`b8;F~38;jT@d~0{AiJW6SqL=@18J~Rau8d*v^5-x;k;x&6Q|G6t zvfDg%=e*cmeMyMlq)AW`r;Lc+QMW%j< zZfKPSO`4Q`>?z})3H?nV_|^5R)t0d2^AC^nz2^-yetK6){_5*odM86i%q=1{&%aZ? zd@nNMNnT;+u`huPAna-!KB5yQ>V54a?$ou*<{nqD@@kDT_;IYRbG$BI5qmgDTA@A% z#}VAe8xtWpBY66v_=|uG2?>^1Yb@%n>WoUcM3tb6P~=6}i@(50ckRkmLnKxkn4X9S z30MoZ+BfCB$~mg1{}PpSPeSh#ZN5HcE3B-V0Jqv<<&W-1YZf>Z7l=EJ& zI2v*DgcT__$R6TwxkyqcFhjL1QwG&ipqbzOHiPIxT=9iWF{*?_>})-pkzUE>d)QR&=Pe9TSX+ zx9>Rao?q*}ap5Ok_=FB&(;eW%%^W?+T8S5Z{ zHH9F^S!)Lrz1W-Wo#>rCai)z&^!0MVc9Xl-rM7-09Zn`h$m zyR-araJ3(+``2TmZkuPlIvR5*rZKfIHMB(_)h2a?eZ$$%ndQ9X{DE!Nb81>+n$+VG zV6Whkxib@y+4AIWNoGkyP$p?mYD6mjK-YmK2XY^5W_?C@CSzvXto|}?S#_DT+@aXI zM?$gZ2;)d7jS<)y6hN(>E*RE%S=3PUa>~U=!RHn|dr?>R&as3RiL{_JSqD<#)k5;k zkd~Bj`EONSkKZljXFkguDEUZw|EU%ED(v2xjaH^Yv)fA~$K?Cm3U=g7>u7diX5ov) zgh|Nhpwq6W*J}3KJ<3wc>4B1(`WsFT)wG=TKo}wkKijAH3||}a-VC{Ux%N&ico12u z65bP@9=?4&XYI((mW}uyb;GO$nR#0Q51!{d<2*Iz<R3gfy%dUn=9XJRt*7|z9+<|e1v7*t#9&=susfnA9FS_>hNWb3`T96Oh zF8~vOpUJF@s4T8L&%FG@d_GNrA-PXz_R3g8Zv*nmD4lmQAVWKAEWzWX%7j;7-?V-_lB*@ervfFOFkZhwUxo|Nv%FettFE;P^gNn+d2G=LD zol}od?7Dp$rGCLhC#5L4xrJQ%{lrteG%m%{{HkEz1 z^hcRR<-~}Kt$TOq5{uN=_xWhWo7rjfbip*4z2aJT|7eY$N%z}Jb0Q@Q^ReR!?xdri z8Mi|Gmg3sV+upSC>Ed)}Pqsc`_b=Q!l)oI3ea@}Ld86cBMeRqmzMi^I!DkmmBNe5O zeC8z2R6kZ{y7PRW}j-snRmo?p03w+1qEgFPn3w{TP&hf%|oJ z4~0mWL$Q0&2RpJsK(=N+DHnOGhV$iGezxLiMa$ZXU*R0sC_b=JH{K;DH+w&Fes%0Z(rBSqVNSNK z5%PWd0Bgm1J>B2!xLb`6sw2lpmhsKH!hNNA@$+Q7+gvsEJ9x#jU$x>`39=Mv8BX08 zM&Cb4nI^2X%(@tqGQQhPAL-=0JNMJCzMKBx#|JI9o2(kn>luX#vf8ITm8w-sKZ2HL zWrKykSr52{#53=&*Y<6E-TqVT>$&pH2ZVak&<~G#kB*nFVjNMS%Q>6LzusLQ?y_vP z>{hW*iH*-zQc@6+uUYVFPZ}P-k#=Q?~!VV+g+6b6F=${MBbx8B_Mqtb= z{&B}0A;G>31{DE;1P2Fe1#4?jXg&~_zP>&L3Wvbqnp}h?okeEgn3`m|%5McT0v%5y zQW-=F8MLK{^P~hakYH}G|H*5O$ zg#X6-e+ttbSyTc9L!eUvX?X71d8=%>q9RZ<0**nUIZ`MAzbDe(m%^aXeJNBBTw4nU z+HZry6Ukea1AkL&Y!FstIs-?>6RgaTV6KQ3k?4g$>7cYt%u%qz+HgG>%p9es1Ban3 zOiiFrb8VEquKsVX83i9mB9IxsxnBR}!vDzKDhLvl>)DJzBL)$?ENB!G=63R|OV+mP%$Mbu7e(xV|@A;f_?)$#(@ArFM*L_{*^EuaSt<44aW%vOA5U@a* zpxNW@orjl;{a(6JI069tNCFaRYk@?9C{(g1!4D4r^!{wSAWYJ#g#OSfUdYk7Z~k$b zUjzVFWb!r(JLd`C1hAKdMGPCGqWK-g#P?;P92ze5@T0P$M{^HVdKq1hJ{{w5R_D9? zVByoyVAkB+#>b87sjR8Z4o0U?_&yQk#K}A#Ko=dQ2k(=Qw?Q?u)P!@2qlURb!jrA9 zym%S`V4jOX52HOY*yMOf1~>sqkNQE8rjcKfRkq4b04Na{28&GX;YdIO&Fc2eVnDML z@W}3o2S1Pu0Dg=RV=z!G0L=cd(B}dAijoE;fxf)`MZ7>P2atZq{2-^{3&71G0q|Mpou9$XIm2ssfWSCRf{>vb5T0(V+6I7hI057V(RMD7C0DLScinK2 zD?A8>kOnE00v^YOJsxbP>@3Apf^02Tc-#9ocEmKhxHN|Dwu@?Yj z*1BG9>lh?VO^%ODdQSPVel+H7`_7ZW`U(p}+toKXxdCD8PFBC`#6&L_rHSKFK%H;V z8KB=0@E%%o(H!8*J5H%h`P41Gq#yx+dBvvQ`q}QMt$y`k-#IvA1To!#fMM8@+6|dK ziGZ+|7L2h907-Rg@rEiKKzmxj7ywj%l{$MrS<>~E)&DO2kZ5OjdzWQ@8`cGm1-nyUk~r&e)@<@CU;-Ph;aE!sE)wYu*lhn8H(gC zH>sRgQq@=ZxQ&{5MX?I-=zZ>Sec%pW$@DmGFczhCGrRya9W8bW+}KPl;4CusNpwLe zE~-(*bYssNt|tsMgJ9P;uUDHxlOxJbaed$nFnoSrUgr9nT>mbbmXJ$$YMyVGO!)ys z__Msiu9IH_Xh7)oI9zxaRM7LrC+yi9S54inVPuq>BybZLZO3?RoE+v@ptx*(4wl7x zkTWJ+be8wrW#LzTml6`pF_swQeWh8&a*--tC%(wb&{uzflkVG;D+PY9W)DA;my+?roODFJ4&$HEsifKn^4E70#2CS+ME&m<6AzKrvh zg)>2Ei4_S#2{t!3T3(M=h`}49M=kmC4x$T^MNVkr4JNqn-i8^c=N6x8FUtAATO19) zecFPU8)yr$yILfw6_BCSo+*KBEl|tvd6z-(BCL8trfF4tpCb>LroBt+_WinhdTKiI zN6=n@D*};CDEC9szS0+@3#BTgA?cR)c;2U_H`{A`gvq9R-4eP*cEB82IT9kC_*NtZ zp5mAimNHdr@8IuX(8DO+WB<4uOsfYFugtYL9z;N<2%#N{;mh_t*Bj z&r##I1#=Yz*lv&>Qq%!)j&Y!H~sgx8OAi<^4n#>>Cau}%fuh~ z%aY$%y{sVeJJsJo_FjVEG`#x$k&r-rohq*|q}GH*HRJ2D)X9X~QHde6?N&JcT@{A^{N zGWTY}Gh3hCFUc%v2+Sl7iH(ZIAMQT9Y)9&c&Th`~&t}Z-n$umut|+Y#S32d|_KV2% z9;Y1-q0$1{0{tk}GX*1BuZtRrUQauD$$H)K&tB4&ymvC8RU|DiP1257c)gHxJGeDv zLgsr__tW>w`I#>=2TMK?KYVUOG=@Iduu{*IZE<;xU>W_GU&V}`ZyU=l%q)DhlrRN3 z7kJM3+(yj-n2aZ{3RjSvSI1lvuFlapQQ&F~Lz2ArtY0%a==@JDvOPZf%}eo)^0yd-cVQ z_wori%Ttrc^^%LSYdFn8FV&1L@wdF$;-_WTHQJOd5A^PfyVA)!BpgP*w`Mur_KY`r z*xWC=Ql224F1Z#ecK8UaSpD0nay#02+Nx?VbKH5ut0rzCzUapD;{!g=sDWNgA3wAo zZZ@+ryt245f`0X<=|Y+aP4pn&+_mwBz6Qj#F@Me}zYNW+@eKP^8m@F=Fz>nK*pdK?zI9eHHo{sWbFSR1NC%2hAbR z?Qd&}doD?Y)FeEzt$g&PuafS(Fbu9UeIcP3V<#D;4s}6SdC&>--Jz}Ct!1fOwxbxd z!=evka4`-Y*?speQst79R!UKFODn1L$LZ%dacqi*1Is6^=ZxdUBa$huObYXU>CZ=I zm6M}R)~-Dv%M4z$6*gRk3%(l1sl^Uk0cD&6q9 z0H#_#F&A;ChV}JEezx2>IrG|zUtuih7%remJKiZLH~SD`VQu_U(paHKVNSNS0pdgY zAY;{XGu_waluL~lvNOj(lJ?!Q!gaM}>C05S%X~HE2YA(eK&j$n38EBX9!A+3K|MS} zp24rS&N=Co(tcRY9PeVizqsyG-{b%B=SOvy+l(64n_1ZklJe*Ml}c61KLc0hB!l?B zTMoJe$I~Bf*7k3G+r2LI?PB@%V|+bv_@`UFTjy(MA(kND)tv3*U+=Gubep%C_b8ev z#>QvM%gYML)GT^*B#ji76^eGg4Rid(nDKuwHMBLlak3M$**CvuEvB=slu@)qWj!c* z2yaqslCSPyAQtXzmUIk+vMO0sLrpdE>4!EAw{4fY)^SaR?`&4}r$V+jA*+{{Ho|q4 z_ObserD>)ZnjP7b7KEkZ0V5BxJ04^~#CqY;c&rEGd<$L=0Jshj>@hTql_eZUCaPn1 zFzR$7h0O*4Jp(!gi}S_PK<;=i0to?Ty{H3&2p$NqleU$H6$Od+CZK|;c)MV0dt9(D zPS*o$pbyfc!`T8vJPiw?6a7g3a5@6~w=SGL-!VhLpuZtBUj+C+L1CCCs^dMdFn3K)EKU^!(||!CQ1*RH4SEa?(}Y8HLH}G}wnM6iCmd~J_K!RE z3IX<}(I{{TBq%6IJxEiXO!b05b#-+i8ZZb9rp897`7=l~EM1M{ulQTR1n-Zd5-2nR znFQKV#JZCMXb3Pn*#Bffr2H#O^8e?g*k=ZzV<`}*y2egczkya(|38#S{1@#{L*xG& z@Bb<6Z_l9MA!ximIe>~|*UnRM#}x&Rq~ftOGS!|;_WOO1w%%kK+25N?0l_rYp`b%n zSR8@0V>$dc#mWk9LGq_zNjSWP2?ER(Q6~^Q;7Bc`rjaR9`S)2BNHb$2 z4GmLGq^`E^Z>|X$7eK_5Xur80|K%S2BX_4Eh!nPG6Fij=i1#p~l8K~IZDKdj&h+2rWiS41e>{oZ^Hg?oM~n|nus@7lwwCs$ z?D1C^P>lR^nmv=VFfp>H_q)5fd2lQ2GLzyiQ{d*V|Ea<2ASCPtaP|Sxo{WhCHW08d LwKgd=cDwXHDN#*w literal 0 HcmV?d00001 diff --git a/firmware/targets/f6/furi_hal/furi_hal_power.c b/firmware/targets/f6/furi_hal/furi_hal_power.c index a211b8c5..e34ba46e 100644 --- a/firmware/targets/f6/furi_hal/furi_hal_power.c +++ b/firmware/targets/f6/furi_hal/furi_hal_power.c @@ -89,17 +89,17 @@ uint16_t furi_hal_power_insomnia_level() { } void furi_hal_power_insomnia_enter() { - vTaskSuspendAll(); + FURI_CRITICAL_ENTER(); furi_assert(furi_hal_power.insomnia < UINT8_MAX); furi_hal_power.insomnia++; - xTaskResumeAll(); + FURI_CRITICAL_EXIT(); } void furi_hal_power_insomnia_exit() { - vTaskSuspendAll(); + FURI_CRITICAL_ENTER(); furi_assert(furi_hal_power.insomnia > 0); furi_hal_power.insomnia--; - xTaskResumeAll(); + FURI_CRITICAL_EXIT(); } bool furi_hal_power_sleep_available() { diff --git a/firmware/targets/f6/furi_hal/furi_hal_rtc.c b/firmware/targets/f6/furi_hal/furi_hal_rtc.c index 4d45c748..1f262692 100644 --- a/firmware/targets/f6/furi_hal/furi_hal_rtc.c +++ b/firmware/targets/f6/furi_hal/furi_hal_rtc.c @@ -163,3 +163,11 @@ void furi_hal_rtc_set_fault_data(uint32_t value) { uint32_t furi_hal_rtc_get_fault_data() { return furi_hal_rtc_get_register(FuriHalRtcRegisterFaultData); } + +void furi_hal_rtc_set_pin_fails(uint32_t value) { + furi_hal_rtc_set_register(FuriHalRtcRegisterPinFails, value); +} + +uint32_t furi_hal_rtc_get_pin_fails() { + return furi_hal_rtc_get_register(FuriHalRtcRegisterPinFails); +} diff --git a/firmware/targets/f6/furi_hal/furi_hal_usb.c b/firmware/targets/f6/furi_hal/furi_hal_usb.c index 881081b6..7ca8ffdf 100644 --- a/firmware/targets/f6/furi_hal/furi_hal_usb.c +++ b/firmware/targets/f6/furi_hal/furi_hal_usb.c @@ -1,6 +1,7 @@ #include "furi_hal_version.h" #include "furi_hal_usb_i.h" #include "furi_hal_usb.h" +#include #include #include "usb.h" @@ -189,6 +190,8 @@ static void susp_evt(usbd_device* dev, uint8_t event, uint8_t ep) { if((usb_if_cur != NULL) && (usb_config.connected == true)) { usb_config.connected = false; usb_if_cur->suspend(&udev); + + furi_hal_power_insomnia_exit(); } if(callback != NULL) { callback(FuriHalUsbStateEventSuspend, cb_ctx); @@ -199,6 +202,8 @@ static void wkup_evt(usbd_device* dev, uint8_t event, uint8_t ep) { if((usb_if_cur != NULL) && (usb_config.connected == false)) { usb_config.connected = true; usb_if_cur->wakeup(&udev); + + furi_hal_power_insomnia_enter(); } if(callback != NULL) { callback(FuriHalUsbStateEventWakeup, cb_ctx); diff --git a/firmware/targets/f7/furi_hal/furi_hal_power.c b/firmware/targets/f7/furi_hal/furi_hal_power.c index a211b8c5..e34ba46e 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_power.c +++ b/firmware/targets/f7/furi_hal/furi_hal_power.c @@ -89,17 +89,17 @@ uint16_t furi_hal_power_insomnia_level() { } void furi_hal_power_insomnia_enter() { - vTaskSuspendAll(); + FURI_CRITICAL_ENTER(); furi_assert(furi_hal_power.insomnia < UINT8_MAX); furi_hal_power.insomnia++; - xTaskResumeAll(); + FURI_CRITICAL_EXIT(); } void furi_hal_power_insomnia_exit() { - vTaskSuspendAll(); + FURI_CRITICAL_ENTER(); furi_assert(furi_hal_power.insomnia > 0); furi_hal_power.insomnia--; - xTaskResumeAll(); + FURI_CRITICAL_EXIT(); } bool furi_hal_power_sleep_available() { diff --git a/firmware/targets/f7/furi_hal/furi_hal_rtc.c b/firmware/targets/f7/furi_hal/furi_hal_rtc.c index 4d45c748..1f262692 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_rtc.c +++ b/firmware/targets/f7/furi_hal/furi_hal_rtc.c @@ -163,3 +163,11 @@ void furi_hal_rtc_set_fault_data(uint32_t value) { uint32_t furi_hal_rtc_get_fault_data() { return furi_hal_rtc_get_register(FuriHalRtcRegisterFaultData); } + +void furi_hal_rtc_set_pin_fails(uint32_t value) { + furi_hal_rtc_set_register(FuriHalRtcRegisterPinFails, value); +} + +uint32_t furi_hal_rtc_get_pin_fails() { + return furi_hal_rtc_get_register(FuriHalRtcRegisterPinFails); +} diff --git a/firmware/targets/f7/furi_hal/furi_hal_usb.c b/firmware/targets/f7/furi_hal/furi_hal_usb.c index 881081b6..7ca8ffdf 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_usb.c +++ b/firmware/targets/f7/furi_hal/furi_hal_usb.c @@ -1,6 +1,7 @@ #include "furi_hal_version.h" #include "furi_hal_usb_i.h" #include "furi_hal_usb.h" +#include #include #include "usb.h" @@ -189,6 +190,8 @@ static void susp_evt(usbd_device* dev, uint8_t event, uint8_t ep) { if((usb_if_cur != NULL) && (usb_config.connected == true)) { usb_config.connected = false; usb_if_cur->suspend(&udev); + + furi_hal_power_insomnia_exit(); } if(callback != NULL) { callback(FuriHalUsbStateEventSuspend, cb_ctx); @@ -199,6 +202,8 @@ static void wkup_evt(usbd_device* dev, uint8_t event, uint8_t ep) { if((usb_if_cur != NULL) && (usb_config.connected == false)) { usb_config.connected = true; usb_if_cur->wakeup(&udev); + + furi_hal_power_insomnia_enter(); } if(callback != NULL) { callback(FuriHalUsbStateEventWakeup, cb_ctx); diff --git a/firmware/targets/furi_hal_include/furi_hal_rtc.h b/firmware/targets/furi_hal_include/furi_hal_rtc.h index 8e79c95a..ee8eb1bb 100644 --- a/firmware/targets/furi_hal_include/furi_hal_rtc.h +++ b/firmware/targets/furi_hal_include/furi_hal_rtc.h @@ -38,6 +38,7 @@ typedef enum { FuriHalRtcRegisterSystemVersion, FuriHalRtcRegisterLfsFingerprint, FuriHalRtcRegisterFaultData, + FuriHalRtcRegisterPinFails, } FuriHalRtcRegister; /** Initialize RTC subsystem */ @@ -67,6 +68,10 @@ void furi_hal_rtc_set_fault_data(uint32_t value); uint32_t furi_hal_rtc_get_fault_data(); +void furi_hal_rtc_set_pin_fails(uint32_t value); + +uint32_t furi_hal_rtc_get_pin_fails(); + #ifdef __cplusplus } #endif