[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:
SG 2021-07-05 08:03:56 +10:00 committed by GitHub
parent 7734fb4018
commit 29da0e360c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 815 additions and 68 deletions

View File

@ -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])();

View File

@ -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);

View File

@ -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;

View File

@ -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;
}

View 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;
}

View 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

View File

@ -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);

View File

@ -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;

View File

@ -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);

View 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;
}

View File

@ -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);
}
}

View File

@ -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);