diff --git a/applications/main/archive/helpers/archive_apps.c b/applications/main/archive/helpers/archive_apps.c index 7aca29364..65d5a5af9 100644 --- a/applications/main/archive/helpers/archive_apps.c +++ b/applications/main/archive/helpers/archive_apps.c @@ -1,8 +1,9 @@ #include "archive_apps.h" #include "archive_browser.h" -static const char* known_apps[] = { +static const char* const known_apps[] = { [ArchiveAppTypeU2f] = "u2f", + [ArchiveAppTypeSetting] = "setting", }; ArchiveAppTypeEnum archive_get_app_type(const char* path) { @@ -36,6 +37,8 @@ bool archive_app_is_available(void* context, const char* path) { furi_record_close(RECORD_STORAGE); return file_exists; + } else if(app == ArchiveAppTypeSetting) { + return true; } else { return false; } @@ -53,6 +56,9 @@ bool archive_app_read_dir(void* context, const char* path) { if(app == ArchiveAppTypeU2f) { archive_add_app_item(browser, "/app:u2f/U2F Token"); return true; + } else if(app == ArchiveAppTypeSetting) { + archive_add_app_item(browser, path); + return true; } else { return false; } @@ -75,6 +81,8 @@ void archive_app_delete_file(void* context, const char* path) { if(archive_is_favorite("/app:u2f/U2F Token")) { archive_favorites_delete("/app:u2f/U2F Token"); } + } else if(app == ArchiveAppTypeSetting) { + // can't delete a setting! } if(res) { diff --git a/applications/main/archive/helpers/archive_apps.h b/applications/main/archive/helpers/archive_apps.h index d9d1dec34..f5d050387 100644 --- a/applications/main/archive/helpers/archive_apps.h +++ b/applications/main/archive/helpers/archive_apps.h @@ -4,12 +4,14 @@ typedef enum { ArchiveAppTypeU2f, + ArchiveAppTypeSetting, ArchiveAppTypeUnknown, ArchiveAppsTotal, } ArchiveAppTypeEnum; static const ArchiveFileTypeEnum app_file_types[] = { [ArchiveAppTypeU2f] = ArchiveFileTypeU2f, + [ArchiveAppTypeSetting] = ArchiveFileTypeSetting, [ArchiveAppTypeUnknown] = ArchiveFileTypeUnknown, }; diff --git a/applications/main/archive/helpers/archive_browser.h b/applications/main/archive/helpers/archive_browser.h index bdfeba035..a2330b494 100644 --- a/applications/main/archive/helpers/archive_browser.h +++ b/applications/main/archive/helpers/archive_browser.h @@ -7,7 +7,7 @@ #define TAB_DEFAULT ArchiveTabFavorites // Start tab #define FILE_LIST_BUF_LEN 50 -static const char* tab_default_paths[] = { +static const char* const tab_default_paths[] = { [ArchiveTabFavorites] = "/app:favorites", [ArchiveTabIButton] = EXT_PATH("ibutton"), [ArchiveTabNFC] = EXT_PATH("nfc"), @@ -22,7 +22,7 @@ static const char* tab_default_paths[] = { [ArchiveTabBrowser] = STORAGE_EXT_PATH_PREFIX, }; -static const char* known_ext[] = { +static const char* const known_ext[] = { [ArchiveFileTypeIButton] = ".ibtn", [ArchiveFileTypeNFC] = ".nfc", [ArchiveFileTypeSubGhz] = ".sub", @@ -37,6 +37,7 @@ static const char* known_ext[] = { [ArchiveFileTypeFolder] = "?", [ArchiveFileTypeUnknown] = "*", [ArchiveFileTypeAppOrJs] = ".fap|.js", + [ArchiveFileTypeSetting] = "?", }; static const ArchiveFileTypeEnum known_type[] = { diff --git a/applications/main/archive/helpers/archive_favorites.c b/applications/main/archive/helpers/archive_favorites.c index 351d68c1d..5ed3adfe3 100644 --- a/applications/main/archive/helpers/archive_favorites.c +++ b/applications/main/archive/helpers/archive_favorites.c @@ -1,9 +1,10 @@ - #include "archive_favorites.h" #include "archive_files.h" #include "archive_apps.h" #include "archive_browser.h" +#include + #define ARCHIVE_FAV_FILE_BUF_LEN 32 static bool archive_favorites_read_line(File* file, FuriString* str_result) { @@ -337,3 +338,46 @@ void archive_favorites_save(void* context) { storage_file_free(file); furi_record_close(RECORD_STORAGE); } + +void archive_favorites_handle_setting_pin_unpin(const char* app_name, const char* setting) { + DialogMessage* message = dialog_message_alloc(); + + FuriString* setting_path = furi_string_alloc_set_str(app_name); + if(setting) { + furi_string_push_back(setting_path, '/'); + furi_string_cat_str(setting_path, setting); + } + const char* setting_path_str = furi_string_get_cstr(setting_path); + + bool is_favorite = archive_is_favorite("/app:setting/%s", setting_path_str); + dialog_message_set_header( + message, + is_favorite ? "Unpin This Setting?" : "Pin This Setting?", + 64, + 0, + AlignCenter, + AlignTop); + dialog_message_set_text( + message, + is_favorite ? "It will no longer be\naccessible from the\nFavorites menu" : + "It will be accessible from the\nFavorites menu", + 64, + 32, + AlignCenter, + AlignCenter); + dialog_message_set_buttons( + message, is_favorite ? "Unpin" : "Go back", NULL, is_favorite ? "Keep pinned" : "Pin"); + + DialogsApp* dialogs = furi_record_open(RECORD_DIALOGS); + DialogMessageButton button = dialog_message_show(dialogs, message); + furi_record_close(RECORD_DIALOGS); + + if(is_favorite && button == DialogMessageButtonLeft) { + archive_favorites_delete("/app:setting/%s", setting_path_str); + } else if(!is_favorite && button == DialogMessageButtonRight) { + archive_file_append(ARCHIVE_FAV_PATH, "/app:setting/%s\n", setting_path_str); + } + + furi_string_free(setting_path); + dialog_message_free(message); +} diff --git a/applications/main/archive/helpers/archive_favorites.h b/applications/main/archive/helpers/archive_favorites.h index 75070c44d..3a43a0e75 100644 --- a/applications/main/archive/helpers/archive_favorites.h +++ b/applications/main/archive/helpers/archive_favorites.h @@ -12,3 +12,13 @@ bool archive_is_favorite(const char* format, ...) _ATTRIBUTE((__format__(__print bool archive_favorites_rename(const char* src, const char* dst); void archive_add_to_favorites(const char* file_path); void archive_favorites_save(void* context); + +/** + * Intended to be called by settings apps to handle long presses, as well as + * internally from within the archive + * + * @param app_name name of the referring application + * @param setting name of the setting, which will be both displayed to the user + * and passed to the application as an argument upon recall + */ +void archive_favorites_handle_setting_pin_unpin(const char* app_name, const char* setting); diff --git a/applications/main/archive/helpers/archive_files.h b/applications/main/archive/helpers/archive_files.h index a4ef5920b..9dd1cbcde 100644 --- a/applications/main/archive/helpers/archive_files.h +++ b/applications/main/archive/helpers/archive_files.h @@ -23,6 +23,7 @@ typedef enum { ArchiveFileTypeFolder, ArchiveFileTypeUnknown, ArchiveFileTypeAppOrJs, + ArchiveFileTypeSetting, ArchiveFileTypeLoading, } ArchiveFileTypeEnum; diff --git a/applications/main/archive/scenes/archive_scene_browser.c b/applications/main/archive/scenes/archive_scene_browser.c index 884d6ae85..bd24b51ad 100644 --- a/applications/main/archive/scenes/archive_scene_browser.c +++ b/applications/main/archive/scenes/archive_scene_browser.c @@ -54,20 +54,37 @@ static void archive_run_in_app(ArchiveBrowserView* browser, ArchiveFile_t* selec UNUSED(browser); Loader* loader = furi_record_open(RECORD_LOADER); - const char* app_name = archive_get_flipper_app_name(selected->type); - - if(app_name) { - if(selected->is_app) { - char* param = strrchr(furi_string_get_cstr(selected->path), '/'); - if(param != NULL) { - param++; - } - loader_start_with_gui_error(loader, app_name, param); + if(selected->type == ArchiveFileTypeSetting) { + FuriString* app_name = furi_string_alloc_set(selected->path); + furi_string_right(app_name, furi_string_search_char(app_name, '/', 1) + 1); + size_t slash = furi_string_search_char(app_name, '/', 1); + if(slash != FURI_STRING_FAILURE) { + furi_string_left(app_name, slash); + FuriString* app_args = + furi_string_alloc_set_str(furi_string_get_cstr(app_name) + slash + 1); + loader_start_with_gui_error( + loader, furi_string_get_cstr(app_name), furi_string_get_cstr(app_args)); + furi_string_free(app_args); } else { - loader_start_with_gui_error(loader, app_name, furi_string_get_cstr(selected->path)); + loader_start_with_gui_error(loader, furi_string_get_cstr(app_name), NULL); } + furi_string_free(app_name); } else { - loader_start_with_gui_error(loader, furi_string_get_cstr(selected->path), NULL); + const char* app_name = archive_get_flipper_app_name(selected->type); + if(app_name) { + if(selected->is_app) { + char* param = strrchr(furi_string_get_cstr(selected->path), '/'); + if(param != NULL) { + param++; + } + loader_start_with_gui_error(loader, app_name, param); + } else { + loader_start_with_gui_error( + loader, app_name, furi_string_get_cstr(selected->path)); + } + } else { + loader_start_with_gui_error(loader, furi_string_get_cstr(selected->path), NULL); + } } furi_record_close(RECORD_LOADER); diff --git a/applications/main/archive/views/archive_browser_view.c b/applications/main/archive/views/archive_browser_view.c index e1739ca5b..aa992264e 100644 --- a/applications/main/archive/views/archive_browser_view.c +++ b/applications/main/archive/views/archive_browser_view.c @@ -33,6 +33,7 @@ static const Icon* ArchiveItemIcons[] = { [ArchiveFileTypeBadUsb] = &I_badusb_10px, [ArchiveFileTypeU2f] = &I_u2f_10px, [ArchiveFileTypeApplication] = &I_Apps_10px, + [ArchiveFileTypeSetting] = &I_settings_10px, [ArchiveFileTypeUpdateManifest] = &I_update_10px, [ArchiveFileTypeFolder] = &I_dir_10px, [ArchiveFileTypeUnknown] = &I_unknown_10px, diff --git a/applications/services/gui/modules/submenu.c b/applications/services/gui/modules/submenu.c index 74f93320f..648e213c0 100644 --- a/applications/services/gui/modules/submenu.c +++ b/applications/services/gui/modules/submenu.c @@ -13,8 +13,12 @@ struct Submenu { typedef struct { FuriString* label; uint32_t index; - SubmenuItemCallback callback; + union { + SubmenuItemCallback callback; + SubmenuItemCallbackEx callback_ex; + }; void* callback_context; + bool has_extended_events; bool locked; FuriString* locked_message; } SubmenuItem; @@ -70,7 +74,7 @@ typedef struct { static void submenu_process_up(Submenu* submenu); static void submenu_process_down(Submenu* submenu); -static void submenu_process_ok(Submenu* submenu); +static void submenu_process_ok(Submenu* submenu, InputType input_type); static size_t submenu_items_on_screen(bool header, bool vertical) { size_t res = (vertical) ? 8 : 4; @@ -196,6 +200,9 @@ static bool submenu_view_input_callback(InputEvent* event, void* context) { with_view_model( submenu->view, SubmenuModel * model, { model->locked_message_visible = false; }, true); consumed = true; + } else if(event->key == InputKeyOk) { + consumed = true; + submenu_process_ok(submenu, event->type); } else if(event->type == InputTypeShort) { switch(event->key) { case InputKeyUp: @@ -206,10 +213,6 @@ static bool submenu_view_input_callback(InputEvent* event, void* context) { consumed = true; submenu_process_down(submenu); break; - case InputKeyOk: - consumed = true; - submenu_process_ok(submenu); - break; default: break; } @@ -310,6 +313,7 @@ void submenu_add_lockable_item( item->index = index; item->callback = callback; item->callback_context = callback_context; + item->has_extended_events = false; item->locked = locked; if(locked) { furi_string_set_str(item->locked_message, locked_message); @@ -318,6 +322,30 @@ void submenu_add_lockable_item( true); } +void submenu_add_item_ex( + Submenu* submenu, + const char* label, + uint32_t index, + SubmenuItemCallbackEx callback, + void* callback_context) { + SubmenuItem* item = NULL; + furi_check(label); + furi_check(submenu); + + with_view_model( + submenu->view, + SubmenuModel * model, + { + item = SubmenuItemArray_push_new(model->items); + furi_string_set_str(item->label, label); + item->index = index; + item->callback_ex = callback; + item->callback_context = callback_context; + item->has_extended_events = true; + }, + true); +} + void submenu_change_item_label(Submenu* submenu, uint32_t index, const char* label) { furi_check(submenu); furi_check(label); @@ -465,7 +493,7 @@ void submenu_process_down(Submenu* submenu) { true); } -void submenu_process_ok(Submenu* submenu) { +void submenu_process_ok(Submenu* submenu, InputType input_type) { SubmenuItem* item = NULL; with_view_model( @@ -483,8 +511,15 @@ void submenu_process_ok(Submenu* submenu) { }, true); - if(item && !item->locked && item->callback) { + if(!item) return; + if(item->locked) { + return; + } + + if(!item->has_extended_events && input_type == InputTypeShort && item->callback) { item->callback(item->callback_context, item->index); + } else if(item->has_extended_events && item->callback_ex) { + item->callback_ex(item->callback_context, input_type, item->index); } } diff --git a/applications/services/gui/modules/submenu.h b/applications/services/gui/modules/submenu.h index d77f570c2..750167543 100644 --- a/applications/services/gui/modules/submenu.h +++ b/applications/services/gui/modules/submenu.h @@ -14,6 +14,7 @@ extern "C" { /** Submenu anonymous structure */ typedef struct Submenu Submenu; typedef void (*SubmenuItemCallback)(void* context, uint32_t index); +typedef void (*SubmenuItemCallbackEx)(void* context, InputType input_type, uint32_t index); /** Allocate and initialize submenu * @@ -73,6 +74,22 @@ void submenu_add_lockable_item( bool locked, const char* locked_message); +/** Add item to submenu with extended press events + * + * @param submenu Submenu instance + * @param label menu item label + * @param index menu item index, used for callback, may be + * the same with other items + * @param callback menu item extended callback + * @param callback_context menu item callback context + */ +void submenu_add_item_ex( + Submenu* submenu, + const char* label, + uint32_t index, + SubmenuItemCallbackEx callback, + void* callback_context); + /** Change label of an existing item * * @param submenu Submenu instance diff --git a/applications/services/loader/loader_menu.c b/applications/services/loader/loader_menu.c index ad4a4c7d5..525861f55 100644 --- a/applications/services/loader/loader_menu.c +++ b/applications/services/loader/loader_menu.c @@ -4,6 +4,7 @@ #include #include #include +#include #include "loader.h" #include "loader_menu.h" @@ -71,10 +72,16 @@ static void loader_menu_applications_callback(void* context, uint32_t index) { loader_menu_start(name); } -static void loader_menu_settings_menu_callback(void* context, uint32_t index) { +static void + loader_menu_settings_menu_callback(void* context, InputType input_type, uint32_t index) { UNUSED(context); - const char* name = FLIPPER_SETTINGS_APPS[index].name; - loader_menu_start(name); + if(input_type == InputTypeShort) { + const char* name = FLIPPER_SETTINGS_APPS[index].name; + loader_menu_start(name); + } else if(input_type == InputTypeLong) { + const char* name = FLIPPER_SETTINGS_APPS[index].name; + archive_favorites_handle_setting_pin_unpin(name, NULL); + } } static void loader_menu_switch_to_settings(void* context, uint32_t index) { @@ -130,7 +137,7 @@ static void loader_menu_build_menu(LoaderMenuApp* app, LoaderMenu* menu) { static void loader_menu_build_submenu(LoaderMenuApp* app, LoaderMenu* loader_menu) { for(size_t i = 0; i < FLIPPER_SETTINGS_APPS_COUNT; i++) { - submenu_add_item( + submenu_add_item_ex( app->settings_menu, FLIPPER_SETTINGS_APPS[i].name, i, diff --git a/applications/settings/storage_settings/scenes/storage_settings_scene_start.c b/applications/settings/storage_settings/scenes/storage_settings_scene_start.c index e351a2ef7..f03474ac1 100644 --- a/applications/settings/storage_settings/scenes/storage_settings_scene_start.c +++ b/applications/settings/storage_settings/scenes/storage_settings_scene_start.c @@ -1,131 +1,20 @@ #include "../storage_settings.h" -enum StorageSettingsStartSubmenuIndex { - StorageSettingsStartSubmenuIndexInternalInfo, - StorageSettingsStartSubmenuIndexSDInfo, - StorageSettingsStartSubmenuIndexUnmount, - StorageSettingsStartSubmenuIndexFormat, - StorageSettingsStartSubmenuIndexBenchy, - StorageSettingsStartSubmenuIndexFactoryReset -}; - -static void storage_settings_scene_start_submenu_callback(void* context, uint32_t index) { - StorageSettings* app = context; - - view_dispatcher_send_custom_event(app->view_dispatcher, index); -} - void storage_settings_scene_start_on_enter(void* context) { StorageSettings* app = context; - Submenu* submenu = app->submenu; - - submenu_add_item( - submenu, - "About Internal Storage", - StorageSettingsStartSubmenuIndexInternalInfo, - storage_settings_scene_start_submenu_callback, - app); - submenu_add_item( - submenu, - "About SD Card", - StorageSettingsStartSubmenuIndexSDInfo, - storage_settings_scene_start_submenu_callback, - app); FS_Error sd_status = storage_sd_status(app->fs_api); - if(sd_status != FSE_OK) { - submenu_add_item( - submenu, - "Mount SD Card", - StorageSettingsStartSubmenuIndexUnmount, - storage_settings_scene_start_submenu_callback, - app); - } else { - submenu_add_item( - submenu, - "Unmount SD Card", - StorageSettingsStartSubmenuIndexUnmount, - storage_settings_scene_start_submenu_callback, - app); - } - - submenu_add_item( - submenu, - "Format SD Card", - StorageSettingsStartSubmenuIndexFormat, - storage_settings_scene_start_submenu_callback, - app); - submenu_add_item( - submenu, - "Benchmark SD Card", - StorageSettingsStartSubmenuIndexBenchy, - storage_settings_scene_start_submenu_callback, - app); - submenu_add_item( - submenu, - "Factory Reset", - StorageSettingsStartSubmenuIndexFactoryReset, - storage_settings_scene_start_submenu_callback, - app); - - submenu_set_selected_item( - submenu, scene_manager_get_scene_state(app->scene_manager, StorageSettingsStart)); - - view_dispatcher_switch_to_view(app->view_dispatcher, StorageSettingsViewSubmenu); + app->helper_descriptor->options[STORAGE_SETTINGS_MOUNT_INDEX].name = + (sd_status != FSE_OK) ? "Mount SD Card" : "Unmount SD Card"; + submenu_settings_helpers_scene_enter(app->settings_helper); } bool storage_settings_scene_start_on_event(void* context, SceneManagerEvent event) { StorageSettings* app = context; - bool consumed = false; - - if(event.type == SceneManagerEventTypeCustom) { - switch(event.event) { - case StorageSettingsStartSubmenuIndexSDInfo: - scene_manager_set_scene_state( - app->scene_manager, StorageSettingsStart, StorageSettingsStartSubmenuIndexSDInfo); - scene_manager_next_scene(app->scene_manager, StorageSettingsSDInfo); - consumed = true; - break; - case StorageSettingsStartSubmenuIndexInternalInfo: - scene_manager_set_scene_state( - app->scene_manager, - StorageSettingsStart, - StorageSettingsStartSubmenuIndexInternalInfo); - scene_manager_next_scene(app->scene_manager, StorageSettingsInternalInfo); - consumed = true; - break; - case StorageSettingsStartSubmenuIndexUnmount: - scene_manager_set_scene_state( - app->scene_manager, StorageSettingsStart, StorageSettingsStartSubmenuIndexUnmount); - scene_manager_next_scene(app->scene_manager, StorageSettingsUnmountConfirm); - consumed = true; - break; - case StorageSettingsStartSubmenuIndexFormat: - scene_manager_set_scene_state( - app->scene_manager, StorageSettingsStart, StorageSettingsStartSubmenuIndexFormat); - scene_manager_next_scene(app->scene_manager, StorageSettingsFormatConfirm); - consumed = true; - break; - case StorageSettingsStartSubmenuIndexBenchy: - scene_manager_set_scene_state( - app->scene_manager, StorageSettingsStart, StorageSettingsStartSubmenuIndexBenchy); - scene_manager_next_scene(app->scene_manager, StorageSettingsBenchmarkConfirm); - consumed = true; - break; - case StorageSettingsStartSubmenuIndexFactoryReset: - scene_manager_set_scene_state( - app->scene_manager, - StorageSettingsStart, - StorageSettingsStartSubmenuIndexFactoryReset); - scene_manager_next_scene(app->scene_manager, StorageSettingsFactoryReset); - consumed = true; - break; - } - } - return consumed; + return submenu_settings_helpers_scene_event(app->settings_helper, event); } void storage_settings_scene_start_on_exit(void* context) { StorageSettings* app = context; - submenu_reset(app->submenu); + submenu_settings_helpers_scene_exit(app->settings_helper); } diff --git a/applications/settings/storage_settings/storage_settings.c b/applications/settings/storage_settings/storage_settings.c index 354632890..b4cde7b6b 100644 --- a/applications/settings/storage_settings/storage_settings.c +++ b/applications/settings/storage_settings/storage_settings.c @@ -1,5 +1,19 @@ #include "storage_settings.h" +const SubmenuSettingsHelperDescriptor descriptor_template = { + .app_name = "Storage", + .options_cnt = 6, + .options = + { + {.name = "About Internal Storage", .scene_id = StorageSettingsSDInfo}, + {.name = "About SD Card", .scene_id = StorageSettingsInternalInfo}, + {.name = "Unmount SD Card", .scene_id = StorageSettingsUnmountConfirm}, + {.name = "Format SD Card", .scene_id = StorageSettingsFormatConfirm}, + {.name = "Benchmark SD Card", .scene_id = StorageSettingsBenchmarkConfirm}, + {.name = "Factory Reset", .scene_id = StorageSettingsFactoryReset}, + }, +}; + static bool storage_settings_custom_event_callback(void* context, uint32_t event) { furi_assert(context); StorageSettings* app = context; @@ -40,12 +54,27 @@ static StorageSettings* storage_settings_alloc(void) { view_dispatcher_add_view( app->view_dispatcher, StorageSettingsViewDialogEx, dialog_ex_get_view(app->dialog_ex)); - scene_manager_next_scene(app->scene_manager, StorageSettingsStart); + size_t descriptor_size = + sizeof(SubmenuSettingsHelperDescriptor) + + (descriptor_template.options_cnt * sizeof(SubmenuSettingsHelperOption)); + app->helper_descriptor = malloc(descriptor_size); + memcpy(app->helper_descriptor, &descriptor_template, descriptor_size); + app->settings_helper = submenu_settings_helpers_alloc(app->helper_descriptor); + submenu_settings_helpers_assign_objects( + app->settings_helper, + app->view_dispatcher, + app->scene_manager, + app->submenu, + StorageSettingsViewSubmenu, + StorageSettingsStart); return app; } static void storage_settings_free(StorageSettings* app) { + submenu_settings_helpers_free(app->settings_helper); + free(app->helper_descriptor); + view_dispatcher_remove_view(app->view_dispatcher, StorageSettingsViewSubmenu); submenu_free(app->submenu); @@ -68,6 +97,10 @@ int32_t storage_settings_app(void* p) { UNUSED(p); StorageSettings* app = storage_settings_alloc(); + if(!submenu_settings_helpers_app_start(app->settings_helper, p)) { + scene_manager_next_scene(app->scene_manager, StorageSettingsStart); + } + view_dispatcher_run(app->view_dispatcher); storage_settings_free(app); diff --git a/applications/settings/storage_settings/storage_settings.h b/applications/settings/storage_settings/storage_settings.h index fd841623e..5f4c6404e 100644 --- a/applications/settings/storage_settings/storage_settings.h +++ b/applications/settings/storage_settings/storage_settings.h @@ -16,6 +16,10 @@ #include "scenes/storage_settings_scene.h" +#include + +#define STORAGE_SETTINGS_MOUNT_INDEX 2 + #ifdef __cplusplus extern "C" { #endif @@ -36,6 +40,10 @@ typedef struct { // text FuriString* text_string; + + // helpers + SubmenuSettingsHelperDescriptor* helper_descriptor; + SubmenuSettingsHelper* settings_helper; } StorageSettings; typedef enum { diff --git a/assets/icons/Archive/settings_10px.png b/assets/icons/Archive/settings_10px.png new file mode 100644 index 000000000..2c1e4a262 Binary files /dev/null and b/assets/icons/Archive/settings_10px.png differ diff --git a/fbt_options.py b/fbt_options.py index f3396f98c..54569d525 100644 --- a/fbt_options.py +++ b/fbt_options.py @@ -76,6 +76,7 @@ FIRMWARE_APPS = { "radio_device_cc1101_ext", "unit_tests", "js_app", + "archive", ], } diff --git a/lib/toolbox/settings_helpers/submenu_based.c b/lib/toolbox/settings_helpers/submenu_based.c new file mode 100644 index 000000000..ce785dd73 --- /dev/null +++ b/lib/toolbox/settings_helpers/submenu_based.c @@ -0,0 +1,103 @@ +#include "submenu_based.h" +#include + +struct SubmenuSettingsHelper { + const SubmenuSettingsHelperDescriptor* descriptor; + ViewDispatcher* view_dispatcher; + SceneManager* scene_manager; + Submenu* submenu; + uint32_t submenu_view_id; + uint32_t main_scene_id; +}; + +SubmenuSettingsHelper* + submenu_settings_helpers_alloc(const SubmenuSettingsHelperDescriptor* descriptor) { + furi_check(descriptor); + SubmenuSettingsHelper* helper = malloc(sizeof(SubmenuSettingsHelper)); + helper->descriptor = descriptor; + return helper; +} + +void submenu_settings_helpers_assign_objects( + SubmenuSettingsHelper* helper, + ViewDispatcher* view_dispatcher, + SceneManager* scene_manager, + Submenu* submenu, + uint32_t submenu_view_id, + uint32_t main_scene_id) { + furi_check(helper); + furi_check(view_dispatcher); + furi_check(scene_manager); + furi_check(submenu); + helper->view_dispatcher = view_dispatcher; + helper->scene_manager = scene_manager; + helper->submenu = submenu; + helper->submenu_view_id = submenu_view_id; + helper->main_scene_id = main_scene_id; +} + +void submenu_settings_helpers_free(SubmenuSettingsHelper* helper) { + free(helper); +} + +bool submenu_settings_helpers_app_start(SubmenuSettingsHelper* helper, void* arg) { + furi_check(helper); + if(!arg) return false; + + const char* option = arg; + for(size_t i = 0; i < helper->descriptor->options_cnt; i++) { + if(strcmp(helper->descriptor->options[i].name, option) == 0) { + scene_manager_next_scene( + helper->scene_manager, helper->descriptor->options[i].scene_id); + return true; + } + } + + return false; +} + +static void + submenu_settings_helpers_callback(void* context, InputType input_type, uint32_t index) { + SubmenuSettingsHelper* helper = context; + if(input_type == InputTypeShort) { + view_dispatcher_send_custom_event(helper->view_dispatcher, index); + } else if(input_type == InputTypeLong) { + archive_favorites_handle_setting_pin_unpin( + helper->descriptor->app_name, helper->descriptor->options[index].name); + } +} + +void submenu_settings_helpers_scene_enter(SubmenuSettingsHelper* helper) { + furi_check(helper); + for(size_t i = 0; i < helper->descriptor->options_cnt; i++) { + submenu_add_item_ex( + helper->submenu, + helper->descriptor->options[i].name, + i, + submenu_settings_helpers_callback, + helper); + } + + submenu_set_selected_item( + helper->submenu, + scene_manager_get_scene_state(helper->scene_manager, helper->main_scene_id)); + view_dispatcher_switch_to_view(helper->view_dispatcher, helper->submenu_view_id); +} + +bool submenu_settings_helpers_scene_event(SubmenuSettingsHelper* helper, SceneManagerEvent event) { + furi_check(helper); + + if(event.type == SceneManagerEventTypeCustom) { + scene_manager_next_scene( + helper->scene_manager, helper->descriptor->options[event.event].scene_id); + scene_manager_set_scene_state(helper->scene_manager, helper->main_scene_id, event.event); + return true; + } + + return false; +} + +void submenu_settings_helpers_scene_exit(SubmenuSettingsHelper* helper) { + furi_check(helper); + submenu_reset(helper->submenu); +} diff --git a/lib/toolbox/settings_helpers/submenu_based.h b/lib/toolbox/settings_helpers/submenu_based.h new file mode 100644 index 000000000..6929cb04a --- /dev/null +++ b/lib/toolbox/settings_helpers/submenu_based.h @@ -0,0 +1,89 @@ +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef void (*SubmenuSettingsHelpherCallback)(void* context, uint32_t index); + +typedef struct { + const char* name; + uint32_t scene_id; +} SubmenuSettingsHelperOption; + +typedef struct { + const char* app_name; + size_t options_cnt; + SubmenuSettingsHelperOption options[]; +} SubmenuSettingsHelperDescriptor; + +typedef struct SubmenuSettingsHelper SubmenuSettingsHelper; + +/** + * @brief Allocates a submenu-based settings helper + * @param descriptor settings descriptor + */ +SubmenuSettingsHelper* + submenu_settings_helpers_alloc(const SubmenuSettingsHelperDescriptor* descriptor); + +/** + * @brief Assigns dynamic objects to the submenu-based settings helper + * @param helper helper object + * @param view_dispatcher ViewDispatcher + * @param scene_manager SceneManager + * @param submenu Submenu + * @param submenu_view_id Submenu view id in the ViewDispatcher + * @param main_scene_id Main scene id in the SceneManager + */ +void submenu_settings_helpers_assign_objects( + SubmenuSettingsHelper* helper, + ViewDispatcher* view_dispatcher, + SceneManager* scene_manager, + Submenu* submenu, + uint32_t submenu_view_id, + uint32_t main_scene_id); + +/** + * @brief Frees a submenu-based settings helper + * @param helper helper object + */ +void submenu_settings_helpers_free(SubmenuSettingsHelper* helper); + +/** + * @brief App start callback for the submenu-based settings helper + * + * If an argument containing one of the options was provided, launches the + * corresponding scene. + * + * @param helper helper object + * @param arg app argument, may be NULL + * @returns true if a setting name was provided in the argument, false if normal + * app operation shall commence + */ +bool submenu_settings_helpers_app_start(SubmenuSettingsHelper* helper, void* arg); + +/** + * @brief Main scene enter callback for the submenu-based settings helper + * @param helper helper object + */ +void submenu_settings_helpers_scene_enter(SubmenuSettingsHelper* helper); + +/** + * @brief Main scene event callback for the submenu-based settings helper + * @param helper helper object + * @param event event data + * @returns true if the event was consumed, false otherwise + */ +bool submenu_settings_helpers_scene_event(SubmenuSettingsHelper* helper, SceneManagerEvent event); + +/** + * @brief Main scene exit callback for the submenu-based settings helper + * @param helper helper object + */ +void submenu_settings_helpers_scene_exit(SubmenuSettingsHelper* helper); + +#ifdef __cplusplus +} +#endif diff --git a/targets/f18/api_symbols.csv b/targets/f18/api_symbols.csv index 3b73d3e9a..91d666922 100644 --- a/targets/f18/api_symbols.csv +++ b/targets/f18/api_symbols.csv @@ -2728,6 +2728,7 @@ Function,-,strverscmp,int,"const char*, const char*" Function,-,strxfrm,size_t,"char*, const char*, size_t" Function,-,strxfrm_l,size_t,"char*, const char*, size_t, locale_t" Function,+,submenu_add_item,void,"Submenu*, const char*, uint32_t, SubmenuItemCallback, void*" +Function,+,submenu_add_item_ex,void,"Submenu*, const char*, uint32_t, SubmenuItemCallbackEx, void*" Function,+,submenu_alloc,Submenu*, Function,+,submenu_change_item_label,void,"Submenu*, uint32_t, const char*" Function,+,submenu_free,void,Submenu* diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index 5cd3c1276..384ae1ea4 100755 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -3664,6 +3664,7 @@ Function,+,subghz_worker_start,void,SubGhzWorker* Function,+,subghz_worker_stop,void,SubGhzWorker* Function,+,submenu_add_item,void,"Submenu*, const char*, uint32_t, SubmenuItemCallback, void*" Function,+,submenu_add_lockable_item,void,"Submenu*, const char*, uint32_t, SubmenuItemCallback, void*, _Bool, const char*" +Function,+,submenu_add_item_ex,void,"Submenu*, const char*, uint32_t, SubmenuItemCallbackEx, void*" Function,+,submenu_alloc,Submenu*, Function,+,submenu_change_item_label,void,"Submenu*, uint32_t, const char*" Function,+,submenu_free,void,Submenu*