From 29da0e360c5b09695b94d9277fb54e4da9a60103 Mon Sep 17 00:00:00 2001 From: SG Date: Mon, 5 Jul 2021 08:03:56 +1000 Subject: [PATCH] [FL-1329] Settings (#563) * Menu: secondary menu rendering * Manu: reset window position on enter to new menu * App-loader: settings menu * Applications: add settings app list * App backlight-control: all work related to turning off the display is now in the notification app * App notification: settings save and load * Gui: variable item list module * App: new notification settings app * Display: backlight is now fully serviced in the notification app * Gui: update variable item list module documentation --- applications/app-loader/app-loader.c | 21 ++ applications/applications.c | 21 +- applications/applications.h | 8 +- .../backlight-control/backlight-control.c | 31 -- applications/gui/modules/variable-item-list.c | 282 ++++++++++++++++++ applications/gui/modules/variable-item-list.h | 64 ++++ applications/menu/menu.c | 113 +++++-- applications/menu/menu_item.c | 12 + applications/menu/menu_item.h | 3 + .../notification/notification-app-settings.c | 231 ++++++++++++++ applications/notification/notification-app.c | 86 +++++- applications/notification/notification-app.h | 11 +- 12 files changed, 815 insertions(+), 68 deletions(-) delete mode 100644 applications/backlight-control/backlight-control.c create mode 100644 applications/gui/modules/variable-item-list.c create mode 100644 applications/gui/modules/variable-item-list.h create mode 100644 applications/notification/notification-app-settings.c diff --git a/applications/app-loader/app-loader.c b/applications/app-loader/app-loader.c index 4cb81413..74dc73e7 100644 --- a/applications/app-loader/app-loader.c +++ b/applications/app-loader/app-loader.c @@ -221,6 +221,27 @@ int32_t app_loader(void* p) { menu_item_add(menu, menu_debug); }); + // Settings + FURI_LOG_I(APP_LOADER_TAG, "Building settings menu"); + with_value_mutex( + menu_mutex, (Menu * menu) { + MenuItem* menu_debug = + menu_item_alloc_menu("Settings", assets_icons_get(A_Settings_14)); + + for(size_t i = 0; i < FLIPPER_SETTINGS_APPS_COUNT; i++) { + // Add menu item + menu_item_subitem_add( + menu_debug, + menu_item_alloc_function( + FLIPPER_SETTINGS_APPS[i].name, + assets_icons_get(FLIPPER_SETTINGS_APPS[i].icon), + app_loader_menu_callback, + (void*)&FLIPPER_SETTINGS_APPS[i])); + } + + menu_item_add(menu, menu_debug); + }); + // Call on start hooks for(size_t i = 0; i < FLIPPER_ON_SYSTEM_START_COUNT; i++) { (*FLIPPER_ON_SYSTEM_START[i])(); diff --git a/applications/applications.c b/applications/applications.c index 0ecf2c24..79c1f352 100644 --- a/applications/applications.c +++ b/applications/applications.c @@ -13,7 +13,6 @@ int32_t menu_task(void* p); int32_t coreglitch_demo_0(void* p); int32_t u8g2_qrcode(void* p); int32_t gui_task(void* p); -int32_t backlight_control(void* p); int32_t irda(void* p); int32_t app_loader(void* p); int32_t nfc_task(void* p); @@ -49,6 +48,9 @@ void bt_cli_init(); void lfrfid_cli_init(); void ibutton_cli_init(); +// Settings +int32_t notification_app_settings(void* p); + const FlipperApplication FLIPPER_SERVICES[] = { #ifdef SRV_CLI {.app = cli_task, .name = "cli_task", .stack_size = 4096, .icon = A_Plugins_14}, @@ -67,10 +69,6 @@ const FlipperApplication FLIPPER_SERVICES[] = { #endif #ifdef SRV_GUI - {.app = backlight_control, - .name = "backlight_control", - .stack_size = 1024, - .icon = A_Plugins_14}, // TODO: fix stack size when sd api will be in separate thread {.app = gui_task, .name = "gui_task", .stack_size = 8192, .icon = A_Plugins_14}, #endif @@ -322,3 +320,16 @@ const FlipperApplication FLIPPER_SCENE_APPS[] = { const size_t FLIPPER_SCENE_APPS_COUNT = sizeof(FLIPPER_SCENE_APPS) / sizeof(FlipperApplication); #endif + +// Settings menu +const FlipperApplication FLIPPER_SETTINGS_APPS[] = { +#ifdef SRV_NOTIFICATION + {.app = notification_app_settings, + .name = "Notification", + .stack_size = 1024, + .icon = A_Plugins_14}, +#endif +}; + +const size_t FLIPPER_SETTINGS_APPS_COUNT = + sizeof(FLIPPER_SETTINGS_APPS) / sizeof(FlipperApplication); diff --git a/applications/applications.h b/applications/applications.h index 65fc9572..45d37bdb 100644 --- a/applications/applications.h +++ b/applications/applications.h @@ -49,4 +49,10 @@ extern const FlipperApplication FLIPPER_SCENE; extern const FlipperApplication FLIPPER_SCENE_APPS[]; extern const size_t FLIPPER_SCENE_APPS_COUNT; -extern const FlipperApplication FLIPPER_ARCHIVE; \ No newline at end of file +extern const FlipperApplication FLIPPER_ARCHIVE; + +/* Settings list + * Spawned by app-loader + */ +extern const FlipperApplication FLIPPER_SETTINGS_APPS[]; +extern const size_t FLIPPER_SETTINGS_APPS_COUNT; \ No newline at end of file diff --git a/applications/backlight-control/backlight-control.c b/applications/backlight-control/backlight-control.c deleted file mode 100644 index 44d91a1a..00000000 --- a/applications/backlight-control/backlight-control.c +++ /dev/null @@ -1,31 +0,0 @@ -#include -#include -#include - -#define BACKLIGHT_TIME 30000 -#define BACKLIGHT_FLAG_ACTIVITY 0x00000001U - -static void event_cb(const void* value, void* ctx) { - osThreadFlagsSet((osThreadId_t)ctx, BACKLIGHT_FLAG_ACTIVITY); -} - -int32_t backlight_control(void* p) { - // open record - NotificationApp* notifications = furi_record_open("notification"); - PubSub* event_record = furi_record_open("input_events"); - subscribe_pubsub(event_record, event_cb, (void*)osThreadGetId()); - - notification_internal_message(notifications, &sequence_display_on); - - while(1) { - // wait for event - if(osThreadFlagsWait(BACKLIGHT_FLAG_ACTIVITY, osFlagsWaitAny, BACKLIGHT_TIME) == - BACKLIGHT_FLAG_ACTIVITY) { - notification_internal_message(notifications, &sequence_display_on); - } else { - notification_internal_message(notifications, &sequence_display_off); - } - } - - return 0; -} \ No newline at end of file diff --git a/applications/gui/modules/variable-item-list.c b/applications/gui/modules/variable-item-list.c new file mode 100644 index 00000000..3ccac4ae --- /dev/null +++ b/applications/gui/modules/variable-item-list.c @@ -0,0 +1,282 @@ +#include "variable-item-list.h" +#include "gui/canvas.h" +#include +#include +#include +#include + +struct VariableItem { + const char* label; + uint8_t current_value_index; + string_t current_value_text; + uint8_t values_count; + VariableItemChangeCallback change_callback; + void* context; +}; + +ARRAY_DEF(VariableItemArray, VariableItem, M_POD_OPLIST); + +struct VariableItemList { + View* view; +}; + +typedef struct { + VariableItemArray_t items; + uint8_t position; + uint8_t window_position; +} VariableItemListModel; + +static void variable_item_list_process_up(VariableItemList* variable_item_list); +static void variable_item_list_process_down(VariableItemList* variable_item_list); +static void variable_item_list_process_left(VariableItemList* variable_item_list); +static void variable_item_list_process_right(VariableItemList* variable_item_list); + +static void variable_item_list_draw_callback(Canvas* canvas, void* _model) { + VariableItemListModel* model = _model; + + const uint8_t item_height = 16; + const uint8_t item_width = 123; + + canvas_clear(canvas); + + uint8_t position = 0; + VariableItemArray_it_t it; + + canvas_set_font(canvas, FontSecondary); + for(VariableItemArray_it(it, model->items); !VariableItemArray_end_p(it); + VariableItemArray_next(it)) { + uint8_t item_position = position - model->window_position; + uint8_t items_on_screen = 4; + uint8_t y_offset = 0; + + if(item_position < items_on_screen) { + const VariableItem* item = VariableItemArray_cref(it); + uint8_t item_y = y_offset + (item_position * item_height); + uint8_t item_text_y = item_y + item_height - 4; + + if(position == model->position) { + canvas_set_color(canvas, ColorBlack); + elements_slightly_rounded_box(canvas, 0, item_y + 1, item_width, item_height - 2); + canvas_set_color(canvas, ColorWhite); + } else { + canvas_set_color(canvas, ColorBlack); + } + + canvas_draw_str(canvas, 6, item_text_y, item->label); + + if(item->current_value_index > 0) { + canvas_draw_str(canvas, 73, item_text_y, "<"); + } + + canvas_draw_str(canvas, 84, item_text_y, string_get_cstr(item->current_value_text)); + + if(item->current_value_index < (item->values_count - 1)) { + canvas_draw_str(canvas, 113, item_text_y, ">"); + } + } + + position++; + } + + elements_scrollbar(canvas, model->position, VariableItemArray_size(model->items)); +} + +static bool variable_item_list_input_callback(InputEvent* event, void* context) { + VariableItemList* variable_item_list = context; + furi_assert(variable_item_list); + bool consumed = false; + + if(event->type == InputTypeShort) { + switch(event->key) { + case InputKeyUp: + consumed = true; + variable_item_list_process_up(variable_item_list); + break; + case InputKeyDown: + consumed = true; + variable_item_list_process_down(variable_item_list); + break; + case InputKeyLeft: + consumed = true; + variable_item_list_process_left(variable_item_list); + break; + case InputKeyRight: + consumed = true; + variable_item_list_process_right(variable_item_list); + break; + default: + break; + } + } + + return consumed; +} + +void variable_item_list_process_up(VariableItemList* variable_item_list) { + with_view_model( + variable_item_list->view, (VariableItemListModel * model) { + uint8_t items_on_screen = 4; + if(model->position > 0) { + model->position--; + if(((model->position - model->window_position) < 1) && + model->window_position > 0) { + model->window_position--; + } + } else { + model->position = VariableItemArray_size(model->items) - 1; + if(model->position > (items_on_screen - 1)) { + model->window_position = model->position - (items_on_screen - 1); + } + } + return true; + }); +} + +void variable_item_list_process_down(VariableItemList* variable_item_list) { + with_view_model( + variable_item_list->view, (VariableItemListModel * model) { + uint8_t items_on_screen = 4; + if(model->position < (VariableItemArray_size(model->items) - 1)) { + model->position++; + if((model->position - model->window_position) > (items_on_screen - 2) && + model->window_position < + (VariableItemArray_size(model->items) - items_on_screen)) { + model->window_position++; + } + } else { + model->position = 0; + model->window_position = 0; + } + return true; + }); +} + +VariableItem* variable_item_list_get_selected_item(VariableItemListModel* model) { + VariableItem* item = NULL; + + VariableItemArray_it_t it; + uint8_t position = 0; + for(VariableItemArray_it(it, model->items); !VariableItemArray_end_p(it); + VariableItemArray_next(it)) { + if(position == model->position) { + break; + } + position++; + } + + item = VariableItemArray_ref(it); + + furi_assert(item); + return item; +} + +void variable_item_list_process_left(VariableItemList* variable_item_list) { + with_view_model( + variable_item_list->view, (VariableItemListModel * model) { + VariableItem* item = variable_item_list_get_selected_item(model); + if(item->current_value_index > 0) { + item->current_value_index--; + if(item->change_callback) { + item->change_callback(item); + } + } + return true; + }); +} + +void variable_item_list_process_right(VariableItemList* variable_item_list) { + with_view_model( + variable_item_list->view, (VariableItemListModel * model) { + VariableItem* item = variable_item_list_get_selected_item(model); + if(item->current_value_index < (item->values_count - 1)) { + item->current_value_index++; + if(item->change_callback) { + item->change_callback(item); + } + } + return true; + }); +} + +VariableItemList* variable_item_list_alloc() { + VariableItemList* variable_item_list = furi_alloc(sizeof(VariableItemList)); + variable_item_list->view = view_alloc(); + view_set_context(variable_item_list->view, variable_item_list); + view_allocate_model( + variable_item_list->view, ViewModelTypeLocking, sizeof(VariableItemListModel)); + view_set_draw_callback(variable_item_list->view, variable_item_list_draw_callback); + view_set_input_callback(variable_item_list->view, variable_item_list_input_callback); + + with_view_model( + variable_item_list->view, (VariableItemListModel * model) { + VariableItemArray_init(model->items); + model->position = 0; + model->window_position = 0; + return true; + }); + + return variable_item_list; +} + +void variable_item_list_free(VariableItemList* variable_item_list) { + furi_assert(variable_item_list); + + with_view_model( + variable_item_list->view, (VariableItemListModel * model) { + VariableItemArray_it_t it; + for(VariableItemArray_it(it, model->items); !VariableItemArray_end_p(it); + VariableItemArray_next(it)) { + string_clear(VariableItemArray_ref(it)->current_value_text); + } + VariableItemArray_clear(model->items); + return false; + }); + view_free(variable_item_list->view); + free(variable_item_list); +} + +View* variable_item_list_get_view(VariableItemList* variable_item_list) { + furi_assert(variable_item_list); + return variable_item_list->view; +} + +VariableItem* variable_item_list_add( + VariableItemList* variable_item_list, + const char* label, + uint8_t values_count, + VariableItemChangeCallback change_callback, + void* context) { + VariableItem* item = NULL; + furi_assert(label); + furi_assert(variable_item_list); + + with_view_model( + variable_item_list->view, (VariableItemListModel * model) { + item = VariableItemArray_push_new(model->items); + item->label = label; + item->values_count = values_count; + item->change_callback = change_callback; + item->context = context; + item->current_value_index = 0; + string_init(item->current_value_text); + return true; + }); + + return item; +} + +void variable_item_set_current_value_index(VariableItem* item, uint8_t current_value_index) { + item->current_value_index = current_value_index; +} + +void variable_item_set_current_value_text(VariableItem* item, const char* current_value_text) { + string_set_str(item->current_value_text, current_value_text); +} + +uint8_t variable_item_get_current_value_index(VariableItem* item) { + return item->current_value_index; +} + +void* variable_item_get_context(VariableItem* item) { + return item->context; +} \ No newline at end of file diff --git a/applications/gui/modules/variable-item-list.h b/applications/gui/modules/variable-item-list.h new file mode 100644 index 00000000..ab64a74a --- /dev/null +++ b/applications/gui/modules/variable-item-list.h @@ -0,0 +1,64 @@ +#pragma once +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct VariableItemList VariableItemList; +typedef struct VariableItem VariableItem; +typedef void (*VariableItemChangeCallback)(VariableItem* item); + +/** Allocate and initialize VariableItemList + * @return VariableItemList* + */ +VariableItemList* variable_item_list_alloc(); + +/** Deinitialize and free VariableItemList + * @param variable_item_list VariableItemList instance + */ +void variable_item_list_free(VariableItemList* variable_item_list); +View* variable_item_list_get_view(VariableItemList* variable_item_list); + +/** Add item to VariableItemList + * @param variable_item_list VariableItemList instance + * @param label item name + * @param values_count item values count + * @param change_callback called on value change in gui + * @param context item context + * @return VariableItem* item instance + */ +VariableItem* variable_item_list_add( + VariableItemList* variable_item_list, + const char* label, + uint8_t values_count, + VariableItemChangeCallback change_callback, + void* context); + +/** Set item current selected index + * @param item VariableItem* instance + * @param current_value_index + */ +void variable_item_set_current_value_index(VariableItem* item, uint8_t current_value_index); + +/** Set item current selected text + * @param item VariableItem* instance + * @param current_value_text + */ +void variable_item_set_current_value_text(VariableItem* item, const char* current_value_text); + +/** Get item current selected index + * @param item VariableItem* instance + * @return uint8_t current selected index + */ +uint8_t variable_item_get_current_value_index(VariableItem* item); + +/** Get item context + * @param item VariableItem* instance + * @return void* item context + */ +void* variable_item_get_context(VariableItem* item); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/applications/menu/menu.c b/applications/menu/menu.c index cc033b67..4b78983f 100644 --- a/applications/menu/menu.c +++ b/applications/menu/menu.c @@ -69,23 +69,6 @@ void menu_settings_item_add(Menu* menu, MenuItem* item) { } void menu_draw_primary(Menu* menu, Canvas* canvas) { -} - -void menu_draw_secondary(Menu* menu, Canvas* canvas) { -} - -void menu_view_port_callback(Canvas* canvas, void* context) { - furi_assert(canvas); - furi_assert(context); - - Menu* menu = acquire_mutex((ValueMutex*)context, 100); // wait 10 ms to get mutex - if(menu == NULL) return; // redraw fail - - furi_assert(menu->current); - - canvas_clear(canvas); - canvas_set_color(canvas, ColorBlack); - size_t position = menu_item_get_position(menu->current); MenuItemArray_t* items = menu_item_get_subitems(menu->current); size_t items_count = MenuItemArray_size(*items); @@ -119,6 +102,63 @@ void menu_view_port_callback(Canvas* canvas, void* context) { canvas_draw_str(canvas, 2, 32, "Empty"); elements_scrollbar(canvas, 0, 0); } +} + +void menu_draw_secondary(Menu* menu, Canvas* canvas) { + size_t position = 0; + size_t selected_position = menu_item_get_position(menu->current); + size_t window_position = menu_item_get_window_position(menu->current); + MenuItemArray_t* items = menu_item_get_subitems(menu->current); + const uint8_t items_on_screen = 4; + const uint8_t item_height = 16; + const uint8_t item_width = 123; + size_t items_count = MenuItemArray_size(*items); + MenuItemArray_it_t it; + + canvas_set_font(canvas, FontSecondary); + for(MenuItemArray_it(it, *items); !MenuItemArray_end_p(it); MenuItemArray_next(it)) { + size_t item_position = position - window_position; + + if(item_position < items_on_screen) { + if(position == selected_position) { + canvas_set_color(canvas, ColorBlack); + elements_slightly_rounded_box( + canvas, 0, (item_position * item_height) + 1, item_width, item_height - 2); + canvas_set_color(canvas, ColorWhite); + } else { + canvas_set_color(canvas, ColorBlack); + } + canvas_draw_str( + canvas, + 6, + (item_position * item_height) + item_height - 4, + menu_item_get_label(*MenuItemArray_ref(it))); + } + + position++; + } + + elements_scrollbar(canvas, selected_position, items_count); +} + +void menu_view_port_callback(Canvas* canvas, void* context) { + furi_assert(canvas); + furi_assert(context); + + Menu* menu = acquire_mutex((ValueMutex*)context, 100); // wait 10 ms to get mutex + if(menu == NULL) return; // redraw fail + + furi_assert(menu->current); + + canvas_clear(canvas); + canvas_set_color(canvas, ColorBlack); + + // if top level + if(menu_item_get_parent(menu->current) == NULL) { + menu_draw_primary(menu, canvas); + } else { + menu_draw_secondary(menu, canvas); + } release_mutex((ValueMutex*)context, menu); } @@ -156,22 +196,49 @@ void menu_update(Menu* menu) { void menu_up(Menu* menu) { furi_assert(menu); - size_t position = menu_item_get_position(menu->current); + size_t window_position = menu_item_get_window_position(menu->current); MenuItemArray_t* items = menu_item_get_subitems(menu->current); - if(position == 0) position = MenuItemArray_size(*items); - position--; + + const uint8_t items_on_screen = 4; + + if(position > 0) { + position--; + if(((position - window_position) < 1) && window_position > 0) { + window_position--; + } + } else { + position = MenuItemArray_size(*items) - 1; + if(position > (items_on_screen - 1)) { + window_position = position - (items_on_screen - 1); + } + } + menu_item_set_position(menu->current, position); + menu_item_set_window_position(menu->current, window_position); menu_update(menu); } void menu_down(Menu* menu) { furi_assert(menu); size_t position = menu_item_get_position(menu->current); + size_t window_position = menu_item_get_window_position(menu->current); MenuItemArray_t* items = menu_item_get_subitems(menu->current); - position++; - position = position % MenuItemArray_size(*items); + + const uint8_t items_on_screen = 4; + if(position < (MenuItemArray_size(*items) - 1)) { + position++; + if((position - window_position) > (items_on_screen - 2) && + window_position < (MenuItemArray_size(*items) - items_on_screen)) { + window_position++; + } + } else { + position = 0; + window_position = 0; + } + menu_item_set_position(menu->current, position); + menu_item_set_window_position(menu->current, window_position); menu_update(menu); } @@ -182,6 +249,7 @@ void menu_ok(Menu* menu) { view_port_enabled_set(menu->view_port, true); menu->current = menu->root; menu_item_set_position(menu->current, 0); + menu_item_set_window_position(menu->current, 0); menu_update(menu); return; } @@ -198,6 +266,7 @@ void menu_ok(Menu* menu) { if(type == MenuItemTypeMenu) { menu->current = item; menu_item_set_position(menu->current, 0); + menu_item_set_window_position(menu->current, 0); menu_update(menu); } else if(type == MenuItemTypeFunction) { menu_item_function_call(item); diff --git a/applications/menu/menu_item.c b/applications/menu/menu_item.c index b3c4af96..ca1c0395 100644 --- a/applications/menu/menu_item.c +++ b/applications/menu/menu_item.c @@ -10,6 +10,7 @@ struct MenuItem { Icon* icon; size_t position; + size_t window_position; MenuItem* parent; void* data; @@ -49,6 +50,7 @@ MenuItem* menu_item_alloc_function( menu_item->icon = icon; menu_item->callback = callback; menu_item->callback_context = context; + menu_item->parent = NULL; return menu_item; } @@ -90,6 +92,16 @@ size_t menu_item_get_position(MenuItem* menu_item) { return menu_item->position; } +void menu_item_set_window_position(MenuItem* menu_item, size_t window_position) { + furi_assert(menu_item); + menu_item->window_position = window_position; +} + +size_t menu_item_get_window_position(MenuItem* menu_item) { + furi_assert(menu_item); + return menu_item->window_position; +} + void menu_item_set_label(MenuItem* menu_item, const char* label) { furi_assert(menu_item); menu_item->label = label; diff --git a/applications/menu/menu_item.h b/applications/menu/menu_item.h index ebfaed94..c10d5a5e 100644 --- a/applications/menu/menu_item.h +++ b/applications/menu/menu_item.h @@ -33,6 +33,9 @@ MenuItemType menu_item_get_type(MenuItem* menu_item); void menu_item_set_position(MenuItem* menu_item, size_t position); size_t menu_item_get_position(MenuItem* menu_item); +void menu_item_set_window_position(MenuItem* menu_item, size_t window_position); +size_t menu_item_get_window_position(MenuItem* menu_item); + void menu_item_set_label(MenuItem* menu_item, const char* label); const char* menu_item_get_label(MenuItem* menu_item); diff --git a/applications/notification/notification-app-settings.c b/applications/notification/notification-app-settings.c new file mode 100644 index 00000000..21d1908c --- /dev/null +++ b/applications/notification/notification-app-settings.c @@ -0,0 +1,231 @@ +#include +#include "notification-app.h" +#include +#include + +#define MAX_NOTIFICATION_SETTINGS 4 + +typedef struct { + NotificationApp* notification; + Gui* gui; + ViewDispatcher* view_dispatcher; + VariableItemList* variable_item_list; +} NotificationAppSettings; + +static const NotificationSequence sequence_note_c = { + &message_note_c5, + &message_delay_100, + &message_sound_off, + NULL, +}; + +static const NotificationSequence sequence_vibro = { + &message_vibro_on, + &message_delay_100, + &message_vibro_off, + NULL, +}; + +#define BACKLIGHT_COUNT 5 +const char* const backlight_text[BACKLIGHT_COUNT] = { + "0%", + "25%", + "50%", + "75%", + "100%", +}; +const float backlight_value[BACKLIGHT_COUNT] = { + 0.0f, + 0.25f, + 0.5f, + 0.75f, + 1.0f, +}; + +#define VOLUME_COUNT 5 +const char* const volume_text[VOLUME_COUNT] = { + "0%", + "25%", + "50%", + "75%", + "100%", +}; +const float volume_value[VOLUME_COUNT] = {0.0f, 0.04f, 0.1f, 0.2f, 1.0f}; + +#define DELAY_COUNT 6 +const char* const delay_text[DELAY_COUNT] = { + "1s", + "5s", + "15s", + "30s", + "60s", + "120s", +}; +const uint32_t delay_value[DELAY_COUNT] = {1000, 5000, 15000, 30000, 60000, 120000}; + +#define VIBRO_COUNT 2 +const char* const vibro_text[VIBRO_COUNT] = { + "OFF", + "ON", +}; +const bool vibro_value[VIBRO_COUNT] = {false, true}; + +uint8_t float_value_index(const float value, const float values[], uint8_t values_count) { + const float epsilon = 0.01f; + float last_value = values[0]; + uint8_t index = 0; + for(uint8_t i = 1; i < values_count; i++) { + if((value >= last_value - epsilon) && (value <= values[i] + epsilon)) { + index = i; + break; + } + last_value = values[i]; + } + return index; +} + +uint8_t uint32_value_index(const uint32_t value, const uint32_t values[], uint8_t values_count) { + float last_value = values[0]; + uint8_t index = 0; + for(uint8_t i = 1; i < values_count; i++) { + if((value >= last_value) && (value <= values[i])) { + index = i; + break; + } + last_value = values[i]; + } + return index; +} + +uint8_t bool_value_index(const bool value, const bool values[], uint8_t values_count) { + uint8_t index = 0; + for(uint8_t i = 0; i < values_count; i++) { + if(value == values[i]) { + index = i; + break; + } + } + return index; +} + +static void backlight_changed(VariableItem* item) { + NotificationAppSettings* app = variable_item_get_context(item); + uint8_t index = variable_item_get_current_value_index(item); + + variable_item_set_current_value_text(item, backlight_text[index]); + app->notification->settings.display_brightness = backlight_value[index]; + notification_message(app->notification, &sequence_display_on); +} + +static void screen_changed(VariableItem* item) { + NotificationAppSettings* app = variable_item_get_context(item); + uint8_t index = variable_item_get_current_value_index(item); + + variable_item_set_current_value_text(item, delay_text[index]); + app->notification->settings.display_off_delay_ms = delay_value[index]; + notification_message(app->notification, &sequence_display_on); +} + +static void led_changed(VariableItem* item) { + NotificationAppSettings* app = variable_item_get_context(item); + uint8_t index = variable_item_get_current_value_index(item); + + variable_item_set_current_value_text(item, backlight_text[index]); + app->notification->settings.led_brightness = backlight_value[index]; + notification_message(app->notification, &sequence_blink_white_100); +} + +static void volume_changed(VariableItem* item) { + NotificationAppSettings* app = variable_item_get_context(item); + uint8_t index = variable_item_get_current_value_index(item); + + variable_item_set_current_value_text(item, volume_text[index]); + app->notification->settings.speaker_volume = volume_value[index]; + notification_message(app->notification, &sequence_note_c); +} + +static void vibro_changed(VariableItem* item) { + NotificationAppSettings* app = variable_item_get_context(item); + uint8_t index = variable_item_get_current_value_index(item); + + variable_item_set_current_value_text(item, vibro_text[index]); + app->notification->settings.vibro_on = vibro_value[index]; + notification_message(app->notification, &sequence_vibro); +} + +static uint32_t notification_app_settings_exit(void* context) { + return VIEW_NONE; +} + +static NotificationAppSettings* alloc_settings() { + NotificationAppSettings* app = malloc(sizeof(NotificationAppSettings)); + app->notification = furi_record_open("notification"); + app->gui = furi_record_open("gui"); + + app->variable_item_list = variable_item_list_alloc(); + View* view = variable_item_list_get_view(app->variable_item_list); + view_set_previous_callback(view, notification_app_settings_exit); + + VariableItem* item; + uint8_t value_index; + + item = variable_item_list_add( + app->variable_item_list, "LCD backlight", BACKLIGHT_COUNT, backlight_changed, app); + value_index = float_value_index( + app->notification->settings.display_brightness, backlight_value, BACKLIGHT_COUNT); + variable_item_set_current_value_index(item, value_index); + variable_item_set_current_value_text(item, backlight_text[value_index]); + + item = variable_item_list_add( + app->variable_item_list, "Backlight time", DELAY_COUNT, screen_changed, app); + value_index = uint32_value_index( + app->notification->settings.display_off_delay_ms, delay_value, DELAY_COUNT); + variable_item_set_current_value_index(item, value_index); + variable_item_set_current_value_text(item, delay_text[value_index]); + + item = variable_item_list_add( + app->variable_item_list, "LED brightness", BACKLIGHT_COUNT, led_changed, app); + value_index = float_value_index( + app->notification->settings.led_brightness, backlight_value, BACKLIGHT_COUNT); + variable_item_set_current_value_index(item, value_index); + variable_item_set_current_value_text(item, backlight_text[value_index]); + + item = variable_item_list_add( + app->variable_item_list, "Volume", VOLUME_COUNT, volume_changed, app); + value_index = + float_value_index(app->notification->settings.speaker_volume, volume_value, VOLUME_COUNT); + variable_item_set_current_value_index(item, value_index); + variable_item_set_current_value_text(item, volume_text[value_index]); + + item = + variable_item_list_add(app->variable_item_list, "Vibro", VIBRO_COUNT, vibro_changed, app); + value_index = bool_value_index(app->notification->settings.vibro_on, vibro_value, VIBRO_COUNT); + variable_item_set_current_value_index(item, value_index); + variable_item_set_current_value_text(item, vibro_text[value_index]); + + app->view_dispatcher = view_dispatcher_alloc(); + view_dispatcher_enable_queue(app->view_dispatcher); + view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen); + view_dispatcher_add_view(app->view_dispatcher, 0, view); + view_dispatcher_switch_to_view(app->view_dispatcher, 0); + + return app; +} + +static void free_settings(NotificationAppSettings* app) { + view_dispatcher_remove_view(app->view_dispatcher, 0); + variable_item_list_free(app->variable_item_list); + view_dispatcher_free(app->view_dispatcher); + + furi_record_close("gui"); + furi_record_close("notification"); + free(app); +} + +int32_t notification_app_settings(void* p) { + NotificationAppSettings* app = alloc_settings(); + view_dispatcher_run(app->view_dispatcher); + notification_message_save_settings(app->notification); + free_settings(app); + return 0; +} \ No newline at end of file diff --git a/applications/notification/notification-app.c b/applications/notification/notification-app.c index d40136a2..c5193a91 100644 --- a/applications/notification/notification-app.c +++ b/applications/notification/notification-app.c @@ -1,5 +1,6 @@ #include #include +#include #include "notification.h" #include "notification-messages.h" #include "notification-app.h" @@ -23,6 +24,13 @@ uint8_t notification_settings_get_display_brightness(NotificationApp* app, uint8 uint8_t notification_settings_get_rgb_led_brightness(NotificationApp* app, uint8_t value); uint32_t notification_settings_display_off_delay_ticks(NotificationApp* app); +void notification_message_save_settings(NotificationApp* app) { + NotificationAppMessage m = {.type = SaveSettingsMessage, .back_event = osEventFlagsNew(NULL)}; + furi_check(osMessageQueuePut(app->queue, &m, 0, osWaitForever) == osOK); + osEventFlagsWait(m.back_event, NOTIFICATION_EVENT_COMPLETE, osFlagsWaitAny, osWaitForever); + osEventFlagsDelete(m.back_event); +}; + // internal layer void notification_apply_internal_led_layer(NotificationLedLayer* layer, uint8_t layer_value) { furi_assert(layer); @@ -103,7 +111,7 @@ void notification_reset_notification_layer(NotificationApp* app, uint8_t reset_m static void notification_apply_notification_leds(NotificationApp* app, const uint8_t* values) { for(uint8_t i = 0; i < NOTIFICATION_LED_COUNT; i++) { notification_apply_notification_led_layer( - &app->led[i], notification_settings_get_display_brightness(app, values[i])); + &app->led[i], notification_settings_get_rgb_led_brightness(app, values[i])); } } @@ -197,7 +205,7 @@ void notification_process_notification_message( break; case NotificationMessageTypeVibro: if(notification_message->data.vibro.on) { - notification_vibro_on(); + if(app->settings.vibro_on) notification_vibro_on(); } else { notification_vibro_off(); } @@ -260,10 +268,6 @@ void notification_process_notification_message( if(reset_notifications) { notification_reset_notification_layer(app, reset_mask); } - - if(message->back_event != NULL) { - osEventFlagsSet(message->back_event, NOTIFICATION_EVENT_COMPLETE); - } } void notification_process_internal_message(NotificationApp* app, NotificationAppMessage* message) { @@ -303,10 +307,58 @@ void notification_process_internal_message(NotificationApp* app, NotificationApp notification_message_index++; notification_message = (*message->sequence)[notification_message_index]; } +} - if(message->back_event != NULL) { - osEventFlagsSet(message->back_event, NOTIFICATION_EVENT_COMPLETE); +static void notification_load_settings(NotificationApp* app) { + NotificationSettings settings; + InternalStorage* internal_storage = furi_record_open("internal-storage"); + const size_t settings_size = sizeof(NotificationSettings); + + FURI_LOG_I("notification", "Loading state from internal-storage"); + int ret = internal_storage_read_key( + internal_storage, NOTIFICATION_SETTINGS_PATH, (uint8_t*)&settings, settings_size); + + if(ret != settings_size) { + FURI_LOG_E("notification", "Load failed. Storage returned: %d", ret); + } else { + FURI_LOG_I("notification", "Load success", ret); + + if(settings.version != NOTIFICATION_SETTINGS_VERSION) { + FURI_LOG_E( + "notification", + "Version(%d != %d) mismatch", + app->settings.version, + NOTIFICATION_SETTINGS_VERSION); + } else { + osKernelLock(); + memcpy(&app->settings, &settings, settings_size); + osKernelUnlock(); + } } + + furi_record_close("internal-storage"); +}; + +static void notification_save_settings(NotificationApp* app) { + InternalStorage* internal_storage = furi_record_open("internal-storage"); + const size_t settings_size = sizeof(NotificationSettings); + + FURI_LOG_I("notification", "Saving state to internal-storage"); + int ret = internal_storage_write_key( + internal_storage, NOTIFICATION_SETTINGS_PATH, (uint8_t*)&app->settings, settings_size); + + if(ret != settings_size) { + FURI_LOG_E("notification", "Save failed. Storage returned: %d", ret); + } else { + FURI_LOG_I("notification", "Saved"); + } + + furi_record_close("internal-storage"); +}; + +static void input_event_callback(const void* value, void* context) { + NotificationApp* app = context; + notification_message(app, &sequence_display_on); } // App alloc @@ -319,6 +371,7 @@ static NotificationApp* notification_app_alloc() { app->settings.display_brightness = 1.0f; app->settings.led_brightness = 1.0f; app->settings.display_off_delay_ms = 30000; + app->settings.vibro_on = true; app->display.value[LayerInternal] = 0x00; app->display.value[LayerNotification] = 0x00; @@ -340,6 +393,13 @@ static NotificationApp* notification_app_alloc() { app->led[2].index = LayerInternal; app->led[2].light = LightBlue; + app->settings.version = NOTIFICATION_SETTINGS_VERSION; + + // display backlight control + app->event_record = furi_record_open("input_events"); + subscribe_pubsub(app->event_record, input_event_callback, app); + notification_message(app, &sequence_display_on); + return app; }; @@ -347,6 +407,8 @@ static NotificationApp* notification_app_alloc() { int32_t notification_app(void* p) { NotificationApp* app = notification_app_alloc(); + notification_load_settings(app); + notification_vibro_off(); notification_sound_off(); notification_apply_internal_led_layer(&app->display, 0x00); @@ -366,6 +428,14 @@ int32_t notification_app(void* p) { break; case InternalLayerMessage: notification_process_internal_message(app, &message); + break; + case SaveSettingsMessage: + notification_save_settings(app); + break; + } + + if(message.back_event != NULL) { + osEventFlagsSet(message.back_event, NOTIFICATION_EVENT_COMPLETE); } } diff --git a/applications/notification/notification-app.h b/applications/notification/notification-app.h index 764db411..38f2504e 100644 --- a/applications/notification/notification-app.h +++ b/applications/notification/notification-app.h @@ -9,6 +9,7 @@ typedef enum { NotificationLayerMessage, InternalLayerMessage, + SaveSettingsMessage, } NotificationAppMessageType; typedef struct { @@ -29,19 +30,27 @@ typedef struct { Light light; } NotificationLedLayer; +#define NOTIFICATION_SETTINGS_VERSION 0x01 +#define NOTIFICATION_SETTINGS_PATH "notification_settings" + typedef struct { + uint8_t version; float display_brightness; float led_brightness; float speaker_volume; uint32_t display_off_delay_ms; + bool vibro_on; } NotificationSettings; struct NotificationApp { osMessageQueueId_t queue; + PubSub* event_record; osTimerId_t display_timer; NotificationLedLayer display; NotificationLedLayer led[NOTIFICATION_LED_COUNT]; NotificationSettings settings; -}; \ No newline at end of file +}; + +void notification_message_save_settings(NotificationApp* app); \ No newline at end of file