[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
This commit is contained in:
parent
7734fb4018
commit
29da0e360c
@ -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])();
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
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;
|
@ -1,31 +0,0 @@
|
||||
#include <furi.h>
|
||||
#include <api-hal.h>
|
||||
#include <notification/notification-messages.h>
|
||||
|
||||
#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;
|
||||
}
|
282
applications/gui/modules/variable-item-list.c
Normal file
282
applications/gui/modules/variable-item-list.c
Normal file
@ -0,0 +1,282 @@
|
||||
#include "variable-item-list.h"
|
||||
#include "gui/canvas.h"
|
||||
#include <m-array.h>
|
||||
#include <furi.h>
|
||||
#include <gui/elements.h>
|
||||
#include <stdint.h>
|
||||
|
||||
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;
|
||||
}
|
64
applications/gui/modules/variable-item-list.h
Normal file
64
applications/gui/modules/variable-item-list.h
Normal file
@ -0,0 +1,64 @@
|
||||
#pragma once
|
||||
#include <gui/view.h>
|
||||
|
||||
#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
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
||||
|
231
applications/notification/notification-app-settings.c
Normal file
231
applications/notification/notification-app-settings.c
Normal file
@ -0,0 +1,231 @@
|
||||
#include <furi.h>
|
||||
#include "notification-app.h"
|
||||
#include <gui/modules/variable-item-list.h>
|
||||
#include <gui/view_dispatcher.h>
|
||||
|
||||
#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;
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
#include <furi.h>
|
||||
#include <api-hal.h>
|
||||
#include <internal-storage/internal-storage.h>
|
||||
#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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
};
|
||||
};
|
||||
|
||||
void notification_message_save_settings(NotificationApp* app);
|
Loading…
Reference in New Issue
Block a user