[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:
		
							
								
								
									
										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); | ||||
		Reference in New Issue
	
	Block a user