From a7c438633c5d0739e163bd1d4ea19ca09a3e0735 Mon Sep 17 00:00:00 2001 From: rdefeo Date: Tue, 12 Mar 2024 00:28:56 -0400 Subject: [PATCH 1/4] revamped UI, settings, icons --- actions/action_rfid.c | 20 +- images/ArrowDown_8x4.png | Bin 0 -> 112 bytes images/ArrowUp_8x4.png | Bin 0 -> 114 bytes images/Directory_10px.png | Bin 0 -> 311 bytes images/IR_10px.png | Bin 0 -> 305 bytes images/NFC_10px.png | Bin 0 -> 304 bytes images/Playlist_10px.png | Bin 0 -> 122 bytes images/RFID_10px.png | Bin 0 -> 308 bytes images/Settings_10px.png | Bin 0 -> 140 bytes images/SubGHz_10px.png | Bin 0 -> 299 bytes item.c | 28 +- item.h | 9 +- quac.c | 31 +- quac.h | 16 + quac_settings.c | 142 +++++++++ quac_settings.h | 9 + scenes/scene_items.c | 140 ++++++--- scenes/scene_settings.c | 133 +++++++++ scenes/scene_settings.h | 10 + scenes/scenes.c | 10 +- scenes/scenes.h | 12 +- views/action_menu.c | 611 ++++++++++++++++++++++++++++++++++++++ views/action_menu.h | 117 ++++++++ 23 files changed, 1207 insertions(+), 81 deletions(-) create mode 100644 images/ArrowDown_8x4.png create mode 100644 images/ArrowUp_8x4.png create mode 100644 images/Directory_10px.png create mode 100644 images/IR_10px.png create mode 100644 images/NFC_10px.png create mode 100644 images/Playlist_10px.png create mode 100644 images/RFID_10px.png create mode 100644 images/Settings_10px.png create mode 100644 images/SubGHz_10px.png create mode 100644 quac_settings.c create mode 100644 quac_settings.h create mode 100644 scenes/scene_settings.c create mode 100644 scenes/scene_settings.h create mode 100644 views/action_menu.c create mode 100644 views/action_menu.h diff --git a/actions/action_rfid.c b/actions/action_rfid.c index 2ec10286840..9c80ca8dac7 100644 --- a/actions/action_rfid.c +++ b/actions/action_rfid.c @@ -34,19 +34,18 @@ void action_rfid_tx(void* context, FuriString* action_path, FuriString* error) { size_t data_size = protocol_dict_get_max_data_size(dict); uint8_t* data = malloc(data_size); - FURI_LOG_I(TAG, "Max dict data size is %d", data_size); + // FURI_LOG_I(TAG, "Max dict data size is %d", data_size); bool successful_read = false; do { if(!flipper_format_file_open_existing(fff_data_file, furi_string_get_cstr(file_name))) { ACTION_SET_ERROR("RFID: Error opening %s", furi_string_get_cstr(file_name)); break; } - FURI_LOG_I(TAG, "Opened file"); if(!flipper_format_read_header(fff_data_file, temp_str, &temp_data32)) { ACTION_SET_ERROR("RFID: Missing or incorrect header"); break; } - FURI_LOG_I(TAG, "Read file headers"); + // FURI_LOG_I(TAG, "Read file headers"); // TODO: add better header checks here... if(!strcmp(furi_string_get_cstr(temp_str), "Flipper RFID key")) { } else { @@ -67,9 +66,9 @@ void action_rfid_tx(void* context, FuriString* action_path, FuriString* error) { // read and check data field size_t required_size = protocol_dict_get_data_size(dict, protocol); - FURI_LOG_I(TAG, "Protocol req data size is %d", required_size); + // FURI_LOG_I(TAG, "Protocol req data size is %d", required_size); if(!flipper_format_read_hex(fff_data_file, "Data", data, required_size)) { - FURI_LOG_E(TAG, "Error reading data"); + FURI_LOG_E(TAG, "RFID: Error reading data"); ACTION_SET_ERROR("RFID: Error reading data"); break; } @@ -86,7 +85,7 @@ void action_rfid_tx(void* context, FuriString* action_path, FuriString* error) { protocol_dict_set_data(dict, protocol, data, data_size); successful_read = true; - FURI_LOG_I(TAG, "protocol dict setup complete!"); + // FURI_LOG_I(TAG, "protocol dict setup complete!"); } while(false); if(successful_read) { @@ -95,14 +94,15 @@ void action_rfid_tx(void* context, FuriString* action_path, FuriString* error) { lfrfid_worker_start_thread(worker); lfrfid_worker_emulate_start(worker, protocol); - FURI_LOG_I(TAG, "Emulating RFID..."); - int16_t time_ms = 3000; - int16_t interval_ms = 200; + int16_t time_ms = app->settings.rfid_duration; + FURI_LOG_I( + TAG, "RFID: Emulating RFID (%s) for %d ms", furi_string_get_cstr(file_name), time_ms); + int16_t interval_ms = 100; while(time_ms > 0) { furi_delay_ms(interval_ms); time_ms -= interval_ms; } - FURI_LOG_I(TAG, "Emulation stopped"); + FURI_LOG_I(TAG, "RFID: Emulation stopped"); lfrfid_worker_stop(worker); lfrfid_worker_stop_thread(worker); diff --git a/images/ArrowDown_8x4.png b/images/ArrowDown_8x4.png new file mode 100644 index 0000000000000000000000000000000000000000..ed5d5c819f11f1736f1d695ac5486f8883d484ca GIT binary patch literal 112 zcmeAS@N?(olHy`uVBq!ia0vp^96-#%!3HEZpRM}<;uJf1hHwBu4M$1`kgx9P;uyj) zGdU$8;m7#~c7wnF`=kH;|No!I$AHbulZ}n7gQ>*zL=FSf8KJMXp;;=S`1oZj%7K~~JYD@< J);T3K0RXWU9qIr8 literal 0 HcmV?d00001 diff --git a/images/Directory_10px.png b/images/Directory_10px.png new file mode 100644 index 0000000000000000000000000000000000000000..a4cdf453e32fc40a206e4225fcf477a64a1f6f99 GIT binary patch literal 311 zcmeAS@N?(olHy`uVBq!ia0vp^AT}2hGm!icqpk;}SkfJR9T^xl_H+M9WCijSl0AZa z85pY67#JE_7#My5g&JNkFq9fFFuY1&V6d9Oz#v{QXIG#NP=YhSC&cyte;|tiNFKbc z^$EygED7=pW^j0RBMrn!@^*J&=wOxg0CLz%Jbhi+pR)4v$gv!Fu~!r*B=T3-0%kIwEBzxs9jH>Z#5JNMC9x#cD!C{XNHG{0 z7#ZpsnCcpuh8P-I8ChDHnra&uSQ!}1Hm=h^(U6;;l9^VCslmhoqQPBw`6-|V22WQ% Jmvv4FO#mLQPFnx~ literal 0 HcmV?d00001 diff --git a/images/IR_10px.png b/images/IR_10px.png new file mode 100644 index 0000000000000000000000000000000000000000..22c986180a2bed76dbe4ff439df1cf9177533c32 GIT binary patch literal 305 zcmeAS@N?(olHy`uVBq!ia0vp^AT}2xkYHHq`AGmsv7|ftIx;Y9?C1WI$O_~uBzpw; zGB8xBF)%c=FfjZA3N^f7U???UV0e|lz+g3lfkC`r&aOZkpafHrx4R1i<>&pI=m5)bWC*+s&X`qmbr;B3<$Ms~3f`-KX%+5*7 zhxan`%;z`w!#>%c-@wM^z<~n{3>VXQv+hhNs0FH5Epd$~Nl7e8wMs5Z1yT$~21bUu z2Bx}(CLxAKR>lTa#unNJ237_J>nn=CplHa=PsvQHglaGb>IpG01*)?$HiBr_e$xf$ PHwFezS3j3^P6<>&pI=m5)b(dHL6nbwD9yPZ!4!j_b)k${QZ;XFmLn zjqNsD+YPq1J8W%_*aXBie!OR3*tC!PwU_7Q9H4U564!{5l*E!$tK_0oAjM#0U}UIk zV5)0q5@Kj%Wo%$&Y@uynU}a#izM}XGiiX_$l+3hBs0L%8o)7~QD^p9LQiz6svOM}g O4Gf;HelF{r5}E+GUQp8j literal 0 HcmV?d00001 diff --git a/images/Playlist_10px.png b/images/Playlist_10px.png new file mode 100644 index 0000000000000000000000000000000000000000..f492b94d4e2fb5464693319c8d337d1ae3517b2a GIT binary patch literal 122 zcmeAS@N?(olHy`uVBq!ia0vp^AT}2V8<6ZZI=>f4u@pObhHwBu4M$1`kZ<>&pI=m5)bWZjP>yH&963)5S4_<9hOs!iIf4u@pObhHwBu4M$1`knim2;us<^ zwReIi7lQ!@^V~oG&%azK>LqTtTtQgk$hO6Tsf>$Wbt!Q&n$8H@roKbp`*`h5rttbp k(-*76Nm=#V^X}r8mkd-CNo>vC1T>Dp)78&qol`;+08`N?umAu6 literal 0 HcmV?d00001 diff --git a/images/SubGHz_10px.png b/images/SubGHz_10px.png new file mode 100644 index 0000000000000000000000000000000000000000..5a25fdf4ef1c6cf53634aa74675001a3e8c85b7b GIT binary patch literal 299 zcmeAS@N?(olHy`uVBq!ia0vp^AT}2xkYHHq`AGmsv7|ftIx;Y9?C1WI$O_~uBzpw; zGB8xBF)%c=FfjZA3N^f7U???UV0e|lz+g3lfkC`r&aOZkpafHrx4R1i<>&pI=m5)cB{fFDGZlI8yr;B3<$MxhJ?+;A4eL&#) z0Ra}bue?07WhLz78x$BO|L3mq-MMxdP^D^#YeY#(Vo9o1a#1RfVlXl=GSoFN)ipE; zF*LF=Hn1|b&^9ozGB8+QQTzo(LvDUbW?CgwgE3G~h=Hkitems); for(FileArray_it(iter, flist); !FileArray_end_p(iter); FileArray_next(iter)) { path = *FileArray_ref(iter); @@ -93,19 +86,32 @@ ItemsView* item_get_items_view_from_path(void* context, FuriString* input_path) Item* item = ItemArray_push_new(iview->items); - // Action files have extensions, so item->ext starts with '.' - ehhhh + // Action files have extensions, so item->ext starts with '.' item->ext[0] = 0; path_extract_extension(path, item->ext, MAX_EXT_LEN); - item->type = (item->ext[0] == '.') ? Item_Action : Item_Group; + // FURI_LOG_I(TAG, ". EXT = %s", item->ext); + if(item->ext[0] == '.') { + // TODO: hack alert - make a helper fn here, or something + if(item->ext[1] == 's') + item->type = Item_SubGhz; + else if(item->ext[1] == 'r') + item->type = Item_RFID; + else if(item->ext[1] == 'q') + item->type = Item_Playlist; + else if(item->ext[1] == 'i') + item->type = Item_IR; + } else { + item->type = Item_Group; + } item->name = furi_string_alloc(); path_extract_filename_no_ext(found_path, item->name); - FURI_LOG_I(TAG, "Basename: %s", furi_string_get_cstr(item->name)); + // FURI_LOG_I(TAG, "Basename: %s", furi_string_get_cstr(item->name)); item_prettify_name(item->name); item->path = furi_string_alloc(); furi_string_set(item->path, path); - FURI_LOG_I(TAG, "Path: %s", furi_string_get_cstr(item->path)); + // FURI_LOG_I(TAG, "Path: %s", furi_string_get_cstr(item->path)); } FileArray_clear(flist); diff --git a/item.h b/item.h index abed30c04c2..cc0c148316b 100644 --- a/item.h +++ b/item.h @@ -9,7 +9,14 @@ * on-screen as well as to perform that action. */ -typedef enum { Item_Action, Item_Group } ItemType; +typedef enum { + Item_SubGhz, + Item_RFID, + Item_IR, + Item_Playlist, + Item_Group, + Item_Settings +} ItemType; typedef struct Item { ItemType type; diff --git a/quac.c b/quac.c index cf2faa35589..04437d99fb7 100644 --- a/quac.c +++ b/quac.c @@ -5,6 +5,7 @@ #include #include #include +#include #include #include @@ -14,6 +15,7 @@ #include "scenes/scene_items.h" #include "quac.h" +#include "quac_settings.h" /* generated by fbt from .png files in images folder */ #include @@ -30,10 +32,18 @@ App* app_alloc() { // Create our UI elements app->btn_menu = button_menu_alloc(); view_dispatcher_add_view( - app->view_dispatcher, SR_ButtonMenu, button_menu_get_view(app->btn_menu)); + app->view_dispatcher, Q_ButtonMenu, button_menu_get_view(app->btn_menu)); + + app->action_menu = action_menu_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, Q_ActionMenu, action_menu_get_view(app->action_menu)); + + app->vil_settings = variable_item_list_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, Q_Settings, variable_item_list_get_view(app->vil_settings)); app->dialog = dialog_ex_alloc(); - view_dispatcher_add_view(app->view_dispatcher, SR_Dialog, dialog_ex_get_view(app->dialog)); + view_dispatcher_add_view(app->view_dispatcher, Q_Dialog, dialog_ex_get_view(app->dialog)); // Storage app->storage = furi_record_open(RECORD_STORAGE); @@ -45,6 +55,16 @@ App* app_alloc() { app->depth = 0; app->selected_item = -1; + // Default settings + // TODO: Store settings in apps_data/quac/.quac.conf as a Flipper Format File! + // Create Settings Scene, save settings _on_exit + // Always use settings in _on_enter of other scenes + + app->settings.rfid_duration = 3000; + app->settings.layout = QUAC_APP_LANDSCAPE; + // app->settings.layout = QUAC_APP_PORTRAIT; + app->settings.show_icons = true; + app->items_view = item_get_items_view_from_path(app, NULL); return app; @@ -55,9 +75,11 @@ void app_free(App* app) { item_items_view_free(app->items_view); - view_dispatcher_remove_view(app->view_dispatcher, SR_ButtonMenu); + view_dispatcher_remove_view(app->view_dispatcher, Q_ButtonMenu); button_menu_free(app->btn_menu); + action_menu_free(app->action_menu); + scene_manager_free(app->scene_manager); view_dispatcher_free(app->view_dispatcher); @@ -73,10 +95,11 @@ int32_t quac_app(void* p) { FURI_LOG_I(TAG, "QUAC! QUAC!"); App* app = app_alloc(); + quac_load_settings(app); Gui* gui = furi_record_open(RECORD_GUI); view_dispatcher_attach_to_gui(app->view_dispatcher, gui, ViewDispatcherTypeFullscreen); - scene_manager_next_scene(app->scene_manager, SR_Scene_Items); + scene_manager_next_scene(app->scene_manager, Q_Scene_Items); view_dispatcher_run(app->view_dispatcher); furi_record_close(RECORD_GUI); diff --git a/quac.h b/quac.h index 39bafdafe28..5c8b2f6e9b5 100644 --- a/quac.h +++ b/quac.h @@ -4,9 +4,13 @@ #include #include #include +#include + #include #include +#include "views/action_menu.h" + #include "item.h" #define QUAC_NAME "Quac!" @@ -17,11 +21,15 @@ // Full path to actions #define QUAC_DATA_PATH EXT_PATH(QUAC_PATH) +typedef enum { QUAC_APP_PORTRAIT, QUAC_APP_LANDSCAPE } QuacAppLayout; + typedef struct App { SceneManager* scene_manager; ViewDispatcher* view_dispatcher; ButtonMenu* btn_menu; DialogEx* dialog; + VariableItemList* vil_settings; + ActionMenu* action_menu; Storage* storage; NotificationApp* notifications; @@ -29,6 +37,14 @@ typedef struct App { int depth; ItemsView* items_view; int selected_item; + + struct { + QuacAppLayout layout; // Defaults to Portrait + bool show_icons; // Defaults to True + bool show_headers; // Defaults to True + uint32_t rfid_duration; // Defaults to 2500 ms + } settings; + } App; App* app_alloc(); diff --git a/quac_settings.c b/quac_settings.c new file mode 100644 index 00000000000..b23a3452e1f --- /dev/null +++ b/quac_settings.c @@ -0,0 +1,142 @@ +#include "quac_settings.h" + +#include + +// Quac Settings File Info +// TODO: Fix this path to use existing #defs for /ext, etc +#define QUAC_SETTINGS_FILENAME "/ext/apps_data/quac/.quac.conf" +#define QUAC_SETTINGS_FILE_TYPE "Quac Settings File" +#define QUAC_SETTINGS_FILE_VERSION 1 + +// Quac Settings Defaults +#define QUAC_SETTINGS_DEFAULT_RFID_DURATION 2500 +#define QUAC_SETTINGS_DEFAULT_LAYOUT QUAC_APP_LANDSCAPE // QUAC_APP_PORTRAIT +#define QUAC_SETTINGS_DEFAULT_SHOW_ICONS true +#define QUAC_SETTINGS_DEFAULT_SHOW_HEADERS true + +void quac_set_default_settings(App* app) { + app->settings.rfid_duration = QUAC_SETTINGS_DEFAULT_RFID_DURATION; + app->settings.layout = QUAC_SETTINGS_DEFAULT_LAYOUT; + app->settings.show_icons = QUAC_SETTINGS_DEFAULT_SHOW_ICONS; + app->settings.show_headers = QUAC_SETTINGS_DEFAULT_SHOW_HEADERS; +} + +void quac_load_settings(App* app) { + FlipperFormat* fff_settings = flipper_format_file_alloc(app->storage); + FuriString* temp_str; + temp_str = furi_string_alloc(); + uint32_t temp_data32 = 0; + + FURI_LOG_I(TAG, "SETTINGS: Reading settings file"); + bool successful = false; + do { + if(!flipper_format_file_open_existing(fff_settings, QUAC_SETTINGS_FILENAME)) { + FURI_LOG_I(TAG, "SETTINGS: File not found, loading defaults"); + break; + } + + if(!flipper_format_read_header(fff_settings, temp_str, &temp_data32)) { + FURI_LOG_E(TAG, "SETTINGS: Missing or incorrect header"); + break; + } + + if((!strcmp(furi_string_get_cstr(temp_str), QUAC_SETTINGS_FILE_TYPE)) && + (temp_data32 == QUAC_SETTINGS_FILE_VERSION)) { + } else { + FURI_LOG_E(TAG, "SETTINGS: Type or version mismatch"); + break; + } + + // Now read actual values we care about + if(!flipper_format_read_string(fff_settings, "Layout", temp_str)) { + FURI_LOG_E(TAG, "SETTINGS: Missing Layout"); + break; + } + if(!strcmp(furi_string_get_cstr(temp_str), "Landscape")) { + app->settings.layout = QUAC_APP_LANDSCAPE; + } else if(!strcmp(furi_string_get_cstr(temp_str), "Portrait")) { + app->settings.layout = QUAC_APP_PORTRAIT; + } else { + FURI_LOG_E(TAG, "SETTINGS: Invalid Layout"); + break; + } + + if(!flipper_format_read_uint32(fff_settings, "Show Icons", &temp_data32, 1)) { + FURI_LOG_E(TAG, "SETTINGS: Missing 'Show Icons'"); + break; + } + app->settings.show_icons = (temp_data32 == 0) ? false : true; + + if(!flipper_format_read_uint32(fff_settings, "Show Headers", &temp_data32, 1)) { + FURI_LOG_E(TAG, "SETTINGS: Missing 'Show Headers'"); + break; + } + app->settings.show_headers = (temp_data32 == 0) ? false : true; + + if(!flipper_format_read_uint32(fff_settings, "RFID Duration", &temp_data32, 1)) { + FURI_LOG_E(TAG, "SETTINGS: Missing 'RFID Duration'"); + break; + } + app->settings.rfid_duration = temp_data32; + + successful = true; + } while(false); + + if(!successful) { + quac_set_default_settings(app); + } + + furi_string_free(temp_str); + flipper_format_free(fff_settings); +} + +void quac_save_settings(App* app) { + FlipperFormat* fff_settings = flipper_format_file_alloc(app->storage); + uint32_t temp_data32; + + bool successful = false; + do { + if(!flipper_format_file_open_always(fff_settings, QUAC_SETTINGS_FILENAME)) { + FURI_LOG_E(TAG, "SETTINGS: Unable to open file for save!!"); + break; + } + + if(!flipper_format_write_header_cstr( + fff_settings, QUAC_SETTINGS_FILE_TYPE, QUAC_SETTINGS_FILE_VERSION)) { + FURI_LOG_E(TAG, "SETTINGS: Failed writing file type and version"); + break; + } + // layout, icons, headers, duration + if(!flipper_format_write_string_cstr( + fff_settings, + "Layout", + app->settings.layout == QUAC_APP_LANDSCAPE ? "Landscape" : "Portrait")) { + FURI_LOG_E(TAG, "SETTINGS: Failed to write Layout"); + break; + } + + temp_data32 = app->settings.show_icons ? 1 : 0; + if(!flipper_format_write_uint32(fff_settings, "Show Icons", &temp_data32, 1)) { + FURI_LOG_E(TAG, "SETTINGS: Failed to write 'Show Icons'"); + break; + } + temp_data32 = app->settings.show_headers ? 1 : 0; + if(!flipper_format_write_uint32(fff_settings, "Show Headers", &temp_data32, 1)) { + FURI_LOG_E(TAG, "SETTINGS: Failed to write 'Show Headers'"); + break; + } + if(!flipper_format_write_uint32( + fff_settings, "RFID Duration", &app->settings.rfid_duration, 1)) { + FURI_LOG_E(TAG, "SETTINGS: Failed to write 'RFID Duration'"); + break; + } + successful = true; + } while(false); + + if(!successful) { + FURI_LOG_E(TAG, "SETTINGS: Failed to save settings!!"); + } + + flipper_format_file_close(fff_settings); + flipper_format_free(fff_settings); +} \ No newline at end of file diff --git a/quac_settings.h b/quac_settings.h new file mode 100644 index 00000000000..7d4f3228dec --- /dev/null +++ b/quac_settings.h @@ -0,0 +1,9 @@ +#pragma once + +#include "quac.h" + +void quac_set_default_settings(App* app); + +void quac_load_settings(App* app); + +void quac_save_settings(App* app); diff --git a/scenes/scene_items.c b/scenes/scene_items.c index c74d121c756..e27d8bc76ac 100644 --- a/scenes/scene_items.c +++ b/scenes/scene_items.c @@ -11,6 +11,8 @@ #include "scenes.h" #include "scene_items.h" #include "../actions/action.h" +#include "../views/action_menu.h" + #include void scene_items_item_callback(void* context, int32_t index, InputType type) { @@ -27,35 +29,70 @@ void scene_items_item_callback(void* context, int32_t index, InputType type) { // For each scene, implement handler callbacks void scene_items_on_enter(void* context) { App* app = context; - ButtonMenu* menu = app->btn_menu; - button_menu_reset(menu); - DialogEx* dialog = app->dialog; - dialog_ex_reset(dialog); + + ActionMenu* menu = app->action_menu; + action_menu_reset(menu); + if(app->settings.layout == QUAC_APP_LANDSCAPE) + action_menu_set_layout(menu, ActionMenuLayoutLandscape); + else + action_menu_set_layout(menu, ActionMenuLayoutPortrait); + action_menu_set_show_icons(menu, app->settings.show_icons); + action_menu_set_show_headers(menu, app->settings.show_headers); ItemsView* items_view = app->items_view; FURI_LOG_I(TAG, "items on_enter: [%d] %s", app->depth, furi_string_get_cstr(items_view->path)); + furi_delay_ms(500); const char* header = furi_string_get_cstr(items_view->name); - button_menu_set_header(menu, header); + action_menu_set_header(menu, header); - if(ItemArray_size(items_view->items)) { + size_t item_view_size = ItemArray_size(items_view->items); + if(item_view_size > 0) { ItemArray_it_t iter; int32_t index = 0; for(ItemArray_it(iter, items_view->items); !ItemArray_end_p(iter); ItemArray_next(iter), ++index) { const char* label = furi_string_get_cstr(ItemArray_cref(iter)->name); - ButtonMenuItemType type = ItemArray_cref(iter)->type == Item_Action ? - ButtonMenuItemTypeCommon : - ButtonMenuItemTypeControl; - button_menu_add_item(menu, label, index, scene_items_item_callback, type, app); + ActionMenuItemType type; + // TODO: Fix this with an array/map + switch(ItemArray_cref(iter)->type) { + case Item_Group: + type = ActionMenuItemTypeGroup; + break; + case Item_Playlist: + type = ActionMenuItemTypePlaylist; + break; + case Item_SubGhz: + type = ActionMenuItemTypeSubGHz; + break; + case Item_RFID: + type = ActionMenuItemTypeRFID; + break; + case Item_IR: + type = ActionMenuItemTypeIR; + break; + default: + type = ActionMenuItemTypeGroup; // TODO: Does this ever get hit? + } + action_menu_add_item(menu, label, index, scene_items_item_callback, type, app); } } else { FURI_LOG_W(TAG, "No items for: %s", furi_string_get_cstr(items_view->path)); // TODO: Display Error popup? Empty folder? } - // ... - view_dispatcher_switch_to_view(app->view_dispatcher, SR_ButtonMenu); + // Always add the "Settings" item at the end of our list - but only at top level! + if(app->depth == 0) { + action_menu_add_item( + menu, + "Settings", + item_view_size, // last item! + scene_items_item_callback, + ActionMenuItemTypeSettings, + app); + } + + view_dispatcher_switch_to_view(app->view_dispatcher, Q_ActionMenu); } bool scene_items_on_event(void* context, SceneManagerEvent event) { App* app = context; @@ -66,39 +103,47 @@ bool scene_items_on_event(void* context, SceneManagerEvent event) { case SceneManagerEventTypeCustom: if(event.event == Event_ButtonPressed) { consumed = true; + furi_delay_ms(100); FURI_LOG_I(TAG, "button pressed is %d", app->selected_item); - Item* item = ItemArray_get(app->items_view->items, app->selected_item); - if(item->type == Item_Group) { - app->depth++; - ItemsView* new_items = item_get_items_view_from_path(app, item->path); - item_items_view_free(app->items_view); - app->items_view = new_items; - scene_manager_next_scene(app->scene_manager, SR_Scene_Items); - } else { - FURI_LOG_I(TAG, "Initiating item action: %s", furi_string_get_cstr(item->name)); - - // LED goes blinky blinky - App* app = context; - notification_message(app->notifications, &sequence_blink_start_blue); - - // Prepare error string for action calls - FuriString* error; - error = furi_string_alloc(); - - action_tx(app, item, error); - - if(furi_string_size(error)) { - FURI_LOG_E(TAG, furi_string_get_cstr(error)); - // Change LED to Red and Vibrate! - notification_message(app->notifications, &sequence_error); - - // Display DialogEx popup or something? + if(app->selected_item < (int)ItemArray_size(app->items_view->items)) { + Item* item = ItemArray_get(app->items_view->items, app->selected_item); + if(item->type == Item_Group) { + app->depth++; + ItemsView* new_items = item_get_items_view_from_path(app, item->path); + item_items_view_free(app->items_view); + app->items_view = new_items; + scene_manager_next_scene(app->scene_manager, Q_Scene_Items); + } else { + FURI_LOG_I( + TAG, "Initiating item action: %s", furi_string_get_cstr(item->name)); + + // LED goes blinky blinky + App* app = context; + notification_message(app->notifications, &sequence_blink_start_blue); + + // Prepare error string for action calls + FuriString* error; + error = furi_string_alloc(); + + action_tx(app, item, error); + + if(furi_string_size(error)) { + FURI_LOG_E(TAG, furi_string_get_cstr(error)); + // Change LED to Red and Vibrate! + notification_message(app->notifications, &sequence_error); + + // Display DialogEx popup or something? + } + + furi_string_free(error); + + // Turn off LED light + notification_message(app->notifications, &sequence_blink_stop); } - - furi_string_free(error); - - // Turn off LED light - notification_message(app->notifications, &sequence_blink_stop); + } else { + FURI_LOG_I(TAG, "Selected Settings!"); + // TODO: Do we need to free this current items_view?? + scene_manager_next_scene(app->scene_manager, Q_Scene_Settings); } } break; @@ -122,17 +167,18 @@ bool scene_items_on_event(void* context, SceneManagerEvent event) { } break; default: + FURI_LOG_I(TAG, "Custom event not handled"); break; } + FURI_LOG_I(TAG, "Generic event not handled"); return consumed; } void scene_items_on_exit(void* context) { App* app = context; - ButtonMenu* menu = app->btn_menu; - button_menu_reset(menu); - DialogEx* dialog = app->dialog; - dialog_ex_reset(dialog); + + ActionMenu* menu = app->action_menu; + action_menu_reset(menu); FURI_LOG_I(TAG, "on_exit. depth = %d", app->depth); } \ No newline at end of file diff --git a/scenes/scene_settings.c b/scenes/scene_settings.c new file mode 100644 index 00000000000..e946c756515 --- /dev/null +++ b/scenes/scene_settings.c @@ -0,0 +1,133 @@ +#include + +#include +#include +#include +#include + +#include "quac.h" +#include "scenes.h" +#include "scene_settings.h" +#include "../actions/action.h" +#include "../views/action_menu.h" +#include "../quac_settings.h" + +#include + +static const char* const layout_text[2] = {"Vert", "Horiz"}; +static const uint32_t layout_value[2] = {QUAC_APP_PORTRAIT, QUAC_APP_LANDSCAPE}; + +static const char* const show_icons_text[2] = {"OFF", "ON"}; +static const uint32_t show_icons_value[2] = {false, true}; + +static const char* const show_headers_text[2] = {"OFF", "ON"}; +static const uint32_t show_headers_value[2] = {false, true}; + +#define V_RFID_DURATION_COUNT 8 +static const char* const rfid_duration_text[V_RFID_DURATION_COUNT] = { + "500 ms", + "1 sec", + "1.5 sec", + "2 sec", + "2.5 sec", + "3 sec", + "5 sec", + "10 sec", +}; +static const uint32_t rfid_duration_value[V_RFID_DURATION_COUNT] = { + 500, + 1000, + 1500, + 2000, + 2500, + 3000, + 5000, + 10000, +}; + +static void scene_settings_layout_changed(VariableItem* item) { + App* app = variable_item_get_context(item); + uint8_t index = variable_item_get_current_value_index(item); + + variable_item_set_current_value_text(item, layout_text[index]); + app->settings.layout = layout_value[index]; +} + +static void scene_settings_show_icons_changed(VariableItem* item) { + App* app = variable_item_get_context(item); + uint8_t index = variable_item_get_current_value_index(item); + variable_item_set_current_value_text(item, show_icons_text[index]); + app->settings.show_icons = show_icons_value[index]; +} + +static void scene_settings_show_headers_changed(VariableItem* item) { + App* app = variable_item_get_context(item); + uint8_t index = variable_item_get_current_value_index(item); + variable_item_set_current_value_text(item, show_headers_text[index]); + app->settings.show_headers = show_headers_value[index]; +} + +static void scene_settings_rfid_duration_changed(VariableItem* item) { + App* app = variable_item_get_context(item); + uint8_t index = variable_item_get_current_value_index(item); + variable_item_set_current_value_text(item, rfid_duration_text[index]); + app->settings.rfid_duration = rfid_duration_value[index]; +} + +// For each scene, implement handler callbacks +void scene_settings_on_enter(void* context) { + App* app = context; + + FURI_LOG_I(TAG, "Settings _on_enter"); + VariableItemList* vil = app->vil_settings; + variable_item_list_reset(vil); + + VariableItem* item; + uint8_t value_index; + + FURI_LOG_I(TAG, "setting up Layout"); + item = variable_item_list_add(vil, "Layout", 2, scene_settings_layout_changed, app); + value_index = value_index_uint32(app->settings.layout, layout_value, 2); + variable_item_set_current_value_index(item, value_index); + variable_item_set_current_value_text(item, layout_text[value_index]); + + FURI_LOG_I(TAG, "setting up Show Icons"); + item = variable_item_list_add(vil, "Show Icons", 2, scene_settings_show_icons_changed, app); + value_index = value_index_uint32(app->settings.show_icons, show_icons_value, 2); + variable_item_set_current_value_index(item, value_index); + variable_item_set_current_value_text(item, show_icons_text[value_index]); + + FURI_LOG_I(TAG, "setting up Show Headers"); + item = + variable_item_list_add(vil, "Show Headers", 2, scene_settings_show_headers_changed, app); + value_index = value_index_uint32(app->settings.show_headers, show_headers_value, 2); + variable_item_set_current_value_index(item, value_index); + variable_item_set_current_value_text(item, show_headers_text[value_index]); + + FURI_LOG_I(TAG, "setting up RFID Duration"); + item = variable_item_list_add( + vil, "RFID Duration", V_RFID_DURATION_COUNT, scene_settings_rfid_duration_changed, app); + value_index = value_index_uint32( + app->settings.rfid_duration, rfid_duration_value, V_RFID_DURATION_COUNT); + variable_item_set_current_value_index(item, value_index); + variable_item_set_current_value_text(item, rfid_duration_text[value_index]); + + // TODO: Set Enter callback here - why?? All settings have custom callbacks + // variable_item_list_set_enter_callback(vil, my_cb, app); + + view_dispatcher_switch_to_view(app->view_dispatcher, Q_Settings); +} +bool scene_settings_on_event(void* context, SceneManagerEvent event) { + UNUSED(context); + UNUSED(event); + + return false; +} + +void scene_settings_on_exit(void* context) { + App* app = context; + VariableItemList* vil = app->vil_settings; + variable_item_list_reset(vil); + + quac_save_settings(app); +} \ No newline at end of file diff --git a/scenes/scene_settings.h b/scenes/scene_settings.h new file mode 100644 index 00000000000..88ef2c0e5ed --- /dev/null +++ b/scenes/scene_settings.h @@ -0,0 +1,10 @@ +#pragma once + +#include + +// void scene_settings_item_callback(void* context, int32_t index, InputType type); + +// For each scene, implement handler callbacks +void scene_settings_on_enter(void* context); +bool scene_settings_on_event(void* context, SceneManagerEvent event); +void scene_settings_on_exit(void* context); diff --git a/scenes/scenes.c b/scenes/scenes.c index 1445fc4e5ed..13da3c259e3 100644 --- a/scenes/scenes.c +++ b/scenes/scenes.c @@ -3,19 +3,23 @@ #include "quac.h" #include "scenes.h" #include "scene_items.h" +#include "scene_settings.h" // define handler callbacks - order must match appScenes enum! -void (*const app_on_enter_handlers[])(void* context) = {scene_items_on_enter}; +void (*const app_on_enter_handlers[])(void* context) = { + scene_items_on_enter, + scene_settings_on_enter}; bool (*const app_on_event_handlers[])(void* context, SceneManagerEvent event) = { scene_items_on_event, + scene_settings_on_event, }; -void (*const app_on_exit_handlers[])(void* context) = {scene_items_on_exit}; +void (*const app_on_exit_handlers[])(void* context) = {scene_items_on_exit, scene_settings_on_exit}; const SceneManagerHandlers app_scene_handlers = { .on_enter_handlers = app_on_enter_handlers, .on_event_handlers = app_on_event_handlers, .on_exit_handlers = app_on_exit_handlers, - .scene_num = SR_Scene_count}; + .scene_num = Q_Scene_count}; bool app_scene_custom_callback(void* context, uint32_t custom_event_id) { App* app = context; diff --git a/scenes/scenes.h b/scenes/scenes.h index 4168485d0ca..cb4f2b84a08 100644 --- a/scenes/scenes.h +++ b/scenes/scenes.h @@ -1,12 +1,14 @@ #pragma once -typedef enum { SR_Scene_Items, SR_Scene_count } appScenes; +typedef enum { Q_Scene_Items, Q_Scene_Settings, Q_Scene_count } appScenes; typedef enum { - SR_ButtonMenu, // used on selected device, to show buttons/groups - SR_Dialog, // shows errors - SR_FileBrowser, // TODO: UNUSED! - SR_TextInput // TODO: UNUSED + Q_ButtonMenu, // used on selected device, to show buttons/groups + Q_Dialog, // shows errors + Q_ActionMenu, // new UI, + Q_Settings, // Variable Item List for settings + Q_FileBrowser, // TODO: UNUSED! + Q_TextInput // TODO: UNUSED } appView; typedef enum { Event_DeviceSelected, Event_ButtonPressed } AppCustomEvents; diff --git a/views/action_menu.c b/views/action_menu.c new file mode 100644 index 00000000000..bf3dc2de554 --- /dev/null +++ b/views/action_menu.c @@ -0,0 +1,611 @@ +#include "action_menu.h" + +#include +#include +#include + +#include + +#include +#include + +#include "quac_icons.h" + +#define ITEM_FIRST_OFFSET 17 +#define ITEM_NEXT_OFFSET 4 +#define ITEM_HEIGHT 14 +#define ITEM_WIDTH 64 +#define BUTTONS_PER_SCREEN 6 + +#define ITEMS_PER_SCREEN_LANDSCAPE 3 +#define ITEMS_PER_SCREEN_PORTRAIT 6 + +static const Icon* ActionMenuIcons[] = { + [ActionMenuItemTypeSubGHz] = &I_SubGHz_10px, + [ActionMenuItemTypeRFID] = &I_RFID_10px, + [ActionMenuItemTypeIR] = &I_IR_10px, + [ActionMenuItemTypePlaylist] = &I_Playlist_10px, + [ActionMenuItemTypeGroup] = &I_Directory_10px, + [ActionMenuItemTypeSettings] = &I_Settings_10px, +}; + +struct ActionMenuItem { + const char* label; + IconAnimation* icon; + uint32_t index; + ActionMenuItemCallback callback; + ActionMenuItemType type; + void* callback_context; +}; + +ARRAY_DEF(ActionMenuItemArray, ActionMenuItem, M_POD_OPLIST); +#define M_OPL_ActionMenuItemArray_t() ARRAY_OPLIST(ActionMenuItemArray, M_POD_OPLIST) + +struct ActionMenu { + View* view; + bool freeze_input; +}; + +typedef struct { + ActionMenuItemArray_t items; + size_t position; + size_t window_position; + FuriString* header; + ActionMenuLayout layout; + bool show_icons; + bool show_headers; +} ActionMenuModel; + +static void action_menu_draw_landscape(Canvas* canvas, ActionMenuModel* model) { + const uint8_t item_height = 16; + uint8_t item_width = canvas_width(canvas) - 5; // space for scrollbar + + const bool have_header = furi_string_size(model->header) && model->show_headers; + + canvas_clear(canvas); + if(have_header) { + canvas_set_font(canvas, FontPrimary); + canvas_draw_str(canvas, 4, 11, furi_string_get_cstr(model->header)); + } + canvas_set_font(canvas, FontSecondary); + + size_t position = 0; + const size_t items_on_screen = ITEMS_PER_SCREEN_LANDSCAPE + (have_header ? 0 : 1); + uint8_t y_offset = have_header ? 16 : 0; + const size_t x_txt_start = model->show_icons ? 18 : 4; + + ActionMenuItemArray_it_t it; + for(ActionMenuItemArray_it(it, model->items); !ActionMenuItemArray_end_p(it); + ActionMenuItemArray_next(it)) { + const size_t item_position = position - model->window_position; + + if(item_position < items_on_screen) { + if(position == model->position) { + canvas_set_color(canvas, ColorBlack); + elements_slightly_rounded_box( + canvas, + 0, + y_offset + (item_position * item_height) + 1, + item_width, + item_height - 2); + canvas_set_color(canvas, ColorWhite); + } else { + canvas_set_color(canvas, ColorBlack); + } + + const ActionMenuItem* item = ActionMenuItemArray_cref(it); + if(model->show_icons) { + canvas_draw_icon( + canvas, + 4, + y_offset + (item_position * item_height) + 3, + ActionMenuIcons[item->type]); + } + + FuriString* disp_str; + disp_str = furi_string_alloc_set(item->label); + elements_string_fit_width(canvas, disp_str, item_width - (6 * 2)); + + canvas_draw_str( + canvas, + x_txt_start, // 6 + y_offset + (item_position * item_height) + item_height - 4, + furi_string_get_cstr(disp_str)); + furi_string_free(disp_str); + } + position++; + } + + elements_scrollbar(canvas, model->position, ActionMenuItemArray_size(model->items)); +} + +// static void action_menu_draw_portrait(Canvas* canvas, ActionMenuModel* model) { +// const bool have_header = furi_string_size(model->header) && model->show_headers; +// const size_t items_per_screen = have_header ? ITEMS_PER_SCREEN_PORTRAIT : +// ITEMS_PER_SCREEN_PORTRAIT + 1; +// const size_t active_screen = model->position / items_per_screen; +// const size_t items_size = ActionMenuItemArray_size(model->items); +// const size_t max_screen = items_size ? (items_size - 1) / items_per_screen : 0; + +// canvas_clear(canvas); + +// // Draw up/down arrows, as needed +// if(active_screen > 0) { +// canvas_draw_icon(canvas, 28, 1, &I_ArrowUp_8x4); +// } +// if(max_screen > active_screen) { +// canvas_draw_icon(canvas, 28, 123, &I_ArrowDown_8x4); +// } + +// if(have_header) { +// canvas_set_font(canvas, FontPrimary); +// elements_string_fit_width(canvas, model->header, ITEM_WIDTH - 6); +// canvas_draw_str_aligned( +// canvas, 32, 10, AlignCenter, AlignCenter, furi_string_get_cstr(model->header)); +// } +// canvas_set_font(canvas, FontSecondary); + +// size_t item_position = 0; +// size_t item_first_offset = have_header ? ITEM_FIRST_OFFSET : 6; +// size_t item_next_offset = have_header ? ITEM_NEXT_OFFSET : ITEM_NEXT_OFFSET - 1; +// ActionMenuItemArray_it_t it; +// for(ActionMenuItemArray_it(it, model->items); !ActionMenuItemArray_end_p(it); +// ActionMenuItemArray_next(it), ++item_position) { +// if(active_screen == (item_position / items_per_screen)) { +// uint8_t position_offset = item_position % items_per_screen; +// bool selected = item_position == model->position; + +// // draw the item +// uint8_t item_x = 0; +// uint8_t item_y = +// item_first_offset + (position_offset * (ITEM_HEIGHT + item_next_offset)); + +// canvas_set_color(canvas, ColorBlack); + +// if(selected) { +// // Same as elements_slightly_rounded_box with radius of 5 +// canvas_draw_rbox(canvas, item_x, item_y, ITEM_WIDTH, ITEM_HEIGHT, 1); +// canvas_set_color(canvas, ColorWhite); +// } else { +// canvas_draw_rframe(canvas, item_x, item_y, ITEM_WIDTH, ITEM_HEIGHT, 1); +// } + +// FuriString* disp_str; +// disp_str = furi_string_alloc_set(ActionMenuItemArray_cref(it)->label); +// elements_string_fit_width(canvas, disp_str, ITEM_WIDTH - 6); + +// canvas_draw_str_aligned( +// canvas, +// item_x + (ITEM_WIDTH / 2), +// item_y + (ITEM_HEIGHT / 2), +// AlignCenter, +// AlignCenter, +// furi_string_get_cstr(disp_str)); +// furi_string_free(disp_str); +// } +// } +// } + +static void action_menu_draw_portrait(Canvas* canvas, ActionMenuModel* model) { + const bool have_header = furi_string_size(model->header) && model->show_headers; + const size_t items_per_screen = have_header ? ITEMS_PER_SCREEN_PORTRAIT : + ITEMS_PER_SCREEN_PORTRAIT + 1; + const size_t active_screen = model->position / items_per_screen; + const size_t items_size = ActionMenuItemArray_size(model->items); + const size_t max_screen = items_size ? (items_size - 1) / items_per_screen : 0; + + canvas_clear(canvas); + + // Draw up/down arrows, as needed + if(active_screen > 0) { + canvas_draw_icon(canvas, 28, 1, &I_ArrowUp_8x4); + } + if(max_screen > active_screen) { + canvas_draw_icon(canvas, 28, 123, &I_ArrowDown_8x4); + } + + if(have_header) { + canvas_set_font(canvas, FontPrimary); + elements_string_fit_width(canvas, model->header, ITEM_WIDTH - 6); + canvas_draw_str_aligned( + canvas, 32, 10, AlignCenter, AlignCenter, furi_string_get_cstr(model->header)); + } + canvas_set_font(canvas, FontSecondary); + + size_t item_position = 0; + const size_t x_txt_start = model->show_icons ? 16 : 4; + const size_t y_offset = have_header ? ITEM_FIRST_OFFSET : 6; + const size_t item_next_offset = have_header ? ITEM_NEXT_OFFSET : ITEM_NEXT_OFFSET - 1; + + ActionMenuItemArray_it_t it; + for(ActionMenuItemArray_it(it, model->items); !ActionMenuItemArray_end_p(it); + ActionMenuItemArray_next(it), ++item_position) { + if(active_screen == (item_position / items_per_screen)) { + uint8_t position_offset = item_position % items_per_screen; + bool selected = item_position == model->position; + + // draw the item + uint8_t item_y = y_offset + (position_offset * (ITEM_HEIGHT + item_next_offset)); + + canvas_set_color(canvas, ColorBlack); + + if(selected) { + // Same as elements_slightly_rounded_box with radius of 5 + canvas_draw_rbox(canvas, 0, item_y, ITEM_WIDTH, ITEM_HEIGHT, 1); + canvas_set_color(canvas, ColorWhite); + } else { + canvas_draw_rframe(canvas, 0, item_y, ITEM_WIDTH, ITEM_HEIGHT, 1); + } + + const ActionMenuItem* item = ActionMenuItemArray_cref(it); + if(model->show_icons) { + canvas_draw_icon(canvas, 3, item_y + 2, ActionMenuIcons[item->type]); + } + + FuriString* disp_str; + disp_str = furi_string_alloc_set(item->label); + elements_string_fit_width(canvas, disp_str, ITEM_WIDTH - 6); + + canvas_draw_str( + canvas, + x_txt_start, + item_y + (ITEM_HEIGHT / 2) + 3, + furi_string_get_cstr(disp_str)); + furi_string_free(disp_str); + } + } +} + +static void action_menu_view_draw_callback(Canvas* canvas, void* context) { + furi_assert(canvas); + ActionMenuModel* model = (ActionMenuModel*)context; + + if(model->layout == ActionMenuLayoutLandscape) { + action_menu_draw_landscape(canvas, model); + } else { + action_menu_draw_portrait(canvas, model); + } +} + +static void action_menu_process_up(ActionMenu* action_menu) { + furi_assert(action_menu); + + with_view_model( + action_menu->view, + ActionMenuModel * model, + { + const size_t items_size = ActionMenuItemArray_size(model->items); + if(model->layout == ActionMenuLayoutPortrait) { + if(model->position > 0) { + model->position--; + } else { + model->position = items_size - 1; + } + } else { + const size_t items_on_screen = furi_string_empty(model->header) ? 4 : 3; + if(model->position > 0) { + model->position--; + if((model->position == model->window_position) && + (model->window_position > 0)) { + model->window_position--; + } + } else { + model->position = items_size - 1; + if(model->position > items_on_screen - 1) { + model->window_position = model->position - (items_on_screen - 1); + } + } + } + }, + true); +} + +// TODO: Up/Down keys are obeyed in the correct orientation! +static void action_menu_process_down(ActionMenu* action_menu) { + furi_assert(action_menu); + + with_view_model( + action_menu->view, + ActionMenuModel * model, + { + const size_t items_size = ActionMenuItemArray_size(model->items); + if(model->layout == ActionMenuLayoutPortrait) { + if(model->position < items_size - 1) { + model->position++; + } else { + model->position = 0; + } + } else { + const size_t items_on_screen = furi_string_empty(model->header) ? 4 : 3; + if(model->position < items_size - 1) { + model->position++; + if((model->position - model->window_position > items_on_screen - 2) && + (model->window_position < items_size - items_on_screen)) { + model->window_position++; + } + } else { + model->position = 0; + model->window_position = 0; + } + } + }, + true); +} + +static void action_menu_process_ok(ActionMenu* action_menu, InputType type) { + furi_assert(action_menu); + // UNUSED(type); + + FURI_LOG_I("AM", "OK pressed! %d: %s", type, input_get_type_name(type)); + ActionMenuItem* item = NULL; + + with_view_model( + action_menu->view, + ActionMenuModel * model, + { + if(model->position < (ActionMenuItemArray_size(model->items))) { + item = ActionMenuItemArray_get(model->items, model->position); + } + }, + false); + + // Landscape: Press, Short, Release + + if(item) { + if(type == InputTypeRelease && item->callback) + item->callback(item->callback_context, item->index, type); + } +} + +static bool action_menu_view_input_callback(InputEvent* event, void* context) { + furi_assert(event); + + ActionMenu* action_menu = context; + bool consumed = false; + + // Item selection + if(event->key == InputKeyOk) { + if((event->type == InputTypeRelease) || (event->type == InputTypePress)) { + consumed = true; + action_menu->freeze_input = (event->type == InputTypePress); + action_menu_process_ok(action_menu, event->type); + } else if(event->type == InputTypeShort) { + consumed = true; + action_menu_process_ok(action_menu, event->type); + } + } + + if(!action_menu->freeze_input && + ((event->type == InputTypeRepeat) || (event->type == InputTypeShort))) { + // FURI_LOG_I("AM", "Directional key: %d", event->key); + switch(event->key) { + case InputKeyUp: + consumed = true; + action_menu_process_up(action_menu); + break; + case InputKeyDown: + consumed = true; + action_menu_process_down(action_menu); + break; + case InputKeyRight: + FURI_LOG_W("AM", "InputKeyRight ignored"); + // consumed = true; + // action_menu_process_right(action_menu); + break; + case InputKeyLeft: + FURI_LOG_W("AM", "InputKeyLeft ignored"); + // consumed = true; + // action_menu_process_left(action_menu); + break; + default: + break; + } + } + + return consumed; +} + +View* action_menu_get_view(ActionMenu* action_menu) { + furi_assert(action_menu); + return action_menu->view; +} + +void action_menu_reset(ActionMenu* action_menu) { + furi_assert(action_menu); + + with_view_model( + action_menu->view, + ActionMenuModel * model, + { + // for + // M_EACH(item, model->items, ActionMenuItemArray_t) { + // icon_animation_stop(item->icon); + // icon_animation_free(item->icon); + // } + ActionMenuItemArray_reset(model->items); + model->position = 0; + model->window_position = 0; + furi_string_reset(model->header); + }, + true); +} + +void action_menu_set_layout(ActionMenu* action_menu, ActionMenuLayout layout) { + furi_assert(action_menu); + + with_view_model( + action_menu->view, + ActionMenuModel * model, + { + model->layout = layout; + if(model->layout == ActionMenuLayoutLandscape) { + view_set_orientation(action_menu->view, ViewOrientationHorizontal); + } else { + view_set_orientation(action_menu->view, ViewOrientationVertical); + } + }, + true); +} + +void action_menu_set_header(ActionMenu* action_menu, const char* header) { + furi_assert(action_menu); + with_view_model( + action_menu->view, + ActionMenuModel * model, + { + if(header == NULL) { + furi_string_reset(model->header); + } else { + furi_string_set_str(model->header, header); + } + }, + true); +} + +void action_menu_set_show_icons(ActionMenu* action_menu, bool show_icons) { + with_view_model( + action_menu->view, ActionMenuModel * model, { model->show_icons = show_icons; }, true); +} + +void action_menu_set_show_headers(ActionMenu* action_menu, bool show_headers) { + with_view_model( + action_menu->view, ActionMenuModel * model, { model->show_headers = show_headers; }, true); +} + +ActionMenuItem* action_menu_add_item( + ActionMenu* action_menu, + const char* label, + int32_t index, + ActionMenuItemCallback callback, + ActionMenuItemType type, + void* callback_context) { + ActionMenuItem* item = NULL; + furi_assert(label); + furi_assert(action_menu); + + with_view_model( + action_menu->view, + ActionMenuModel * model, + { + item = ActionMenuItemArray_push_new(model->items); + item->label = label; + // item->icon = icon ? icon_animation_alloc(icon) : NULL; // or default icon? + // view_tie_icon_animation(action_menu->view, item->icon); + item->index = index; + item->type = type; + item->callback = callback; + item->callback_context = callback_context; + }, + true); + + return item; +} + +ActionMenu* action_menu_alloc(void) { + ActionMenu* action_menu = malloc(sizeof(ActionMenu)); + action_menu->view = view_alloc(); + view_set_orientation(action_menu->view, ViewOrientationHorizontal); + view_set_context(action_menu->view, action_menu); + view_allocate_model(action_menu->view, ViewModelTypeLocking, sizeof(ActionMenuModel)); + view_set_draw_callback(action_menu->view, action_menu_view_draw_callback); + view_set_input_callback(action_menu->view, action_menu_view_input_callback); + + with_view_model( + action_menu->view, + ActionMenuModel * model, + { + ActionMenuItemArray_init(model->items); + model->position = 0; + model->window_position = 0; + model->header = furi_string_alloc(); + model->layout = ActionMenuLayoutLandscape; // TODO: ehhhhhhhhhhhhhhhhhhh + model->show_icons = true; + model->show_headers = true; + }, + true); + + action_menu->freeze_input = false; + return action_menu; +} + +void action_menu_free(ActionMenu* action_menu) { + furi_assert(action_menu); + + with_view_model( + action_menu->view, + ActionMenuModel * model, + { + // for + // M_EACH(item, model->items, ActionMenuItemArray_t) { + // icon_animation_stop(item->icon); + // icon_animation_free(item->icon); + // } + ActionMenuItemArray_clear(model->items); + furi_string_free(model->header); + }, + true); + view_free(action_menu->view); + free(action_menu); +} + +void action_menu_set_selected_item(ActionMenu* action_menu, uint32_t index) { + furi_assert(action_menu); + + ActionMenuModel* m = view_get_model(action_menu->view); + if(m->layout == ActionMenuLayoutPortrait) { + with_view_model( + action_menu->view, + ActionMenuModel * model, + { + size_t item_position = 0; + ActionMenuItemArray_it_t it; + for(ActionMenuItemArray_it(it, model->items); !ActionMenuItemArray_end_p(it); + ActionMenuItemArray_next(it), ++item_position) { + if((uint32_t)ActionMenuItemArray_cref(it)->index == index) { + model->position = item_position; + break; + } + } + }, + true); + } else { + with_view_model( + action_menu->view, + ActionMenuModel * model, + { + size_t position = 0; + ActionMenuItemArray_it_t it; + for(ActionMenuItemArray_it(it, model->items); !ActionMenuItemArray_end_p(it); + ActionMenuItemArray_next(it)) { + if(index == ActionMenuItemArray_cref(it)->index) { + break; + } + position++; + } + const size_t items_size = ActionMenuItemArray_size(model->items); + + if(position >= items_size) { + position = 0; + } + + model->position = position; + model->window_position = position; + + if(model->window_position > 0) { + model->window_position -= 1; + } + + const size_t items_on_screen = furi_string_empty(model->header) ? 4 : 3; + + if(items_size <= items_on_screen) { + model->window_position = 0; + } else { + const size_t pos = items_size - items_on_screen; + if(model->window_position > pos) { + model->window_position = pos; + } + } + }, + true); + } +} \ No newline at end of file diff --git a/views/action_menu.h b/views/action_menu.h new file mode 100644 index 00000000000..60659303010 --- /dev/null +++ b/views/action_menu.h @@ -0,0 +1,117 @@ +#pragma once + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** ActionMenu anonymous structure */ +typedef struct ActionMenu ActionMenu; + +/** ActionMenuItem anonymous structure */ +typedef struct ActionMenuItem ActionMenuItem; + +/** Callback for any button menu actions */ +typedef void (*ActionMenuItemCallback)(void* context, int32_t index, InputType type); + +/** Type of button. Difference in drawing buttons. */ +typedef enum { + ActionMenuItemTypeSubGHz, + ActionMenuItemTypeRFID, + ActionMenuItemTypeIR, + ActionMenuItemTypePlaylist, + ActionMenuItemTypeGroup, + ActionMenuItemTypeSettings, +} ActionMenuItemType; + +typedef enum { + ActionMenuLayoutPortrait, + ActionMenuLayoutLandscape, +} ActionMenuLayout; + +/** Get button menu view + * + * @param action_menu ActionMenu instance + * + * @return View instance that can be used for embedding + */ +View* action_menu_get_view(ActionMenu* action_menu); + +/** Clean button menu + * + * @param action_menu ActionMenu instance + */ +void action_menu_reset(ActionMenu* action_menu); + +/** Set the layout + * + * @param layout Portrait or Landscape +*/ +void action_menu_set_layout(ActionMenu* menu, ActionMenuLayout layout); + +/** Show/Hide icons in UI + * + * @param show_icons Show or Hide icons +*/ +void action_menu_set_show_icons(ActionMenu* menu, bool show_icons); + +/** Show/Hide header labels in UI + * + * @param show_headers Show or Hide header labels +*/ +void action_menu_set_show_headers(ActionMenu* menu, bool show_headers); + +/** Add item to button menu instance + * + * @param action_menu ActionMenu instance + * @param label text inside new button + * @param icon IconAnimation instance + * @param index value to distinct between buttons inside + * ActionMenuItemCallback + * @param callback The callback + * @param type type of button to create. Differ by button + * drawing. Control buttons have no frames, and + * have more squared borders. + * @param callback_context The callback context + * + * @return pointer to just-created item + */ +ActionMenuItem* action_menu_add_item( + ActionMenu* action_menu, + const char* label, + int32_t index, + ActionMenuItemCallback callback, + ActionMenuItemType type, + void* callback_context); + +/** Allocate and initialize new instance of ActionMenu model + * + * @return just-created ActionMenu model + */ +ActionMenu* action_menu_alloc(void); + +/** Free ActionMenu element + * + * @param action_menu ActionMenu instance + */ +void action_menu_free(ActionMenu* action_menu); + +/** Set ActionMenu header on top of canvas + * + * @param action_menu ActionMenu instance + * @param header header on the top of button menu + */ +void action_menu_set_header(ActionMenu* action_menu, const char* header); + +/** Set selected item + * + * @param action_menu ActionMenu instance + * @param index index of ActionMenu to be selected + */ +void action_menu_set_selected_item(ActionMenu* action_menu, uint32_t index); + +#ifdef __cplusplus +} +#endif \ No newline at end of file From 5baf7c1befedf8f358ccd611719faa3c6c7ee05a Mon Sep 17 00:00:00 2001 From: rdefeo Date: Tue, 12 Mar 2024 00:34:13 -0400 Subject: [PATCH 2/4] small cleanup --- quac.c | 10 ---------- quac_settings.c | 14 ++++---------- quac_settings.h | 5 +++++ 3 files changed, 9 insertions(+), 20 deletions(-) diff --git a/quac.c b/quac.c index 04437d99fb7..d346a892af4 100644 --- a/quac.c +++ b/quac.c @@ -55,16 +55,6 @@ App* app_alloc() { app->depth = 0; app->selected_item = -1; - // Default settings - // TODO: Store settings in apps_data/quac/.quac.conf as a Flipper Format File! - // Create Settings Scene, save settings _on_exit - // Always use settings in _on_enter of other scenes - - app->settings.rfid_duration = 3000; - app->settings.layout = QUAC_APP_LANDSCAPE; - // app->settings.layout = QUAC_APP_PORTRAIT; - app->settings.show_icons = true; - app->items_view = item_get_items_view_from_path(app, NULL); return app; diff --git a/quac_settings.c b/quac_settings.c index b23a3452e1f..a583cbc83d4 100644 --- a/quac_settings.c +++ b/quac_settings.c @@ -8,17 +8,11 @@ #define QUAC_SETTINGS_FILE_TYPE "Quac Settings File" #define QUAC_SETTINGS_FILE_VERSION 1 -// Quac Settings Defaults -#define QUAC_SETTINGS_DEFAULT_RFID_DURATION 2500 -#define QUAC_SETTINGS_DEFAULT_LAYOUT QUAC_APP_LANDSCAPE // QUAC_APP_PORTRAIT -#define QUAC_SETTINGS_DEFAULT_SHOW_ICONS true -#define QUAC_SETTINGS_DEFAULT_SHOW_HEADERS true - void quac_set_default_settings(App* app) { - app->settings.rfid_duration = QUAC_SETTINGS_DEFAULT_RFID_DURATION; - app->settings.layout = QUAC_SETTINGS_DEFAULT_LAYOUT; - app->settings.show_icons = QUAC_SETTINGS_DEFAULT_SHOW_ICONS; - app->settings.show_headers = QUAC_SETTINGS_DEFAULT_SHOW_HEADERS; + app->settings.rfid_duration = 2500; + app->settings.layout = QUAC_APP_LANDSCAPE; + app->settings.show_icons = true; + app->settings.show_headers = true; } void quac_load_settings(App* app) { diff --git a/quac_settings.h b/quac_settings.h index 7d4f3228dec..4b4bef6e233 100644 --- a/quac_settings.h +++ b/quac_settings.h @@ -2,8 +2,13 @@ #include "quac.h" +/** Set the default Settings for Quac */ void quac_set_default_settings(App* app); +/** Load the Settings from the .quac.conf file. If not found, + * then load the defaults. +*/ void quac_load_settings(App* app); +/** Save the current settings to the .quac.conf file */ void quac_save_settings(App* app); From 9d34229dbd396537ec4847f38f0dfcd51a54af7e Mon Sep 17 00:00:00 2001 From: rdefeo Date: Fri, 15 Mar 2024 19:12:30 -0400 Subject: [PATCH 3/4] pushing latest --- quac.c | 6 ++++ views/action_menu.c | 68 --------------------------------------------- 2 files changed, 6 insertions(+), 68 deletions(-) diff --git a/quac.c b/quac.c index d346a892af4..7bc14d1179d 100644 --- a/quac.c +++ b/quac.c @@ -84,6 +84,8 @@ int32_t quac_app(void* p) { UNUSED(p); FURI_LOG_I(TAG, "QUAC! QUAC!"); + size_t free_start = memmgr_get_free_heap(); + App* app = app_alloc(); quac_load_settings(app); @@ -94,5 +96,9 @@ int32_t quac_app(void* p) { furi_record_close(RECORD_GUI); app_free(app); + + size_t free_end = memmgr_get_free_heap(); + FURI_LOG_W(TAG, "Heap: Start = %d, End = %d", free_start, free_end); + return 0; } diff --git a/views/action_menu.c b/views/action_menu.c index bf3dc2de554..14b45f58fed 100644 --- a/views/action_menu.c +++ b/views/action_menu.c @@ -119,73 +119,6 @@ static void action_menu_draw_landscape(Canvas* canvas, ActionMenuModel* model) { elements_scrollbar(canvas, model->position, ActionMenuItemArray_size(model->items)); } -// static void action_menu_draw_portrait(Canvas* canvas, ActionMenuModel* model) { -// const bool have_header = furi_string_size(model->header) && model->show_headers; -// const size_t items_per_screen = have_header ? ITEMS_PER_SCREEN_PORTRAIT : -// ITEMS_PER_SCREEN_PORTRAIT + 1; -// const size_t active_screen = model->position / items_per_screen; -// const size_t items_size = ActionMenuItemArray_size(model->items); -// const size_t max_screen = items_size ? (items_size - 1) / items_per_screen : 0; - -// canvas_clear(canvas); - -// // Draw up/down arrows, as needed -// if(active_screen > 0) { -// canvas_draw_icon(canvas, 28, 1, &I_ArrowUp_8x4); -// } -// if(max_screen > active_screen) { -// canvas_draw_icon(canvas, 28, 123, &I_ArrowDown_8x4); -// } - -// if(have_header) { -// canvas_set_font(canvas, FontPrimary); -// elements_string_fit_width(canvas, model->header, ITEM_WIDTH - 6); -// canvas_draw_str_aligned( -// canvas, 32, 10, AlignCenter, AlignCenter, furi_string_get_cstr(model->header)); -// } -// canvas_set_font(canvas, FontSecondary); - -// size_t item_position = 0; -// size_t item_first_offset = have_header ? ITEM_FIRST_OFFSET : 6; -// size_t item_next_offset = have_header ? ITEM_NEXT_OFFSET : ITEM_NEXT_OFFSET - 1; -// ActionMenuItemArray_it_t it; -// for(ActionMenuItemArray_it(it, model->items); !ActionMenuItemArray_end_p(it); -// ActionMenuItemArray_next(it), ++item_position) { -// if(active_screen == (item_position / items_per_screen)) { -// uint8_t position_offset = item_position % items_per_screen; -// bool selected = item_position == model->position; - -// // draw the item -// uint8_t item_x = 0; -// uint8_t item_y = -// item_first_offset + (position_offset * (ITEM_HEIGHT + item_next_offset)); - -// canvas_set_color(canvas, ColorBlack); - -// if(selected) { -// // Same as elements_slightly_rounded_box with radius of 5 -// canvas_draw_rbox(canvas, item_x, item_y, ITEM_WIDTH, ITEM_HEIGHT, 1); -// canvas_set_color(canvas, ColorWhite); -// } else { -// canvas_draw_rframe(canvas, item_x, item_y, ITEM_WIDTH, ITEM_HEIGHT, 1); -// } - -// FuriString* disp_str; -// disp_str = furi_string_alloc_set(ActionMenuItemArray_cref(it)->label); -// elements_string_fit_width(canvas, disp_str, ITEM_WIDTH - 6); - -// canvas_draw_str_aligned( -// canvas, -// item_x + (ITEM_WIDTH / 2), -// item_y + (ITEM_HEIGHT / 2), -// AlignCenter, -// AlignCenter, -// furi_string_get_cstr(disp_str)); -// furi_string_free(disp_str); -// } -// } -// } - static void action_menu_draw_portrait(Canvas* canvas, ActionMenuModel* model) { const bool have_header = furi_string_size(model->header) && model->show_headers; const size_t items_per_screen = have_header ? ITEMS_PER_SCREEN_PORTRAIT : @@ -300,7 +233,6 @@ static void action_menu_process_up(ActionMenu* action_menu) { true); } -// TODO: Up/Down keys are obeyed in the correct orientation! static void action_menu_process_down(ActionMenu* action_menu) { furi_assert(action_menu); From 09230aab5f215b9d63ffc097008a94c9e8a9bb9b Mon Sep 17 00:00:00 2001 From: rdefeo Date: Sun, 17 Mar 2024 14:00:17 -0400 Subject: [PATCH 4/4] code cleanup, log cleanup --- actions/action.c | 2 +- actions/action_i.h | 8 +++--- actions/action_ir.c | 2 +- actions/action_qpl.c | 26 ++++++++++++------- actions/action_rfid.c | 19 ++++---------- actions/action_subghz.c | 14 +++++----- flipper.h | 16 ------------ item.c | 55 ++++++++++++++++++++------------------- item.h | 12 ++++++--- quac.c | 21 +++++++-------- quac.h | 9 +++---- quac_settings.c | 7 ++--- scenes/scene_items.c | 57 +++++++++++++++-------------------------- scenes/scene_settings.c | 6 ----- scenes/scenes.h | 5 +--- views/action_menu.c | 4 +-- views/action_menu.h | 3 ++- 17 files changed, 116 insertions(+), 150 deletions(-) delete mode 100644 flipper.h diff --git a/actions/action.c b/actions/action.c index cd4dceef653..96271c7219f 100644 --- a/actions/action.c +++ b/actions/action.c @@ -4,7 +4,7 @@ #include "action_i.h" void action_tx(void* context, Item* item, FuriString* error) { - FURI_LOG_I(TAG, "action_run: %s : %s", furi_string_get_cstr(item->name), item->ext); + // FURI_LOG_I(TAG, "action_run: %s : %s", furi_string_get_cstr(item->name), item->ext); if(!strcmp(item->ext, ".sub")) { action_subghz_tx(context, item->path, error); diff --git a/actions/action_i.h b/actions/action_i.h index 92335bae504..7bb39a08ac9 100644 --- a/actions/action_i.h +++ b/actions/action_i.h @@ -5,7 +5,7 @@ #define ACTION_SET_ERROR(_msg_fmt, ...) furi_string_printf(error, _msg_fmt, ##__VA_ARGS__) -void action_subghz_tx(void* context, FuriString* action_path, FuriString* error); -void action_rfid_tx(void* context, FuriString* action_path, FuriString* error); -void action_ir_tx(void* context, FuriString* action_path, FuriString* error); -void action_qpl_tx(void* context, FuriString* action_path, FuriString* error); \ No newline at end of file +void action_subghz_tx(void* context, const FuriString* action_path, FuriString* error); +void action_rfid_tx(void* context, const FuriString* action_path, FuriString* error); +void action_ir_tx(void* context, const FuriString* action_path, FuriString* error); +void action_qpl_tx(void* context, const FuriString* action_path, FuriString* error); \ No newline at end of file diff --git a/actions/action_ir.c b/actions/action_ir.c index c974ab2bc58..3f74fe4c75c 100644 --- a/actions/action_ir.c +++ b/actions/action_ir.c @@ -29,7 +29,7 @@ InfraredSignal* infrared_signal_alloc() { return signal; } -void action_ir_tx(void* context, FuriString* action_path, FuriString* error) { +void action_ir_tx(void* context, const FuriString* action_path, FuriString* error) { UNUSED(action_path); UNUSED(error); UNUSED(context); diff --git a/actions/action_qpl.c b/actions/action_qpl.c index 00d76b4170e..ac69719fa84 100644 --- a/actions/action_qpl.c +++ b/actions/action_qpl.c @@ -11,22 +11,28 @@ #include "quac.h" /** Open the Playlist file and then transmit each action + * * Each line of the playlist file is one of: * * Full SD card path, or relative path to action to be transmitted. Must be * one of the supported filetypes (.sub, .rfid, [.ir coming soon]) - * pause - NOT IMPLEMENTED + * + * If an .rfid file has a space followed by a number, that will be the + * duration for that RFID transmission. All other .rfid files will use + * the value specified in the Settings + * + * pause * Pauses the playback for 'ms' milliseconds. * * Blank lines, and comments (start with '#') are ignored. Whitespace is trimmed. * - * Not yet Implemented: - * - For RFID files, if they have a space followed by a number after their name, - * that number will be the duration of that RFID tx */ -void action_qpl_tx(void* context, FuriString* action_path, FuriString* error) { +void action_qpl_tx(void* context, const FuriString* action_path, FuriString* error) { App* app = context; + // Save the current RFID Duration, in case it is changed during playback + uint32_t orig_rfid_duration = app->settings.rfid_duration; + FuriString* buffer; buffer = furi_string_alloc(); @@ -34,7 +40,7 @@ void action_qpl_tx(void* context, FuriString* action_path, FuriString* error) { if(file_stream_open(file, furi_string_get_cstr(action_path), FSAM_READ, FSOM_OPEN_EXISTING)) { while(stream_read_line(file, buffer)) { furi_string_trim(buffer); // remove '\n\r' line endings, cleanup spaces - FURI_LOG_I(TAG, "line: %s", furi_string_get_cstr(buffer)); + // FURI_LOG_I(TAG, "line: %s", furi_string_get_cstr(buffer)); // Skip blank lines if(furi_string_size(buffer) == 0) { @@ -100,7 +106,7 @@ void action_qpl_tx(void* context, FuriString* action_path, FuriString* error) { // FURI_LOG_I(TAG, "RFID file with duration"); if(sscanf(furi_string_get_cstr(buffer), "%lu", &rfid_duration) == 1) { FURI_LOG_I(TAG, "RFID duration = %lu", rfid_duration); - // TODO: Need to get the duration to the action_rfid_tx command... + app->settings.rfid_duration = rfid_duration; } } @@ -128,10 +134,12 @@ void action_qpl_tx(void* context, FuriString* action_path, FuriString* error) { path_extract_extension(buffer, ext, MAX_EXT_LEN); if(!strcmp(ext, ".sub")) { action_subghz_tx(context, buffer, error); - } else if(!strcmp(ext, ".ir")) { - action_ir_tx(context, buffer, error); } else if(!strcmp(ext, ".rfid")) { action_rfid_tx(context, buffer, error); + // Reset our default duration back - in case it was changed during playback + app->settings.rfid_duration = orig_rfid_duration; + } else if(!strcmp(ext, ".ir")) { + action_ir_tx(context, buffer, error); } else if(!strcmp(ext, ".qpl")) { ACTION_SET_ERROR("Playlist: Can't call playlist from playlist"); } else { diff --git a/actions/action_rfid.c b/actions/action_rfid.c index 9c80ca8dac7..ebca12603eb 100644 --- a/actions/action_rfid.c +++ b/actions/action_rfid.c @@ -13,22 +13,17 @@ #include "quac.h" // lifted from flipperzero-firmware/applications/main/lfrfid/lfrfid_cli.c -void action_rfid_tx(void* context, FuriString* action_path, FuriString* error) { +void action_rfid_tx(void* context, const FuriString* action_path, FuriString* error) { UNUSED(error); App* app = context; - FuriString* file_name = action_path; + const FuriString* file_name = action_path; FlipperFormat* fff_data_file = flipper_format_file_alloc(app->storage); FuriString* temp_str; temp_str = furi_string_alloc(); uint32_t temp_data32; - FuriString* protocol_name; - FuriString* data_text; - protocol_name = furi_string_alloc(); - data_text = furi_string_alloc(); - ProtocolDict* dict = protocol_dict_alloc(lfrfid_protocols, LFRFIDProtocolMax); ProtocolId protocol; size_t data_size = protocol_dict_get_max_data_size(dict); @@ -54,13 +49,13 @@ void action_rfid_tx(void* context, FuriString* action_path, FuriString* error) { } // read and check the protocol field - if(!flipper_format_read_string(fff_data_file, "Key type", protocol_name)) { + if(!flipper_format_read_string(fff_data_file, "Key type", temp_str)) { ACTION_SET_ERROR("RFID: Error reading protocol"); break; } - protocol = protocol_dict_get_protocol_by_name(dict, furi_string_get_cstr(protocol_name)); + protocol = protocol_dict_get_protocol_by_name(dict, furi_string_get_cstr(temp_str)); if(protocol == PROTOCOL_NO) { - ACTION_SET_ERROR("RFID: Unknown protocol: %s", furi_string_get_cstr(protocol_name)); + ACTION_SET_ERROR("RFID: Unknown protocol: %s", furi_string_get_cstr(temp_str)); break; } @@ -110,11 +105,7 @@ void action_rfid_tx(void* context, FuriString* action_path, FuriString* error) { } furi_string_free(temp_str); - furi_string_free(protocol_name); - furi_string_free(data_text); free(data); - protocol_dict_free(dict); - flipper_format_free(fff_data_file); } diff --git a/actions/action_subghz.c b/actions/action_subghz.c index 2a8f5e76bc7..45b87398f59 100644 --- a/actions/action_subghz.c +++ b/actions/action_subghz.c @@ -31,9 +31,9 @@ static FuriHalSubGhzPreset action_subghz_get_preset_name(const char* preset_name } // Lifted from flipperzero-firmware/applications/main/subghz/subghz_cli.c -void action_subghz_tx(void* context, FuriString* action_path, FuriString* error) { +void action_subghz_tx(void* context, const FuriString* action_path, FuriString* error) { App* app = context; - FuriString* file_name = action_path; + const char* file_name = furi_string_get_cstr(action_path); uint32_t repeat = 1; // // uint32_t device_ind = 0; // 0 - CC1101_INT, 1 - CC1101_EXT @@ -76,9 +76,9 @@ void action_subghz_tx(void* context, FuriString* action_path, FuriString* error) // device_ind = 0; } - if(!flipper_format_file_open_existing(fff_data_file, furi_string_get_cstr(file_name))) { - FURI_LOG_E(TAG, "Error opening %s", furi_string_get_cstr(file_name)); - ACTION_SET_ERROR("SUBGHZ: Error opening %s", furi_string_get_cstr(file_name)); + if(!flipper_format_file_open_existing(fff_data_file, file_name)) { + FURI_LOG_E(TAG, "Error opening %s", file_name); + ACTION_SET_ERROR("SUBGHZ: Error opening %s", file_name); break; } @@ -169,7 +169,7 @@ void action_subghz_tx(void* context, FuriString* action_path, FuriString* error) if(!strcmp(furi_string_get_cstr(temp_str), "RAW")) { FURI_LOG_I(TAG, "Protocol = RAW"); subghz_protocol_raw_gen_fff_data( - fff_data_raw, furi_string_get_cstr(file_name), subghz_devices_get_name(device)); + fff_data_raw, file_name, subghz_devices_get_name(device)); transmitter = subghz_transmitter_alloc_init(environment, furi_string_get_cstr(temp_str)); if(transmitter == NULL) { @@ -219,7 +219,7 @@ void action_subghz_tx(void* context, FuriString* action_path, FuriString* error) FURI_LOG_I( TAG, "Listening at %s. Frequency=%lu, Protocol=%s", - furi_string_get_cstr(file_name), + file_name, frequency, furi_string_get_cstr(temp_str)); do { diff --git a/flipper.h b/flipper.h deleted file mode 100644 index 6aff972d712..00000000000 --- a/flipper.h +++ /dev/null @@ -1,16 +0,0 @@ -#pragma once - -#include - -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -#define TAG "Quac" // log statement id diff --git a/item.c b/item.c index da2fabd1c9c..c7f380486ff 100644 --- a/item.c +++ b/item.c @@ -10,25 +10,26 @@ ARRAY_DEF(FileArray, FuriString*, FURI_STRING_OPLIST); -ItemsView* item_get_items_view_from_path(void* context, FuriString* input_path) { +ItemsView* item_get_items_view_from_path(void* context, const FuriString* input_path) { App* app = context; // Handle the app start condition + FuriString* in_path; if(input_path == NULL) { - input_path = furi_string_alloc_set_str(QUAC_DATA_PATH); + in_path = furi_string_alloc_set_str(QUAC_DATA_PATH); + } else { + in_path = furi_string_alloc_set(input_path); } - const char* cpath = furi_string_get_cstr(input_path); + const char* cpath = furi_string_get_cstr(in_path); - FURI_LOG_I(TAG, "Getting items from: %s", cpath); + FURI_LOG_I(TAG, "Reading items from path: %s", cpath); ItemsView* iview = malloc(sizeof(ItemsView)); - iview->path = furi_string_alloc_set(input_path); + iview->path = furi_string_alloc_set(in_path); iview->name = furi_string_alloc(); if(app->depth == 0) { - FURI_LOG_I(TAG, "Depth is ZERO!"); furi_string_set_str(iview->name, QUAC_NAME); } else { - FURI_LOG_I(TAG, "Depth is %d", app->depth); path_extract_basename(cpath, iview->name); item_prettify_name(iview->name); } @@ -43,17 +44,17 @@ ItemsView* item_get_items_view_from_path(void* context, FuriString* input_path) FuriString* filename_tmp; filename_tmp = furi_string_alloc(); - // FURI_LOG_I(TAG, "About to walk the dir"); + // Walk the directory and store all file names in sorted order if(dir_walk_open(dir_walk, cpath)) { while(dir_walk_read(dir_walk, path, NULL) == DirWalkOK) { - FURI_LOG_I(TAG, "> dir_walk: %s", furi_string_get_cstr(path)); + // FURI_LOG_I(TAG, "> dir_walk: %s", furi_string_get_cstr(path)); const char* cpath = furi_string_get_cstr(path); // Skip "hidden" files path_extract_filename(path, filename_tmp, false); char first_char = furi_string_get_char(filename_tmp, 0); if(first_char == '.') { - FURI_LOG_I(TAG, ">> skipping hidden file: %s", furi_string_get_cstr(filename_tmp)); + // FURI_LOG_I(TAG, ">> skipping hidden file: %s", furi_string_get_cstr(filename_tmp)); continue; } @@ -78,6 +79,7 @@ ItemsView* item_get_items_view_from_path(void* context, FuriString* input_path) furi_string_free(filename_tmp); furi_string_free(path); + // Generate our Item list FileArray_it_t iter; ItemArray_init(iview->items); for(FileArray_it(iter, flist); !FileArray_end_p(iter); FileArray_next(iter)) { @@ -89,20 +91,7 @@ ItemsView* item_get_items_view_from_path(void* context, FuriString* input_path) // Action files have extensions, so item->ext starts with '.' item->ext[0] = 0; path_extract_extension(path, item->ext, MAX_EXT_LEN); - // FURI_LOG_I(TAG, ". EXT = %s", item->ext); - if(item->ext[0] == '.') { - // TODO: hack alert - make a helper fn here, or something - if(item->ext[1] == 's') - item->type = Item_SubGhz; - else if(item->ext[1] == 'r') - item->type = Item_RFID; - else if(item->ext[1] == 'q') - item->type = Item_Playlist; - else if(item->ext[1] == 'i') - item->type = Item_IR; - } else { - item->type = Item_Group; - } + item->type = item_get_item_type_from_extension(item->ext); item->name = furi_string_alloc(); path_extract_filename_no_ext(found_path, item->name); @@ -114,6 +103,7 @@ ItemsView* item_get_items_view_from_path(void* context, FuriString* input_path) // FURI_LOG_I(TAG, "Path: %s", furi_string_get_cstr(item->path)); } + furi_string_free(in_path); FileArray_clear(flist); dir_walk_free(dir_walk); @@ -121,7 +111,6 @@ ItemsView* item_get_items_view_from_path(void* context, FuriString* input_path) } void item_items_view_free(ItemsView* items_view) { - FURI_LOG_I(TAG, "item_items_view_free - begin"); furi_string_free(items_view->name); furi_string_free(items_view->path); ItemArray_it_t iter; @@ -131,7 +120,6 @@ void item_items_view_free(ItemsView* items_view) { } ItemArray_clear(items_view->items); free(items_view); - FURI_LOG_I(TAG, "item_items_view_free - end"); } void item_prettify_name(FuriString* name) { @@ -148,4 +136,19 @@ void item_prettify_name(FuriString* name) { } furi_string_replace_str(name, "_", " ", 0); // FURI_LOG_I(TAG, "... %s", furi_string_get_cstr(name)); +} + +ItemType item_get_item_type_from_extension(const char* ext) { + ItemType type = Item_Group; + + if(!strcmp(ext, ".sub")) { + type = Item_SubGhz; + } else if(!strcmp(ext, ".rfid")) { + type = Item_RFID; + } else if(!strcmp(ext, ".ir")) { + type = Item_IR; + } else if(!strcmp(ext, ".qpl")) { + type = Item_Playlist; + } + return type; } \ No newline at end of file diff --git a/item.h b/item.h index cc0c148316b..b5e8ad37634 100644 --- a/item.h +++ b/item.h @@ -15,7 +15,8 @@ typedef enum { Item_IR, Item_Playlist, Item_Group, - Item_Settings + Item_Settings, + Item_count } ItemType; typedef struct Item { @@ -41,7 +42,7 @@ typedef struct ItemsView { * @param path FuriString* * @return ItemsView* */ -ItemsView* item_get_items_view_from_path(void* context, FuriString* path); +ItemsView* item_get_items_view_from_path(void* context, const FuriString* path); /** Free ItemsView * @param items_view @@ -52,4 +53,9 @@ void item_items_view_free(ItemsView* items_view); * as well as replace all '_' with ' '. * @param name FuriString* */ -void item_prettify_name(FuriString* name); \ No newline at end of file +void item_prettify_name(FuriString* name); + +/** Return the ItemType enum for the given extension + * @param ext File extension +*/ +ItemType item_get_item_type_from_extension(const char* ext); \ No newline at end of file diff --git a/quac.c b/quac.c index 7bc14d1179d..519d3b28d56 100644 --- a/quac.c +++ b/quac.c @@ -3,7 +3,6 @@ #include #include #include -#include #include #include @@ -30,14 +29,12 @@ App* app_alloc() { view_dispatcher_set_navigation_event_callback(app->view_dispatcher, app_back_event_callback); // Create our UI elements - app->btn_menu = button_menu_alloc(); - view_dispatcher_add_view( - app->view_dispatcher, Q_ButtonMenu, button_menu_get_view(app->btn_menu)); - + // Main interface app->action_menu = action_menu_alloc(); view_dispatcher_add_view( app->view_dispatcher, Q_ActionMenu, action_menu_get_view(app->action_menu)); + // App settings app->vil_settings = variable_item_list_alloc(); view_dispatcher_add_view( app->view_dispatcher, Q_Settings, variable_item_list_get_view(app->vil_settings)); @@ -51,12 +48,10 @@ App* app_alloc() { // Notifications - for LED light access app->notifications = furi_record_open(RECORD_NOTIFICATION); - // initialize device items list + // data member initialize app->depth = 0; app->selected_item = -1; - app->items_view = item_get_items_view_from_path(app, NULL); - return app; } @@ -65,10 +60,13 @@ void app_free(App* app) { item_items_view_free(app->items_view); - view_dispatcher_remove_view(app->view_dispatcher, Q_ButtonMenu); + view_dispatcher_remove_view(app->view_dispatcher, Q_ActionMenu); + view_dispatcher_remove_view(app->view_dispatcher, Q_Settings); + view_dispatcher_remove_view(app->view_dispatcher, Q_Dialog); - button_menu_free(app->btn_menu); action_menu_free(app->action_menu); + variable_item_list_free(app->vil_settings); + dialog_ex_free(app->dialog); scene_manager_free(app->scene_manager); view_dispatcher_free(app->view_dispatcher); @@ -89,6 +87,9 @@ int32_t quac_app(void* p) { App* app = app_alloc(); quac_load_settings(app); + // Read items at our root + app->items_view = item_get_items_view_from_path(app, NULL); + Gui* gui = furi_record_open(RECORD_GUI); view_dispatcher_attach_to_gui(app->view_dispatcher, gui, ViewDispatcherTypeFullscreen); scene_manager_next_scene(app->scene_manager, Q_Scene_Items); diff --git a/quac.h b/quac.h index 5c8b2f6e9b5..b2de7154927 100644 --- a/quac.h +++ b/quac.h @@ -2,7 +2,6 @@ #include #include -#include #include #include @@ -26,16 +25,16 @@ typedef enum { QUAC_APP_PORTRAIT, QUAC_APP_LANDSCAPE } QuacAppLayout; typedef struct App { SceneManager* scene_manager; ViewDispatcher* view_dispatcher; - ButtonMenu* btn_menu; - DialogEx* dialog; - VariableItemList* vil_settings; + ActionMenu* action_menu; + VariableItemList* vil_settings; + DialogEx* dialog; Storage* storage; NotificationApp* notifications; - int depth; ItemsView* items_view; + int depth; int selected_item; struct { diff --git a/quac_settings.c b/quac_settings.c index a583cbc83d4..39069e35462 100644 --- a/quac_settings.c +++ b/quac_settings.c @@ -3,8 +3,8 @@ #include // Quac Settings File Info -// TODO: Fix this path to use existing #defs for /ext, etc -#define QUAC_SETTINGS_FILENAME "/ext/apps_data/quac/.quac.conf" +// "/ext/apps_data/quac/.quac.conf" +#define QUAC_SETTINGS_FILENAME QUAC_DATA_PATH "/.quac.conf" #define QUAC_SETTINGS_FILE_TYPE "Quac Settings File" #define QUAC_SETTINGS_FILE_VERSION 1 @@ -21,7 +21,7 @@ void quac_load_settings(App* app) { temp_str = furi_string_alloc(); uint32_t temp_data32 = 0; - FURI_LOG_I(TAG, "SETTINGS: Reading settings file"); + FURI_LOG_I(TAG, "SETTINGS: Reading: %s", QUAC_SETTINGS_FILENAME); bool successful = false; do { if(!flipper_format_file_open_existing(fff_settings, QUAC_SETTINGS_FILENAME)) { @@ -88,6 +88,7 @@ void quac_save_settings(App* app) { FlipperFormat* fff_settings = flipper_format_file_alloc(app->storage); uint32_t temp_data32; + FURI_LOG_I(TAG, "SETTINGS: Saving"); bool successful = false; do { if(!flipper_format_file_open_always(fff_settings, QUAC_SETTINGS_FILENAME)) { diff --git a/scenes/scene_items.c b/scenes/scene_items.c index e27d8bc76ac..4d1b9eb6059 100644 --- a/scenes/scene_items.c +++ b/scenes/scene_items.c @@ -2,7 +2,6 @@ #include #include -#include #include #include @@ -15,6 +14,15 @@ #include +static const ActionMenuItemType ItemToMenuItem[] = { + [Item_SubGhz] = ActionMenuItemTypeSubGHz, + [Item_RFID] = ActionMenuItemTypeRFID, + [Item_IR] = ActionMenuItemTypeIR, + [Item_Playlist] = ActionMenuItemTypePlaylist, + [Item_Group] = ActionMenuItemTypeGroup, + [Item_Settings] = ActionMenuItemTypeSettings, +}; + void scene_items_item_callback(void* context, int32_t index, InputType type) { App* app = context; @@ -40,11 +48,10 @@ void scene_items_on_enter(void* context) { action_menu_set_show_headers(menu, app->settings.show_headers); ItemsView* items_view = app->items_view; - FURI_LOG_I(TAG, "items on_enter: [%d] %s", app->depth, furi_string_get_cstr(items_view->path)); - furi_delay_ms(500); + FURI_LOG_I( + TAG, "Generating scene: [depth=%d] %s", app->depth, furi_string_get_cstr(items_view->path)); - const char* header = furi_string_get_cstr(items_view->name); - action_menu_set_header(menu, header); + action_menu_set_header(menu, furi_string_get_cstr(items_view->name)); size_t item_view_size = ItemArray_size(items_view->items); if(item_view_size > 0) { @@ -53,27 +60,7 @@ void scene_items_on_enter(void* context) { for(ItemArray_it(iter, items_view->items); !ItemArray_end_p(iter); ItemArray_next(iter), ++index) { const char* label = furi_string_get_cstr(ItemArray_cref(iter)->name); - ActionMenuItemType type; - // TODO: Fix this with an array/map - switch(ItemArray_cref(iter)->type) { - case Item_Group: - type = ActionMenuItemTypeGroup; - break; - case Item_Playlist: - type = ActionMenuItemTypePlaylist; - break; - case Item_SubGhz: - type = ActionMenuItemTypeSubGHz; - break; - case Item_RFID: - type = ActionMenuItemTypeRFID; - break; - case Item_IR: - type = ActionMenuItemTypeIR; - break; - default: - type = ActionMenuItemTypeGroup; // TODO: Does this ever get hit? - } + ActionMenuItemType type = ItemToMenuItem[ItemArray_cref(iter)->type]; action_menu_add_item(menu, label, index, scene_items_item_callback, type, app); } } else { @@ -98,13 +85,12 @@ bool scene_items_on_event(void* context, SceneManagerEvent event) { App* app = context; bool consumed = false; - FURI_LOG_I(TAG, "device on_event"); switch(event.type) { case SceneManagerEventTypeCustom: if(event.event == Event_ButtonPressed) { consumed = true; - furi_delay_ms(100); - FURI_LOG_I(TAG, "button pressed is %d", app->selected_item); + // furi_delay_ms(100); + // FURI_LOG_I(TAG, "button pressed is %d", app->selected_item); if(app->selected_item < (int)ItemArray_size(app->items_view->items)) { Item* item = ItemArray_get(app->items_view->items, app->selected_item); if(item->type == Item_Group) { @@ -141,16 +127,16 @@ bool scene_items_on_event(void* context, SceneManagerEvent event) { notification_message(app->notifications, &sequence_blink_stop); } } else { - FURI_LOG_I(TAG, "Selected Settings!"); + // FURI_LOG_I(TAG, "Selected Settings!"); // TODO: Do we need to free this current items_view?? scene_manager_next_scene(app->scene_manager, Q_Scene_Settings); } } break; case SceneManagerEventTypeBack: - FURI_LOG_I(TAG, "Back button pressed!"); + // FURI_LOG_I(TAG, "Back button pressed!"); consumed = false; // Ensure Back event continues to propagate - if(app->depth >= 0) { + if(app->depth > 0) { // take our current ItemsView path, and go back up a level FuriString* parent_path; parent_path = furi_string_alloc(); @@ -163,22 +149,19 @@ bool scene_items_on_event(void* context, SceneManagerEvent event) { furi_string_free(parent_path); } else { - FURI_LOG_W(TAG, "At the root level!"); + // FURI_LOG_I(TAG, "At the root level!"); } break; default: FURI_LOG_I(TAG, "Custom event not handled"); break; } - FURI_LOG_I(TAG, "Generic event not handled"); + // FURI_LOG_I(TAG, "Generic event not handled"); return consumed; } void scene_items_on_exit(void* context) { App* app = context; - ActionMenu* menu = app->action_menu; action_menu_reset(menu); - - FURI_LOG_I(TAG, "on_exit. depth = %d", app->depth); } \ No newline at end of file diff --git a/scenes/scene_settings.c b/scenes/scene_settings.c index e946c756515..f24674c0395 100644 --- a/scenes/scene_settings.c +++ b/scenes/scene_settings.c @@ -48,7 +48,6 @@ static const uint32_t rfid_duration_value[V_RFID_DURATION_COUNT] = { static void scene_settings_layout_changed(VariableItem* item) { App* app = variable_item_get_context(item); uint8_t index = variable_item_get_current_value_index(item); - variable_item_set_current_value_text(item, layout_text[index]); app->settings.layout = layout_value[index]; } @@ -78,33 +77,28 @@ static void scene_settings_rfid_duration_changed(VariableItem* item) { void scene_settings_on_enter(void* context) { App* app = context; - FURI_LOG_I(TAG, "Settings _on_enter"); VariableItemList* vil = app->vil_settings; variable_item_list_reset(vil); VariableItem* item; uint8_t value_index; - FURI_LOG_I(TAG, "setting up Layout"); item = variable_item_list_add(vil, "Layout", 2, scene_settings_layout_changed, app); value_index = value_index_uint32(app->settings.layout, layout_value, 2); variable_item_set_current_value_index(item, value_index); variable_item_set_current_value_text(item, layout_text[value_index]); - FURI_LOG_I(TAG, "setting up Show Icons"); item = variable_item_list_add(vil, "Show Icons", 2, scene_settings_show_icons_changed, app); value_index = value_index_uint32(app->settings.show_icons, show_icons_value, 2); variable_item_set_current_value_index(item, value_index); variable_item_set_current_value_text(item, show_icons_text[value_index]); - FURI_LOG_I(TAG, "setting up Show Headers"); item = variable_item_list_add(vil, "Show Headers", 2, scene_settings_show_headers_changed, app); value_index = value_index_uint32(app->settings.show_headers, show_headers_value, 2); variable_item_set_current_value_index(item, value_index); variable_item_set_current_value_text(item, show_headers_text[value_index]); - FURI_LOG_I(TAG, "setting up RFID Duration"); item = variable_item_list_add( vil, "RFID Duration", V_RFID_DURATION_COUNT, scene_settings_rfid_duration_changed, app); value_index = value_index_uint32( diff --git a/scenes/scenes.h b/scenes/scenes.h index cb4f2b84a08..aba0774666d 100644 --- a/scenes/scenes.h +++ b/scenes/scenes.h @@ -3,12 +3,9 @@ typedef enum { Q_Scene_Items, Q_Scene_Settings, Q_Scene_count } appScenes; typedef enum { - Q_ButtonMenu, // used on selected device, to show buttons/groups - Q_Dialog, // shows errors Q_ActionMenu, // new UI, Q_Settings, // Variable Item List for settings - Q_FileBrowser, // TODO: UNUSED! - Q_TextInput // TODO: UNUSED + Q_Dialog, // TODO: shows errors } appView; typedef enum { Event_DeviceSelected, Event_ButtonPressed } AppCustomEvents; diff --git a/views/action_menu.c b/views/action_menu.c index 14b45f58fed..f5011a30347 100644 --- a/views/action_menu.c +++ b/views/action_menu.c @@ -31,7 +31,6 @@ static const Icon* ActionMenuIcons[] = { struct ActionMenuItem { const char* label; - IconAnimation* icon; uint32_t index; ActionMenuItemCallback callback; ActionMenuItemType type; @@ -266,9 +265,8 @@ static void action_menu_process_down(ActionMenu* action_menu) { static void action_menu_process_ok(ActionMenu* action_menu, InputType type) { furi_assert(action_menu); - // UNUSED(type); - FURI_LOG_I("AM", "OK pressed! %d: %s", type, input_get_type_name(type)); + // FURI_LOG_I("AM", "OK pressed! %d: %s", type, input_get_type_name(type)); ActionMenuItem* item = NULL; with_view_model( diff --git a/views/action_menu.h b/views/action_menu.h index 60659303010..d0cc848deb6 100644 --- a/views/action_menu.h +++ b/views/action_menu.h @@ -16,7 +16,7 @@ typedef struct ActionMenuItem ActionMenuItem; /** Callback for any button menu actions */ typedef void (*ActionMenuItemCallback)(void* context, int32_t index, InputType type); -/** Type of button. Difference in drawing buttons. */ +/** Type of UI element */ typedef enum { ActionMenuItemTypeSubGHz, ActionMenuItemTypeRFID, @@ -24,6 +24,7 @@ typedef enum { ActionMenuItemTypePlaylist, ActionMenuItemTypeGroup, ActionMenuItemTypeSettings, + ActionMenuItemType_count } ActionMenuItemType; typedef enum {