[FL-1968] Pin code locking (#788)

* Gui: code input module
* Gui: fix size to fit frame
* Desktop: PIN config and lock option
* Gui: code input: cleanup, offset input fields if no header present
* Desktop: move code unlock to desktop_locked scene
* Desktop: fix unlock with back key
* Desktop: bump settings version
* Desktop: correct scene usage.

Co-authored-by: あく <alleteam@gmail.com>
This commit is contained in:
its your bedtime 2021-10-26 21:34:31 +03:00 committed by GitHub
parent 400d672e81
commit fae8d8f23c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 948 additions and 43 deletions

View File

@ -41,6 +41,7 @@ Desktop* desktop_alloc() {
desktop->debug_view = desktop_debug_alloc();
desktop->first_start_view = desktop_first_start_alloc();
desktop->hw_mismatch_popup = popup_alloc();
desktop->code_input = code_input_alloc();
view_dispatcher_add_view(
desktop->view_dispatcher, DesktopViewMain, desktop_main_get_view(desktop->main_view));
@ -62,7 +63,8 @@ Desktop* desktop_alloc() {
desktop->view_dispatcher,
DesktopViewHwMismatch,
popup_get_view(desktop->hw_mismatch_popup));
view_dispatcher_add_view(
desktop->view_dispatcher, DesktopViewPinSetup, code_input_get_view(desktop->code_input));
// Lock icon
desktop->lock_viewport = view_port_alloc();
view_port_set_width(desktop->lock_viewport, icon_get_width(&I_Lock_8x8));
@ -82,6 +84,7 @@ void desktop_free(Desktop* desktop) {
view_dispatcher_remove_view(desktop->view_dispatcher, DesktopViewDebug);
view_dispatcher_remove_view(desktop->view_dispatcher, DesktopViewFirstStart);
view_dispatcher_remove_view(desktop->view_dispatcher, DesktopViewHwMismatch);
view_dispatcher_remove_view(desktop->view_dispatcher, DesktopViewPinSetup);
view_dispatcher_free(desktop->view_dispatcher);
scene_manager_free(desktop->scene_manager);
@ -92,6 +95,7 @@ void desktop_free(Desktop* desktop) {
desktop_debug_free(desktop->debug_view);
desktop_first_start_free(desktop->first_start_view);
popup_free(desktop->hw_mismatch_popup);
code_input_free(desktop->code_input);
furi_record_close("gui");
desktop->gui = NULL;

View File

@ -8,6 +8,7 @@
#include <gui/gui.h>
#include <gui/view_dispatcher.h>
#include <gui/modules/popup.h>
#include <gui/modules/code_input.h>
#include <gui/scene_manager.h>
#include <assets_icons.h>
#include <storage/storage.h>
@ -29,6 +30,7 @@ typedef enum {
DesktopViewDebug,
DesktopViewFirstStart,
DesktopViewHwMismatch,
DesktopViewPinSetup,
DesktopViewTotal,
} DesktopViewEnum;
@ -46,7 +48,10 @@ struct Desktop {
DesktopLockMenuView* lock_menu;
DesktopLockedView* locked_view;
DesktopDebugView* debug_view;
CodeInput* code_input;
DesktopSettings settings;
PinCode pincode_buffer;
ViewPort* lock_viewport;
};

View File

@ -3,11 +3,20 @@
#include <stdint.h>
#include <stdbool.h>
#define DESKTOP_SETTINGS_VER (0)
#define DESKTOP_SETTINGS_VER (1)
#define PIN_MAX_LENGTH 12
typedef struct {
uint8_t length;
uint8_t data[PIN_MAX_LENGTH];
} PinCode;
typedef struct {
uint8_t version;
uint16_t favorite;
PinCode pincode;
bool locked;
} DesktopSettings;
bool desktop_settings_load(DesktopSettings* desktop_settings);

View File

@ -33,10 +33,13 @@ DesktopSettingsApp* desktop_settings_app_alloc() {
app->submenu = submenu_alloc();
view_dispatcher_add_view(
app->view_dispatcher, DesktopSettingsAppViewMain, submenu_get_view(app->submenu));
app->view_dispatcher, DesktopSettingsAppViewMenu, submenu_get_view(app->submenu));
app->code_input = code_input_alloc();
view_dispatcher_add_view(
app->view_dispatcher, DesktopSettingsAppViewFavorite, submenu_get_view(app->submenu));
app->view_dispatcher,
DesktopSettingsAppViewPincodeInput,
code_input_get_view(app->code_input));
scene_manager_next_scene(app->scene_manager, DesktopSettingsAppSceneStart);
return app;
@ -45,9 +48,10 @@ DesktopSettingsApp* desktop_settings_app_alloc() {
void desktop_settings_app_free(DesktopSettingsApp* app) {
furi_assert(app);
// Variable item list
view_dispatcher_remove_view(app->view_dispatcher, DesktopSettingsAppViewMain);
view_dispatcher_remove_view(app->view_dispatcher, DesktopSettingsAppViewFavorite);
view_dispatcher_remove_view(app->view_dispatcher, DesktopSettingsAppViewMenu);
submenu_free(app->submenu);
view_dispatcher_remove_view(app->view_dispatcher, DesktopSettingsAppViewPincodeInput);
code_input_free(app->code_input);
// View dispatcher
view_dispatcher_free(app->view_dispatcher);
scene_manager_free(app->scene_manager);

View File

@ -6,20 +6,32 @@
#include <gui/view_dispatcher.h>
#include <gui/scene_manager.h>
#include <gui/modules/submenu.h>
#include <gui/modules/code_input.h>
#include "desktop_settings.h"
#include "scenes/desktop_settings_scene.h"
typedef enum {
DesktopSettingsAppViewMain,
DesktopSettingsAppViewFavorite,
CodeEventsSetPin,
CodeEventsChangePin,
CodeEventsDisablePin,
} CodeEventsEnum;
typedef enum {
DesktopSettingsAppViewMenu,
DesktopSettingsAppViewPincodeInput,
} DesktopSettingsAppView;
typedef struct {
DesktopSettings settings;
Gui* gui;
SceneManager* scene_manager;
ViewDispatcher* view_dispatcher;
Submenu* submenu;
CodeInput* code_input;
uint8_t menu_idx;
} DesktopSettingsApp;

View File

@ -1,2 +1,4 @@
ADD_SCENE(desktop_settings, start, Start)
ADD_SCENE(desktop_settings, favorite, Favorite)
ADD_SCENE(desktop_settings, pincode_menu, PinCodeMenu)
ADD_SCENE(desktop_settings, pincode_input, PinCodeInput)

View File

@ -22,7 +22,7 @@ void desktop_settings_scene_favorite_on_enter(void* context) {
submenu_set_header(app->submenu, "Quick access app:");
submenu_set_selected_item(app->submenu, app->settings.favorite);
view_dispatcher_switch_to_view(app->view_dispatcher, DesktopSettingsAppViewFavorite);
view_dispatcher_switch_to_view(app->view_dispatcher, DesktopSettingsAppViewMenu);
}
bool desktop_settings_scene_favorite_on_event(void* context, SceneManagerEvent event) {

View File

@ -0,0 +1,62 @@
#include "../desktop_settings_app.h"
#define SCENE_EXIT_EVENT (0U)
void desktop_settings_scene_ok_callback(void* context) {
DesktopSettingsApp* app = context;
uint32_t state =
scene_manager_get_scene_state(app->scene_manager, DesktopSettingsAppScenePinCodeInput);
if(state == CodeEventsDisablePin) {
memset(app->settings.pincode.data, 0, app->settings.pincode.length * sizeof(uint8_t));
app->settings.pincode.length = 0;
}
view_dispatcher_send_custom_event(app->view_dispatcher, SCENE_EXIT_EVENT);
}
void desktop_settings_scene_pincode_input_on_enter(void* context) {
DesktopSettingsApp* app = context;
CodeInput* code_input = app->code_input;
uint32_t state =
scene_manager_get_scene_state(app->scene_manager, DesktopSettingsAppScenePinCodeInput);
bool update = state != CodeEventsDisablePin;
code_input_set_header_text(code_input, "PIN Code Setup");
code_input_set_result_callback(
code_input,
desktop_settings_scene_ok_callback,
NULL,
app,
app->settings.pincode.data,
&app->settings.pincode.length,
update);
view_dispatcher_switch_to_view(app->view_dispatcher, DesktopSettingsAppViewPincodeInput);
}
bool desktop_settings_scene_pincode_input_on_event(void* context, SceneManagerEvent event) {
DesktopSettingsApp* app = context;
bool consumed = false;
if(event.type == SceneManagerEventTypeCustom) {
switch(event.event) {
case SCENE_EXIT_EVENT:
scene_manager_previous_scene(app->scene_manager);
consumed = true;
break;
default:
consumed = true;
break;
}
}
return consumed;
}
void desktop_settings_scene_pincode_input_on_exit(void* context) {
DesktopSettingsApp* app = context;
code_input_set_result_callback(app->code_input, NULL, NULL, NULL, NULL, NULL, 0);
code_input_set_header_text(app->code_input, "");
}

View File

@ -0,0 +1,78 @@
#include "../desktop_settings_app.h"
#include "applications.h"
static void desktop_settings_scene_pincode_menu_submenu_callback(void* context, uint32_t index) {
DesktopSettingsApp* app = context;
view_dispatcher_send_custom_event(app->view_dispatcher, index);
}
void desktop_settings_scene_pincode_menu_on_enter(void* context) {
DesktopSettingsApp* app = context;
Submenu* submenu = app->submenu;
submenu_clean(submenu);
if(!app->settings.pincode.length) {
submenu_add_item(
submenu,
"Set Pin",
CodeEventsSetPin,
desktop_settings_scene_pincode_menu_submenu_callback,
app);
} else {
submenu_add_item(
submenu,
"Change Pin",
CodeEventsChangePin,
desktop_settings_scene_pincode_menu_submenu_callback,
app);
submenu_add_item(
submenu,
"Disable",
CodeEventsDisablePin,
desktop_settings_scene_pincode_menu_submenu_callback,
app);
}
submenu_set_header(app->submenu, "Pin code settings:");
submenu_set_selected_item(app->submenu, app->menu_idx);
view_dispatcher_switch_to_view(app->view_dispatcher, DesktopSettingsAppViewMenu);
}
bool desktop_settings_scene_pincode_menu_on_event(void* context, SceneManagerEvent event) {
DesktopSettingsApp* app = context;
bool consumed = false;
if(event.type == SceneManagerEventTypeCustom) {
switch(event.event) {
case CodeEventsSetPin:
scene_manager_set_scene_state(
app->scene_manager, DesktopSettingsAppScenePinCodeInput, event.event);
scene_manager_next_scene(app->scene_manager, DesktopSettingsAppScenePinCodeInput);
consumed = true;
break;
case CodeEventsChangePin:
scene_manager_set_scene_state(
app->scene_manager, DesktopSettingsAppScenePinCodeInput, event.event);
scene_manager_next_scene(app->scene_manager, DesktopSettingsAppScenePinCodeInput);
consumed = true;
break;
case CodeEventsDisablePin:
scene_manager_set_scene_state(
app->scene_manager, DesktopSettingsAppScenePinCodeInput, event.event);
scene_manager_next_scene(app->scene_manager, DesktopSettingsAppScenePinCodeInput);
consumed = true;
break;
default:
consumed = true;
break;
}
}
return consumed;
}
void desktop_settings_scene_pincode_menu_on_exit(void* context) {
DesktopSettingsApp* app = context;
submenu_clean(app->submenu);
}

View File

@ -29,7 +29,7 @@ void desktop_settings_scene_start_on_enter(void* context) {
desktop_settings_scene_start_submenu_callback,
app);
view_dispatcher_switch_to_view(app->view_dispatcher, DesktopSettingsAppViewMain);
view_dispatcher_switch_to_view(app->view_dispatcher, DesktopSettingsAppViewMenu);
}
bool desktop_settings_scene_start_on_event(void* context, SceneManagerEvent event) {
@ -39,7 +39,11 @@ bool desktop_settings_scene_start_on_event(void* context, SceneManagerEvent even
if(event.type == SceneManagerEventTypeCustom) {
switch(event.event) {
case DesktopSettingsStartSubmenuIndexFavorite:
scene_manager_next_scene(app->scene_manager, DesktopSettingsAppViewFavorite);
scene_manager_next_scene(app->scene_manager, DesktopSettingsAppSceneFavorite);
consumed = true;
break;
case DesktopSettingsStartSubmenuIndexPinSetup:
scene_manager_next_scene(app->scene_manager, DesktopSettingsAppScenePinCodeMenu);
consumed = true;
break;
}

View File

@ -4,3 +4,4 @@ ADD_SCENE(desktop, locked, Locked)
ADD_SCENE(desktop, debug, Debug)
ADD_SCENE(desktop, first_start, FirstStart)
ADD_SCENE(desktop, hw_mismatch, HwMismatch)
ADD_SCENE(desktop, pinsetup, PinSetup)

View File

@ -9,7 +9,10 @@ void desktop_scene_lock_menu_callback(DesktopLockMenuEvent event, void* context)
void desktop_scene_lock_menu_on_enter(void* context) {
Desktop* desktop = (Desktop*)context;
desktop_settings_load(&desktop->settings);
desktop_lock_menu_set_callback(desktop->lock_menu, desktop_scene_lock_menu_callback, desktop);
desktop_lock_menu_pin_set(desktop->lock_menu, desktop->settings.pincode.length > 0);
view_dispatcher_switch_to_view(desktop->view_dispatcher, DesktopViewLockMenu);
}
@ -20,10 +23,25 @@ bool desktop_scene_lock_menu_on_event(void* context, SceneManagerEvent event) {
if(event.type == SceneManagerEventTypeCustom) {
switch(event.event) {
case DesktopLockMenuEventLock:
scene_manager_set_scene_state(
desktop->scene_manager, DesktopSceneLocked, DesktopLockedNoPin);
scene_manager_next_scene(desktop->scene_manager, DesktopSceneLocked);
consumed = true;
break;
case DesktopLockMenuEventPinLock:
if(desktop->settings.pincode.length > 0) {
desktop->settings.locked = true;
desktop_settings_save(&desktop->settings);
scene_manager_set_scene_state(
desktop->scene_manager, DesktopSceneLocked, DesktopLockedWithPin);
scene_manager_next_scene(desktop->scene_manager, DesktopSceneLocked);
} else {
scene_manager_next_scene(desktop->scene_manager, DesktopScenePinSetup);
}
consumed = true;
break;
case DesktopLockMenuEventExit:
scene_manager_next_scene(desktop->scene_manager, DesktopSceneMain);
consumed = true;

View File

@ -15,12 +15,39 @@ void desktop_scene_locked_on_enter(void* context) {
desktop_locked_update_hint_timeout(locked_view);
desktop_locked_set_dolphin_animation(locked_view);
uint32_t state = scene_manager_get_scene_state(desktop->scene_manager, DesktopViewLocked);
desktop_locked_with_pin(desktop->locked_view, state == DesktopLockedWithPin);
view_port_enabled_set(desktop->lock_viewport, true);
osTimerStart(locked_view->timer, 63);
view_dispatcher_switch_to_view(desktop->view_dispatcher, DesktopViewLocked);
}
static bool desktop_scene_locked_check_pin(Desktop* desktop, DesktopMainEvent event) {
bool match = false;
size_t length = desktop->pincode_buffer.length;
length = code_input_push(desktop->pincode_buffer.data, length, event);
desktop->pincode_buffer.length = length;
match = code_input_compare(
desktop->pincode_buffer.data,
length,
desktop->settings.pincode.data,
desktop->settings.pincode.length);
if(match) {
desktop->pincode_buffer.length = 0;
desktop->settings.locked = false;
desktop_settings_save(&desktop->settings);
desktop_main_unlocked(desktop->main_view);
}
return match;
}
bool desktop_scene_locked_on_event(void* context, SceneManagerEvent event) {
Desktop* desktop = (Desktop*)context;
@ -36,7 +63,17 @@ bool desktop_scene_locked_on_event(void* context, SceneManagerEvent event) {
case DesktopLockedEventUpdate:
desktop_locked_manage_redraw(desktop->locked_view);
consumed = true;
break;
case DesktopLockedEventInputReset:
desktop->pincode_buffer.length = 0;
break;
default:
if(desktop_scene_locked_check_pin(desktop, event.event)) {
scene_manager_set_scene_state(
desktop->scene_manager, DesktopSceneMain, DesktopMainEventUnlocked);
scene_manager_next_scene(desktop->scene_manager, DesktopSceneMain);
consumed = true;
}
break;
}
}

View File

@ -34,6 +34,8 @@ void desktop_scene_main_on_enter(void* context) {
desktop_main_set_callback(main_view, desktop_scene_main_callback, desktop);
view_port_enabled_set(desktop->lock_viewport, false);
desktop_settings_load(&desktop->settings);
if(scene_manager_get_scene_state(desktop->scene_manager, DesktopSceneMain) ==
DesktopMainEventUnlocked) {
desktop_main_unlocked(desktop->main_view);

View File

@ -0,0 +1,50 @@
#include "../desktop_i.h"
#define SCENE_EXIT_EVENT (0U)
void desktop_scene_ok_callback(void* context) {
Desktop* app = context;
desktop_settings_save(&app->settings);
view_dispatcher_send_custom_event(app->view_dispatcher, SCENE_EXIT_EVENT);
}
void desktop_scene_pinsetup_on_enter(void* context) {
Desktop* app = context;
CodeInput* code_input = app->code_input;
code_input_set_result_callback(
code_input,
desktop_scene_ok_callback,
NULL,
app,
app->settings.pincode.data,
&app->settings.pincode.length,
true);
view_dispatcher_switch_to_view(app->view_dispatcher, DesktopViewPinSetup);
}
bool desktop_scene_pinsetup_on_event(void* context, SceneManagerEvent event) {
Desktop* app = context;
bool consumed = false;
if(event.type == SceneManagerEventTypeCustom) {
switch(event.event) {
case SCENE_EXIT_EVENT:
scene_manager_previous_scene(app->scene_manager);
consumed = true;
break;
default:
consumed = true;
break;
}
}
return consumed;
}
void desktop_scene_pinsetup_on_exit(void* context) {
Desktop* app = context;
code_input_set_result_callback(app->code_input, NULL, NULL, NULL, NULL, NULL, 0);
code_input_set_header_text(app->code_input, "");
}

View File

@ -12,6 +12,14 @@ void desktop_lock_menu_set_callback(
lock_menu->context = context;
}
void desktop_lock_menu_pin_set(DesktopLockMenuView* lock_menu, bool pin_is_set) {
with_view_model(
lock_menu->view, (DesktopLockMenuViewModel * model) {
model->pin_set = pin_is_set;
return true;
});
}
void desktop_lock_menu_reset_idx(DesktopLockMenuView* lock_menu) {
with_view_model(
lock_menu->view, (DesktopLockMenuViewModel * model) {
@ -26,6 +34,10 @@ static void lock_menu_callback(void* context, uint8_t index) {
switch(index) {
case 0: // lock
lock_menu->callback(DesktopLockMenuEventLock, lock_menu->context);
break;
case 1: // lock
lock_menu->callback(DesktopLockMenuEventPinLock, lock_menu->context);
break;
default: // wip message
with_view_model(
lock_menu->view, (DesktopLockMenuViewModel * model) {
@ -37,7 +49,7 @@ static void lock_menu_callback(void* context, uint8_t index) {
}
void desktop_lock_menu_render(Canvas* canvas, void* model) {
const char* Lockmenu_Items[3] = {"Lock", "Set PIN", "DUMB mode"};
const char* Lockmenu_Items[3] = {"Lock", "Lock with PIN", "DUMB mode"};
DesktopLockMenuViewModel* m = model;
canvas_clear(canvas);
@ -47,13 +59,13 @@ void desktop_lock_menu_render(Canvas* canvas, void* model) {
canvas_set_font(canvas, FontSecondary);
for(uint8_t i = 0; i < 3; ++i) {
canvas_draw_str_aligned(
canvas,
64,
13 + (i * 17),
AlignCenter,
AlignCenter,
(m->hint_timeout && m->idx == i && m->idx) ? "Not implemented" : Lockmenu_Items[i]);
const char* str = Lockmenu_Items[i];
if(i == 1 && !m->pin_set) str = "Set PIN";
if(m->hint_timeout && m->idx == 2 && m->idx == i) str = "Not implemented";
canvas_draw_str_aligned(canvas, 64, 13 + (i * 17), AlignCenter, AlignCenter, str);
if(m->idx == i) elements_frame(canvas, 15, 5 + (i * 17), 98, 15);
}
}

View File

@ -11,6 +11,7 @@
typedef enum {
DesktopLockMenuEventLock,
DesktopLockMenuEventUnlock,
DesktopLockMenuEventPinLock,
DesktopLockMenuEventExit,
} DesktopLockMenuEvent;
@ -27,6 +28,7 @@ struct DesktopLockMenuView {
typedef struct {
uint8_t idx;
uint8_t hint_timeout;
bool pin_set;
} DesktopLockMenuViewModel;
void desktop_lock_menu_set_callback(
@ -35,6 +37,7 @@ void desktop_lock_menu_set_callback(
void* context);
View* desktop_lock_menu_get_view(DesktopLockMenuView* lock_menu);
void desktop_lock_menu_pin_set(DesktopLockMenuView* lock_menu, bool pin_is_set);
void desktop_lock_menu_reset_idx(DesktopLockMenuView* lock_menu);
DesktopLockMenuView* desktop_lock_menu_alloc();
void desktop_lock_menu_free(DesktopLockMenuView* lock_menu);

View File

@ -80,6 +80,14 @@ void desktop_locked_reset_counter(DesktopLockedView* locked_view) {
});
}
void desktop_locked_with_pin(DesktopLockedView* locked_view, bool locked) {
with_view_model(
locked_view->view, (DesktopLockedViewModel * model) {
model->pin_lock = locked;
return true;
});
}
void desktop_locked_render(Canvas* canvas, void* model) {
DesktopLockedViewModel* m = model;
uint32_t now = osKernelGetTickCount();
@ -100,7 +108,7 @@ void desktop_locked_render(Canvas* canvas, void* model) {
canvas_set_font(canvas, FontPrimary);
elements_multiline_text_framed(canvas, 42, 30, "Locked");
} else {
} else if(!m->pin_lock) {
canvas_set_font(canvas, FontSecondary);
canvas_draw_icon(canvas, 13, 5, &I_LockPopup_100x49);
elements_multiline_text(canvas, 65, 20, "To unlock\npress:");
@ -116,27 +124,49 @@ View* desktop_locked_get_view(DesktopLockedView* locked_view) {
bool desktop_locked_input(InputEvent* event, void* context) {
furi_assert(event);
furi_assert(context);
DesktopLockedView* locked_view = context;
uint32_t press_time = 0;
bool locked_with_pin = false;
with_view_model(
locked_view->view, (DesktopLockedViewModel * model) {
locked_with_pin = model->pin_lock;
return false;
});
if(event->type == InputTypeShort) {
desktop_locked_update_hint_timeout(locked_view);
if(locked_with_pin) {
press_time = osKernelGetTickCount();
if(event->key == InputKeyBack) {
uint32_t press_time = osKernelGetTickCount();
// check if pressed sequentially
if(press_time - locked_view->lock_lastpress > UNLOCK_RST_TIMEOUT) {
if(press_time - locked_view->lock_lastpress > UNLOCK_RST_TIMEOUT * 3) {
locked_view->lock_lastpress = press_time;
locked_view->lock_count = 0;
} else if(press_time - locked_view->lock_lastpress < UNLOCK_RST_TIMEOUT) {
locked_view->lock_lastpress = press_time;
locked_view->lock_count++;
locked_view->callback(DesktopLockedEventInputReset, locked_view->context);
}
if(locked_view->lock_count == UNLOCK_CNT) {
locked_view->lock_count = 0;
locked_view->callback(DesktopLockedEventUnlock, locked_view->context);
locked_view->callback(event->key, locked_view->context);
} else {
desktop_locked_update_hint_timeout(locked_view);
if(event->key == InputKeyBack) {
press_time = osKernelGetTickCount();
// check if pressed sequentially
if(press_time - locked_view->lock_lastpress < UNLOCK_RST_TIMEOUT) {
locked_view->lock_lastpress = press_time;
locked_view->lock_count++;
}
if(locked_view->lock_count == UNLOCK_CNT) {
locked_view->lock_count = 0;
locked_view->callback(DesktopLockedEventUnlock, locked_view->context);
}
}
}
if(press_time - locked_view->lock_lastpress > UNLOCK_RST_TIMEOUT) {
locked_view->lock_lastpress = press_time;
locked_view->lock_count = 0;
}
}
// All events consumed
return true;

View File

@ -15,10 +15,16 @@
#define DOOR_R_POS_MIN 60
typedef enum {
DesktopLockedEventUnlock,
DesktopLockedEventUpdate,
DesktopLockedEventUnlock = 10U,
DesktopLockedEventUpdate = 11U,
DesktopLockedEventInputReset = 12U,
} DesktopLockedEvent;
typedef enum {
DesktopLockedWithPin,
DesktopLockedNoPin,
} DesktopLockedSceneState;
typedef struct DesktopLockedView DesktopLockedView;
typedef void (*DesktopLockedViewCallback)(DesktopLockedEvent event, void* context);
@ -42,6 +48,7 @@ typedef struct {
int8_t door_right_x;
bool animation_seq_end;
bool pin_lock;
} DesktopLockedViewModel;
void desktop_locked_set_callback(
@ -58,5 +65,4 @@ void desktop_locked_manage_redraw(DesktopLockedView* locked_view);
View* desktop_locked_get_view(DesktopLockedView* locked_view);
DesktopLockedView* desktop_locked_alloc();
void desktop_locked_free(DesktopLockedView* locked_view);
void desktop_main_unlocked(DesktopMainView* main_view);
void desktop_main_reset_hint(DesktopMainView* main_view);
void desktop_locked_with_pin(DesktopLockedView* lock_menu, bool locked);

View File

@ -67,6 +67,7 @@ bool desktop_main_input(InputEvent* event, void* context) {
} else if(event->key == InputKeyLeft && event->type == InputTypeShort) {
main_view->callback(DesktopMainEventOpenFavorite, main_view->context);
}
desktop_main_reset_hint(main_view);
return true;

View File

@ -7,12 +7,12 @@
#include <furi.h>
typedef enum {
DesktopMainEventOpenMenu,
DesktopMainEventOpenLockMenu,
DesktopMainEventOpenDebug,
DesktopMainEventUnlocked,
DesktopMainEventOpenArchive,
DesktopMainEventOpenFavorite,
DesktopMainEventOpenMenu,
DesktopMainEventOpenDebug,
DesktopMainEventUnlocked,
} DesktopMainEvent;
typedef struct DesktopMainView DesktopMainView;
@ -37,9 +37,8 @@ void desktop_main_set_callback(
void* context);
View* desktop_main_get_view(DesktopMainView* main_view);
DesktopMainView* desktop_main_alloc();
void desktop_main_free(DesktopMainView* main_view);
void desktop_main_switch_dolphin_animation(DesktopMainView* main_view);
void desktop_main_unlocked(DesktopMainView* main_view);
void desktop_main_reset_hint(DesktopMainView* main_view);

View File

@ -0,0 +1,475 @@
#include "code_input.h"
#include <gui/elements.h>
#include <furi.h>
#define MAX_CODE_LEN 10
struct CodeInput {
View* view;
};
typedef enum {
CodeInputStateVerify,
CodeInputStateUpdate,
CodeInputStateTotal,
} CodeInputStateEnum;
typedef enum {
CodeInputFirst,
CodeInputSecond,
CodeInputTotal,
} CodeInputsEnum;
typedef struct {
uint8_t state;
uint8_t current;
bool ext_update;
uint8_t input_length[CodeInputTotal];
uint8_t local_buffer[CodeInputTotal][MAX_CODE_LEN];
CodeInputOkCallback ok_callback;
CodeInputFailCallback fail_callback;
void* callback_context;
const char* header;
uint8_t* ext_buffer;
uint8_t* ext_buffer_length;
} CodeInputModel;
static const Icon* keys_assets[] = {
[InputKeyUp] = &I_ButtonUp_7x4,
[InputKeyDown] = &I_ButtonDown_7x4,
[InputKeyRight] = &I_ButtonRight_4x7,
[InputKeyLeft] = &I_ButtonLeft_4x7,
};
/**
* @brief Compare buffers
*
* @param in Input buffer pointer
* @param len_in Input array length
* @param src Source buffer pointer
* @param len_src Source array length
*/
bool code_input_compare(uint8_t* in, size_t len_in, uint8_t* src, size_t len_src) {
bool result = false;
do {
result = (len_in && len_src);
if(!result) {
break;
}
result = (len_in == len_src);
if(!result) {
break;
}
for(size_t i = 0; i < len_in; i++) {
result = (in[i] == src[i]);
if(!result) {
break;
}
}
} while(false);
return result;
}
/**
* @brief Compare local buffers
*
* @param model
*/
static bool code_input_compare_local(CodeInputModel* model) {
uint8_t* source = model->local_buffer[CodeInputFirst];
size_t source_length = model->input_length[CodeInputFirst];
uint8_t* input = model->local_buffer[CodeInputSecond];
size_t input_length = model->input_length[CodeInputSecond];
return code_input_compare(input, input_length, source, source_length);
}
/**
* @brief Compare ext with local
*
* @param model
*/
static bool code_input_compare_ext(CodeInputModel* model) {
uint8_t* input = model->local_buffer[CodeInputFirst];
size_t input_length = model->input_length[CodeInputFirst];
uint8_t* source = model->ext_buffer;
size_t source_length = *model->ext_buffer_length;
return code_input_compare(input, input_length, source, source_length);
}
/**
* @brief Set ext buffer
*
* @param model
*/
static void code_input_set_ext(CodeInputModel* model) {
*model->ext_buffer_length = model->input_length[CodeInputFirst];
for(size_t i = 0; i <= model->input_length[CodeInputFirst]; i++) {
model->ext_buffer[i] = model->local_buffer[CodeInputFirst][i];
}
}
/**
* @brief Draw input sequence
*
* @param canvas
* @param buffer
* @param length
* @param x
* @param y
* @param active
*/
static void code_input_draw_sequence(
Canvas* canvas,
uint8_t* buffer,
uint8_t length,
uint8_t x,
uint8_t y,
bool active) {
uint8_t pos_x = x + 6;
uint8_t pos_y = y + 3;
if(active) canvas_draw_icon(canvas, x - 4, y + 5, &I_ButtonRightSmall_3x5);
elements_slightly_rounded_frame(canvas, x, y, 116, 15);
for(size_t i = 0; i < length; i++) {
// maybe symmetrical assets? :-/
uint8_t offset_y = buffer[i] < 2 ? 2 + (buffer[i] * 2) : 1;
canvas_draw_icon(canvas, pos_x, pos_y + offset_y, keys_assets[buffer[i]]);
pos_x += buffer[i] > 1 ? 9 : 11;
}
}
/**
* @brief Reset input count
*
* @param model
*/
static void code_input_reset_count(CodeInputModel* model) {
model->input_length[model->current] = 0;
}
/**
* @brief Call input callback
*
* @param model
*/
static void code_input_call_ok_callback(CodeInputModel* model) {
if(model->ok_callback != NULL) {
model->ok_callback(model->callback_context);
}
}
/**
* @brief Call changed callback
*
* @param model
*/
static void code_input_call_fail_callback(CodeInputModel* model) {
if(model->fail_callback != NULL) {
model->fail_callback(model->callback_context);
}
}
/**
* @brief Handle Back button
*
* @param model
*/
static bool code_input_handle_back(CodeInputModel* model) {
if(model->current && !model->input_length[model->current]) {
--model->current;
return true;
}
if(model->input_length[model->current]) {
code_input_reset_count(model);
return true;
}
code_input_call_fail_callback(model);
return false;
}
/**
* @brief Handle OK button
*
* @param model
*/
static void code_input_handle_ok(CodeInputModel* model) {
switch(model->state) {
case CodeInputStateVerify:
if(code_input_compare_ext(model)) {
if(model->ext_update) {
model->state = CodeInputStateUpdate;
} else {
code_input_call_ok_callback(model);
}
}
code_input_reset_count(model);
break;
case CodeInputStateUpdate:
if(!model->current && model->input_length[model->current]) {
model->current++;
} else {
if(code_input_compare_local(model)) {
if(model->ext_update) {
code_input_set_ext(model);
}
code_input_call_ok_callback(model);
} else {
code_input_reset_count(model);
}
}
break;
default:
break;
}
}
/**
* @brief Handle input
*
* @param model
* @param key
*/
size_t code_input_push(uint8_t* buffer, size_t length, InputKey key) {
buffer[length] = key;
length = CLAMP(length + 1, MAX_CODE_LEN, 0);
return length;
}
/**
* @brief Handle D-pad keys
*
* @param model
* @param key
*/
static void code_input_handle_dpad(CodeInputModel* model, InputKey key) {
uint8_t at = model->current;
size_t new_length = code_input_push(model->local_buffer[at], model->input_length[at], key);
model->input_length[at] = new_length;
}
/**
* @brief Draw callback
*
* @param canvas
* @param _model
*/
static void code_input_view_draw_callback(Canvas* canvas, void* _model) {
CodeInputModel* model = _model;
uint8_t y_offset = 0;
if(!strlen(model->header)) y_offset = 5;
canvas_clear(canvas);
canvas_set_color(canvas, ColorBlack);
canvas_draw_str(canvas, 2, 9, model->header);
canvas_set_font(canvas, FontSecondary);
switch(model->state) {
case CodeInputStateVerify:
code_input_draw_sequence(
canvas,
model->local_buffer[CodeInputFirst],
model->input_length[CodeInputFirst],
6,
30 - y_offset,
true);
break;
case CodeInputStateUpdate:
code_input_draw_sequence(
canvas,
model->local_buffer[CodeInputFirst],
model->input_length[CodeInputFirst],
6,
14 - y_offset,
!model->current);
code_input_draw_sequence(
canvas,
model->local_buffer[CodeInputSecond],
model->input_length[CodeInputSecond],
6,
44 - y_offset,
model->current);
if(model->current) canvas_draw_str(canvas, 2, 39 - y_offset, "Repeat code");
break;
default:
break;
}
}
/**
* @brief Input callback
*
* @param event
* @param context
* @return true
* @return false
*/
static bool code_input_view_input_callback(InputEvent* event, void* context) {
CodeInput* code_input = context;
furi_assert(code_input);
bool consumed = false;
if(event->type == InputTypeShort || event->type == InputTypeRepeat) {
switch(event->key) {
case InputKeyBack:
with_view_model(
code_input->view, (CodeInputModel * model) {
consumed = code_input_handle_back(model);
return true;
});
break;
case InputKeyOk:
with_view_model(
code_input->view, (CodeInputModel * model) {
code_input_handle_ok(model);
return true;
});
consumed = true;
break;
default:
with_view_model(
code_input->view, (CodeInputModel * model) {
code_input_handle_dpad(model, event->key);
return true;
});
consumed = true;
break;
}
}
return consumed;
}
/**
* @brief Reset all input-related data in model
*
* @param model CodeInputModel
*/
static void code_input_reset_model_input_data(CodeInputModel* model) {
model->current = 0;
model->input_length[CodeInputFirst] = 0;
model->input_length[CodeInputSecond] = 0;
model->ext_buffer = NULL;
model->ext_update = false;
model->state = 0;
}
/**
* @brief Allocate and initialize code input. This code input is used to enter codes.
*
* @return CodeInput instance pointer
*/
CodeInput* code_input_alloc() {
CodeInput* code_input = furi_alloc(sizeof(CodeInput));
code_input->view = view_alloc();
view_set_context(code_input->view, code_input);
view_allocate_model(code_input->view, ViewModelTypeLocking, sizeof(CodeInputModel));
view_set_draw_callback(code_input->view, code_input_view_draw_callback);
view_set_input_callback(code_input->view, code_input_view_input_callback);
with_view_model(
code_input->view, (CodeInputModel * model) {
model->header = "";
model->ok_callback = NULL;
model->fail_callback = NULL;
model->callback_context = NULL;
code_input_reset_model_input_data(model);
return true;
});
return code_input;
}
/**
* @brief Deinitialize and free code input
*
* @param code_input Code input instance
*/
void code_input_free(CodeInput* code_input) {
furi_assert(code_input);
view_free(code_input->view);
free(code_input);
}
/**
* @brief Get code input view
*
* @param code_input code input instance
* @return View instance that can be used for embedding
*/
View* code_input_get_view(CodeInput* code_input) {
furi_assert(code_input);
return code_input->view;
}
/**
* @brief Set code input callbacks
*
* @param code_input code input instance
* @param ok_callback input callback fn
* @param fail_callback code match callback fn
* @param callback_context callback context
* @param buffer buffer
* @param buffer_length ptr to buffer length uint
* @param ext_update true to update buffer
*/
void code_input_set_result_callback(
CodeInput* code_input,
CodeInputOkCallback ok_callback,
CodeInputFailCallback fail_callback,
void* callback_context,
uint8_t* buffer,
uint8_t* buffer_length,
bool ext_update) {
with_view_model(
code_input->view, (CodeInputModel * model) {
code_input_reset_model_input_data(model);
model->ok_callback = ok_callback;
model->fail_callback = fail_callback;
model->callback_context = callback_context;
model->ext_buffer = buffer;
model->ext_buffer_length = buffer_length;
model->state = (*buffer_length == 0) ? 1 : 0;
model->ext_update = ext_update;
return true;
});
}
/**
* @brief Set code input header text
*
* @param code_input code input instance
* @param text text to be shown
*/
void code_input_set_header_text(CodeInput* code_input, const char* text) {
with_view_model(
code_input->view, (CodeInputModel * model) {
model->header = text;
return true;
});
}

View File

@ -0,0 +1,91 @@
/**
* @file code_input.h
* GUI: CodeInput keyboard view module API
*/
#pragma once
#include <gui/view.h>
#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