1
mirror of https://github.com/flipperdevices/flipperzero-firmware.git synced 2025-12-12 04:41:26 +04:00

[FL-3947] Pinning of settings options (#4077)

* feat: pinning settings in favorites

* include archive in unit tests fw

* change settings icon

* update text with suggestions from the ui team

* Small touch of constness

---------

Co-authored-by: あく <alleteam@gmail.com>
Co-authored-by: hedger <hedger@users.noreply.github.com>
Co-authored-by: hedger <hedger@nanode.su>
This commit is contained in:
Anna Antonenko
2025-04-07 03:17:58 +04:00
committed by GitHub
parent f07048d1b0
commit eb0f5ef8c0
23 changed files with 447 additions and 203 deletions

View File

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

View File

@@ -4,12 +4,14 @@
typedef enum {
ArchiveAppTypeU2f,
ArchiveAppTypeSetting,
ArchiveAppTypeUnknown,
ArchiveAppsTotal,
} ArchiveAppTypeEnum;
static const ArchiveFileTypeEnum app_file_types[] = {
[ArchiveAppTypeU2f] = ArchiveFileTypeU2f,
[ArchiveAppTypeSetting] = ArchiveFileTypeSetting,
[ArchiveAppTypeUnknown] = ArchiveFileTypeUnknown,
};

View File

@@ -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"),
@@ -20,7 +20,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",
@@ -34,6 +34,7 @@ static const char* known_ext[] = {
[ArchiveFileTypeFolder] = "?",
[ArchiveFileTypeUnknown] = "*",
[ArchiveFileTypeAppOrJs] = ".fap|.js",
[ArchiveFileTypeSetting] = "?",
};
static const ArchiveFileTypeEnum known_type[] = {

View File

@@ -1,9 +1,10 @@
#include "archive_favorites.h"
#include "archive_files.h"
#include "archive_apps.h"
#include "archive_browser.h"
#include <dialogs/dialogs.h>
#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);
}

View File

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

View File

@@ -20,6 +20,7 @@ typedef enum {
ArchiveFileTypeFolder,
ArchiveFileTypeUnknown,
ArchiveFileTypeAppOrJs,
ArchiveFileTypeSetting,
ArchiveFileTypeLoading,
} ArchiveFileTypeEnum;

View File

@@ -52,8 +52,23 @@ static void archive_run_in_app(ArchiveBrowserView* browser, ArchiveFile_t* selec
UNUSED(browser);
Loader* loader = furi_record_open(RECORD_LOADER);
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, furi_string_get_cstr(app_name), NULL);
}
furi_string_free(app_name);
} else {
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), '/');
@@ -62,11 +77,13 @@ static void archive_run_in_app(ArchiveBrowserView* browser, ArchiveFile_t* selec
}
loader_start_with_gui_error(loader, app_name, param);
} else {
loader_start_with_gui_error(loader, app_name, furi_string_get_cstr(selected->path));
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);
}

View File

@@ -28,6 +28,7 @@ static const Icon* ArchiveItemIcons[] = {
[ArchiveFileTypeInfrared] = &I_ir_10px,
[ArchiveFileTypeBadUsb] = &I_badusb_10px,
[ArchiveFileTypeU2f] = &I_u2f_10px,
[ArchiveFileTypeSetting] = &I_settings_10px,
[ArchiveFileTypeUpdateManifest] = &I_update_10px,
[ArchiveFileTypeFolder] = &I_dir_10px,
[ArchiveFileTypeUnknown] = &I_unknown_10px,

View File

@@ -11,8 +11,12 @@ struct Submenu {
typedef struct {
FuriString* label;
uint32_t index;
union {
SubmenuItemCallback callback;
SubmenuItemCallbackEx callback_ex;
};
void* callback_context;
bool has_extended_events;
} SubmenuItem;
static void SubmenuItem_init(SubmenuItem* item) {
@@ -57,7 +61,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 void submenu_view_draw_callback(Canvas* canvas, void* _model) {
SubmenuModel* model = _model;
@@ -120,7 +124,10 @@ static bool submenu_view_input_callback(InputEvent* event, void* context) {
furi_assert(submenu);
bool consumed = false;
if(event->type == InputTypeShort) {
if(event->key == InputKeyOk) {
consumed = true;
submenu_process_ok(submenu, event->type);
} else if(event->type == InputTypeShort) {
switch(event->key) {
case InputKeyUp:
consumed = true;
@@ -130,10 +137,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;
}
@@ -211,6 +214,31 @@ void submenu_add_item(
item->index = index;
item->callback = callback;
item->callback_context = callback_context;
item->has_extended_events = false;
},
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);
}
@@ -357,7 +385,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(
@@ -371,8 +399,12 @@ void submenu_process_ok(Submenu* submenu) {
},
true);
if(item && item->callback) {
if(!item) 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);
}
}

View File

@@ -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
*
@@ -53,6 +54,22 @@ void submenu_add_item(
SubmenuItemCallback callback,
void* callback_context);
/** 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

View File

@@ -4,6 +4,7 @@
#include <gui/modules/submenu.h>
#include <assets_icons.h>
#include <applications.h>
#include <archive/helpers/archive_favorites.h>
#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);
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) {
@@ -128,7 +135,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,

View File

@@ -1,5 +1,16 @@
#include "power_settings_app.h"
const SubmenuSettingsHelperDescriptor settings_helper_descriptor = {
.app_name = "Power",
.options_cnt = 3,
.options =
{
{.name = "Battery Info", .scene_id = PowerSettingsAppSceneBatteryInfo},
{.name = "Reboot", .scene_id = PowerSettingsAppSceneReboot},
{.name = "Power OFF", .scene_id = PowerSettingsAppScenePowerOff},
},
};
static bool power_settings_custom_event_callback(void* context, uint32_t event) {
furi_assert(context);
PowerSettingsApp* app = context;
@@ -18,7 +29,7 @@ static void power_settings_tick_event_callback(void* context) {
scene_manager_handle_tick_event(app->scene_manager);
}
PowerSettingsApp* power_settings_app_alloc(uint32_t first_scene) {
PowerSettingsApp* power_settings_app_alloc(void) {
PowerSettingsApp* app = malloc(sizeof(PowerSettingsApp));
// Records
@@ -50,13 +61,23 @@ PowerSettingsApp* power_settings_app_alloc(uint32_t first_scene) {
view_dispatcher_add_view(
app->view_dispatcher, PowerSettingsAppViewDialog, dialog_ex_get_view(app->dialog));
// Set first scene
scene_manager_next_scene(app->scene_manager, first_scene);
// Helper
app->settings_helper = submenu_settings_helpers_alloc(&settings_helper_descriptor);
submenu_settings_helpers_assign_objects(
app->settings_helper,
app->view_dispatcher,
app->scene_manager,
app->submenu,
PowerSettingsAppViewSubmenu,
PowerSettingsAppSceneStart);
return app;
}
void power_settings_app_free(PowerSettingsApp* app) {
furi_assert(app);
// Helper
submenu_settings_helpers_free(app->settings_helper);
// Views
view_dispatcher_remove_view(app->view_dispatcher, PowerSettingsAppViewBatteryInfo);
battery_info_free(app->batery_info);
@@ -74,11 +95,14 @@ void power_settings_app_free(PowerSettingsApp* app) {
}
int32_t power_settings_app(void* p) {
PowerSettingsApp* app = power_settings_app_alloc();
if(!submenu_settings_helpers_app_start(app->settings_helper, p)) {
uint32_t first_scene = PowerSettingsAppSceneStart;
if(p && strlen(p) && !strcmp(p, "off")) {
first_scene = PowerSettingsAppScenePowerOff;
}
PowerSettingsApp* app = power_settings_app_alloc(first_scene);
scene_manager_next_scene(app->scene_manager, first_scene);
}
view_dispatcher_run(app->view_dispatcher);
power_settings_app_free(app);
return 0;

View File

@@ -14,6 +14,8 @@
#include "scenes/power_settings_scene.h"
#include <settings_helpers/submenu_based.h>
typedef struct {
Power* power;
Gui* gui;
@@ -23,6 +25,7 @@ typedef struct {
Submenu* submenu;
DialogEx* dialog;
PowerInfo info;
SubmenuSettingsHelper* settings_helper;
} PowerSettingsApp;
typedef enum {

View File

@@ -1,64 +1,16 @@
#include "../power_settings_app.h"
enum PowerSettingsSubmenuIndex {
PowerSettingsSubmenuIndexBatteryInfo,
PowerSettingsSubmenuIndexReboot,
PowerSettingsSubmenuIndexOff,
};
static void power_settings_scene_start_submenu_callback(void* context, uint32_t index) {
furi_assert(context);
PowerSettingsApp* app = context;
view_dispatcher_send_custom_event(app->view_dispatcher, index);
}
void power_settings_scene_start_on_enter(void* context) {
PowerSettingsApp* app = context;
Submenu* submenu = app->submenu;
submenu_add_item(
submenu,
"Battery Info",
PowerSettingsSubmenuIndexBatteryInfo,
power_settings_scene_start_submenu_callback,
app);
submenu_add_item(
submenu,
"Reboot",
PowerSettingsSubmenuIndexReboot,
power_settings_scene_start_submenu_callback,
app);
submenu_add_item(
submenu,
"Power OFF",
PowerSettingsSubmenuIndexOff,
power_settings_scene_start_submenu_callback,
app);
submenu_set_selected_item(
submenu, scene_manager_get_scene_state(app->scene_manager, PowerSettingsAppSceneStart));
view_dispatcher_switch_to_view(app->view_dispatcher, PowerSettingsAppViewSubmenu);
submenu_settings_helpers_scene_enter(app->settings_helper);
}
bool power_settings_scene_start_on_event(void* context, SceneManagerEvent event) {
PowerSettingsApp* app = context;
bool consumed = false;
if(event.type == SceneManagerEventTypeCustom) {
if(event.event == PowerSettingsSubmenuIndexBatteryInfo) {
scene_manager_next_scene(app->scene_manager, PowerSettingsAppSceneBatteryInfo);
} else if(event.event == PowerSettingsSubmenuIndexReboot) {
scene_manager_next_scene(app->scene_manager, PowerSettingsAppSceneReboot);
} else if(event.event == PowerSettingsSubmenuIndexOff) {
scene_manager_next_scene(app->scene_manager, PowerSettingsAppScenePowerOff);
}
scene_manager_set_scene_state(app->scene_manager, PowerSettingsAppSceneStart, event.event);
consumed = true;
}
return consumed;
return submenu_settings_helpers_scene_event(app->settings_helper, event);
}
void power_settings_scene_start_on_exit(void* context) {
PowerSettingsApp* app = context;
submenu_reset(app->submenu);
submenu_settings_helpers_scene_exit(app->settings_helper);
}

View File

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

View File

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

View File

@@ -16,6 +16,10 @@
#include "scenes/storage_settings_scene.h"
#include <settings_helpers/submenu_based.h>
#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 {

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 B

View File

@@ -76,6 +76,7 @@ FIRMWARE_APPS = {
"radio_device_cc1101_ext",
"unit_tests",
"js_app",
"archive",
],
}

View File

@@ -0,0 +1,103 @@
#include "submenu_based.h"
#include <archive/helpers/archive_favorites.h>
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);
}

View File

@@ -0,0 +1,89 @@
#include <gui/modules/submenu.h>
#include <gui/view_dispatcher.h>
#include <gui/scene_manager.h>
#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

View File

@@ -2726,6 +2726,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*
1 entry status name type params
2726 Function - strxfrm size_t char*, const char*, size_t
2727 Function - strxfrm_l size_t char*, const char*, size_t, locale_t
2728 Function + submenu_add_item void Submenu*, const char*, uint32_t, SubmenuItemCallback, void*
2729 Function + submenu_add_item_ex void Submenu*, const char*, uint32_t, SubmenuItemCallbackEx, void*
2730 Function + submenu_alloc Submenu*
2731 Function + submenu_change_item_label void Submenu*, uint32_t, const char*
2732 Function + submenu_free void Submenu*

View File

@@ -3575,6 +3575,7 @@ Function,+,subghz_worker_set_pair_callback,void,"SubGhzWorker*, SubGhzWorkerPair
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_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*
1 entry status name type params
3575 Function + subghz_worker_start void SubGhzWorker*
3576 Function + subghz_worker_stop void SubGhzWorker*
3577 Function + submenu_add_item void Submenu*, const char*, uint32_t, SubmenuItemCallback, void*
3578 Function + submenu_add_item_ex void Submenu*, const char*, uint32_t, SubmenuItemCallbackEx, void*
3579 Function + submenu_alloc Submenu*
3580 Function + submenu_change_item_label void Submenu*, uint32_t, const char*
3581 Function + submenu_free void Submenu*