[FL-2491] File browser GUI module (#1237)
* File browser module and test app * nfc: Add support for saved files in subdirectories * nfc: Use helper function to get shadow path when loading data * File browser dialog integration pt.1 * File browser dialog integration pt.2 * Gui,Dialogs: drop file select * Correct use of dynamic string_t(string_ptr) Co-authored-by: Yukai Li <yukaili.geek@gmail.com> Co-authored-by: Aleksandr Kutuzov <alleteam@gmail.com>
This commit is contained in:
		
							parent
							
								
									533f12af15
								
							
						
					
					
						commit
						79920a3522
					
				| @ -44,6 +44,7 @@ extern int32_t vibro_test_app(void* p); | |||||||
| extern int32_t bt_hid_app(void* p); | extern int32_t bt_hid_app(void* p); | ||||||
| extern int32_t battery_test_app(void* p); | extern int32_t battery_test_app(void* p); | ||||||
| extern int32_t text_box_test_app(void* p); | extern int32_t text_box_test_app(void* p); | ||||||
|  | extern int32_t file_browser_app(void* p); | ||||||
| 
 | 
 | ||||||
| // Plugins
 | // Plugins
 | ||||||
| extern int32_t music_player_app(void* p); | extern int32_t music_player_app(void* p); | ||||||
| @ -459,6 +460,14 @@ const FlipperApplication FLIPPER_DEBUG_APPS[] = { | |||||||
|      .flags = FlipperApplicationFlagDefault}, |      .flags = FlipperApplicationFlagDefault}, | ||||||
| #endif | #endif | ||||||
| 
 | 
 | ||||||
|  | #ifdef APP_FILE_BROWSER_TEST | ||||||
|  |     {.app = file_browser_app, | ||||||
|  |      .name = "File Browser test", | ||||||
|  |      .stack_size = 2048, | ||||||
|  |      .icon = &A_BadUsb_14, | ||||||
|  |      .flags = FlipperApplicationFlagDefault}, | ||||||
|  | #endif | ||||||
|  | 
 | ||||||
| #ifdef APP_BATTERY_TEST | #ifdef APP_BATTERY_TEST | ||||||
|     {.app = battery_test_app, |     {.app = battery_test_app, | ||||||
|      .name = "Battery Test", |      .name = "Battery Test", | ||||||
|  | |||||||
| @ -62,6 +62,7 @@ APP_USB_MOUSE = 1 | |||||||
| APP_BAD_USB = 1 | APP_BAD_USB = 1 | ||||||
| APP_U2F = 1 | APP_U2F = 1 | ||||||
| APP_UART_ECHO = 1 | APP_UART_ECHO = 1 | ||||||
|  | APP_FILE_BROWSER_TEST = 1 | ||||||
| endif | endif | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| @ -207,6 +208,11 @@ CFLAGS		+= -DAPP_KEYPAD_TEST | |||||||
| SRV_GUI		= 1 | SRV_GUI		= 1 | ||||||
| endif | endif | ||||||
| 
 | 
 | ||||||
|  | APP_FILE_BROWSER_TEST ?= 0 | ||||||
|  | ifeq ($(APP_FILE_BROWSER_TEST), 1) | ||||||
|  | CFLAGS		+= -DAPP_FILE_BROWSER_TEST | ||||||
|  | SRV_GUI = 1 | ||||||
|  | endif | ||||||
| 
 | 
 | ||||||
| APP_ACCESSOR ?= 0 | APP_ACCESSOR ?= 0 | ||||||
| ifeq ($(APP_ACCESSOR), 1) | ifeq ($(APP_ACCESSOR), 1) | ||||||
|  | |||||||
| @ -1,4 +1,5 @@ | |||||||
| #include "bad_usb_app_i.h" | #include "bad_usb_app_i.h" | ||||||
|  | #include "m-string.h" | ||||||
| #include <furi.h> | #include <furi.h> | ||||||
| #include <furi_hal.h> | #include <furi_hal.h> | ||||||
| #include <storage/storage.h> | #include <storage/storage.h> | ||||||
| @ -22,33 +23,13 @@ static void bad_usb_app_tick_event_callback(void* context) { | |||||||
|     scene_manager_handle_tick_event(app->scene_manager); |     scene_manager_handle_tick_event(app->scene_manager); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static bool bad_usb_check_assets() { |  | ||||||
|     Storage* fs_api = furi_record_open("storage"); |  | ||||||
| 
 |  | ||||||
|     File* dir = storage_file_alloc(fs_api); |  | ||||||
|     bool ret = false; |  | ||||||
| 
 |  | ||||||
|     if(storage_dir_open(dir, BAD_USB_APP_PATH_FOLDER)) { |  | ||||||
|         ret = true; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     storage_dir_close(dir); |  | ||||||
|     storage_file_free(dir); |  | ||||||
| 
 |  | ||||||
|     furi_record_close("storage"); |  | ||||||
| 
 |  | ||||||
|     return ret; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| BadUsbApp* bad_usb_app_alloc(char* arg) { | BadUsbApp* bad_usb_app_alloc(char* arg) { | ||||||
|     BadUsbApp* app = malloc(sizeof(BadUsbApp)); |     BadUsbApp* app = malloc(sizeof(BadUsbApp)); | ||||||
| 
 | 
 | ||||||
|  |     string_init(app->file_path); | ||||||
|  | 
 | ||||||
|     if(arg != NULL) { |     if(arg != NULL) { | ||||||
|         string_t filename; |         string_set_str(app->file_path, arg); | ||||||
|         string_init(filename); |  | ||||||
|         path_extract_filename_no_ext(arg, filename); |  | ||||||
|         strncpy(app->file_name, string_get_cstr(filename), BAD_USB_FILE_NAME_LEN); |  | ||||||
|         string_clear(filename); |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     app->gui = furi_record_open("gui"); |     app->gui = furi_record_open("gui"); | ||||||
| @ -83,13 +64,11 @@ BadUsbApp* bad_usb_app_alloc(char* arg) { | |||||||
|         app->error = BadUsbAppErrorCloseRpc; |         app->error = BadUsbAppErrorCloseRpc; | ||||||
|         scene_manager_next_scene(app->scene_manager, BadUsbSceneError); |         scene_manager_next_scene(app->scene_manager, BadUsbSceneError); | ||||||
|     } else { |     } else { | ||||||
|         if(*app->file_name != '\0') { |         if(!string_empty_p(app->file_path)) { | ||||||
|             scene_manager_next_scene(app->scene_manager, BadUsbSceneWork); |             scene_manager_next_scene(app->scene_manager, BadUsbSceneWork); | ||||||
|         } else if(bad_usb_check_assets()) { |  | ||||||
|             scene_manager_next_scene(app->scene_manager, BadUsbSceneFileSelect); |  | ||||||
|         } else { |         } else { | ||||||
|             app->error = BadUsbAppErrorNoFiles; |             string_set_str(app->file_path, BAD_USB_APP_PATH_FOLDER); | ||||||
|             scene_manager_next_scene(app->scene_manager, BadUsbSceneError); |             scene_manager_next_scene(app->scene_manager, BadUsbSceneFileSelect); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| @ -117,6 +96,8 @@ void bad_usb_app_free(BadUsbApp* app) { | |||||||
|     furi_record_close("notification"); |     furi_record_close("notification"); | ||||||
|     furi_record_close("dialogs"); |     furi_record_close("dialogs"); | ||||||
| 
 | 
 | ||||||
|  |     string_clear(app->file_path); | ||||||
|  | 
 | ||||||
|     free(app); |     free(app); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -16,7 +16,6 @@ | |||||||
| 
 | 
 | ||||||
| #define BAD_USB_APP_PATH_FOLDER "/any/badusb" | #define BAD_USB_APP_PATH_FOLDER "/any/badusb" | ||||||
| #define BAD_USB_APP_EXTENSION ".txt" | #define BAD_USB_APP_EXTENSION ".txt" | ||||||
| #define BAD_USB_FILE_NAME_LEN 40 |  | ||||||
| 
 | 
 | ||||||
| typedef enum { | typedef enum { | ||||||
|     BadUsbAppErrorNoFiles, |     BadUsbAppErrorNoFiles, | ||||||
| @ -32,7 +31,7 @@ struct BadUsbApp { | |||||||
|     Widget* widget; |     Widget* widget; | ||||||
| 
 | 
 | ||||||
|     BadUsbAppError error; |     BadUsbAppError error; | ||||||
|     char file_name[BAD_USB_FILE_NAME_LEN + 1]; |     string_t file_path; | ||||||
|     BadUsb* bad_usb_view; |     BadUsb* bad_usb_view; | ||||||
|     BadUsbScript* bad_usb_script; |     BadUsbScript* bad_usb_script; | ||||||
| }; | }; | ||||||
|  | |||||||
| @ -5,14 +5,16 @@ | |||||||
| static bool bad_usb_file_select(BadUsbApp* bad_usb) { | static bool bad_usb_file_select(BadUsbApp* bad_usb) { | ||||||
|     furi_assert(bad_usb); |     furi_assert(bad_usb); | ||||||
| 
 | 
 | ||||||
|     // Input events and views are managed by file_select
 |     // Input events and views are managed by file_browser
 | ||||||
|     bool res = dialog_file_select_show( |     bool res = dialog_file_browser_show( | ||||||
|         bad_usb->dialogs, |         bad_usb->dialogs, | ||||||
|         BAD_USB_APP_PATH_FOLDER, |         bad_usb->file_path, | ||||||
|  |         bad_usb->file_path, | ||||||
|         BAD_USB_APP_EXTENSION, |         BAD_USB_APP_EXTENSION, | ||||||
|         bad_usb->file_name, |         true, | ||||||
|         sizeof(bad_usb->file_name), |         &I_badusb_10px, | ||||||
|         NULL); |         true); | ||||||
|  | 
 | ||||||
|     return res; |     return res; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -2,6 +2,8 @@ | |||||||
| #include "../bad_usb_app_i.h" | #include "../bad_usb_app_i.h" | ||||||
| #include "../views/bad_usb_view.h" | #include "../views/bad_usb_view.h" | ||||||
| #include "furi_hal.h" | #include "furi_hal.h" | ||||||
|  | #include "m-string.h" | ||||||
|  | #include "toolbox/path.h" | ||||||
| 
 | 
 | ||||||
| void bad_usb_scene_work_ok_callback(InputType type, void* context) { | void bad_usb_scene_work_ok_callback(InputType type, void* context) { | ||||||
|     furi_assert(context); |     furi_assert(context); | ||||||
| @ -28,10 +30,9 @@ void bad_usb_scene_work_on_enter(void* context) { | |||||||
|     string_t file_name; |     string_t file_name; | ||||||
|     string_init(file_name); |     string_init(file_name); | ||||||
| 
 | 
 | ||||||
|     bad_usb_set_file_name(app->bad_usb_view, app->file_name); |     path_extract_filename(app->file_path, file_name, true); | ||||||
|     string_printf( |     bad_usb_set_file_name(app->bad_usb_view, string_get_cstr(file_name)); | ||||||
|         file_name, "%s/%s%s", BAD_USB_APP_PATH_FOLDER, app->file_name, BAD_USB_APP_EXTENSION); |     app->bad_usb_script = bad_usb_script_open(app->file_path); | ||||||
|     app->bad_usb_script = bad_usb_script_open(file_name); |  | ||||||
| 
 | 
 | ||||||
|     string_clear(file_name); |     string_clear(file_name); | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -2,6 +2,8 @@ | |||||||
| #include "../bad_usb_script.h" | #include "../bad_usb_script.h" | ||||||
| #include <gui/elements.h> | #include <gui/elements.h> | ||||||
| 
 | 
 | ||||||
|  | #define MAX_NAME_LEN 64 | ||||||
|  | 
 | ||||||
| struct BadUsb { | struct BadUsb { | ||||||
|     View* view; |     View* view; | ||||||
|     BadUsbOkCallback callback; |     BadUsbOkCallback callback; | ||||||
| @ -9,7 +11,7 @@ struct BadUsb { | |||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| typedef struct { | typedef struct { | ||||||
|     char* file_name; |     char file_name[MAX_NAME_LEN]; | ||||||
|     BadUsbState state; |     BadUsbState state; | ||||||
|     uint8_t anim_frame; |     uint8_t anim_frame; | ||||||
| } BadUsbModel; | } BadUsbModel; | ||||||
| @ -149,11 +151,11 @@ void bad_usb_set_ok_callback(BadUsb* bad_usb, BadUsbOkCallback callback, void* c | |||||||
|         }); |         }); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void bad_usb_set_file_name(BadUsb* bad_usb, char* name) { | void bad_usb_set_file_name(BadUsb* bad_usb, const char* name) { | ||||||
|     furi_assert(name); |     furi_assert(name); | ||||||
|     with_view_model( |     with_view_model( | ||||||
|         bad_usb->view, (BadUsbModel * model) { |         bad_usb->view, (BadUsbModel * model) { | ||||||
|             model->file_name = name; |             strncpy(model->file_name, name, MAX_NAME_LEN); | ||||||
|             return true; |             return true; | ||||||
|         }); |         }); | ||||||
| } | } | ||||||
|  | |||||||
| @ -14,6 +14,6 @@ View* bad_usb_get_view(BadUsb* bad_usb); | |||||||
| 
 | 
 | ||||||
| void bad_usb_set_ok_callback(BadUsb* bad_usb, BadUsbOkCallback callback, void* context); | void bad_usb_set_ok_callback(BadUsb* bad_usb, BadUsbOkCallback callback, void* context); | ||||||
| 
 | 
 | ||||||
| void bad_usb_set_file_name(BadUsb* bad_usb, char* name); | void bad_usb_set_file_name(BadUsb* bad_usb, const char* name); | ||||||
| 
 | 
 | ||||||
| void bad_usb_set_state(BadUsb* bad_usb, BadUsbState* st); | void bad_usb_set_state(BadUsb* bad_usb, BadUsbState* st); | ||||||
|  | |||||||
| @ -0,0 +1,99 @@ | |||||||
|  | #include "assets_icons.h" | ||||||
|  | #include "file_browser_app_i.h" | ||||||
|  | #include "gui/modules/file_browser.h" | ||||||
|  | #include "m-string.h" | ||||||
|  | #include <furi.h> | ||||||
|  | #include <furi_hal.h> | ||||||
|  | #include <storage/storage.h> | ||||||
|  | #include <lib/toolbox/path.h> | ||||||
|  | 
 | ||||||
|  | static bool file_browser_app_custom_event_callback(void* context, uint32_t event) { | ||||||
|  |     furi_assert(context); | ||||||
|  |     FileBrowserApp* app = context; | ||||||
|  |     return scene_manager_handle_custom_event(app->scene_manager, event); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static bool file_browser_app_back_event_callback(void* context) { | ||||||
|  |     furi_assert(context); | ||||||
|  |     FileBrowserApp* app = context; | ||||||
|  |     return scene_manager_handle_back_event(app->scene_manager); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void file_browser_app_tick_event_callback(void* context) { | ||||||
|  |     furi_assert(context); | ||||||
|  |     FileBrowserApp* app = context; | ||||||
|  |     scene_manager_handle_tick_event(app->scene_manager); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | FileBrowserApp* file_browser_app_alloc(char* arg) { | ||||||
|  |     UNUSED(arg); | ||||||
|  |     FileBrowserApp* app = malloc(sizeof(FileBrowserApp)); | ||||||
|  | 
 | ||||||
|  |     app->gui = furi_record_open("gui"); | ||||||
|  |     app->dialogs = furi_record_open("dialogs"); | ||||||
|  | 
 | ||||||
|  |     app->view_dispatcher = view_dispatcher_alloc(); | ||||||
|  |     view_dispatcher_enable_queue(app->view_dispatcher); | ||||||
|  | 
 | ||||||
|  |     app->scene_manager = scene_manager_alloc(&file_browser_scene_handlers, app); | ||||||
|  | 
 | ||||||
|  |     view_dispatcher_set_event_callback_context(app->view_dispatcher, app); | ||||||
|  |     view_dispatcher_set_tick_event_callback( | ||||||
|  |         app->view_dispatcher, file_browser_app_tick_event_callback, 500); | ||||||
|  |     view_dispatcher_set_custom_event_callback( | ||||||
|  |         app->view_dispatcher, file_browser_app_custom_event_callback); | ||||||
|  |     view_dispatcher_set_navigation_event_callback( | ||||||
|  |         app->view_dispatcher, file_browser_app_back_event_callback); | ||||||
|  | 
 | ||||||
|  |     app->widget = widget_alloc(); | ||||||
|  | 
 | ||||||
|  |     string_init(app->file_path); | ||||||
|  |     app->file_browser = file_browser_alloc(app->file_path); | ||||||
|  |     file_browser_configure(app->file_browser, "*", true, &I_badusb_10px, true); | ||||||
|  | 
 | ||||||
|  |     view_dispatcher_add_view( | ||||||
|  |         app->view_dispatcher, FileBrowserAppViewStart, widget_get_view(app->widget)); | ||||||
|  |     view_dispatcher_add_view( | ||||||
|  |         app->view_dispatcher, FileBrowserAppViewResult, widget_get_view(app->widget)); | ||||||
|  |     view_dispatcher_add_view( | ||||||
|  |         app->view_dispatcher, FileBrowserAppViewBrowser, file_browser_get_view(app->file_browser)); | ||||||
|  | 
 | ||||||
|  |     view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen); | ||||||
|  | 
 | ||||||
|  |     scene_manager_next_scene(app->scene_manager, FileBrowserSceneStart); | ||||||
|  | 
 | ||||||
|  |     return app; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void file_browser_app_free(FileBrowserApp* app) { | ||||||
|  |     furi_assert(app); | ||||||
|  | 
 | ||||||
|  |     // Views
 | ||||||
|  |     view_dispatcher_remove_view(app->view_dispatcher, FileBrowserAppViewStart); | ||||||
|  |     view_dispatcher_remove_view(app->view_dispatcher, FileBrowserAppViewResult); | ||||||
|  |     view_dispatcher_remove_view(app->view_dispatcher, FileBrowserAppViewBrowser); | ||||||
|  |     widget_free(app->widget); | ||||||
|  |     file_browser_free(app->file_browser); | ||||||
|  | 
 | ||||||
|  |     // View dispatcher
 | ||||||
|  |     view_dispatcher_free(app->view_dispatcher); | ||||||
|  |     scene_manager_free(app->scene_manager); | ||||||
|  | 
 | ||||||
|  |     // Close records
 | ||||||
|  |     furi_record_close("gui"); | ||||||
|  |     furi_record_close("notification"); | ||||||
|  |     furi_record_close("dialogs"); | ||||||
|  | 
 | ||||||
|  |     string_clear(app->file_path); | ||||||
|  | 
 | ||||||
|  |     free(app); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | int32_t file_browser_app(void* p) { | ||||||
|  |     FileBrowserApp* file_browser_app = file_browser_app_alloc((char*)p); | ||||||
|  | 
 | ||||||
|  |     view_dispatcher_run(file_browser_app->view_dispatcher); | ||||||
|  | 
 | ||||||
|  |     file_browser_app_free(file_browser_app); | ||||||
|  |     return 0; | ||||||
|  | } | ||||||
| @ -0,0 +1,32 @@ | |||||||
|  | #pragma once | ||||||
|  | 
 | ||||||
|  | #include "scenes/file_browser_scene.h" | ||||||
|  | 
 | ||||||
|  | #include <gui/gui.h> | ||||||
|  | #include <gui/view_dispatcher.h> | ||||||
|  | #include <gui/scene_manager.h> | ||||||
|  | #include <gui/modules/submenu.h> | ||||||
|  | #include <gui/modules/file_browser.h> | ||||||
|  | #include <dialogs/dialogs.h> | ||||||
|  | #include <notification/notification_messages.h> | ||||||
|  | #include <gui/modules/variable_item_list.h> | ||||||
|  | #include <gui/modules/widget.h> | ||||||
|  | 
 | ||||||
|  | typedef struct FileBrowserApp FileBrowserApp; | ||||||
|  | 
 | ||||||
|  | struct FileBrowserApp { | ||||||
|  |     Gui* gui; | ||||||
|  |     ViewDispatcher* view_dispatcher; | ||||||
|  |     SceneManager* scene_manager; | ||||||
|  |     DialogsApp* dialogs; | ||||||
|  |     Widget* widget; | ||||||
|  |     FileBrowser* file_browser; | ||||||
|  | 
 | ||||||
|  |     string_t file_path; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | typedef enum { | ||||||
|  |     FileBrowserAppViewStart, | ||||||
|  |     FileBrowserAppViewBrowser, | ||||||
|  |     FileBrowserAppViewResult, | ||||||
|  | } FileBrowserAppView; | ||||||
| @ -0,0 +1,30 @@ | |||||||
|  | #include "file_browser_scene.h" | ||||||
|  | 
 | ||||||
|  | // Generate scene on_enter handlers array
 | ||||||
|  | #define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_enter, | ||||||
|  | void (*const file_browser_scene_on_enter_handlers[])(void*) = { | ||||||
|  | #include "file_browser_scene_config.h" | ||||||
|  | }; | ||||||
|  | #undef ADD_SCENE | ||||||
|  | 
 | ||||||
|  | // Generate scene on_event handlers array
 | ||||||
|  | #define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_event, | ||||||
|  | bool (*const file_browser_scene_on_event_handlers[])(void* context, SceneManagerEvent event) = { | ||||||
|  | #include "file_browser_scene_config.h" | ||||||
|  | }; | ||||||
|  | #undef ADD_SCENE | ||||||
|  | 
 | ||||||
|  | // Generate scene on_exit handlers array
 | ||||||
|  | #define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_exit, | ||||||
|  | void (*const file_browser_scene_on_exit_handlers[])(void* context) = { | ||||||
|  | #include "file_browser_scene_config.h" | ||||||
|  | }; | ||||||
|  | #undef ADD_SCENE | ||||||
|  | 
 | ||||||
|  | // Initialize scene handlers configuration structure
 | ||||||
|  | const SceneManagerHandlers file_browser_scene_handlers = { | ||||||
|  |     .on_enter_handlers = file_browser_scene_on_enter_handlers, | ||||||
|  |     .on_event_handlers = file_browser_scene_on_event_handlers, | ||||||
|  |     .on_exit_handlers = file_browser_scene_on_exit_handlers, | ||||||
|  |     .scene_num = FileBrowserSceneNum, | ||||||
|  | }; | ||||||
| @ -0,0 +1,29 @@ | |||||||
|  | #pragma once | ||||||
|  | 
 | ||||||
|  | #include <gui/scene_manager.h> | ||||||
|  | 
 | ||||||
|  | // Generate scene id and total number
 | ||||||
|  | #define ADD_SCENE(prefix, name, id) FileBrowserScene##id, | ||||||
|  | typedef enum { | ||||||
|  | #include "file_browser_scene_config.h" | ||||||
|  |     FileBrowserSceneNum, | ||||||
|  | } FileBrowserScene; | ||||||
|  | #undef ADD_SCENE | ||||||
|  | 
 | ||||||
|  | extern const SceneManagerHandlers file_browser_scene_handlers; | ||||||
|  | 
 | ||||||
|  | // Generate scene on_enter handlers declaration
 | ||||||
|  | #define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_enter(void*); | ||||||
|  | #include "file_browser_scene_config.h" | ||||||
|  | #undef ADD_SCENE | ||||||
|  | 
 | ||||||
|  | // Generate scene on_event handlers declaration
 | ||||||
|  | #define ADD_SCENE(prefix, name, id) \ | ||||||
|  |     bool prefix##_scene_##name##_on_event(void* context, SceneManagerEvent event); | ||||||
|  | #include "file_browser_scene_config.h" | ||||||
|  | #undef ADD_SCENE | ||||||
|  | 
 | ||||||
|  | // Generate scene on_exit handlers declaration
 | ||||||
|  | #define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_exit(void* context); | ||||||
|  | #include "file_browser_scene_config.h" | ||||||
|  | #undef ADD_SCENE | ||||||
| @ -0,0 +1,43 @@ | |||||||
|  | #include "../file_browser_app_i.h" | ||||||
|  | #include "furi/check.h" | ||||||
|  | #include "furi/log.h" | ||||||
|  | #include "furi_hal.h" | ||||||
|  | #include "m-string.h" | ||||||
|  | 
 | ||||||
|  | #define DEFAULT_PATH "/" | ||||||
|  | #define EXTENSION "*" | ||||||
|  | 
 | ||||||
|  | bool file_browser_scene_browser_on_event(void* context, SceneManagerEvent event) { | ||||||
|  |     UNUSED(context); | ||||||
|  |     FileBrowserApp* app = context; | ||||||
|  |     bool consumed = false; | ||||||
|  | 
 | ||||||
|  |     if(event.type == SceneManagerEventTypeCustom) { | ||||||
|  |         scene_manager_next_scene(app->scene_manager, FileBrowserSceneResult); | ||||||
|  |         consumed = true; | ||||||
|  |     } else if(event.type == SceneManagerEventTypeTick) { | ||||||
|  |     } | ||||||
|  |     return consumed; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void file_browser_callback(void* context) { | ||||||
|  |     FileBrowserApp* app = context; | ||||||
|  |     furi_assert(app); | ||||||
|  |     view_dispatcher_send_custom_event(app->view_dispatcher, SceneManagerEventTypeCustom); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void file_browser_scene_browser_on_enter(void* context) { | ||||||
|  |     FileBrowserApp* app = context; | ||||||
|  | 
 | ||||||
|  |     file_browser_set_callback(app->file_browser, file_browser_callback, app); | ||||||
|  | 
 | ||||||
|  |     file_browser_start(app->file_browser, app->file_path); | ||||||
|  | 
 | ||||||
|  |     view_dispatcher_switch_to_view(app->view_dispatcher, FileBrowserAppViewBrowser); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void file_browser_scene_browser_on_exit(void* context) { | ||||||
|  |     FileBrowserApp* app = context; | ||||||
|  | 
 | ||||||
|  |     file_browser_stop(app->file_browser); | ||||||
|  | } | ||||||
| @ -0,0 +1,3 @@ | |||||||
|  | ADD_SCENE(file_browser, start, Start) | ||||||
|  | ADD_SCENE(file_browser, browser, Browser) | ||||||
|  | ADD_SCENE(file_browser, result, Result) | ||||||
| @ -0,0 +1,36 @@ | |||||||
|  | #include "../file_browser_app_i.h" | ||||||
|  | #include "furi_hal.h" | ||||||
|  | #include "m-string.h" | ||||||
|  | 
 | ||||||
|  | void file_browser_scene_result_ok_callback(InputType type, void* context) { | ||||||
|  |     furi_assert(context); | ||||||
|  |     FileBrowserApp* app = context; | ||||||
|  |     view_dispatcher_send_custom_event(app->view_dispatcher, type); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool file_browser_scene_result_on_event(void* context, SceneManagerEvent event) { | ||||||
|  |     UNUSED(context); | ||||||
|  |     //FileBrowserApp* app = context;
 | ||||||
|  |     bool consumed = false; | ||||||
|  | 
 | ||||||
|  |     if(event.type == SceneManagerEventTypeCustom) { | ||||||
|  |         consumed = true; | ||||||
|  |     } else if(event.type == SceneManagerEventTypeTick) { | ||||||
|  |     } | ||||||
|  |     return consumed; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void file_browser_scene_result_on_enter(void* context) { | ||||||
|  |     FileBrowserApp* app = context; | ||||||
|  | 
 | ||||||
|  |     widget_add_string_multiline_element( | ||||||
|  |         app->widget, 64, 10, AlignCenter, AlignTop, FontSecondary, string_get_cstr(app->file_path)); | ||||||
|  | 
 | ||||||
|  |     view_dispatcher_switch_to_view(app->view_dispatcher, FileBrowserAppViewResult); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void file_browser_scene_result_on_exit(void* context) { | ||||||
|  |     UNUSED(context); | ||||||
|  |     FileBrowserApp* app = context; | ||||||
|  |     widget_reset(app->widget); | ||||||
|  | } | ||||||
| @ -0,0 +1,44 @@ | |||||||
|  | #include "../file_browser_app_i.h" | ||||||
|  | #include "furi_hal.h" | ||||||
|  | #include "gui/modules/widget_elements/widget_element_i.h" | ||||||
|  | 
 | ||||||
|  | static void | ||||||
|  |     file_browser_scene_start_ok_callback(GuiButtonType result, InputType type, void* context) { | ||||||
|  |     UNUSED(result); | ||||||
|  |     furi_assert(context); | ||||||
|  |     FileBrowserApp* app = context; | ||||||
|  |     if(type == InputTypeShort) { | ||||||
|  |         view_dispatcher_send_custom_event(app->view_dispatcher, type); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool file_browser_scene_start_on_event(void* context, SceneManagerEvent event) { | ||||||
|  |     FileBrowserApp* app = context; | ||||||
|  |     bool consumed = false; | ||||||
|  | 
 | ||||||
|  |     if(event.type == SceneManagerEventTypeCustom) { | ||||||
|  |         string_set_str(app->file_path, "/any/badusb/demo_windows.txt"); | ||||||
|  |         scene_manager_next_scene(app->scene_manager, FileBrowserSceneBrowser); | ||||||
|  |         consumed = true; | ||||||
|  |     } else if(event.type == SceneManagerEventTypeTick) { | ||||||
|  |     } | ||||||
|  |     return consumed; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void file_browser_scene_start_on_enter(void* context) { | ||||||
|  |     FileBrowserApp* app = context; | ||||||
|  | 
 | ||||||
|  |     widget_add_string_multiline_element( | ||||||
|  |         app->widget, 64, 20, AlignCenter, AlignTop, FontSecondary, "Press OK to start"); | ||||||
|  | 
 | ||||||
|  |     widget_add_button_element( | ||||||
|  |         app->widget, GuiButtonTypeCenter, "Ok", file_browser_scene_start_ok_callback, app); | ||||||
|  | 
 | ||||||
|  |     view_dispatcher_switch_to_view(app->view_dispatcher, FileBrowserAppViewStart); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void file_browser_scene_start_on_exit(void* context) { | ||||||
|  |     UNUSED(context); | ||||||
|  |     FileBrowserApp* app = context; | ||||||
|  |     widget_reset(app->widget); | ||||||
|  | } | ||||||
| @ -1,6 +1,7 @@ | |||||||
|  | #include "dialogs/dialogs_message.h" | ||||||
| #include "dialogs_i.h" | #include "dialogs_i.h" | ||||||
| #include "dialogs_api_lock.h" | #include "dialogs_api_lock.h" | ||||||
| #include "dialogs_module_file_select.h" | #include "dialogs_module_file_browser.h" | ||||||
| #include "dialogs_module_message.h" | #include "dialogs_module_message.h" | ||||||
| 
 | 
 | ||||||
| static DialogsApp* dialogs_app_alloc() { | static DialogsApp* dialogs_app_alloc() { | ||||||
| @ -13,9 +14,9 @@ static DialogsApp* dialogs_app_alloc() { | |||||||
| static void dialogs_app_process_message(DialogsApp* app, DialogsAppMessage* message) { | static void dialogs_app_process_message(DialogsApp* app, DialogsAppMessage* message) { | ||||||
|     UNUSED(app); |     UNUSED(app); | ||||||
|     switch(message->command) { |     switch(message->command) { | ||||||
|     case DialogsAppCommandFileOpen: |     case DialogsAppCommandFileBrowser: | ||||||
|         message->return_data->bool_value = |         message->return_data->bool_value = | ||||||
|             dialogs_app_process_module_file_select(&message->data->file_select); |             dialogs_app_process_module_file_browser(&message->data->file_browser); | ||||||
|         break; |         break; | ||||||
|     case DialogsAppCommandDialog: |     case DialogsAppCommandDialog: | ||||||
|         message->return_data->dialog_value = |         message->return_data->dialog_value = | ||||||
|  | |||||||
| @ -1,6 +1,7 @@ | |||||||
| #pragma once | #pragma once | ||||||
| #include <furi.h> | #include <furi.h> | ||||||
| #include <gui/canvas.h> | #include <gui/canvas.h> | ||||||
|  | #include "m-string.h" | ||||||
| 
 | 
 | ||||||
| #ifdef __cplusplus | #ifdef __cplusplus | ||||||
| extern "C" { | extern "C" { | ||||||
| @ -10,25 +11,27 @@ extern "C" { | |||||||
| 
 | 
 | ||||||
| typedef struct DialogsApp DialogsApp; | typedef struct DialogsApp DialogsApp; | ||||||
| 
 | 
 | ||||||
| /****************** FILE SELECT ******************/ | /****************** FILE BROWSER ******************/ | ||||||
| 
 | 
 | ||||||
| /**
 | /**
 | ||||||
|  * Shows and processes the file selection dialog |  * Shows and processes the file browser dialog | ||||||
|  * @param context api pointer |  * @param context api pointer | ||||||
|  * @param path path to directory |  * @param result_path selected file path string pointer | ||||||
|  |  * @param path preselected file path string pointer | ||||||
|  * @param extension file extension to be offered for selection |  * @param extension file extension to be offered for selection | ||||||
|  * @param selected_filename buffer where the selected filename will be saved |  * @param skip_assets true - do not show assets folders | ||||||
|  * @param selected_filename_size and the size of this buffer |  * @param icon file icon pointer, NULL for default icon | ||||||
|  * @param preselected_filename filename to be preselected |  * @param hide_ext true - hide extensions for files | ||||||
|  * @return bool whether a file was selected |  * @return bool whether a file was selected | ||||||
|  */ |  */ | ||||||
| bool dialog_file_select_show( | bool dialog_file_browser_show( | ||||||
|     DialogsApp* context, |     DialogsApp* context, | ||||||
|     const char* path, |     string_ptr result_path, | ||||||
|  |     string_ptr path, | ||||||
|     const char* extension, |     const char* extension, | ||||||
|     char* result, |     bool skip_assets, | ||||||
|     uint8_t result_size, |     const Icon* icon, | ||||||
|     const char* preselected_filename); |     bool hide_ext); | ||||||
| 
 | 
 | ||||||
| /****************** MESSAGE ******************/ | /****************** MESSAGE ******************/ | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -1,31 +1,36 @@ | |||||||
|  | #include "dialogs/dialogs_message.h" | ||||||
| #include "dialogs_i.h" | #include "dialogs_i.h" | ||||||
| #include "dialogs_api_lock.h" | #include "dialogs_api_lock.h" | ||||||
|  | #include "m-string.h" | ||||||
| 
 | 
 | ||||||
| /****************** File select ******************/ | /****************** File browser ******************/ | ||||||
| 
 | 
 | ||||||
| bool dialog_file_select_show( | bool dialog_file_browser_show( | ||||||
|     DialogsApp* context, |     DialogsApp* context, | ||||||
|     const char* path, |     string_ptr result_path, | ||||||
|  |     string_ptr path, | ||||||
|     const char* extension, |     const char* extension, | ||||||
|     char* result, |     bool skip_assets, | ||||||
|     uint8_t result_size, |     const Icon* icon, | ||||||
|     const char* preselected_filename) { |     bool hide_ext) { | ||||||
|     FuriApiLock lock = API_LOCK_INIT_LOCKED(); |     FuriApiLock lock = API_LOCK_INIT_LOCKED(); | ||||||
|     furi_check(lock != NULL); |     furi_check(lock != NULL); | ||||||
| 
 | 
 | ||||||
|     DialogsAppData data = { |     DialogsAppData data = { | ||||||
|         .file_select = { |         .file_browser = { | ||||||
|             .path = path, |  | ||||||
|             .extension = extension, |             .extension = extension, | ||||||
|             .result = result, |             .result_path = result_path, | ||||||
|             .result_size = result_size, |             .file_icon = icon, | ||||||
|             .preselected_filename = preselected_filename, |             .hide_ext = hide_ext, | ||||||
|  |             .skip_assets = skip_assets, | ||||||
|  |             .preselected_filename = path, | ||||||
|  | 
 | ||||||
|         }}; |         }}; | ||||||
| 
 | 
 | ||||||
|     DialogsAppReturn return_data; |     DialogsAppReturn return_data; | ||||||
|     DialogsAppMessage message = { |     DialogsAppMessage message = { | ||||||
|         .lock = lock, |         .lock = lock, | ||||||
|         .command = DialogsAppCommandFileOpen, |         .command = DialogsAppCommandFileBrowser, | ||||||
|         .data = &data, |         .data = &data, | ||||||
|         .return_data = &return_data, |         .return_data = &return_data, | ||||||
|     }; |     }; | ||||||
|  | |||||||
| @ -2,25 +2,27 @@ | |||||||
| #include <furi.h> | #include <furi.h> | ||||||
| #include "dialogs_i.h" | #include "dialogs_i.h" | ||||||
| #include "dialogs_api_lock.h" | #include "dialogs_api_lock.h" | ||||||
|  | #include "m-string.h" | ||||||
| 
 | 
 | ||||||
| #ifdef __cplusplus | #ifdef __cplusplus | ||||||
| extern "C" { | extern "C" { | ||||||
| #endif | #endif | ||||||
| 
 | 
 | ||||||
| typedef struct { | typedef struct { | ||||||
|     const char* path; |  | ||||||
|     const char* extension; |     const char* extension; | ||||||
|     char* result; |     bool skip_assets; | ||||||
|     uint8_t result_size; |     bool hide_ext; | ||||||
|     const char* preselected_filename; |     const Icon* file_icon; | ||||||
| } DialogsAppMessageDataFileSelect; |     string_ptr result_path; | ||||||
|  |     string_ptr preselected_filename; | ||||||
|  | } DialogsAppMessageDataFileBrowser; | ||||||
| 
 | 
 | ||||||
| typedef struct { | typedef struct { | ||||||
|     const DialogMessage* message; |     const DialogMessage* message; | ||||||
| } DialogsAppMessageDataDialog; | } DialogsAppMessageDataDialog; | ||||||
| 
 | 
 | ||||||
| typedef union { | typedef union { | ||||||
|     DialogsAppMessageDataFileSelect file_select; |     DialogsAppMessageDataFileBrowser file_browser; | ||||||
|     DialogsAppMessageDataDialog dialog; |     DialogsAppMessageDataDialog dialog; | ||||||
| } DialogsAppData; | } DialogsAppData; | ||||||
| 
 | 
 | ||||||
| @ -30,7 +32,7 @@ typedef union { | |||||||
| } DialogsAppReturn; | } DialogsAppReturn; | ||||||
| 
 | 
 | ||||||
| typedef enum { | typedef enum { | ||||||
|     DialogsAppCommandFileOpen, |     DialogsAppCommandFileBrowser, | ||||||
|     DialogsAppCommandDialog, |     DialogsAppCommandDialog, | ||||||
| } DialogsAppCommand; | } DialogsAppCommand; | ||||||
| 
 | 
 | ||||||
|  | |||||||
							
								
								
									
										59
									
								
								applications/dialogs/dialogs_module_file_browser.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										59
									
								
								applications/dialogs/dialogs_module_file_browser.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,59 @@ | |||||||
|  | #include "dialogs_i.h" | ||||||
|  | #include "dialogs_api_lock.h" | ||||||
|  | #include "gui/modules/file_browser.h" | ||||||
|  | 
 | ||||||
|  | typedef struct { | ||||||
|  |     FuriApiLock lock; | ||||||
|  |     bool result; | ||||||
|  | } DialogsAppFileBrowserContext; | ||||||
|  | 
 | ||||||
|  | static void dialogs_app_file_browser_back_callback(void* context) { | ||||||
|  |     furi_assert(context); | ||||||
|  |     DialogsAppFileBrowserContext* file_browser_context = context; | ||||||
|  |     file_browser_context->result = false; | ||||||
|  |     API_LOCK_UNLOCK(file_browser_context->lock); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void dialogs_app_file_browser_callback(void* context) { | ||||||
|  |     furi_assert(context); | ||||||
|  |     DialogsAppFileBrowserContext* file_browser_context = context; | ||||||
|  |     file_browser_context->result = true; | ||||||
|  |     API_LOCK_UNLOCK(file_browser_context->lock); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool dialogs_app_process_module_file_browser(const DialogsAppMessageDataFileBrowser* data) { | ||||||
|  |     bool ret = false; | ||||||
|  |     Gui* gui = furi_record_open("gui"); | ||||||
|  | 
 | ||||||
|  |     DialogsAppFileBrowserContext* file_browser_context = | ||||||
|  |         malloc(sizeof(DialogsAppFileBrowserContext)); | ||||||
|  |     file_browser_context->lock = API_LOCK_INIT_LOCKED(); | ||||||
|  | 
 | ||||||
|  |     ViewHolder* view_holder = view_holder_alloc(); | ||||||
|  |     view_holder_attach_to_gui(view_holder, gui); | ||||||
|  |     view_holder_set_back_callback( | ||||||
|  |         view_holder, dialogs_app_file_browser_back_callback, file_browser_context); | ||||||
|  | 
 | ||||||
|  |     FileBrowser* file_browser = file_browser_alloc(data->result_path); | ||||||
|  |     file_browser_set_callback( | ||||||
|  |         file_browser, dialogs_app_file_browser_callback, file_browser_context); | ||||||
|  |     file_browser_configure( | ||||||
|  |         file_browser, data->extension, data->skip_assets, data->file_icon, data->hide_ext); | ||||||
|  |     file_browser_start(file_browser, data->preselected_filename); | ||||||
|  | 
 | ||||||
|  |     view_holder_set_view(view_holder, file_browser_get_view(file_browser)); | ||||||
|  |     view_holder_start(view_holder); | ||||||
|  |     API_LOCK_WAIT_UNTIL_UNLOCK(file_browser_context->lock); | ||||||
|  | 
 | ||||||
|  |     ret = file_browser_context->result; | ||||||
|  | 
 | ||||||
|  |     view_holder_stop(view_holder); | ||||||
|  |     view_holder_free(view_holder); | ||||||
|  |     file_browser_stop(file_browser); | ||||||
|  |     file_browser_free(file_browser); | ||||||
|  |     API_LOCK_FREE(file_browser_context->lock); | ||||||
|  |     free(file_browser_context); | ||||||
|  |     furi_record_close("gui"); | ||||||
|  | 
 | ||||||
|  |     return ret; | ||||||
|  | } | ||||||
| @ -5,7 +5,7 @@ | |||||||
| extern "C" { | extern "C" { | ||||||
| #endif | #endif | ||||||
| 
 | 
 | ||||||
| bool dialogs_app_process_module_file_select(const DialogsAppMessageDataFileSelect* data); | bool dialogs_app_process_module_file_browser(const DialogsAppMessageDataFileBrowser* data); | ||||||
| 
 | 
 | ||||||
| #ifdef __cplusplus | #ifdef __cplusplus | ||||||
| } | } | ||||||
| @ -1,59 +0,0 @@ | |||||||
| #include "dialogs_i.h" |  | ||||||
| #include "dialogs_api_lock.h" |  | ||||||
| #include <gui/modules/file_select.h> |  | ||||||
| 
 |  | ||||||
| typedef struct { |  | ||||||
|     FuriApiLock lock; |  | ||||||
|     bool result; |  | ||||||
| } DialogsAppFileSelectContext; |  | ||||||
| 
 |  | ||||||
| static void dialogs_app_file_select_back_callback(void* context) { |  | ||||||
|     furi_assert(context); |  | ||||||
|     DialogsAppFileSelectContext* file_select_context = context; |  | ||||||
|     file_select_context->result = false; |  | ||||||
|     API_LOCK_UNLOCK(file_select_context->lock); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| static void dialogs_app_file_select_callback(bool result, void* context) { |  | ||||||
|     furi_assert(context); |  | ||||||
|     DialogsAppFileSelectContext* file_select_context = context; |  | ||||||
|     file_select_context->result = result; |  | ||||||
|     API_LOCK_UNLOCK(file_select_context->lock); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| bool dialogs_app_process_module_file_select(const DialogsAppMessageDataFileSelect* data) { |  | ||||||
|     bool ret = false; |  | ||||||
|     Gui* gui = furi_record_open("gui"); |  | ||||||
| 
 |  | ||||||
|     DialogsAppFileSelectContext* file_select_context = malloc(sizeof(DialogsAppFileSelectContext)); |  | ||||||
|     file_select_context->lock = API_LOCK_INIT_LOCKED(); |  | ||||||
| 
 |  | ||||||
|     ViewHolder* view_holder = view_holder_alloc(); |  | ||||||
|     view_holder_attach_to_gui(view_holder, gui); |  | ||||||
|     view_holder_set_back_callback( |  | ||||||
|         view_holder, dialogs_app_file_select_back_callback, file_select_context); |  | ||||||
| 
 |  | ||||||
|     FileSelect* file_select = file_select_alloc(); |  | ||||||
|     file_select_set_callback(file_select, dialogs_app_file_select_callback, file_select_context); |  | ||||||
|     file_select_set_filter(file_select, data->path, data->extension); |  | ||||||
|     file_select_set_result_buffer(file_select, data->result, data->result_size); |  | ||||||
|     file_select_init(file_select); |  | ||||||
|     if(data->preselected_filename != NULL) { |  | ||||||
|         file_select_set_selected_file(file_select, data->preselected_filename); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     view_holder_set_view(view_holder, file_select_get_view(file_select)); |  | ||||||
|     view_holder_start(view_holder); |  | ||||||
|     API_LOCK_WAIT_UNTIL_UNLOCK(file_select_context->lock); |  | ||||||
| 
 |  | ||||||
|     ret = file_select_context->result; |  | ||||||
| 
 |  | ||||||
|     view_holder_stop(view_holder); |  | ||||||
|     view_holder_free(view_holder); |  | ||||||
|     file_select_free(file_select); |  | ||||||
|     API_LOCK_FREE(file_select_context->lock); |  | ||||||
|     free(file_select_context); |  | ||||||
|     furi_record_close("gui"); |  | ||||||
| 
 |  | ||||||
|     return ret; |  | ||||||
| } |  | ||||||
							
								
								
									
										534
									
								
								applications/gui/modules/file_browser.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										534
									
								
								applications/gui/modules/file_browser.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,534 @@ | |||||||
|  | #include "file_browser.h" | ||||||
|  | #include "assets_icons.h" | ||||||
|  | #include "cmsis_os2.h" | ||||||
|  | #include "file_browser_worker.h" | ||||||
|  | #include "furi/check.h" | ||||||
|  | #include "furi/common_defines.h" | ||||||
|  | #include "furi/log.h" | ||||||
|  | #include "furi_hal_resources.h" | ||||||
|  | #include "m-string.h" | ||||||
|  | #include <m-array.h> | ||||||
|  | #include <gui/elements.h> | ||||||
|  | #include <furi.h> | ||||||
|  | #include "toolbox/path.h" | ||||||
|  | 
 | ||||||
|  | #define LIST_ITEMS 5u | ||||||
|  | #define MAX_LEN_PX 110 | ||||||
|  | #define FRAME_HEIGHT 12 | ||||||
|  | #define Y_OFFSET 3 | ||||||
|  | 
 | ||||||
|  | #define ITEM_LIST_LEN_MAX 100 | ||||||
|  | 
 | ||||||
|  | typedef enum { | ||||||
|  |     BrowserItemTypeLoading, | ||||||
|  |     BrowserItemTypeBack, | ||||||
|  |     BrowserItemTypeFolder, | ||||||
|  |     BrowserItemTypeFile, | ||||||
|  | } BrowserItemType; | ||||||
|  | 
 | ||||||
|  | typedef struct { | ||||||
|  |     string_t path; | ||||||
|  |     BrowserItemType type; | ||||||
|  | } BrowserItem_t; | ||||||
|  | 
 | ||||||
|  | static void BrowserItem_t_init(BrowserItem_t* obj) { | ||||||
|  |     obj->type = BrowserItemTypeLoading; | ||||||
|  |     string_init(obj->path); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void BrowserItem_t_init_set(BrowserItem_t* obj, const BrowserItem_t* src) { | ||||||
|  |     obj->type = src->type; | ||||||
|  |     string_init_set(obj->path, src->path); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void BrowserItem_t_set(BrowserItem_t* obj, const BrowserItem_t* src) { | ||||||
|  |     obj->type = src->type; | ||||||
|  |     string_set(obj->path, src->path); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void BrowserItem_t_clear(BrowserItem_t* obj) { | ||||||
|  |     string_clear(obj->path); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | ARRAY_DEF( | ||||||
|  |     items_array, | ||||||
|  |     BrowserItem_t, | ||||||
|  |     (INIT(API_2(BrowserItem_t_init)), | ||||||
|  |      SET(API_6(BrowserItem_t_set)), | ||||||
|  |      INIT_SET(API_6(BrowserItem_t_init_set)), | ||||||
|  |      CLEAR(API_2(BrowserItem_t_clear)))) | ||||||
|  | 
 | ||||||
|  | struct FileBrowser { | ||||||
|  |     View* view; | ||||||
|  |     BrowserWorker* worker; | ||||||
|  |     const char* ext_filter; | ||||||
|  |     bool skip_assets; | ||||||
|  | 
 | ||||||
|  |     FileBrowserCallback callback; | ||||||
|  |     void* context; | ||||||
|  | 
 | ||||||
|  |     string_ptr result_path; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | typedef struct { | ||||||
|  |     items_array_t items; | ||||||
|  | 
 | ||||||
|  |     bool is_root; | ||||||
|  |     bool folder_loading; | ||||||
|  |     bool list_loading; | ||||||
|  |     uint32_t item_cnt; | ||||||
|  |     int32_t item_idx; | ||||||
|  |     int32_t array_offset; | ||||||
|  |     int32_t list_offset; | ||||||
|  | 
 | ||||||
|  |     const Icon* file_icon; | ||||||
|  |     bool hide_ext; | ||||||
|  | } FileBrowserModel; | ||||||
|  | 
 | ||||||
|  | static const Icon* BrowserItemIcons[] = { | ||||||
|  |     [BrowserItemTypeLoading] = &I_loading_10px, | ||||||
|  |     [BrowserItemTypeBack] = &I_back_10px, | ||||||
|  |     [BrowserItemTypeFolder] = &I_dir_10px, | ||||||
|  |     [BrowserItemTypeFile] = &I_unknown_10px, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | static void file_browser_view_draw_callback(Canvas* canvas, void* _model); | ||||||
|  | static bool file_browser_view_input_callback(InputEvent* event, void* context); | ||||||
|  | 
 | ||||||
|  | static void | ||||||
|  |     browser_folder_open_cb(void* context, uint32_t item_cnt, int32_t file_idx, bool is_root); | ||||||
|  | static void browser_list_load_cb(void* context, uint32_t list_load_offset); | ||||||
|  | static void browser_list_item_cb(void* context, string_t item_path, bool is_folder, bool is_last); | ||||||
|  | static void browser_long_load_cb(void* context); | ||||||
|  | 
 | ||||||
|  | FileBrowser* file_browser_alloc(string_ptr result_path) { | ||||||
|  |     furi_assert(result_path); | ||||||
|  |     FileBrowser* browser = malloc(sizeof(FileBrowser)); | ||||||
|  |     browser->view = view_alloc(); | ||||||
|  |     view_allocate_model(browser->view, ViewModelTypeLocking, sizeof(FileBrowserModel)); | ||||||
|  |     view_set_context(browser->view, browser); | ||||||
|  |     view_set_draw_callback(browser->view, file_browser_view_draw_callback); | ||||||
|  |     view_set_input_callback(browser->view, file_browser_view_input_callback); | ||||||
|  | 
 | ||||||
|  |     browser->result_path = result_path; | ||||||
|  | 
 | ||||||
|  |     with_view_model( | ||||||
|  |         browser->view, (FileBrowserModel * model) { | ||||||
|  |             items_array_init(model->items); | ||||||
|  |             return false; | ||||||
|  |         }); | ||||||
|  | 
 | ||||||
|  |     return browser; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void file_browser_free(FileBrowser* browser) { | ||||||
|  |     furi_assert(browser); | ||||||
|  | 
 | ||||||
|  |     with_view_model( | ||||||
|  |         browser->view, (FileBrowserModel * model) { | ||||||
|  |             items_array_clear(model->items); | ||||||
|  |             return false; | ||||||
|  |         }); | ||||||
|  | 
 | ||||||
|  |     view_free(browser->view); | ||||||
|  |     free(browser); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | View* file_browser_get_view(FileBrowser* browser) { | ||||||
|  |     furi_assert(browser); | ||||||
|  |     return browser->view; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void file_browser_configure( | ||||||
|  |     FileBrowser* browser, | ||||||
|  |     const char* extension, | ||||||
|  |     bool skip_assets, | ||||||
|  |     const Icon* file_icon, | ||||||
|  |     bool hide_ext) { | ||||||
|  |     furi_assert(browser); | ||||||
|  | 
 | ||||||
|  |     browser->ext_filter = extension; | ||||||
|  |     browser->skip_assets = skip_assets; | ||||||
|  | 
 | ||||||
|  |     with_view_model( | ||||||
|  |         browser->view, (FileBrowserModel * model) { | ||||||
|  |             model->file_icon = file_icon; | ||||||
|  |             model->hide_ext = hide_ext; | ||||||
|  |             return false; | ||||||
|  |         }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void file_browser_start(FileBrowser* browser, string_t path) { | ||||||
|  |     furi_assert(browser); | ||||||
|  |     browser->worker = file_browser_worker_alloc(path, browser->ext_filter, browser->skip_assets); | ||||||
|  |     file_browser_worker_set_callback_context(browser->worker, browser); | ||||||
|  |     file_browser_worker_set_folder_callback(browser->worker, browser_folder_open_cb); | ||||||
|  |     file_browser_worker_set_list_callback(browser->worker, browser_list_load_cb); | ||||||
|  |     file_browser_worker_set_item_callback(browser->worker, browser_list_item_cb); | ||||||
|  |     file_browser_worker_set_long_load_callback(browser->worker, browser_long_load_cb); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void file_browser_stop(FileBrowser* browser) { | ||||||
|  |     furi_assert(browser); | ||||||
|  |     file_browser_worker_free(browser->worker); | ||||||
|  |     with_view_model( | ||||||
|  |         browser->view, (FileBrowserModel * model) { | ||||||
|  |             items_array_reset(model->items); | ||||||
|  |             model->item_cnt = 0; | ||||||
|  |             model->item_idx = 0; | ||||||
|  |             model->array_offset = 0; | ||||||
|  |             model->list_offset = 0; | ||||||
|  |             return false; | ||||||
|  |         }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void file_browser_set_callback(FileBrowser* browser, FileBrowserCallback callback, void* context) { | ||||||
|  |     browser->context = context; | ||||||
|  |     browser->callback = callback; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static bool browser_is_item_in_array(FileBrowserModel* model, uint32_t idx) { | ||||||
|  |     size_t array_size = items_array_size(model->items); | ||||||
|  | 
 | ||||||
|  |     if((idx >= (uint32_t)model->array_offset + array_size) || | ||||||
|  |        (idx < (uint32_t)model->array_offset)) { | ||||||
|  |         return false; | ||||||
|  |     } | ||||||
|  |     return true; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static bool browser_is_list_load_required(FileBrowserModel* model) { | ||||||
|  |     size_t array_size = items_array_size(model->items); | ||||||
|  |     uint32_t item_cnt = (model->is_root) ? model->item_cnt : model->item_cnt - 1; | ||||||
|  | 
 | ||||||
|  |     if((model->list_loading) || (array_size >= item_cnt)) { | ||||||
|  |         return false; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if((model->array_offset > 0) && | ||||||
|  |        (model->item_idx < (model->array_offset + ITEM_LIST_LEN_MAX / 4))) { | ||||||
|  |         return true; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if(((model->array_offset + array_size) < item_cnt) && | ||||||
|  |        (model->item_idx > (int32_t)(model->array_offset + array_size - ITEM_LIST_LEN_MAX / 4))) { | ||||||
|  |         return true; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return false; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void browser_update_offset(FileBrowser* browser) { | ||||||
|  |     furi_assert(browser); | ||||||
|  | 
 | ||||||
|  |     with_view_model( | ||||||
|  |         browser->view, (FileBrowserModel * model) { | ||||||
|  |             uint16_t bounds = model->item_cnt > (LIST_ITEMS - 1) ? 2 : model->item_cnt; | ||||||
|  | 
 | ||||||
|  |             if((model->item_cnt > (LIST_ITEMS - 1)) && | ||||||
|  |                (model->item_idx >= ((int32_t)model->item_cnt - 1))) { | ||||||
|  |                 model->list_offset = model->item_idx - (LIST_ITEMS - 1); | ||||||
|  |             } else if(model->list_offset < model->item_idx - bounds) { | ||||||
|  |                 model->list_offset = CLAMP( | ||||||
|  |                     model->item_idx - (int32_t)(LIST_ITEMS - 2), | ||||||
|  |                     (int32_t)model->item_cnt - bounds, | ||||||
|  |                     0); | ||||||
|  |             } else if(model->list_offset > model->item_idx - bounds) { | ||||||
|  |                 model->list_offset = | ||||||
|  |                     CLAMP(model->item_idx - 1, (int32_t)model->item_cnt - bounds, 0); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             return false; | ||||||
|  |         }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void | ||||||
|  |     browser_folder_open_cb(void* context, uint32_t item_cnt, int32_t file_idx, bool is_root) { | ||||||
|  |     furi_assert(context); | ||||||
|  |     FileBrowser* browser = (FileBrowser*)context; | ||||||
|  | 
 | ||||||
|  |     int32_t load_offset = 0; | ||||||
|  | 
 | ||||||
|  |     with_view_model( | ||||||
|  |         browser->view, (FileBrowserModel * model) { | ||||||
|  |             items_array_reset(model->items); | ||||||
|  |             if(is_root) { | ||||||
|  |                 model->item_cnt = item_cnt; | ||||||
|  |                 model->item_idx = (file_idx > 0) ? file_idx : 0; | ||||||
|  |                 load_offset = | ||||||
|  |                     CLAMP(model->item_idx - ITEM_LIST_LEN_MAX / 2, (int32_t)model->item_cnt, 0); | ||||||
|  |             } else { | ||||||
|  |                 model->item_cnt = item_cnt + 1; | ||||||
|  |                 model->item_idx = file_idx + 1; | ||||||
|  |                 load_offset = CLAMP( | ||||||
|  |                     model->item_idx - ITEM_LIST_LEN_MAX / 2 - 1, (int32_t)model->item_cnt - 1, 0); | ||||||
|  |             } | ||||||
|  |             model->array_offset = 0; | ||||||
|  |             model->list_offset = 0; | ||||||
|  |             model->is_root = is_root; | ||||||
|  |             model->list_loading = true; | ||||||
|  |             model->folder_loading = false; | ||||||
|  |             return true; | ||||||
|  |         }); | ||||||
|  |     browser_update_offset(browser); | ||||||
|  | 
 | ||||||
|  |     file_browser_worker_load(browser->worker, load_offset, ITEM_LIST_LEN_MAX); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void browser_list_load_cb(void* context, uint32_t list_load_offset) { | ||||||
|  |     furi_assert(context); | ||||||
|  |     FileBrowser* browser = (FileBrowser*)context; | ||||||
|  | 
 | ||||||
|  |     BrowserItem_t back_item; | ||||||
|  |     BrowserItem_t_init(&back_item); | ||||||
|  |     back_item.type = BrowserItemTypeBack; | ||||||
|  | 
 | ||||||
|  |     with_view_model( | ||||||
|  |         browser->view, (FileBrowserModel * model) { | ||||||
|  |             items_array_reset(model->items); | ||||||
|  |             model->array_offset = list_load_offset; | ||||||
|  |             if(!model->is_root) { | ||||||
|  |                 if(list_load_offset == 0) { | ||||||
|  |                     items_array_push_back(model->items, back_item); | ||||||
|  |                 } else { | ||||||
|  |                     model->array_offset += 1; | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             return false; | ||||||
|  |         }); | ||||||
|  | 
 | ||||||
|  |     BrowserItem_t_clear(&back_item); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void browser_list_item_cb(void* context, string_t item_path, bool is_folder, bool is_last) { | ||||||
|  |     furi_assert(context); | ||||||
|  |     FileBrowser* browser = (FileBrowser*)context; | ||||||
|  | 
 | ||||||
|  |     BrowserItem_t item; | ||||||
|  | 
 | ||||||
|  |     if(!is_last) { | ||||||
|  |         BrowserItem_t_init(&item); | ||||||
|  |         string_set(item.path, item_path); | ||||||
|  |         item.type = (is_folder) ? BrowserItemTypeFolder : BrowserItemTypeFile; | ||||||
|  | 
 | ||||||
|  |         with_view_model( | ||||||
|  |             browser->view, (FileBrowserModel * model) { | ||||||
|  |                 items_array_push_back(model->items, item); | ||||||
|  |                 return false; | ||||||
|  |             }); | ||||||
|  |         BrowserItem_t_clear(&item); | ||||||
|  |     } else { | ||||||
|  |         with_view_model( | ||||||
|  |             browser->view, (FileBrowserModel * model) { | ||||||
|  |                 model->list_loading = false; | ||||||
|  |                 return true; | ||||||
|  |             }); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void browser_long_load_cb(void* context) { | ||||||
|  |     furi_assert(context); | ||||||
|  |     FileBrowser* browser = (FileBrowser*)context; | ||||||
|  | 
 | ||||||
|  |     with_view_model( | ||||||
|  |         browser->view, (FileBrowserModel * model) { | ||||||
|  |             model->folder_loading = true; | ||||||
|  |             return true; | ||||||
|  |         }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void browser_draw_frame(Canvas* canvas, uint16_t idx, bool scrollbar) { | ||||||
|  |     canvas_set_color(canvas, ColorBlack); | ||||||
|  |     canvas_draw_box( | ||||||
|  |         canvas, 0, Y_OFFSET + idx * FRAME_HEIGHT, (scrollbar ? 122 : 127), FRAME_HEIGHT); | ||||||
|  | 
 | ||||||
|  |     canvas_set_color(canvas, ColorWhite); | ||||||
|  |     canvas_draw_dot(canvas, 0, Y_OFFSET + idx * FRAME_HEIGHT); | ||||||
|  |     canvas_draw_dot(canvas, 1, Y_OFFSET + idx * FRAME_HEIGHT); | ||||||
|  |     canvas_draw_dot(canvas, 0, (Y_OFFSET + idx * FRAME_HEIGHT) + 1); | ||||||
|  | 
 | ||||||
|  |     canvas_draw_dot(canvas, 0, (Y_OFFSET + idx * FRAME_HEIGHT) + (FRAME_HEIGHT - 1)); | ||||||
|  |     canvas_draw_dot(canvas, scrollbar ? 121 : 126, Y_OFFSET + idx * FRAME_HEIGHT); | ||||||
|  |     canvas_draw_dot( | ||||||
|  |         canvas, scrollbar ? 121 : 126, (Y_OFFSET + idx * FRAME_HEIGHT) + (FRAME_HEIGHT - 1)); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void browser_draw_loading(Canvas* canvas, FileBrowserModel* model) { | ||||||
|  |     uint8_t width = 49; | ||||||
|  |     uint8_t height = 47; | ||||||
|  |     uint8_t x = 128 / 2 - width / 2; | ||||||
|  |     uint8_t y = 64 / 2 - height / 2; | ||||||
|  | 
 | ||||||
|  |     UNUSED(model); | ||||||
|  | 
 | ||||||
|  |     elements_bold_rounded_frame(canvas, x, y, width, height); | ||||||
|  | 
 | ||||||
|  |     canvas_set_font(canvas, FontSecondary); | ||||||
|  |     elements_multiline_text(canvas, x + 7, y + 13, "Loading..."); | ||||||
|  | 
 | ||||||
|  |     canvas_draw_icon(canvas, x + 13, y + 19, &A_Loading_24); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void browser_draw_list(Canvas* canvas, FileBrowserModel* model) { | ||||||
|  |     uint32_t array_size = items_array_size(model->items); | ||||||
|  |     bool show_scrollbar = model->item_cnt > LIST_ITEMS; | ||||||
|  | 
 | ||||||
|  |     string_t filename; | ||||||
|  |     string_init(filename); | ||||||
|  | 
 | ||||||
|  |     for(uint32_t i = 0; i < MIN(model->item_cnt, LIST_ITEMS); i++) { | ||||||
|  |         int32_t idx = CLAMP((uint32_t)(i + model->list_offset), model->item_cnt, 0u); | ||||||
|  | 
 | ||||||
|  |         BrowserItemType item_type = BrowserItemTypeLoading; | ||||||
|  | 
 | ||||||
|  |         if(browser_is_item_in_array(model, idx)) { | ||||||
|  |             BrowserItem_t* item = items_array_get( | ||||||
|  |                 model->items, CLAMP(idx - model->array_offset, (int32_t)(array_size - 1), 0)); | ||||||
|  |             item_type = item->type; | ||||||
|  |             path_extract_filename( | ||||||
|  |                 item->path, filename, (model->hide_ext) && (item_type == BrowserItemTypeFile)); | ||||||
|  |         } else { | ||||||
|  |             string_set_str(filename, "---"); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         if(item_type == BrowserItemTypeBack) { | ||||||
|  |             string_set_str(filename, ". ."); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         elements_string_fit_width( | ||||||
|  |             canvas, filename, (show_scrollbar ? MAX_LEN_PX - 6 : MAX_LEN_PX)); | ||||||
|  | 
 | ||||||
|  |         if(model->item_idx == idx) { | ||||||
|  |             browser_draw_frame(canvas, i, show_scrollbar); | ||||||
|  |         } else { | ||||||
|  |             canvas_set_color(canvas, ColorBlack); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         if((item_type == BrowserItemTypeFile) && (model->file_icon)) { | ||||||
|  |             canvas_draw_icon(canvas, 2, Y_OFFSET + 1 + i * FRAME_HEIGHT, model->file_icon); | ||||||
|  |         } else if(BrowserItemIcons[item_type] != NULL) { | ||||||
|  |             canvas_draw_icon( | ||||||
|  |                 canvas, 2, Y_OFFSET + 1 + i * FRAME_HEIGHT, BrowserItemIcons[item_type]); | ||||||
|  |         } | ||||||
|  |         canvas_draw_str(canvas, 15, Y_OFFSET + 9 + i * FRAME_HEIGHT, string_get_cstr(filename)); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if(show_scrollbar) { | ||||||
|  |         elements_scrollbar_pos( | ||||||
|  |             canvas, | ||||||
|  |             126, | ||||||
|  |             Y_OFFSET, | ||||||
|  |             canvas_height(canvas) - Y_OFFSET, | ||||||
|  |             model->item_idx, | ||||||
|  |             model->item_cnt); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     string_clear(filename); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void file_browser_view_draw_callback(Canvas* canvas, void* _model) { | ||||||
|  |     FileBrowserModel* model = _model; | ||||||
|  | 
 | ||||||
|  |     if(model->folder_loading) { | ||||||
|  |         browser_draw_loading(canvas, model); | ||||||
|  |     } else { | ||||||
|  |         browser_draw_list(canvas, model); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static bool file_browser_view_input_callback(InputEvent* event, void* context) { | ||||||
|  |     FileBrowser* browser = context; | ||||||
|  |     furi_assert(browser); | ||||||
|  |     bool consumed = false; | ||||||
|  |     bool is_loading = false; | ||||||
|  | 
 | ||||||
|  |     with_view_model( | ||||||
|  |         browser->view, (FileBrowserModel * model) { | ||||||
|  |             is_loading = model->folder_loading; | ||||||
|  |             return false; | ||||||
|  |         }); | ||||||
|  | 
 | ||||||
|  |     if(is_loading) { | ||||||
|  |         return false; | ||||||
|  |     } else if(event->key == InputKeyUp || event->key == InputKeyDown) { | ||||||
|  |         if(event->type == InputTypeShort || event->type == InputTypeRepeat) { | ||||||
|  |             with_view_model( | ||||||
|  |                 browser->view, (FileBrowserModel * model) { | ||||||
|  |                     if(event->key == InputKeyUp) { | ||||||
|  |                         model->item_idx = | ||||||
|  |                             ((model->item_idx - 1) + model->item_cnt) % model->item_cnt; | ||||||
|  |                         if(browser_is_list_load_required(model)) { | ||||||
|  |                             model->list_loading = true; | ||||||
|  |                             int32_t load_offset = CLAMP( | ||||||
|  |                                 model->item_idx - ITEM_LIST_LEN_MAX / 4 * 3, | ||||||
|  |                                 (int32_t)model->item_cnt - ITEM_LIST_LEN_MAX, | ||||||
|  |                                 0); | ||||||
|  |                             file_browser_worker_load( | ||||||
|  |                                 browser->worker, load_offset, ITEM_LIST_LEN_MAX); | ||||||
|  |                         } | ||||||
|  |                     } else if(event->key == InputKeyDown) { | ||||||
|  |                         model->item_idx = (model->item_idx + 1) % model->item_cnt; | ||||||
|  |                         if(browser_is_list_load_required(model)) { | ||||||
|  |                             model->list_loading = true; | ||||||
|  |                             int32_t load_offset = CLAMP( | ||||||
|  |                                 model->item_idx - ITEM_LIST_LEN_MAX / 4 * 1, | ||||||
|  |                                 (int32_t)model->item_cnt - ITEM_LIST_LEN_MAX, | ||||||
|  |                                 0); | ||||||
|  |                             file_browser_worker_load( | ||||||
|  |                                 browser->worker, load_offset, ITEM_LIST_LEN_MAX); | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |                     return true; | ||||||
|  |                 }); | ||||||
|  |             browser_update_offset(browser); | ||||||
|  |             consumed = true; | ||||||
|  |         } | ||||||
|  |     } else if(event->key == InputKeyOk) { | ||||||
|  |         if(event->type == InputTypeShort) { | ||||||
|  |             BrowserItem_t* selected_item = NULL; | ||||||
|  |             int32_t select_index = 0; | ||||||
|  |             with_view_model( | ||||||
|  |                 browser->view, (FileBrowserModel * model) { | ||||||
|  |                     if(browser_is_item_in_array(model, model->item_idx)) { | ||||||
|  |                         selected_item = | ||||||
|  |                             items_array_get(model->items, model->item_idx - model->array_offset); | ||||||
|  |                         select_index = model->item_idx; | ||||||
|  |                         if((!model->is_root) && (select_index > 0)) { | ||||||
|  |                             select_index -= 1; | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |                     return false; | ||||||
|  |                 }); | ||||||
|  | 
 | ||||||
|  |             if(selected_item) { | ||||||
|  |                 if(selected_item->type == BrowserItemTypeBack) { | ||||||
|  |                     file_browser_worker_folder_exit(browser->worker); | ||||||
|  |                 } else if(selected_item->type == BrowserItemTypeFolder) { | ||||||
|  |                     file_browser_worker_folder_enter( | ||||||
|  |                         browser->worker, selected_item->path, select_index); | ||||||
|  |                 } else if(selected_item->type == BrowserItemTypeFile) { | ||||||
|  |                     string_set(browser->result_path, selected_item->path); | ||||||
|  |                     if(browser->callback) { | ||||||
|  |                         browser->callback(browser->context); | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             consumed = true; | ||||||
|  |         } | ||||||
|  |     } else if(event->key == InputKeyLeft) { | ||||||
|  |         if(event->type == InputTypeShort) { | ||||||
|  |             bool is_root = false; | ||||||
|  |             with_view_model( | ||||||
|  |                 browser->view, (FileBrowserModel * model) { | ||||||
|  |                     is_root = model->is_root; | ||||||
|  |                     return false; | ||||||
|  |                 }); | ||||||
|  |             if(!is_root) { | ||||||
|  |                 file_browser_worker_folder_exit(browser->worker); | ||||||
|  |             } | ||||||
|  |             consumed = true; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return consumed; | ||||||
|  | } | ||||||
							
								
								
									
										39
									
								
								applications/gui/modules/file_browser.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								applications/gui/modules/file_browser.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,39 @@ | |||||||
|  | /**
 | ||||||
|  |  * @file file_browser.h | ||||||
|  |  * GUI: FileBrowser view module API | ||||||
|  |  */ | ||||||
|  | 
 | ||||||
|  | #pragma once | ||||||
|  | 
 | ||||||
|  | #include "m-string.h" | ||||||
|  | #include <gui/view.h> | ||||||
|  | 
 | ||||||
|  | #ifdef __cplusplus | ||||||
|  | extern "C" { | ||||||
|  | #endif | ||||||
|  | 
 | ||||||
|  | typedef struct FileBrowser FileBrowser; | ||||||
|  | typedef void (*FileBrowserCallback)(void* context); | ||||||
|  | 
 | ||||||
|  | FileBrowser* file_browser_alloc(string_ptr result_path); | ||||||
|  | 
 | ||||||
|  | void file_browser_free(FileBrowser* browser); | ||||||
|  | 
 | ||||||
|  | View* file_browser_get_view(FileBrowser* browser); | ||||||
|  | 
 | ||||||
|  | void file_browser_configure( | ||||||
|  |     FileBrowser* browser, | ||||||
|  |     const char* extension, | ||||||
|  |     bool skip_assets, | ||||||
|  |     const Icon* file_icon, | ||||||
|  |     bool hide_ext); | ||||||
|  | 
 | ||||||
|  | void file_browser_start(FileBrowser* browser, string_t path); | ||||||
|  | 
 | ||||||
|  | void file_browser_stop(FileBrowser* browser); | ||||||
|  | 
 | ||||||
|  | void file_browser_set_callback(FileBrowser* browser, FileBrowserCallback callback, void* context); | ||||||
|  | 
 | ||||||
|  | #ifdef __cplusplus | ||||||
|  | } | ||||||
|  | #endif | ||||||
							
								
								
									
										408
									
								
								applications/gui/modules/file_browser_worker.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										408
									
								
								applications/gui/modules/file_browser_worker.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,408 @@ | |||||||
|  | #include "file_browser_worker.h" | ||||||
|  | #include "furi/check.h" | ||||||
|  | #include "furi/common_defines.h" | ||||||
|  | #include "m-string.h" | ||||||
|  | #include "storage/filesystem_api_defines.h" | ||||||
|  | #include <m-array.h> | ||||||
|  | #include <stdbool.h> | ||||||
|  | #include <storage/storage.h> | ||||||
|  | #include <furi.h> | ||||||
|  | #include <stddef.h> | ||||||
|  | #include "toolbox/path.h" | ||||||
|  | 
 | ||||||
|  | #define TAG "BrowserWorker" | ||||||
|  | 
 | ||||||
|  | #define ASSETS_DIR "assets" | ||||||
|  | #define BROWSER_ROOT "/any" | ||||||
|  | #define FILE_NAME_LEN_MAX 256 | ||||||
|  | #define LONG_LOAD_THRESHOLD 100 | ||||||
|  | 
 | ||||||
|  | typedef enum { | ||||||
|  |     WorkerEvtStop = (1 << 0), | ||||||
|  |     WorkerEvtLoad = (1 << 1), | ||||||
|  |     WorkerEvtFolderEnter = (1 << 2), | ||||||
|  |     WorkerEvtFolderExit = (1 << 3), | ||||||
|  | } WorkerEvtFlags; | ||||||
|  | 
 | ||||||
|  | #define WORKER_FLAGS_ALL \ | ||||||
|  |     (WorkerEvtStop | WorkerEvtLoad | WorkerEvtFolderEnter | WorkerEvtFolderExit) | ||||||
|  | 
 | ||||||
|  | ARRAY_DEF(idx_last_array, int32_t) | ||||||
|  | 
 | ||||||
|  | struct BrowserWorker { | ||||||
|  |     FuriThread* thread; | ||||||
|  | 
 | ||||||
|  |     string_t filter_extension; | ||||||
|  |     string_t path_next; | ||||||
|  |     int32_t item_sel_idx; | ||||||
|  |     uint32_t load_offset; | ||||||
|  |     uint32_t load_count; | ||||||
|  |     bool skip_assets; | ||||||
|  |     idx_last_array_t idx_last; | ||||||
|  | 
 | ||||||
|  |     void* cb_ctx; | ||||||
|  |     BrowserWorkerFolderOpenCallback folder_cb; | ||||||
|  |     BrowserWorkerListLoadCallback list_load_cb; | ||||||
|  |     BrowserWorkerListItemCallback list_item_cb; | ||||||
|  |     BrowserWorkerLongLoadCallback long_load_cb; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | static bool browser_path_is_file(string_t path) { | ||||||
|  |     bool state = false; | ||||||
|  |     FileInfo file_info; | ||||||
|  |     Storage* storage = furi_record_open("storage"); | ||||||
|  |     if(storage_common_stat(storage, string_get_cstr(path), &file_info) == FSE_OK) { | ||||||
|  |         if((file_info.flags & FSF_DIRECTORY) == 0) { | ||||||
|  |             state = true; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     furi_record_close("storage"); | ||||||
|  |     return state; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static bool browser_path_trim(string_t path) { | ||||||
|  |     bool is_root = false; | ||||||
|  |     size_t filename_start = string_search_rchar(path, '/'); | ||||||
|  |     string_left(path, filename_start); | ||||||
|  |     if((string_empty_p(path)) || (filename_start == STRING_FAILURE)) { | ||||||
|  |         string_set_str(path, BROWSER_ROOT); | ||||||
|  |         is_root = true; | ||||||
|  |     } | ||||||
|  |     return is_root; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static bool browser_filter_by_name(BrowserWorker* browser, string_t name, bool is_folder) { | ||||||
|  |     if(is_folder) { | ||||||
|  |         // Skip assets folders (if enabled)
 | ||||||
|  |         if(browser->skip_assets) { | ||||||
|  |             return ((string_cmp_str(name, ASSETS_DIR) == 0) ? (false) : (true)); | ||||||
|  |         } else { | ||||||
|  |             return true; | ||||||
|  |         } | ||||||
|  |     } else { | ||||||
|  |         // Filter files by extension
 | ||||||
|  |         if((string_empty_p(browser->filter_extension)) || | ||||||
|  |            (string_cmp_str(browser->filter_extension, "*") == 0)) { | ||||||
|  |             return true; | ||||||
|  |         } | ||||||
|  |         if(string_end_with_string_p(name, browser->filter_extension)) { | ||||||
|  |             return true; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     return false; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static bool browser_folder_check_and_switch(string_t path) { | ||||||
|  |     FileInfo file_info; | ||||||
|  |     Storage* storage = furi_record_open("storage"); | ||||||
|  |     bool is_root = false; | ||||||
|  |     while(1) { | ||||||
|  |         // Check if folder is existing and navigate back if not
 | ||||||
|  |         if(storage_common_stat(storage, string_get_cstr(path), &file_info) == FSE_OK) { | ||||||
|  |             if(file_info.flags & FSF_DIRECTORY) { | ||||||
|  |                 break; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         if(is_root) { | ||||||
|  |             break; | ||||||
|  |         } | ||||||
|  |         is_root = browser_path_trim(path); | ||||||
|  |     } | ||||||
|  |     furi_record_close("storage"); | ||||||
|  |     return is_root; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static bool browser_folder_init( | ||||||
|  |     BrowserWorker* browser, | ||||||
|  |     string_t path, | ||||||
|  |     string_t filename, | ||||||
|  |     uint32_t* item_cnt, | ||||||
|  |     int32_t* file_idx) { | ||||||
|  |     bool state = false; | ||||||
|  |     FileInfo file_info; | ||||||
|  |     uint32_t total_files_cnt = 0; | ||||||
|  | 
 | ||||||
|  |     Storage* storage = furi_record_open("storage"); | ||||||
|  |     File* directory = storage_file_alloc(storage); | ||||||
|  | 
 | ||||||
|  |     char name_temp[FILE_NAME_LEN_MAX]; | ||||||
|  |     string_t name_str; | ||||||
|  |     string_init(name_str); | ||||||
|  | 
 | ||||||
|  |     *item_cnt = 0; | ||||||
|  |     *file_idx = -1; | ||||||
|  | 
 | ||||||
|  |     if(storage_dir_open(directory, string_get_cstr(path))) { | ||||||
|  |         state = true; | ||||||
|  |         while(1) { | ||||||
|  |             if(!storage_dir_read(directory, &file_info, name_temp, FILE_NAME_LEN_MAX)) { | ||||||
|  |                 break; | ||||||
|  |             } | ||||||
|  |             if((storage_file_get_error(directory) == FSE_OK) && (name_temp[0] != '\0')) { | ||||||
|  |                 total_files_cnt++; | ||||||
|  |                 string_set_str(name_str, name_temp); | ||||||
|  |                 if(browser_filter_by_name(browser, name_str, (file_info.flags & FSF_DIRECTORY))) { | ||||||
|  |                     if(!string_empty_p(filename)) { | ||||||
|  |                         if(string_cmp(name_str, filename) == 0) { | ||||||
|  |                             *file_idx = *item_cnt; | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |                     (*item_cnt)++; | ||||||
|  |                 } | ||||||
|  |                 if(total_files_cnt == LONG_LOAD_THRESHOLD) { | ||||||
|  |                     // There are too many files in folder and counting them will take some time - send callback to app
 | ||||||
|  |                     if(browser->long_load_cb) { | ||||||
|  |                         browser->long_load_cb(browser->cb_ctx); | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     string_clear(name_str); | ||||||
|  | 
 | ||||||
|  |     storage_dir_close(directory); | ||||||
|  |     storage_file_free(directory); | ||||||
|  | 
 | ||||||
|  |     furi_record_close("storage"); | ||||||
|  | 
 | ||||||
|  |     return state; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static bool | ||||||
|  |     browser_folder_load(BrowserWorker* browser, string_t path, uint32_t offset, uint32_t count) { | ||||||
|  |     FileInfo file_info; | ||||||
|  | 
 | ||||||
|  |     Storage* storage = furi_record_open("storage"); | ||||||
|  |     File* directory = storage_file_alloc(storage); | ||||||
|  | 
 | ||||||
|  |     char name_temp[FILE_NAME_LEN_MAX]; | ||||||
|  |     string_t name_str; | ||||||
|  |     string_init(name_str); | ||||||
|  | 
 | ||||||
|  |     uint32_t items_cnt = 0; | ||||||
|  | 
 | ||||||
|  |     do { | ||||||
|  |         if(!storage_dir_open(directory, string_get_cstr(path))) { | ||||||
|  |             break; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         items_cnt = 0; | ||||||
|  |         while(items_cnt < offset) { | ||||||
|  |             if(!storage_dir_read(directory, &file_info, name_temp, FILE_NAME_LEN_MAX)) { | ||||||
|  |                 break; | ||||||
|  |             } | ||||||
|  |             if(storage_file_get_error(directory) == FSE_OK) { | ||||||
|  |                 string_set_str(name_str, name_temp); | ||||||
|  |                 if(browser_filter_by_name(browser, name_str, (file_info.flags & FSF_DIRECTORY))) { | ||||||
|  |                     items_cnt++; | ||||||
|  |                 } | ||||||
|  |             } else { | ||||||
|  |                 break; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         if(items_cnt != offset) { | ||||||
|  |             break; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         if(browser->list_load_cb) { | ||||||
|  |             browser->list_load_cb(browser->cb_ctx, offset); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         items_cnt = 0; | ||||||
|  |         while(items_cnt < count) { | ||||||
|  |             if(!storage_dir_read(directory, &file_info, name_temp, FILE_NAME_LEN_MAX)) { | ||||||
|  |                 break; | ||||||
|  |             } | ||||||
|  |             if(storage_file_get_error(directory) == FSE_OK) { | ||||||
|  |                 string_set_str(name_str, name_temp); | ||||||
|  |                 if(browser_filter_by_name(browser, name_str, (file_info.flags & FSF_DIRECTORY))) { | ||||||
|  |                     string_printf(name_str, "%s/%s", string_get_cstr(path), name_temp); | ||||||
|  |                     if(browser->list_item_cb) { | ||||||
|  |                         browser->list_item_cb( | ||||||
|  |                             browser->cb_ctx, name_str, (file_info.flags & FSF_DIRECTORY), false); | ||||||
|  |                     } | ||||||
|  |                     items_cnt++; | ||||||
|  |                 } | ||||||
|  |             } else { | ||||||
|  |                 break; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         if(browser->list_item_cb) { | ||||||
|  |             browser->list_item_cb(browser->cb_ctx, NULL, false, true); | ||||||
|  |         } | ||||||
|  |     } while(0); | ||||||
|  | 
 | ||||||
|  |     string_clear(name_str); | ||||||
|  | 
 | ||||||
|  |     storage_dir_close(directory); | ||||||
|  |     storage_file_free(directory); | ||||||
|  | 
 | ||||||
|  |     furi_record_close("storage"); | ||||||
|  | 
 | ||||||
|  |     return (items_cnt == count); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static int32_t browser_worker(void* context) { | ||||||
|  |     BrowserWorker* browser = (BrowserWorker*)context; | ||||||
|  |     furi_assert(browser); | ||||||
|  |     FURI_LOG_D(TAG, "Start"); | ||||||
|  | 
 | ||||||
|  |     uint32_t items_cnt = 0; | ||||||
|  |     string_t path; | ||||||
|  |     string_init_set_str(path, BROWSER_ROOT); | ||||||
|  |     browser->item_sel_idx = -1; | ||||||
|  | 
 | ||||||
|  |     // If start path is a path to the file - try finding index of this file in a folder
 | ||||||
|  |     string_t filename; | ||||||
|  |     string_init(filename); | ||||||
|  |     if(browser_path_is_file(browser->path_next)) { | ||||||
|  |         path_extract_filename(browser->path_next, filename, false); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     osThreadFlagsSet(furi_thread_get_thread_id(browser->thread), WorkerEvtFolderEnter); | ||||||
|  | 
 | ||||||
|  |     while(1) { | ||||||
|  |         uint32_t flags = osThreadFlagsWait(WORKER_FLAGS_ALL, osFlagsWaitAny, osWaitForever); | ||||||
|  |         furi_assert((flags & osFlagsError) == 0); | ||||||
|  | 
 | ||||||
|  |         if(flags & WorkerEvtFolderEnter) { | ||||||
|  |             string_set(path, browser->path_next); | ||||||
|  |             bool is_root = browser_folder_check_and_switch(path); | ||||||
|  | 
 | ||||||
|  |             // Push previous selected item index to history array
 | ||||||
|  |             idx_last_array_push_back(browser->idx_last, browser->item_sel_idx); | ||||||
|  | 
 | ||||||
|  |             int32_t file_idx = 0; | ||||||
|  |             browser_folder_init(browser, path, filename, &items_cnt, &file_idx); | ||||||
|  |             FURI_LOG_D( | ||||||
|  |                 TAG, | ||||||
|  |                 "Enter folder: %s items: %u idx: %d", | ||||||
|  |                 string_get_cstr(path), | ||||||
|  |                 items_cnt, | ||||||
|  |                 file_idx); | ||||||
|  |             if(browser->folder_cb) { | ||||||
|  |                 browser->folder_cb(browser->cb_ctx, items_cnt, file_idx, is_root); | ||||||
|  |             } | ||||||
|  |             string_reset(filename); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         if(flags & WorkerEvtFolderExit) { | ||||||
|  |             browser_path_trim(path); | ||||||
|  |             bool is_root = browser_folder_check_and_switch(path); | ||||||
|  | 
 | ||||||
|  |             int32_t file_idx = 0; | ||||||
|  |             browser_folder_init(browser, path, filename, &items_cnt, &file_idx); | ||||||
|  |             if(idx_last_array_size(browser->idx_last) > 0) { | ||||||
|  |                 // Pop previous selected item index from history array
 | ||||||
|  |                 idx_last_array_pop_back(&file_idx, browser->idx_last); | ||||||
|  |             } | ||||||
|  |             FURI_LOG_D( | ||||||
|  |                 TAG, "Exit to: %s items: %u idx: %d", string_get_cstr(path), items_cnt, file_idx); | ||||||
|  |             if(browser->folder_cb) { | ||||||
|  |                 browser->folder_cb(browser->cb_ctx, items_cnt, file_idx, is_root); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         if(flags & WorkerEvtLoad) { | ||||||
|  |             FURI_LOG_D(TAG, "Load offset: %u cnt: %u", browser->load_offset, browser->load_count); | ||||||
|  |             browser_folder_load(browser, path, browser->load_offset, browser->load_count); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         if(flags & WorkerEvtStop) { | ||||||
|  |             break; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     string_clear(filename); | ||||||
|  |     string_clear(path); | ||||||
|  | 
 | ||||||
|  |     FURI_LOG_D(TAG, "End"); | ||||||
|  |     return 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | BrowserWorker* file_browser_worker_alloc(string_t path, const char* filter_ext, bool skip_assets) { | ||||||
|  |     BrowserWorker* browser = malloc(sizeof(BrowserWorker)); | ||||||
|  | 
 | ||||||
|  |     idx_last_array_init(browser->idx_last); | ||||||
|  | 
 | ||||||
|  |     string_init_set_str(browser->filter_extension, filter_ext); | ||||||
|  |     browser->skip_assets = skip_assets; | ||||||
|  |     string_init_set(browser->path_next, path); | ||||||
|  | 
 | ||||||
|  |     browser->thread = furi_thread_alloc(); | ||||||
|  |     furi_thread_set_name(browser->thread, "BrowserWorker"); | ||||||
|  |     furi_thread_set_stack_size(browser->thread, 2048); | ||||||
|  |     furi_thread_set_context(browser->thread, browser); | ||||||
|  |     furi_thread_set_callback(browser->thread, browser_worker); | ||||||
|  |     furi_thread_start(browser->thread); | ||||||
|  | 
 | ||||||
|  |     return browser; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void file_browser_worker_free(BrowserWorker* browser) { | ||||||
|  |     furi_assert(browser); | ||||||
|  | 
 | ||||||
|  |     osThreadFlagsSet(furi_thread_get_thread_id(browser->thread), WorkerEvtStop); | ||||||
|  |     furi_thread_join(browser->thread); | ||||||
|  |     furi_thread_free(browser->thread); | ||||||
|  | 
 | ||||||
|  |     string_clear(browser->filter_extension); | ||||||
|  |     string_clear(browser->path_next); | ||||||
|  | 
 | ||||||
|  |     idx_last_array_clear(browser->idx_last); | ||||||
|  | 
 | ||||||
|  |     free(browser); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void file_browser_worker_set_callback_context(BrowserWorker* browser, void* context) { | ||||||
|  |     furi_assert(browser); | ||||||
|  |     browser->cb_ctx = context; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void file_browser_worker_set_folder_callback( | ||||||
|  |     BrowserWorker* browser, | ||||||
|  |     BrowserWorkerFolderOpenCallback cb) { | ||||||
|  |     furi_assert(browser); | ||||||
|  |     browser->folder_cb = cb; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void file_browser_worker_set_list_callback( | ||||||
|  |     BrowserWorker* browser, | ||||||
|  |     BrowserWorkerListLoadCallback cb) { | ||||||
|  |     furi_assert(browser); | ||||||
|  |     browser->list_load_cb = cb; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void file_browser_worker_set_item_callback( | ||||||
|  |     BrowserWorker* browser, | ||||||
|  |     BrowserWorkerListItemCallback cb) { | ||||||
|  |     furi_assert(browser); | ||||||
|  |     browser->list_item_cb = cb; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void file_browser_worker_set_long_load_callback( | ||||||
|  |     BrowserWorker* browser, | ||||||
|  |     BrowserWorkerLongLoadCallback cb) { | ||||||
|  |     furi_assert(browser); | ||||||
|  |     browser->long_load_cb = cb; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void file_browser_worker_folder_enter(BrowserWorker* browser, string_t path, int32_t item_idx) { | ||||||
|  |     furi_assert(browser); | ||||||
|  |     string_set(browser->path_next, path); | ||||||
|  |     browser->item_sel_idx = item_idx; | ||||||
|  |     osThreadFlagsSet(furi_thread_get_thread_id(browser->thread), WorkerEvtFolderEnter); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void file_browser_worker_folder_exit(BrowserWorker* browser) { | ||||||
|  |     furi_assert(browser); | ||||||
|  |     osThreadFlagsSet(furi_thread_get_thread_id(browser->thread), WorkerEvtFolderExit); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void file_browser_worker_load(BrowserWorker* browser, uint32_t offset, uint32_t count) { | ||||||
|  |     furi_assert(browser); | ||||||
|  |     browser->load_offset = offset; | ||||||
|  |     browser->load_count = count; | ||||||
|  |     osThreadFlagsSet(furi_thread_get_thread_id(browser->thread), WorkerEvtLoad); | ||||||
|  | } | ||||||
							
								
								
									
										55
									
								
								applications/gui/modules/file_browser_worker.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										55
									
								
								applications/gui/modules/file_browser_worker.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,55 @@ | |||||||
|  | #pragma once | ||||||
|  | 
 | ||||||
|  | #include "m-string.h" | ||||||
|  | #include <gui/view.h> | ||||||
|  | #include <stdint.h> | ||||||
|  | 
 | ||||||
|  | #ifdef __cplusplus | ||||||
|  | extern "C" { | ||||||
|  | #endif | ||||||
|  | 
 | ||||||
|  | typedef struct BrowserWorker BrowserWorker; | ||||||
|  | typedef void (*BrowserWorkerFolderOpenCallback)( | ||||||
|  |     void* context, | ||||||
|  |     uint32_t item_cnt, | ||||||
|  |     int32_t file_idx, | ||||||
|  |     bool is_root); | ||||||
|  | typedef void (*BrowserWorkerListLoadCallback)(void* context, uint32_t list_load_offset); | ||||||
|  | typedef void (*BrowserWorkerListItemCallback)( | ||||||
|  |     void* context, | ||||||
|  |     string_t item_path, | ||||||
|  |     bool is_folder, | ||||||
|  |     bool is_last); | ||||||
|  | typedef void (*BrowserWorkerLongLoadCallback)(void* context); | ||||||
|  | 
 | ||||||
|  | BrowserWorker* file_browser_worker_alloc(string_t path, const char* filter_ext, bool skip_assets); | ||||||
|  | 
 | ||||||
|  | void file_browser_worker_free(BrowserWorker* browser); | ||||||
|  | 
 | ||||||
|  | void file_browser_worker_set_callback_context(BrowserWorker* browser, void* context); | ||||||
|  | 
 | ||||||
|  | void file_browser_worker_set_folder_callback( | ||||||
|  |     BrowserWorker* browser, | ||||||
|  |     BrowserWorkerFolderOpenCallback cb); | ||||||
|  | 
 | ||||||
|  | void file_browser_worker_set_list_callback( | ||||||
|  |     BrowserWorker* browser, | ||||||
|  |     BrowserWorkerListLoadCallback cb); | ||||||
|  | 
 | ||||||
|  | void file_browser_worker_set_item_callback( | ||||||
|  |     BrowserWorker* browser, | ||||||
|  |     BrowserWorkerListItemCallback cb); | ||||||
|  | 
 | ||||||
|  | void file_browser_worker_set_long_load_callback( | ||||||
|  |     BrowserWorker* browser, | ||||||
|  |     BrowserWorkerLongLoadCallback cb); | ||||||
|  | 
 | ||||||
|  | void file_browser_worker_folder_enter(BrowserWorker* browser, string_t path, int32_t item_idx); | ||||||
|  | 
 | ||||||
|  | void file_browser_worker_folder_exit(BrowserWorker* browser); | ||||||
|  | 
 | ||||||
|  | void file_browser_worker_load(BrowserWorker* browser, uint32_t offset, uint32_t count); | ||||||
|  | 
 | ||||||
|  | #ifdef __cplusplus | ||||||
|  | } | ||||||
|  | #endif | ||||||
| @ -1,475 +0,0 @@ | |||||||
| #include "file_select.h" |  | ||||||
| #include <gui/elements.h> |  | ||||||
| #include <m-string.h> |  | ||||||
| #include <storage/storage.h> |  | ||||||
| 
 |  | ||||||
| #define FILENAME_COUNT 4 |  | ||||||
| 
 |  | ||||||
| struct FileSelect { |  | ||||||
|     // public
 |  | ||||||
|     View* view; |  | ||||||
|     Storage* fs_api; |  | ||||||
|     const char* path; |  | ||||||
|     const char* extension; |  | ||||||
| 
 |  | ||||||
|     bool init_completed; |  | ||||||
| 
 |  | ||||||
|     FileSelectCallback callback; |  | ||||||
|     void* context; |  | ||||||
| 
 |  | ||||||
|     char* buffer; |  | ||||||
|     uint8_t buffer_size; |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| typedef struct { |  | ||||||
|     string_t filename[FILENAME_COUNT]; |  | ||||||
|     uint8_t position; |  | ||||||
| 
 |  | ||||||
|     uint16_t first_file_index; |  | ||||||
|     uint16_t file_count; |  | ||||||
| 
 |  | ||||||
| } FileSelectModel; |  | ||||||
| 
 |  | ||||||
| bool file_select_fill_strings(FileSelect* file_select); |  | ||||||
| bool file_select_fill_count(FileSelect* file_select); |  | ||||||
| static bool file_select_init_inner(FileSelect* file_select); |  | ||||||
| 
 |  | ||||||
| static void file_select_draw_callback(Canvas* canvas, void* _model) { |  | ||||||
|     FileSelectModel* model = _model; |  | ||||||
| 
 |  | ||||||
|     string_t string_buff; |  | ||||||
|     const uint8_t item_height = 16; |  | ||||||
|     const uint8_t item_width = 123; |  | ||||||
|     const uint8_t text_max_width = 115; |  | ||||||
| 
 |  | ||||||
|     canvas_clear(canvas); |  | ||||||
|     canvas_set_font(canvas, FontSecondary); |  | ||||||
| 
 |  | ||||||
|     if(model->file_count) { |  | ||||||
|         for(uint8_t i = 0; i < MIN(FILENAME_COUNT, model->file_count); i++) { |  | ||||||
|             if(i == model->position) { |  | ||||||
|                 canvas_set_color(canvas, ColorBlack); |  | ||||||
|                 canvas_draw_box(canvas, 0, (i * item_height) + 1, item_width, item_height - 2); |  | ||||||
| 
 |  | ||||||
|                 canvas_set_color(canvas, ColorWhite); |  | ||||||
|                 canvas_draw_dot(canvas, 0, (i * item_height) + 1); |  | ||||||
|                 canvas_draw_dot(canvas, 0, (i * item_height) + item_height - 2); |  | ||||||
|                 canvas_draw_dot(canvas, item_width - 1, (i * item_height) + 1); |  | ||||||
|                 canvas_draw_dot(canvas, item_width - 1, (i * item_height) + item_height - 2); |  | ||||||
|             } else { |  | ||||||
|                 canvas_set_color(canvas, ColorBlack); |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             string_init_set(string_buff, model->filename[i]); |  | ||||||
|             elements_string_fit_width(canvas, string_buff, text_max_width); |  | ||||||
|             canvas_draw_str( |  | ||||||
|                 canvas, 6, (i * item_height) + item_height - 4, string_get_cstr(string_buff)); |  | ||||||
| 
 |  | ||||||
|             string_clear(string_buff); |  | ||||||
|         } |  | ||||||
|     } else { |  | ||||||
|         canvas_draw_str(canvas, 6, item_height, "Empty folder"); |  | ||||||
|     } |  | ||||||
|     elements_scrollbar(canvas, model->first_file_index + model->position, model->file_count); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| static bool file_select_input_callback(InputEvent* event, void* context) { |  | ||||||
|     FileSelect* file_select = (FileSelect*)context; |  | ||||||
|     bool consumed = false; |  | ||||||
| 
 |  | ||||||
|     if((event->type == InputTypeShort) | (event->type == InputTypeRepeat)) { |  | ||||||
|         if(!file_select->init_completed) { |  | ||||||
|             if(!file_select_init_inner(file_select)) { |  | ||||||
|                 file_select->callback(false, file_select->context); |  | ||||||
|             } |  | ||||||
|         } else if(event->key == InputKeyUp) { |  | ||||||
|             with_view_model( |  | ||||||
|                 file_select->view, (FileSelectModel * model) { |  | ||||||
|                     if(model->position == 0) { |  | ||||||
|                         if(model->first_file_index == 0) { |  | ||||||
|                             // wrap
 |  | ||||||
|                             int16_t max_first_file_index = model->file_count - FILENAME_COUNT; |  | ||||||
|                             model->position = MIN(FILENAME_COUNT - 1, model->file_count - 1); |  | ||||||
|                             model->first_file_index = |  | ||||||
|                                 max_first_file_index < 0 ? 0 : max_first_file_index; |  | ||||||
|                         } else { |  | ||||||
|                             model->first_file_index--; |  | ||||||
|                         } |  | ||||||
|                     } else if(model->position == 1) { |  | ||||||
|                         if(model->first_file_index == 0) { |  | ||||||
|                             model->position--; |  | ||||||
|                         } else { |  | ||||||
|                             model->first_file_index--; |  | ||||||
|                         } |  | ||||||
|                     } else { |  | ||||||
|                         model->position--; |  | ||||||
|                     } |  | ||||||
|                     return true; |  | ||||||
|                 }); |  | ||||||
|             consumed = true; |  | ||||||
|             if(!file_select_fill_strings(file_select)) { |  | ||||||
|                 file_select->callback(false, file_select->context); |  | ||||||
|             } |  | ||||||
|         } else if(event->key == InputKeyDown) { |  | ||||||
|             with_view_model( |  | ||||||
|                 file_select->view, (FileSelectModel * model) { |  | ||||||
|                     uint16_t max_first_file_index = model->file_count > FILENAME_COUNT ? |  | ||||||
|                                                         model->file_count - FILENAME_COUNT : |  | ||||||
|                                                         0; |  | ||||||
| 
 |  | ||||||
|                     if(model->position >= MIN(FILENAME_COUNT - 1, model->file_count - 1)) { |  | ||||||
|                         if(model->first_file_index >= max_first_file_index) { |  | ||||||
|                             // wrap
 |  | ||||||
|                             model->position = 0; |  | ||||||
|                             model->first_file_index = 0; |  | ||||||
|                         } else { |  | ||||||
|                             model->first_file_index++; |  | ||||||
|                         } |  | ||||||
|                     } else if(model->position >= (FILENAME_COUNT - 2)) { |  | ||||||
|                         if(model->first_file_index >= max_first_file_index) { |  | ||||||
|                             model->position++; |  | ||||||
|                         } else { |  | ||||||
|                             model->first_file_index++; |  | ||||||
|                         } |  | ||||||
|                     } else { |  | ||||||
|                         model->position++; |  | ||||||
|                     } |  | ||||||
|                     return true; |  | ||||||
|                 }); |  | ||||||
|             consumed = true; |  | ||||||
|             if(!file_select_fill_strings(file_select)) { |  | ||||||
|                 file_select->callback(false, file_select->context); |  | ||||||
|             } |  | ||||||
|         } else if(event->key == InputKeyOk) { |  | ||||||
|             if(file_select->callback != NULL) { |  | ||||||
|                 size_t files = 0; |  | ||||||
|                 if(file_select->buffer) { |  | ||||||
|                     with_view_model( |  | ||||||
|                         file_select->view, (FileSelectModel * model) { |  | ||||||
|                             files = model->file_count; |  | ||||||
|                             strlcpy( |  | ||||||
|                                 file_select->buffer, |  | ||||||
|                                 string_get_cstr(model->filename[model->position]), |  | ||||||
|                                 file_select->buffer_size); |  | ||||||
| 
 |  | ||||||
|                             return false; |  | ||||||
|                         }); |  | ||||||
|                 }; |  | ||||||
|                 if(files > 0) { |  | ||||||
|                     file_select->callback(true, file_select->context); |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|             consumed = true; |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     return consumed; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| static bool file_select_init_inner(FileSelect* file_select) { |  | ||||||
|     bool result = false; |  | ||||||
|     if(file_select->path && file_select->extension && file_select->fs_api) { |  | ||||||
|         if(file_select_fill_count(file_select)) { |  | ||||||
|             if(file_select_fill_strings(file_select)) { |  | ||||||
|                 file_select->init_completed = true; |  | ||||||
|                 result = true; |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     return result; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| FileSelect* file_select_alloc() { |  | ||||||
|     FileSelect* file_select = malloc(sizeof(FileSelect)); |  | ||||||
|     file_select->view = view_alloc(); |  | ||||||
|     file_select->fs_api = furi_record_open("storage"); |  | ||||||
| 
 |  | ||||||
|     view_set_context(file_select->view, file_select); |  | ||||||
|     view_allocate_model(file_select->view, ViewModelTypeLockFree, sizeof(FileSelectModel)); |  | ||||||
|     view_set_draw_callback(file_select->view, file_select_draw_callback); |  | ||||||
|     view_set_input_callback(file_select->view, file_select_input_callback); |  | ||||||
| 
 |  | ||||||
|     with_view_model( |  | ||||||
|         file_select->view, (FileSelectModel * model) { |  | ||||||
|             for(uint8_t i = 0; i < FILENAME_COUNT; i++) { |  | ||||||
|                 string_init(model->filename[i]); |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             model->first_file_index = 0; |  | ||||||
|             model->file_count = 0; |  | ||||||
|             return false; |  | ||||||
|         }); |  | ||||||
| 
 |  | ||||||
|     return file_select; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void file_select_free(FileSelect* file_select) { |  | ||||||
|     furi_assert(file_select); |  | ||||||
|     with_view_model( |  | ||||||
|         file_select->view, (FileSelectModel * model) { |  | ||||||
|             for(uint8_t i = 0; i < FILENAME_COUNT; i++) { |  | ||||||
|                 string_clear(model->filename[i]); |  | ||||||
|             } |  | ||||||
|             return false; |  | ||||||
|         }); |  | ||||||
|     view_free(file_select->view); |  | ||||||
|     free(file_select); |  | ||||||
|     furi_record_close("storage"); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| View* file_select_get_view(FileSelect* file_select) { |  | ||||||
|     furi_assert(file_select); |  | ||||||
|     return file_select->view; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void file_select_set_callback(FileSelect* file_select, FileSelectCallback callback, void* context) { |  | ||||||
|     file_select->context = context; |  | ||||||
|     file_select->callback = callback; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void file_select_set_filter(FileSelect* file_select, const char* path, const char* extension) { |  | ||||||
|     furi_assert(file_select); |  | ||||||
|     file_select->path = path; |  | ||||||
|     file_select->extension = extension; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void file_select_set_result_buffer(FileSelect* file_select, char* buffer, uint8_t buffer_size) { |  | ||||||
|     file_select->buffer = buffer; |  | ||||||
|     file_select->buffer_size = buffer_size; |  | ||||||
| 
 |  | ||||||
|     if(file_select->buffer) { |  | ||||||
|         strlcpy(file_select->buffer, "", file_select->buffer_size); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| bool file_select_init(FileSelect* file_select) { |  | ||||||
|     if(!file_select_init_inner(file_select)) { |  | ||||||
|         file_select->callback(false, file_select->context); |  | ||||||
|         return false; |  | ||||||
|     } else { |  | ||||||
|         return true; |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| static bool filter_file(FileSelect* file_select, FileInfo* file_info, char* name) { |  | ||||||
|     bool result = false; |  | ||||||
| 
 |  | ||||||
|     if(!(file_info->flags & FSF_DIRECTORY)) { |  | ||||||
|         if(strcmp(file_select->extension, "*") == 0) { |  | ||||||
|             result = true; |  | ||||||
|         } else if(strstr(name, file_select->extension) != NULL) { |  | ||||||
|             result = true; |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     return result; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| bool file_select_fill_strings(FileSelect* file_select) { |  | ||||||
|     furi_assert(file_select); |  | ||||||
|     furi_assert(file_select->fs_api); |  | ||||||
|     furi_assert(file_select->path); |  | ||||||
|     furi_assert(file_select->extension); |  | ||||||
| 
 |  | ||||||
|     FileInfo file_info; |  | ||||||
|     File* directory = storage_file_alloc(file_select->fs_api); |  | ||||||
| 
 |  | ||||||
|     uint8_t string_counter = 0; |  | ||||||
|     uint16_t file_counter = 0; |  | ||||||
|     const uint8_t name_length = 100; |  | ||||||
|     char* name = malloc(name_length); |  | ||||||
|     uint16_t first_file_index = 0; |  | ||||||
| 
 |  | ||||||
|     with_view_model( |  | ||||||
|         file_select->view, (FileSelectModel * model) { |  | ||||||
|             first_file_index = model->first_file_index; |  | ||||||
|             return false; |  | ||||||
|         }); |  | ||||||
| 
 |  | ||||||
|     if(!storage_dir_open(directory, file_select->path)) { |  | ||||||
|         storage_dir_close(directory); |  | ||||||
|         storage_file_free(directory); |  | ||||||
|         free(name); |  | ||||||
|         return true; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     while(1) { |  | ||||||
|         if(!storage_dir_read(directory, &file_info, name, name_length)) { |  | ||||||
|             break; |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         if(storage_file_get_error(directory) == FSE_OK) { |  | ||||||
|             if(filter_file(file_select, &file_info, name)) { |  | ||||||
|                 if(file_counter >= first_file_index) { |  | ||||||
|                     with_view_model( |  | ||||||
|                         file_select->view, (FileSelectModel * model) { |  | ||||||
|                             string_set_str(model->filename[string_counter], name); |  | ||||||
| 
 |  | ||||||
|                             if(strcmp(file_select->extension, "*") != 0) { |  | ||||||
|                                 string_replace_all_str( |  | ||||||
|                                     model->filename[string_counter], file_select->extension, ""); |  | ||||||
|                             } |  | ||||||
| 
 |  | ||||||
|                             return true; |  | ||||||
|                         }); |  | ||||||
|                     string_counter++; |  | ||||||
| 
 |  | ||||||
|                     if(string_counter >= FILENAME_COUNT) { |  | ||||||
|                         break; |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
|                 file_counter++; |  | ||||||
|             } |  | ||||||
|         } else { |  | ||||||
|             storage_dir_close(directory); |  | ||||||
|             storage_file_free(directory); |  | ||||||
|             free(name); |  | ||||||
|             return false; |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     storage_dir_close(directory); |  | ||||||
|     storage_file_free(directory); |  | ||||||
|     free(name); |  | ||||||
|     return true; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| bool file_select_fill_count(FileSelect* file_select) { |  | ||||||
|     furi_assert(file_select); |  | ||||||
|     furi_assert(file_select->fs_api); |  | ||||||
|     furi_assert(file_select->path); |  | ||||||
|     furi_assert(file_select->extension); |  | ||||||
| 
 |  | ||||||
|     FileInfo file_info; |  | ||||||
|     File* directory = storage_file_alloc(file_select->fs_api); |  | ||||||
| 
 |  | ||||||
|     uint16_t file_counter = 0; |  | ||||||
|     const uint8_t name_length = 100; |  | ||||||
|     char* name = malloc(name_length); |  | ||||||
| 
 |  | ||||||
|     if(!storage_dir_open(directory, file_select->path)) { |  | ||||||
|         storage_dir_close(directory); |  | ||||||
|         storage_file_free(directory); |  | ||||||
|         free(name); |  | ||||||
|         return true; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     while(1) { |  | ||||||
|         if(!storage_dir_read(directory, &file_info, name, name_length)) { |  | ||||||
|             break; |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         if(storage_file_get_error(directory) == FSE_OK) { |  | ||||||
|             if(filter_file(file_select, &file_info, name)) { |  | ||||||
|                 file_counter++; |  | ||||||
|             } |  | ||||||
|         } else { |  | ||||||
|             storage_dir_close(directory); |  | ||||||
|             storage_file_free(directory); |  | ||||||
|             free(name); |  | ||||||
|             return false; |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     with_view_model( |  | ||||||
|         file_select->view, (FileSelectModel * model) { |  | ||||||
|             model->file_count = file_counter; |  | ||||||
|             return false; |  | ||||||
|         }); |  | ||||||
| 
 |  | ||||||
|     storage_dir_close(directory); |  | ||||||
|     storage_file_free(directory); |  | ||||||
|     free(name); |  | ||||||
|     return true; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void file_select_set_selected_file_internal(FileSelect* file_select, const char* filename) { |  | ||||||
|     furi_assert(file_select); |  | ||||||
|     furi_assert(filename); |  | ||||||
|     furi_assert(file_select->fs_api); |  | ||||||
|     furi_assert(file_select->path); |  | ||||||
|     furi_assert(file_select->extension); |  | ||||||
| 
 |  | ||||||
|     if(strlen(filename) == 0) return; |  | ||||||
| 
 |  | ||||||
|     FileInfo file_info; |  | ||||||
|     File* directory = storage_file_alloc(file_select->fs_api); |  | ||||||
| 
 |  | ||||||
|     const uint8_t name_length = 100; |  | ||||||
|     char* name = malloc(name_length); |  | ||||||
|     uint16_t file_position = 0; |  | ||||||
|     bool file_found = false; |  | ||||||
| 
 |  | ||||||
|     string_t filename_str; |  | ||||||
|     string_init_set_str(filename_str, filename); |  | ||||||
|     if(strcmp(file_select->extension, "*") != 0) { |  | ||||||
|         string_cat_str(filename_str, file_select->extension); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     if(!storage_dir_open(directory, file_select->path)) { |  | ||||||
|         string_clear(filename_str); |  | ||||||
|         storage_dir_close(directory); |  | ||||||
|         storage_file_free(directory); |  | ||||||
|         free(name); |  | ||||||
|         return; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     while(1) { |  | ||||||
|         if(!storage_dir_read(directory, &file_info, name, name_length)) { |  | ||||||
|             break; |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         if(storage_file_get_error(directory) == FSE_OK) { |  | ||||||
|             if(filter_file(file_select, &file_info, name)) { |  | ||||||
|                 if(strcmp(string_get_cstr(filename_str), name) == 0) { |  | ||||||
|                     file_found = true; |  | ||||||
|                     break; |  | ||||||
|                 } |  | ||||||
| 
 |  | ||||||
|                 file_position++; |  | ||||||
|             } |  | ||||||
|         } else { |  | ||||||
|             string_clear(filename_str); |  | ||||||
|             storage_dir_close(directory); |  | ||||||
|             storage_file_free(directory); |  | ||||||
|             free(name); |  | ||||||
|             return; |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     if(file_found) { |  | ||||||
|         with_view_model( |  | ||||||
|             file_select->view, (FileSelectModel * model) { |  | ||||||
|                 uint16_t max_first_file_index = |  | ||||||
|                     model->file_count > FILENAME_COUNT ? model->file_count - FILENAME_COUNT : 0; |  | ||||||
| 
 |  | ||||||
|                 model->first_file_index = file_position; |  | ||||||
| 
 |  | ||||||
|                 if(model->first_file_index > 0) { |  | ||||||
|                     model->first_file_index -= 1; |  | ||||||
|                 } |  | ||||||
| 
 |  | ||||||
|                 if(model->first_file_index >= max_first_file_index) { |  | ||||||
|                     model->first_file_index = max_first_file_index; |  | ||||||
|                 } |  | ||||||
| 
 |  | ||||||
|                 model->position = file_position - model->first_file_index; |  | ||||||
| 
 |  | ||||||
|                 return true; |  | ||||||
|             }); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     string_clear(filename_str); |  | ||||||
|     storage_dir_close(directory); |  | ||||||
|     storage_file_free(directory); |  | ||||||
|     free(name); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void file_select_set_selected_file(FileSelect* file_select, const char* filename) { |  | ||||||
|     file_select_set_selected_file_internal(file_select, filename); |  | ||||||
| 
 |  | ||||||
|     if(!file_select_fill_strings(file_select)) { |  | ||||||
|         file_select->callback(false, file_select->context); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @ -1,31 +0,0 @@ | |||||||
| /**
 |  | ||||||
|  * @file file_select.h |  | ||||||
|  * GUI: FileSelect view module API |  | ||||||
|  */ |  | ||||||
| 
 |  | ||||||
| #pragma once |  | ||||||
| 
 |  | ||||||
| #include <gui/view.h> |  | ||||||
| 
 |  | ||||||
| #ifdef __cplusplus |  | ||||||
| extern "C" { |  | ||||||
| #endif |  | ||||||
| 
 |  | ||||||
| typedef struct FileSelect FileSelect; |  | ||||||
| 
 |  | ||||||
| typedef void (*FileSelectCallback)(bool result, void* context); |  | ||||||
| 
 |  | ||||||
| FileSelect* file_select_alloc(); |  | ||||||
| 
 |  | ||||||
| void file_select_free(FileSelect* file_select); |  | ||||||
| View* file_select_get_view(FileSelect* file_select); |  | ||||||
| 
 |  | ||||||
| void file_select_set_callback(FileSelect* file_select, FileSelectCallback callback, void* context); |  | ||||||
| void file_select_set_filter(FileSelect* file_select, const char* path, const char* extension); |  | ||||||
| void file_select_set_result_buffer(FileSelect* file_select, char* buffer, uint8_t buffer_size); |  | ||||||
| bool file_select_init(FileSelect* file_select); |  | ||||||
| void file_select_set_selected_file(FileSelect* file_select, const char* filename); |  | ||||||
| 
 |  | ||||||
| #ifdef __cplusplus |  | ||||||
| } |  | ||||||
| #endif |  | ||||||
| @ -3,7 +3,7 @@ | |||||||
| #include "applications/storage/storage.h" | #include "applications/storage/storage.h" | ||||||
| 
 | 
 | ||||||
| struct ValidatorIsFile { | struct ValidatorIsFile { | ||||||
|     const char* app_path_folder; |     char* app_path_folder; | ||||||
|     const char* app_extension; |     const char* app_extension; | ||||||
|     char* current_name; |     char* current_name; | ||||||
| }; | }; | ||||||
| @ -40,7 +40,7 @@ ValidatorIsFile* validator_is_file_alloc_init( | |||||||
|     const char* current_name) { |     const char* current_name) { | ||||||
|     ValidatorIsFile* instance = malloc(sizeof(ValidatorIsFile)); |     ValidatorIsFile* instance = malloc(sizeof(ValidatorIsFile)); | ||||||
| 
 | 
 | ||||||
|     instance->app_path_folder = app_path_folder; |     instance->app_path_folder = strdup(app_path_folder); | ||||||
|     instance->app_extension = app_extension; |     instance->app_extension = app_extension; | ||||||
|     instance->current_name = strdup(current_name); |     instance->current_name = strdup(current_name); | ||||||
| 
 | 
 | ||||||
| @ -49,6 +49,7 @@ ValidatorIsFile* validator_is_file_alloc_init( | |||||||
| 
 | 
 | ||||||
| void validator_is_file_free(ValidatorIsFile* instance) { | void validator_is_file_free(ValidatorIsFile* instance) { | ||||||
|     furi_assert(instance); |     furi_assert(instance); | ||||||
|  |     free(instance->app_path_folder); | ||||||
|     free(instance->current_name); |     free(instance->current_name); | ||||||
|     free(instance); |     free(instance); | ||||||
| } | } | ||||||
|  | |||||||
| @ -1,7 +1,8 @@ | |||||||
| #include "ibutton.h" | #include "ibutton.h" | ||||||
|  | #include "assets_icons.h" | ||||||
| #include "ibutton_i.h" | #include "ibutton_i.h" | ||||||
| #include "ibutton/scenes/ibutton_scene.h" | #include "ibutton/scenes/ibutton_scene.h" | ||||||
| 
 | #include "m-string.h" | ||||||
| #include <toolbox/path.h> | #include <toolbox/path.h> | ||||||
| #include <flipper_format/flipper_format.h> | #include <flipper_format/flipper_format.h> | ||||||
| 
 | 
 | ||||||
| @ -85,6 +86,8 @@ void ibutton_tick_event_callback(void* context) { | |||||||
| iButton* ibutton_alloc() { | iButton* ibutton_alloc() { | ||||||
|     iButton* ibutton = malloc(sizeof(iButton)); |     iButton* ibutton = malloc(sizeof(iButton)); | ||||||
| 
 | 
 | ||||||
|  |     string_init(ibutton->file_path); | ||||||
|  | 
 | ||||||
|     ibutton->scene_manager = scene_manager_alloc(&ibutton_scene_handlers, ibutton); |     ibutton->scene_manager = scene_manager_alloc(&ibutton_scene_handlers, ibutton); | ||||||
| 
 | 
 | ||||||
|     ibutton->view_dispatcher = view_dispatcher_alloc(); |     ibutton->view_dispatcher = view_dispatcher_alloc(); | ||||||
| @ -176,46 +179,25 @@ void ibutton_free(iButton* ibutton) { | |||||||
|     ibutton_worker_free(ibutton->key_worker); |     ibutton_worker_free(ibutton->key_worker); | ||||||
|     ibutton_key_free(ibutton->key); |     ibutton_key_free(ibutton->key); | ||||||
| 
 | 
 | ||||||
|  |     string_clear(ibutton->file_path); | ||||||
|  | 
 | ||||||
|     free(ibutton); |     free(ibutton); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| bool ibutton_file_select(iButton* ibutton) { | bool ibutton_file_select(iButton* ibutton) { | ||||||
|     bool success = dialog_file_select_show( |     bool success = dialog_file_browser_show( | ||||||
|         ibutton->dialogs, |         ibutton->dialogs, | ||||||
|         IBUTTON_APP_FOLDER, |         ibutton->file_path, | ||||||
|  |         ibutton->file_path, | ||||||
|         IBUTTON_APP_EXTENSION, |         IBUTTON_APP_EXTENSION, | ||||||
|         ibutton->file_name, |         true, | ||||||
|         IBUTTON_FILE_NAME_SIZE, |         &I_ibutt_10px, | ||||||
|         ibutton_key_get_name_p(ibutton->key)); |         true); | ||||||
| 
 | 
 | ||||||
|     if(success) { |     if(success) { | ||||||
|         string_t key_str; |         success = ibutton_load_key_data(ibutton, ibutton->file_path); | ||||||
|         string_init_printf( |  | ||||||
|             key_str, "%s/%s%s", IBUTTON_APP_FOLDER, ibutton->file_name, IBUTTON_APP_EXTENSION); |  | ||||||
|         success = ibutton_load_key_data(ibutton, key_str); |  | ||||||
| 
 |  | ||||||
|         if(success) { |  | ||||||
|             ibutton_key_set_name(ibutton->key, ibutton->file_name); |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|         string_clear(key_str); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     return success; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| bool ibutton_load_key(iButton* ibutton, const char* key_name) { |  | ||||||
|     string_t key_path; |  | ||||||
|     string_init_set_str(key_path, key_name); |  | ||||||
| 
 |  | ||||||
|     const bool success = ibutton_load_key_data(ibutton, key_path); |  | ||||||
| 
 |  | ||||||
|     if(success) { |  | ||||||
|         path_extract_filename_no_ext(key_name, key_path); |  | ||||||
|         ibutton_key_set_name(ibutton->key, string_get_cstr(key_path)); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     string_clear(key_path); |  | ||||||
|     return success; |     return success; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @ -226,27 +208,22 @@ bool ibutton_save_key(iButton* ibutton, const char* key_name) { | |||||||
|     FlipperFormat* file = flipper_format_file_alloc(ibutton->storage); |     FlipperFormat* file = flipper_format_file_alloc(ibutton->storage); | ||||||
|     iButtonKey* key = ibutton->key; |     iButtonKey* key = ibutton->key; | ||||||
| 
 | 
 | ||||||
|     string_t key_file_name; |  | ||||||
|     bool result = false; |     bool result = false; | ||||||
|     string_init(key_file_name); |  | ||||||
| 
 | 
 | ||||||
|     do { |     do { | ||||||
|         // First remove key if it was saved (we rename the key)
 |         // First remove key if it was saved (we rename the key)
 | ||||||
|         if(!ibutton_delete_key(ibutton)) break; |         ibutton_delete_key(ibutton); | ||||||
| 
 |  | ||||||
|         // Save the key
 |  | ||||||
|         ibutton_key_set_name(key, key_name); |  | ||||||
| 
 | 
 | ||||||
|         // Set full file name, for new key
 |         // Set full file name, for new key
 | ||||||
|         string_printf( |         if(string_end_with_str_p(ibutton->file_path, IBUTTON_APP_EXTENSION)) { | ||||||
|             key_file_name, |             size_t filename_start = string_search_rchar(ibutton->file_path, '/'); | ||||||
|             "%s/%s%s", |             string_left(ibutton->file_path, filename_start); | ||||||
|             IBUTTON_APP_FOLDER, |         } | ||||||
|             ibutton_key_get_name_p(key), | 
 | ||||||
|             IBUTTON_APP_EXTENSION); |         string_cat_printf(ibutton->file_path, "/%s%s", key_name, IBUTTON_APP_EXTENSION); | ||||||
| 
 | 
 | ||||||
|         // Open file for write
 |         // Open file for write
 | ||||||
|         if(!flipper_format_file_open_always(file, string_get_cstr(key_file_name))) break; |         if(!flipper_format_file_open_always(file, string_get_cstr(ibutton->file_path))) break; | ||||||
| 
 | 
 | ||||||
|         // Write header
 |         // Write header
 | ||||||
|         if(!flipper_format_write_header_cstr(file, IBUTTON_APP_FILE_TYPE, 1)) break; |         if(!flipper_format_write_header_cstr(file, IBUTTON_APP_FILE_TYPE, 1)) break; | ||||||
| @ -271,8 +248,6 @@ bool ibutton_save_key(iButton* ibutton, const char* key_name) { | |||||||
| 
 | 
 | ||||||
|     flipper_format_free(file); |     flipper_format_free(file); | ||||||
| 
 | 
 | ||||||
|     string_clear(key_file_name); |  | ||||||
| 
 |  | ||||||
|     if(!result) { |     if(!result) { | ||||||
|         dialog_message_show_storage_error(ibutton->dialogs, "Cannot save\nkey file"); |         dialog_message_show_storage_error(ibutton->dialogs, "Cannot save\nkey file"); | ||||||
|     } |     } | ||||||
| @ -281,17 +256,8 @@ bool ibutton_save_key(iButton* ibutton, const char* key_name) { | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| bool ibutton_delete_key(iButton* ibutton) { | bool ibutton_delete_key(iButton* ibutton) { | ||||||
|     string_t file_name; |  | ||||||
|     bool result = false; |     bool result = false; | ||||||
| 
 |     result = storage_simply_remove(ibutton->storage, string_get_cstr(ibutton->file_path)); | ||||||
|     string_init_printf( |  | ||||||
|         file_name, |  | ||||||
|         "%s/%s%s", |  | ||||||
|         IBUTTON_APP_FOLDER, |  | ||||||
|         ibutton_key_get_name_p(ibutton->key), |  | ||||||
|         IBUTTON_APP_EXTENSION); |  | ||||||
|     result = storage_simply_remove(ibutton->storage, string_get_cstr(file_name)); |  | ||||||
|     string_clear(file_name); |  | ||||||
| 
 | 
 | ||||||
|     return result; |     return result; | ||||||
| } | } | ||||||
| @ -335,8 +301,17 @@ int32_t ibutton_app(void* p) { | |||||||
| 
 | 
 | ||||||
|     ibutton_make_app_folder(ibutton); |     ibutton_make_app_folder(ibutton); | ||||||
| 
 | 
 | ||||||
|     if(p && ibutton_load_key(ibutton, (const char*)p)) { |     bool key_loaded = false; | ||||||
|  | 
 | ||||||
|  |     if(p) { | ||||||
|  |         string_set_str(ibutton->file_path, (const char*)p); | ||||||
|  |         if(ibutton_load_key_data(ibutton, ibutton->file_path)) { | ||||||
|  |             key_loaded = true; | ||||||
|             // TODO: Display an error if the key from p could not be loaded
 |             // TODO: Display an error if the key from p could not be loaded
 | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if(key_loaded) { | ||||||
|         scene_manager_next_scene(ibutton->scene_manager, iButtonSceneEmulate); |         scene_manager_next_scene(ibutton->scene_manager, iButtonSceneEmulate); | ||||||
|     } else { |     } else { | ||||||
|         scene_manager_next_scene(ibutton->scene_manager, iButtonSceneStart); |         scene_manager_next_scene(ibutton->scene_manager, iButtonSceneStart); | ||||||
|  | |||||||
| @ -41,7 +41,7 @@ struct iButton { | |||||||
|     iButtonWorker* key_worker; |     iButtonWorker* key_worker; | ||||||
|     iButtonKey* key; |     iButtonKey* key; | ||||||
| 
 | 
 | ||||||
|     char file_name[IBUTTON_FILE_NAME_SIZE]; |     string_t file_path; | ||||||
|     char text_store[IBUTTON_TEXT_STORE_SIZE + 1]; |     char text_store[IBUTTON_TEXT_STORE_SIZE + 1]; | ||||||
| 
 | 
 | ||||||
|     Submenu* submenu; |     Submenu* submenu; | ||||||
| @ -74,7 +74,6 @@ typedef enum { | |||||||
| } iButtonNotificationMessage; | } iButtonNotificationMessage; | ||||||
| 
 | 
 | ||||||
| bool ibutton_file_select(iButton* ibutton); | bool ibutton_file_select(iButton* ibutton); | ||||||
| bool ibutton_load_key(iButton* ibutton, const char* key_name); |  | ||||||
| bool ibutton_save_key(iButton* ibutton, const char* key_name); | bool ibutton_save_key(iButton* ibutton, const char* key_name); | ||||||
| bool ibutton_delete_key(iButton* ibutton); | bool ibutton_delete_key(iButton* ibutton); | ||||||
| void ibutton_text_store_set(iButton* ibutton, const char* text, ...); | void ibutton_text_store_set(iButton* ibutton, const char* text, ...); | ||||||
|  | |||||||
| @ -1,4 +1,5 @@ | |||||||
| #include "../ibutton_i.h" | #include "../ibutton_i.h" | ||||||
|  | #include "m-string.h" | ||||||
| 
 | 
 | ||||||
| enum SubmenuIndex { | enum SubmenuIndex { | ||||||
|     SubmenuIndexCyfral, |     SubmenuIndexCyfral, | ||||||
| @ -44,7 +45,7 @@ bool ibutton_scene_add_type_on_event(void* context, SceneManagerEvent event) { | |||||||
|             furi_crash("Unknown key type"); |             furi_crash("Unknown key type"); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         ibutton_key_set_name(key, ""); |         string_set_str(ibutton->file_path, IBUTTON_APP_FOLDER); | ||||||
|         ibutton_key_clear_data(key); |         ibutton_key_clear_data(key); | ||||||
|         scene_manager_next_scene(ibutton->scene_manager, iButtonSceneAddValue); |         scene_manager_next_scene(ibutton->scene_manager, iButtonSceneAddValue); | ||||||
|     } |     } | ||||||
|  | |||||||
| @ -1,4 +1,5 @@ | |||||||
| #include "../ibutton_i.h" | #include "../ibutton_i.h" | ||||||
|  | #include <toolbox/path.h> | ||||||
| 
 | 
 | ||||||
| static void ibutton_scene_delete_confirm_widget_callback( | static void ibutton_scene_delete_confirm_widget_callback( | ||||||
|     GuiButtonType result, |     GuiButtonType result, | ||||||
| @ -16,7 +17,11 @@ void ibutton_scene_delete_confirm_on_enter(void* context) { | |||||||
|     iButtonKey* key = ibutton->key; |     iButtonKey* key = ibutton->key; | ||||||
|     const uint8_t* key_data = ibutton_key_get_data_p(key); |     const uint8_t* key_data = ibutton_key_get_data_p(key); | ||||||
| 
 | 
 | ||||||
|     ibutton_text_store_set(ibutton, "\e#Delete %s?\e#", ibutton_key_get_name_p(key)); |     string_t key_name; | ||||||
|  |     string_init(key_name); | ||||||
|  |     path_extract_filename(ibutton->file_path, key_name, true); | ||||||
|  | 
 | ||||||
|  |     ibutton_text_store_set(ibutton, "\e#Delete %s?\e#", string_get_cstr(key_name)); | ||||||
|     widget_add_text_box_element( |     widget_add_text_box_element( | ||||||
|         widget, 0, 0, 128, 27, AlignCenter, AlignCenter, ibutton->text_store, false); |         widget, 0, 0, 128, 27, AlignCenter, AlignCenter, ibutton->text_store, false); | ||||||
|     widget_add_button_element( |     widget_add_button_element( | ||||||
| @ -62,6 +67,8 @@ void ibutton_scene_delete_confirm_on_enter(void* context) { | |||||||
|         widget, 64, 33, AlignCenter, AlignBottom, FontSecondary, ibutton->text_store); |         widget, 64, 33, AlignCenter, AlignBottom, FontSecondary, ibutton->text_store); | ||||||
| 
 | 
 | ||||||
|     view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewWidget); |     view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewWidget); | ||||||
|  | 
 | ||||||
|  |     string_clear(key_name); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| bool ibutton_scene_delete_confirm_on_event(void* context, SceneManagerEvent event) { | bool ibutton_scene_delete_confirm_on_event(void* context, SceneManagerEvent event) { | ||||||
|  | |||||||
| @ -1,5 +1,6 @@ | |||||||
| #include "../ibutton_i.h" | #include "../ibutton_i.h" | ||||||
| #include <dolphin/dolphin.h> | #include <dolphin/dolphin.h> | ||||||
|  | #include <toolbox/path.h> | ||||||
| 
 | 
 | ||||||
| static void ibutton_scene_emulate_callback(void* context, bool emulated) { | static void ibutton_scene_emulate_callback(void* context, bool emulated) { | ||||||
|     iButton* ibutton = context; |     iButton* ibutton = context; | ||||||
| @ -15,14 +16,19 @@ void ibutton_scene_emulate_on_enter(void* context) { | |||||||
|     iButtonKey* key = ibutton->key; |     iButtonKey* key = ibutton->key; | ||||||
| 
 | 
 | ||||||
|     const uint8_t* key_data = ibutton_key_get_data_p(key); |     const uint8_t* key_data = ibutton_key_get_data_p(key); | ||||||
|     const char* key_name = ibutton_key_get_name_p(key); | 
 | ||||||
|  |     string_t key_name; | ||||||
|  |     string_init(key_name); | ||||||
|  |     if(string_end_with_str_p(ibutton->file_path, IBUTTON_APP_EXTENSION)) { | ||||||
|  |         path_extract_filename(ibutton->file_path, key_name, true); | ||||||
|  |     } | ||||||
| 
 | 
 | ||||||
|     uint8_t line_count = 2; |     uint8_t line_count = 2; | ||||||
|     DOLPHIN_DEED(DolphinDeedIbuttonEmulate); |     DOLPHIN_DEED(DolphinDeedIbuttonEmulate); | ||||||
| 
 | 
 | ||||||
|     // check that stored key has name
 |     // check that stored key has name
 | ||||||
|     if(strcmp(key_name, "") != 0) { |     if(!string_empty_p(key_name)) { | ||||||
|         ibutton_text_store_set(ibutton, "emulating\n%s", key_name); |         ibutton_text_store_set(ibutton, "emulating\n%s", string_get_cstr(key_name)); | ||||||
|         line_count = 2; |         line_count = 2; | ||||||
|     } else { |     } else { | ||||||
|         // if not, show key data
 |         // if not, show key data
 | ||||||
| @ -77,6 +83,8 @@ void ibutton_scene_emulate_on_enter(void* context) { | |||||||
|     ibutton_worker_emulate_set_callback( |     ibutton_worker_emulate_set_callback( | ||||||
|         ibutton->key_worker, ibutton_scene_emulate_callback, ibutton); |         ibutton->key_worker, ibutton_scene_emulate_callback, ibutton); | ||||||
|     ibutton_worker_emulate_start(ibutton->key_worker, key); |     ibutton_worker_emulate_start(ibutton->key_worker, key); | ||||||
|  | 
 | ||||||
|  |     string_clear(key_name); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| bool ibutton_scene_emulate_on_event(void* context, SceneManagerEvent event) { | bool ibutton_scene_emulate_on_event(void* context, SceneManagerEvent event) { | ||||||
|  | |||||||
| @ -1,4 +1,5 @@ | |||||||
| #include "../ibutton_i.h" | #include "../ibutton_i.h" | ||||||
|  | #include <toolbox/path.h> | ||||||
| 
 | 
 | ||||||
| void ibutton_scene_info_on_enter(void* context) { | void ibutton_scene_info_on_enter(void* context) { | ||||||
|     iButton* ibutton = context; |     iButton* ibutton = context; | ||||||
| @ -7,7 +8,11 @@ void ibutton_scene_info_on_enter(void* context) { | |||||||
| 
 | 
 | ||||||
|     const uint8_t* key_data = ibutton_key_get_data_p(key); |     const uint8_t* key_data = ibutton_key_get_data_p(key); | ||||||
| 
 | 
 | ||||||
|     ibutton_text_store_set(ibutton, "%s", ibutton_key_get_name_p(key)); |     string_t key_name; | ||||||
|  |     string_init(key_name); | ||||||
|  |     path_extract_filename(ibutton->file_path, key_name, true); | ||||||
|  | 
 | ||||||
|  |     ibutton_text_store_set(ibutton, "%s", string_get_cstr(key_name)); | ||||||
|     widget_add_text_box_element( |     widget_add_text_box_element( | ||||||
|         widget, 0, 0, 128, 28, AlignCenter, AlignCenter, ibutton->text_store, false); |         widget, 0, 0, 128, 28, AlignCenter, AlignCenter, ibutton->text_store, false); | ||||||
| 
 | 
 | ||||||
| @ -46,6 +51,8 @@ void ibutton_scene_info_on_enter(void* context) { | |||||||
|         widget, 64, 35, AlignCenter, AlignBottom, FontPrimary, ibutton->text_store); |         widget, 64, 35, AlignCenter, AlignBottom, FontPrimary, ibutton->text_store); | ||||||
| 
 | 
 | ||||||
|     view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewWidget); |     view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewWidget); | ||||||
|  | 
 | ||||||
|  |     string_clear(key_name); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| bool ibutton_scene_info_on_event(void* context, SceneManagerEvent event) { | bool ibutton_scene_info_on_event(void* context, SceneManagerEvent event) { | ||||||
|  | |||||||
| @ -18,7 +18,7 @@ void ibutton_scene_read_on_enter(void* context) { | |||||||
|     popup_set_icon(popup, 0, 5, &I_DolphinWait_61x59); |     popup_set_icon(popup, 0, 5, &I_DolphinWait_61x59); | ||||||
| 
 | 
 | ||||||
|     view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewPopup); |     view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewPopup); | ||||||
|     ibutton_key_set_name(key, ""); |     string_set_str(ibutton->file_path, IBUTTON_APP_FOLDER); | ||||||
| 
 | 
 | ||||||
|     ibutton_worker_read_set_callback(worker, ibutton_scene_read_callback, ibutton); |     ibutton_worker_read_set_callback(worker, ibutton_scene_read_callback, ibutton); | ||||||
|     ibutton_worker_read_start(worker, key); |     ibutton_worker_read_start(worker, key); | ||||||
|  | |||||||
| @ -1,5 +1,7 @@ | |||||||
| #include "../ibutton_i.h" | #include "../ibutton_i.h" | ||||||
|  | #include "m-string.h" | ||||||
| #include <lib/toolbox/random_name.h> | #include <lib/toolbox/random_name.h> | ||||||
|  | #include <toolbox/path.h> | ||||||
| 
 | 
 | ||||||
| static void ibutton_scene_save_name_text_input_callback(void* context) { | static void ibutton_scene_save_name_text_input_callback(void* context) { | ||||||
|     iButton* ibutton = context; |     iButton* ibutton = context; | ||||||
| @ -10,13 +12,17 @@ void ibutton_scene_save_name_on_enter(void* context) { | |||||||
|     iButton* ibutton = context; |     iButton* ibutton = context; | ||||||
|     TextInput* text_input = ibutton->text_input; |     TextInput* text_input = ibutton->text_input; | ||||||
| 
 | 
 | ||||||
|     const char* key_name = ibutton_key_get_name_p(ibutton->key); |     string_t key_name; | ||||||
|     const bool key_name_is_empty = !strcmp(key_name, ""); |     string_init(key_name); | ||||||
|  |     if(string_end_with_str_p(ibutton->file_path, IBUTTON_APP_EXTENSION)) { | ||||||
|  |         path_extract_filename(ibutton->file_path, key_name, true); | ||||||
|  |     } | ||||||
| 
 | 
 | ||||||
|  |     const bool key_name_is_empty = string_empty_p(key_name); | ||||||
|     if(key_name_is_empty) { |     if(key_name_is_empty) { | ||||||
|         set_random_name(ibutton->text_store, IBUTTON_TEXT_STORE_SIZE); |         set_random_name(ibutton->text_store, IBUTTON_TEXT_STORE_SIZE); | ||||||
|     } else { |     } else { | ||||||
|         ibutton_text_store_set(ibutton, "%s", key_name); |         ibutton_text_store_set(ibutton, "%s", string_get_cstr(key_name)); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     text_input_set_header_text(text_input, "Name the key"); |     text_input_set_header_text(text_input, "Name the key"); | ||||||
| @ -28,11 +34,19 @@ void ibutton_scene_save_name_on_enter(void* context) { | |||||||
|         IBUTTON_KEY_NAME_SIZE, |         IBUTTON_KEY_NAME_SIZE, | ||||||
|         key_name_is_empty); |         key_name_is_empty); | ||||||
| 
 | 
 | ||||||
|     ValidatorIsFile* validator_is_file = |     string_t folder_path; | ||||||
|         validator_is_file_alloc_init(IBUTTON_APP_FOLDER, IBUTTON_APP_EXTENSION, key_name); |     string_init(folder_path); | ||||||
|  | 
 | ||||||
|  |     path_extract_dirname(string_get_cstr(ibutton->file_path), folder_path); | ||||||
|  | 
 | ||||||
|  |     ValidatorIsFile* validator_is_file = validator_is_file_alloc_init( | ||||||
|  |         string_get_cstr(folder_path), IBUTTON_APP_EXTENSION, string_get_cstr(key_name)); | ||||||
|     text_input_set_validator(text_input, validator_is_file_callback, validator_is_file); |     text_input_set_validator(text_input, validator_is_file_callback, validator_is_file); | ||||||
| 
 | 
 | ||||||
|     view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewTextInput); |     view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewTextInput); | ||||||
|  | 
 | ||||||
|  |     string_clear(key_name); | ||||||
|  |     string_clear(folder_path); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| bool ibutton_scene_save_name_on_event(void* context, SceneManagerEvent event) { | bool ibutton_scene_save_name_on_event(void* context, SceneManagerEvent event) { | ||||||
|  | |||||||
| @ -36,6 +36,7 @@ bool ibutton_scene_start_on_event(void* context, SceneManagerEvent event) { | |||||||
|         if(event.event == SubmenuIndexRead) { |         if(event.event == SubmenuIndexRead) { | ||||||
|             scene_manager_next_scene(ibutton->scene_manager, iButtonSceneRead); |             scene_manager_next_scene(ibutton->scene_manager, iButtonSceneRead); | ||||||
|         } else if(event.event == SubmenuIndexSaved) { |         } else if(event.event == SubmenuIndexSaved) { | ||||||
|  |             string_set_str(ibutton->file_path, IBUTTON_APP_FOLDER); | ||||||
|             scene_manager_next_scene(ibutton->scene_manager, iButtonSceneSelectKey); |             scene_manager_next_scene(ibutton->scene_manager, iButtonSceneSelectKey); | ||||||
|         } else if(event.event == SubmenuIndexAdd) { |         } else if(event.event == SubmenuIndexAdd) { | ||||||
|             scene_manager_next_scene(ibutton->scene_manager, iButtonSceneAddType); |             scene_manager_next_scene(ibutton->scene_manager, iButtonSceneAddType); | ||||||
|  | |||||||
| @ -1,4 +1,6 @@ | |||||||
| #include "../ibutton_i.h" | #include "../ibutton_i.h" | ||||||
|  | #include "m-string.h" | ||||||
|  | #include "toolbox/path.h" | ||||||
| 
 | 
 | ||||||
| typedef enum { | typedef enum { | ||||||
|     iButtonSceneWriteStateDefault, |     iButtonSceneWriteStateDefault, | ||||||
| @ -17,13 +19,18 @@ void ibutton_scene_write_on_enter(void* context) { | |||||||
|     iButtonWorker* worker = ibutton->key_worker; |     iButtonWorker* worker = ibutton->key_worker; | ||||||
| 
 | 
 | ||||||
|     const uint8_t* key_data = ibutton_key_get_data_p(key); |     const uint8_t* key_data = ibutton_key_get_data_p(key); | ||||||
|     const char* key_name = ibutton_key_get_name_p(key); | 
 | ||||||
|  |     string_t key_name; | ||||||
|  |     string_init(key_name); | ||||||
|  |     if(string_end_with_str_p(ibutton->file_path, IBUTTON_APP_EXTENSION)) { | ||||||
|  |         path_extract_filename(ibutton->file_path, key_name, true); | ||||||
|  |     } | ||||||
| 
 | 
 | ||||||
|     uint8_t line_count = 2; |     uint8_t line_count = 2; | ||||||
| 
 | 
 | ||||||
|     // check that stored key has name
 |     // check that stored key has name
 | ||||||
|     if(strcmp(key_name, "") != 0) { |     if(!string_empty_p(key_name)) { | ||||||
|         ibutton_text_store_set(ibutton, "writing\n%s", key_name); |         ibutton_text_store_set(ibutton, "writing\n%s", string_get_cstr(key_name)); | ||||||
|         line_count = 2; |         line_count = 2; | ||||||
|     } else { |     } else { | ||||||
|         // if not, show key data
 |         // if not, show key data
 | ||||||
| @ -79,6 +86,8 @@ void ibutton_scene_write_on_enter(void* context) { | |||||||
| 
 | 
 | ||||||
|     ibutton_worker_write_set_callback(worker, ibutton_scene_write_callback, ibutton); |     ibutton_worker_write_set_callback(worker, ibutton_scene_write_callback, ibutton); | ||||||
|     ibutton_worker_write_start(worker, key); |     ibutton_worker_write_start(worker, key); | ||||||
|  | 
 | ||||||
|  |     string_clear(key_name); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| bool ibutton_scene_write_on_event(void* context, SceneManagerEvent event) { | bool ibutton_scene_write_on_event(void* context, SceneManagerEvent event) { | ||||||
|  | |||||||
| @ -1,4 +1,5 @@ | |||||||
| #include "infrared_app.h" | #include "infrared_app.h" | ||||||
|  | #include "m-string.h" | ||||||
| #include <infrared_worker.h> | #include <infrared_worker.h> | ||||||
| #include <furi.h> | #include <furi.h> | ||||||
| #include <gui/gui.h> | #include <gui/gui.h> | ||||||
| @ -12,20 +13,18 @@ int32_t InfraredApp::run(void* args) { | |||||||
|     bool exit = false; |     bool exit = false; | ||||||
| 
 | 
 | ||||||
|     if(args) { |     if(args) { | ||||||
|         std::string path = static_cast<const char*>(args); |         string_t path; | ||||||
|         std::string remote_name(path, path.find_last_of('/') + 1, path.size()); |         string_init_set_str(path, (char*)args); | ||||||
|         auto last_dot = remote_name.find_last_of('.'); |         if(string_end_with_str_p(path, InfraredApp::infrared_extension)) { | ||||||
|         if(last_dot != std::string::npos) { |             bool result = remote_manager.load(path); | ||||||
|             remote_name.erase(last_dot); |  | ||||||
|             path.erase(path.find_last_of('/')); |  | ||||||
|             bool result = remote_manager.load(path, remote_name); |  | ||||||
|             if(result) { |             if(result) { | ||||||
|                 current_scene = InfraredApp::Scene::Remote; |                 current_scene = InfraredApp::Scene::Remote; | ||||||
|             } else { |             } else { | ||||||
|                 printf("Failed to load remote \'%s\'\r\n", remote_name.c_str()); |                 printf("Failed to load remote \'%s\'\r\n", string_get_cstr(path)); | ||||||
|                 return -1; |                 return -1; | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|  |         string_clear(path); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     scenes[current_scene]->on_enter(this); |     scenes[current_scene]->on_enter(this); | ||||||
| @ -51,6 +50,7 @@ int32_t InfraredApp::run(void* args) { | |||||||
| 
 | 
 | ||||||
| InfraredApp::InfraredApp() { | InfraredApp::InfraredApp() { | ||||||
|     furi_check(InfraredAppRemoteManager::max_button_name_length < get_text_store_size()); |     furi_check(InfraredAppRemoteManager::max_button_name_length < get_text_store_size()); | ||||||
|  |     string_init_set_str(file_path, InfraredApp::infrared_directory); | ||||||
|     notification = static_cast<NotificationApp*>(furi_record_open("notification")); |     notification = static_cast<NotificationApp*>(furi_record_open("notification")); | ||||||
|     dialogs = static_cast<DialogsApp*>(furi_record_open("dialogs")); |     dialogs = static_cast<DialogsApp*>(furi_record_open("dialogs")); | ||||||
|     infrared_worker = infrared_worker_alloc(); |     infrared_worker = infrared_worker_alloc(); | ||||||
| @ -60,6 +60,7 @@ InfraredApp::~InfraredApp() { | |||||||
|     infrared_worker_free(infrared_worker); |     infrared_worker_free(infrared_worker); | ||||||
|     furi_record_close("notification"); |     furi_record_close("notification"); | ||||||
|     furi_record_close("dialogs"); |     furi_record_close("dialogs"); | ||||||
|  |     string_clear(file_path); | ||||||
|     for(auto& [key, scene] : scenes) delete scene; |     for(auto& [key, scene] : scenes) delete scene; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -251,6 +251,8 @@ public: | |||||||
|     /** Main class destructor, deinitializes all critical objects */ |     /** Main class destructor, deinitializes all critical objects */ | ||||||
|     ~InfraredApp(); |     ~InfraredApp(); | ||||||
| 
 | 
 | ||||||
|  |     string_t file_path; | ||||||
|  | 
 | ||||||
|     /** Path to Infrared directory */ |     /** Path to Infrared directory */ | ||||||
|     static constexpr const char* infrared_directory = "/any/infrared"; |     static constexpr const char* infrared_directory = "/any/infrared"; | ||||||
|     /** Infrared files extension (remote files and universal databases) */ |     /** Infrared files extension (remote files and universal databases) */ | ||||||
|  | |||||||
| @ -1,3 +1,5 @@ | |||||||
|  | #include "m-string.h" | ||||||
|  | #include "storage/filesystem_api_defines.h" | ||||||
| #include <flipper_format/flipper_format.h> | #include <flipper_format/flipper_format.h> | ||||||
| #include "infrared_app_remote_manager.h" | #include "infrared_app_remote_manager.h" | ||||||
| #include "infrared/helpers/infrared_parser.h" | #include "infrared/helpers/infrared_parser.h" | ||||||
| @ -11,44 +13,58 @@ | |||||||
| #include <gui/modules/button_menu.h> | #include <gui/modules/button_menu.h> | ||||||
| #include <storage/storage.h> | #include <storage/storage.h> | ||||||
| #include "infrared_app.h" | #include "infrared_app.h" | ||||||
|  | #include <toolbox/path.h> | ||||||
| 
 | 
 | ||||||
| static const char* default_remote_name = "remote"; | static const char* default_remote_name = "remote"; | ||||||
| 
 | 
 | ||||||
| std::string InfraredAppRemoteManager::make_full_name( | void InfraredAppRemoteManager::find_vacant_remote_name(string_t name, string_t path) { | ||||||
|     const std::string& path, |  | ||||||
|     const std::string& remote_name) const { |  | ||||||
|     return std::string("") + path + "/" + remote_name + InfraredApp::infrared_extension; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| std::string InfraredAppRemoteManager::find_vacant_remote_name(const std::string& name) { |  | ||||||
|     std::string result_name; |  | ||||||
|     Storage* storage = static_cast<Storage*>(furi_record_open("storage")); |     Storage* storage = static_cast<Storage*>(furi_record_open("storage")); | ||||||
| 
 | 
 | ||||||
|     FS_Error error = storage_common_stat( |     string_t base_path; | ||||||
|         storage, make_full_name(InfraredApp::infrared_directory, name).c_str(), NULL); |     string_init_set(base_path, path); | ||||||
| 
 | 
 | ||||||
|     if(error == FSE_NOT_EXIST) { |     if(string_end_with_str_p(base_path, InfraredApp::infrared_extension)) { | ||||||
|         result_name = name; |         size_t filename_start = string_search_rchar(base_path, '/'); | ||||||
|     } else if(error != FSE_OK) { |         string_left(base_path, filename_start); | ||||||
|         result_name = std::string(); |     } | ||||||
|     } else { | 
 | ||||||
|  |     string_printf( | ||||||
|  |         base_path, | ||||||
|  |         "%s/%s%s", | ||||||
|  |         string_get_cstr(path), | ||||||
|  |         string_get_cstr(name), | ||||||
|  |         InfraredApp::infrared_extension); | ||||||
|  | 
 | ||||||
|  |     FS_Error error = storage_common_stat(storage, string_get_cstr(base_path), NULL); | ||||||
|  | 
 | ||||||
|  |     if(error == FSE_OK) { | ||||||
|         /* if suggested name is occupied, try another one (name2, name3, etc) */ |         /* if suggested name is occupied, try another one (name2, name3, etc) */ | ||||||
|  |         size_t dot = string_search_rchar(base_path, '.'); | ||||||
|  |         string_left(base_path, dot); | ||||||
|  | 
 | ||||||
|  |         string_t path_temp; | ||||||
|  |         string_init(path_temp); | ||||||
|  | 
 | ||||||
|         uint32_t i = 1; |         uint32_t i = 1; | ||||||
|         std::string new_name; |  | ||||||
|         do { |         do { | ||||||
|             new_name = make_full_name(InfraredApp::infrared_directory, name + std::to_string(++i)); |             string_printf( | ||||||
|             error = storage_common_stat(storage, new_name.c_str(), NULL); |                 path_temp, | ||||||
|  |                 "%s%u%s", | ||||||
|  |                 string_get_cstr(base_path), | ||||||
|  |                 ++i, | ||||||
|  |                 InfraredApp::infrared_extension); | ||||||
|  |             error = storage_common_stat(storage, string_get_cstr(path_temp), NULL); | ||||||
|         } while(error == FSE_OK); |         } while(error == FSE_OK); | ||||||
| 
 | 
 | ||||||
|  |         string_clear(path_temp); | ||||||
|  | 
 | ||||||
|         if(error == FSE_NOT_EXIST) { |         if(error == FSE_NOT_EXIST) { | ||||||
|             result_name = name + std::to_string(i); |             string_cat_printf(name, "%u", i); | ||||||
|         } else { |  | ||||||
|             result_name = std::string(); |  | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     string_clear(base_path); | ||||||
|     furi_record_close("storage"); |     furi_record_close("storage"); | ||||||
|     return result_name; |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| bool InfraredAppRemoteManager::add_button(const char* button_name, const InfraredAppSignal& signal) { | bool InfraredAppRemoteManager::add_button(const char* button_name, const InfraredAppSignal& signal) { | ||||||
| @ -61,12 +77,23 @@ bool InfraredAppRemoteManager::add_remote_with_button( | |||||||
|     const InfraredAppSignal& signal) { |     const InfraredAppSignal& signal) { | ||||||
|     furi_check(button_name != nullptr); |     furi_check(button_name != nullptr); | ||||||
| 
 | 
 | ||||||
|     auto new_name = find_vacant_remote_name(default_remote_name); |     string_t new_name; | ||||||
|     if(new_name.empty()) { |     string_init_set_str(new_name, default_remote_name); | ||||||
|         return false; | 
 | ||||||
|     } |     string_t new_path; | ||||||
|  |     string_init_set_str(new_path, InfraredApp::infrared_directory); | ||||||
|  | 
 | ||||||
|  |     find_vacant_remote_name(new_name, new_path); | ||||||
|  | 
 | ||||||
|  |     string_cat_printf( | ||||||
|  |         new_path, "/%s%s", string_get_cstr(new_name), InfraredApp::infrared_extension); | ||||||
|  | 
 | ||||||
|  |     remote = std::make_unique<InfraredAppRemote>(new_path); | ||||||
|  |     remote->name = std::string(string_get_cstr(new_name)); | ||||||
|  | 
 | ||||||
|  |     string_clear(new_path); | ||||||
|  |     string_clear(new_name); | ||||||
| 
 | 
 | ||||||
|     remote = std::make_unique<InfraredAppRemote>(InfraredApp::infrared_directory, new_name); |  | ||||||
|     return add_button(button_name, signal); |     return add_button(button_name, signal); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @ -93,8 +120,7 @@ const InfraredAppSignal& InfraredAppRemoteManager::get_button_data(size_t index) | |||||||
| bool InfraredAppRemoteManager::delete_remote() { | bool InfraredAppRemoteManager::delete_remote() { | ||||||
|     Storage* storage = static_cast<Storage*>(furi_record_open("storage")); |     Storage* storage = static_cast<Storage*>(furi_record_open("storage")); | ||||||
| 
 | 
 | ||||||
|     FS_Error error = |     FS_Error error = storage_common_remove(storage, string_get_cstr(remote->path)); | ||||||
|         storage_common_remove(storage, make_full_name(remote->path, remote->name).c_str()); |  | ||||||
|     reset_remote(); |     reset_remote(); | ||||||
| 
 | 
 | ||||||
|     furi_record_close("storage"); |     furi_record_close("storage"); | ||||||
| @ -128,22 +154,33 @@ std::string InfraredAppRemoteManager::get_remote_name() { | |||||||
| bool InfraredAppRemoteManager::rename_remote(const char* str) { | bool InfraredAppRemoteManager::rename_remote(const char* str) { | ||||||
|     furi_check(str != nullptr); |     furi_check(str != nullptr); | ||||||
|     furi_check(remote.get() != nullptr); |     furi_check(remote.get() != nullptr); | ||||||
|  |     furi_check(!string_empty_p(remote->path)); | ||||||
| 
 | 
 | ||||||
|     if(!remote->name.compare(str)) { |     if(!remote->name.compare(str)) { | ||||||
|         return true; |         return true; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     auto new_name = find_vacant_remote_name(str); |     string_t new_name; | ||||||
|     if(new_name.empty()) { |     string_init_set_str(new_name, str); | ||||||
|         return false; |     find_vacant_remote_name(new_name, remote->path); | ||||||
|  | 
 | ||||||
|  |     string_t new_path; | ||||||
|  |     string_init_set(new_path, remote->path); | ||||||
|  |     if(string_end_with_str_p(new_path, InfraredApp::infrared_extension)) { | ||||||
|  |         size_t filename_start = string_search_rchar(new_path, '/'); | ||||||
|  |         string_left(new_path, filename_start); | ||||||
|     } |     } | ||||||
|  |     string_cat_printf( | ||||||
|  |         new_path, "/%s%s", string_get_cstr(new_name), InfraredApp::infrared_extension); | ||||||
| 
 | 
 | ||||||
|     Storage* storage = static_cast<Storage*>(furi_record_open("storage")); |     Storage* storage = static_cast<Storage*>(furi_record_open("storage")); | ||||||
| 
 | 
 | ||||||
|     std::string old_filename = make_full_name(remote->path, remote->name); |     FS_Error error = | ||||||
|     std::string new_filename = make_full_name(remote->path, new_name); |         storage_common_rename(storage, string_get_cstr(remote->path), string_get_cstr(new_path)); | ||||||
|     FS_Error error = storage_common_rename(storage, old_filename.c_str(), new_filename.c_str()); |     remote->name = std::string(string_get_cstr(new_name)); | ||||||
|     remote->name = new_name; | 
 | ||||||
|  |     string_clear(new_name); | ||||||
|  |     string_clear(new_path); | ||||||
| 
 | 
 | ||||||
|     furi_record_close("storage"); |     furi_record_close("storage"); | ||||||
|     return (error == FSE_OK || error == FSE_EXIST); |     return (error == FSE_OK || error == FSE_EXIST); | ||||||
| @ -171,10 +208,8 @@ bool InfraredAppRemoteManager::store(void) { | |||||||
| 
 | 
 | ||||||
|     FlipperFormat* ff = flipper_format_file_alloc(storage); |     FlipperFormat* ff = flipper_format_file_alloc(storage); | ||||||
| 
 | 
 | ||||||
|     FURI_LOG_I( |     FURI_LOG_I("RemoteManager", "store file: \'%s\'", string_get_cstr(remote->path)); | ||||||
|         "RemoteManager", "store file: \'%s\'", make_full_name(remote->path, remote->name).c_str()); |     result = flipper_format_file_open_always(ff, string_get_cstr(remote->path)); | ||||||
|     result = |  | ||||||
|         flipper_format_file_open_always(ff, make_full_name(remote->path, remote->name).c_str()); |  | ||||||
|     if(result) { |     if(result) { | ||||||
|         result = flipper_format_write_header_cstr(ff, "IR signals file", 1); |         result = flipper_format_write_header_cstr(ff, "IR signals file", 1); | ||||||
|     } |     } | ||||||
| @ -192,13 +227,13 @@ bool InfraredAppRemoteManager::store(void) { | |||||||
|     return result; |     return result; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| bool InfraredAppRemoteManager::load(const std::string& path, const std::string& remote_name) { | bool InfraredAppRemoteManager::load(string_t path) { | ||||||
|     bool result = false; |     bool result = false; | ||||||
|     Storage* storage = static_cast<Storage*>(furi_record_open("storage")); |     Storage* storage = static_cast<Storage*>(furi_record_open("storage")); | ||||||
|     FlipperFormat* ff = flipper_format_file_alloc(storage); |     FlipperFormat* ff = flipper_format_file_alloc(storage); | ||||||
| 
 | 
 | ||||||
|     FURI_LOG_I("RemoteManager", "load file: \'%s\'", make_full_name(path, remote_name).c_str()); |     FURI_LOG_I("RemoteManager", "load file: \'%s\'", string_get_cstr(path)); | ||||||
|     result = flipper_format_file_open_existing(ff, make_full_name(path, remote_name).c_str()); |     result = flipper_format_file_open_existing(ff, string_get_cstr(path)); | ||||||
|     if(result) { |     if(result) { | ||||||
|         string_t header; |         string_t header; | ||||||
|         string_init(header); |         string_init(header); | ||||||
| @ -210,7 +245,14 @@ bool InfraredAppRemoteManager::load(const std::string& path, const std::string& | |||||||
|         string_clear(header); |         string_clear(header); | ||||||
|     } |     } | ||||||
|     if(result) { |     if(result) { | ||||||
|         remote = std::make_unique<InfraredAppRemote>(path, remote_name); |         string_t new_name; | ||||||
|  |         string_init(new_name); | ||||||
|  | 
 | ||||||
|  |         remote = std::make_unique<InfraredAppRemote>(path); | ||||||
|  |         path_extract_filename(path, new_name, true); | ||||||
|  |         remote->name = std::string(string_get_cstr(new_name)); | ||||||
|  | 
 | ||||||
|  |         string_clear(new_name); | ||||||
|         InfraredAppSignal signal; |         InfraredAppSignal signal; | ||||||
|         std::string signal_name; |         std::string signal_name; | ||||||
|         while(infrared_parser_read_signal(ff, signal, signal_name)) { |         while(infrared_parser_read_signal(ff, signal, signal_name)) { | ||||||
|  | |||||||
| @ -8,6 +8,7 @@ | |||||||
| 
 | 
 | ||||||
| #include "infrared_app_signal.h" | #include "infrared_app_signal.h" | ||||||
| 
 | 
 | ||||||
|  | #include "m-string.h" | ||||||
| #include <infrared_worker.h> | #include <infrared_worker.h> | ||||||
| #include <infrared.h> | #include <infrared.h> | ||||||
| 
 | 
 | ||||||
| @ -60,17 +61,19 @@ class InfraredAppRemote { | |||||||
|     /** Name of remote */ |     /** Name of remote */ | ||||||
|     std::string name; |     std::string name; | ||||||
|     /** Path to remote file */ |     /** Path to remote file */ | ||||||
|     std::string path; |     string_t path; | ||||||
| 
 | 
 | ||||||
| public: | public: | ||||||
|     /** Initialize new remote
 |     /** Initialize new remote
 | ||||||
|      *  |      *  | ||||||
|      * @param path - remote file path |      * @param path - remote file path | ||||||
|      * @param name - new remote name |  | ||||||
|      */ |      */ | ||||||
|     InfraredAppRemote(const std::string& path, const std::string& name) |     InfraredAppRemote(string_t file_path) { | ||||||
|         : name(name) |         string_init_set(path, file_path); | ||||||
|         , path(path) { |     } | ||||||
|  | 
 | ||||||
|  |     ~InfraredAppRemote() { | ||||||
|  |         string_clear(path); | ||||||
|     } |     } | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| @ -78,12 +81,6 @@ public: | |||||||
| class InfraredAppRemoteManager { | class InfraredAppRemoteManager { | ||||||
|     /** Remote instance. There can be 1 remote loaded at a time. */ |     /** Remote instance. There can be 1 remote loaded at a time. */ | ||||||
|     std::unique_ptr<InfraredAppRemote> remote; |     std::unique_ptr<InfraredAppRemote> remote; | ||||||
|     /** Make full name from remote name
 |  | ||||||
|      * |  | ||||||
|      * @param remote_name name of remote |  | ||||||
|      * @retval full name of remote on disk |  | ||||||
|      */ |  | ||||||
|     std::string make_full_name(const std::string& path, const std::string& remote_name) const; |  | ||||||
| 
 | 
 | ||||||
| public: | public: | ||||||
|     /** Restriction to button name length. Buttons larger are ignored. */ |     /** Restriction to button name length. Buttons larger are ignored. */ | ||||||
| @ -125,9 +122,9 @@ public: | |||||||
|      * incremented digit(2,3,4,etc) added to name and check repeated. |      * incremented digit(2,3,4,etc) added to name and check repeated. | ||||||
|      * |      * | ||||||
|      * @param name - suggested remote name |      * @param name - suggested remote name | ||||||
|      * @retval garanteed free remote name, prefixed with suggested |      * @param path - remote file path | ||||||
|      */ |      */ | ||||||
|     std::string find_vacant_remote_name(const std::string& name); |     void find_vacant_remote_name(string_t name, string_t path); | ||||||
| 
 | 
 | ||||||
|     /** Get button list
 |     /** Get button list
 | ||||||
|      * |      * | ||||||
| @ -185,8 +182,8 @@ public: | |||||||
| 
 | 
 | ||||||
|     /** Load data from disk into current remote
 |     /** Load data from disk into current remote
 | ||||||
|      * |      * | ||||||
|      * @param name - name of remote to load |      * @param path - path to remote file | ||||||
|      * @retval true if success, false otherwise |      * @retval true if success, false otherwise | ||||||
|      */ |      */ | ||||||
|     bool load(const std::string& path, const std::string& name); |     bool load(string_t path); | ||||||
| }; | }; | ||||||
|  | |||||||
| @ -1,4 +1,6 @@ | |||||||
| #include "../infrared_app.h" | #include "../infrared_app.h" | ||||||
|  | #include "m-string.h" | ||||||
|  | #include "toolbox/path.h" | ||||||
| 
 | 
 | ||||||
| void InfraredAppSceneEditRename::on_enter(InfraredApp* app) { | void InfraredAppSceneEditRename::on_enter(InfraredApp* app) { | ||||||
|     InfraredAppViewManager* view_manager = app->get_view_manager(); |     InfraredAppViewManager* view_manager = app->get_view_manager(); | ||||||
| @ -21,9 +23,18 @@ void InfraredAppSceneEditRename::on_enter(InfraredApp* app) { | |||||||
|         enter_name_length = InfraredAppRemoteManager::max_remote_name_length; |         enter_name_length = InfraredAppRemoteManager::max_remote_name_length; | ||||||
|         text_input_set_header_text(text_input, "Name the remote"); |         text_input_set_header_text(text_input, "Name the remote"); | ||||||
| 
 | 
 | ||||||
|  |         string_t folder_path; | ||||||
|  |         string_init(folder_path); | ||||||
|  | 
 | ||||||
|  |         if(string_end_with_str_p(app->file_path, InfraredApp::infrared_extension)) { | ||||||
|  |             path_extract_dirname(string_get_cstr(app->file_path), folder_path); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|         ValidatorIsFile* validator_is_file = validator_is_file_alloc_init( |         ValidatorIsFile* validator_is_file = validator_is_file_alloc_init( | ||||||
|             app->infrared_directory, app->infrared_extension, remote_name.c_str()); |             string_get_cstr(folder_path), app->infrared_extension, remote_name.c_str()); | ||||||
|         text_input_set_validator(text_input, validator_is_file_callback, validator_is_file); |         text_input_set_validator(text_input, validator_is_file_callback, validator_is_file); | ||||||
|  | 
 | ||||||
|  |         string_clear(folder_path); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     text_input_set_result_callback( |     text_input_set_result_callback( | ||||||
|  | |||||||
| @ -1,4 +1,5 @@ | |||||||
| #include "../infrared_app.h" | #include "../infrared_app.h" | ||||||
|  | #include "assets_icons.h" | ||||||
| #include "infrared/infrared_app_event.h" | #include "infrared/infrared_app_event.h" | ||||||
| #include <text_store.h> | #include <text_store.h> | ||||||
| 
 | 
 | ||||||
| @ -8,11 +9,6 @@ void InfraredAppSceneRemoteList::on_enter(InfraredApp* app) { | |||||||
|     bool result = false; |     bool result = false; | ||||||
|     bool file_select_result; |     bool file_select_result; | ||||||
|     auto remote_manager = app->get_remote_manager(); |     auto remote_manager = app->get_remote_manager(); | ||||||
|     auto last_selected_remote = remote_manager->get_remote_name(); |  | ||||||
|     const char* last_selected_remote_name = |  | ||||||
|         last_selected_remote.size() ? last_selected_remote.c_str() : nullptr; |  | ||||||
|     auto filename_ts = |  | ||||||
|         std::make_unique<TextStore>(InfraredAppRemoteManager::max_remote_name_length); |  | ||||||
|     DialogsApp* dialogs = app->get_dialogs(); |     DialogsApp* dialogs = app->get_dialogs(); | ||||||
| 
 | 
 | ||||||
|     InfraredAppViewManager* view_manager = app->get_view_manager(); |     InfraredAppViewManager* view_manager = app->get_view_manager(); | ||||||
| @ -20,16 +16,17 @@ void InfraredAppSceneRemoteList::on_enter(InfraredApp* app) { | |||||||
|     button_menu_reset(button_menu); |     button_menu_reset(button_menu); | ||||||
|     view_manager->switch_to(InfraredAppViewManager::ViewId::ButtonMenu); |     view_manager->switch_to(InfraredAppViewManager::ViewId::ButtonMenu); | ||||||
| 
 | 
 | ||||||
|     file_select_result = dialog_file_select_show( |     file_select_result = dialog_file_browser_show( | ||||||
|         dialogs, |         dialogs, | ||||||
|         InfraredApp::infrared_directory, |         app->file_path, | ||||||
|  |         app->file_path, | ||||||
|         InfraredApp::infrared_extension, |         InfraredApp::infrared_extension, | ||||||
|         filename_ts->text, |         true, | ||||||
|         filename_ts->text_size, |         &I_ir_10px, | ||||||
|         last_selected_remote_name); |         true); | ||||||
| 
 | 
 | ||||||
|     if(file_select_result) { |     if(file_select_result) { | ||||||
|         if(remote_manager->load(InfraredApp::infrared_directory, std::string(filename_ts->text))) { |         if(remote_manager->load(app->file_path)) { | ||||||
|             app->switch_to_next_scene(InfraredApp::Scene::Remote); |             app->switch_to_next_scene(InfraredApp::Scene::Remote); | ||||||
|             result = true; |             result = true; | ||||||
|         } |         } | ||||||
|  | |||||||
| @ -26,6 +26,8 @@ void InfraredAppSceneStart::on_enter(InfraredApp* app) { | |||||||
|         submenu, "Learn New Remote", SubmenuIndexLearnNewRemote, submenu_callback, app); |         submenu, "Learn New Remote", SubmenuIndexLearnNewRemote, submenu_callback, app); | ||||||
|     submenu_add_item(submenu, "Saved Remotes", SubmenuIndexSavedRemotes, submenu_callback, app); |     submenu_add_item(submenu, "Saved Remotes", SubmenuIndexSavedRemotes, submenu_callback, app); | ||||||
|     submenu_set_selected_item(submenu, submenu_item_selected); |     submenu_set_selected_item(submenu, submenu_item_selected); | ||||||
|  | 
 | ||||||
|  |     string_set_str(app->file_path, InfraredApp::infrared_directory); | ||||||
|     submenu_item_selected = 0; |     submenu_item_selected = 0; | ||||||
| 
 | 
 | ||||||
|     view_manager->switch_to(InfraredAppViewManager::ViewId::Submenu); |     view_manager->switch_to(InfraredAppViewManager::ViewId::Submenu); | ||||||
|  | |||||||
| @ -1,4 +1,7 @@ | |||||||
| #include "lfrfid_app.h" | #include "lfrfid_app.h" | ||||||
|  | #include "assets_icons.h" | ||||||
|  | #include "furi/common_defines.h" | ||||||
|  | #include "m-string.h" | ||||||
| #include "scene/lfrfid_app_scene_start.h" | #include "scene/lfrfid_app_scene_start.h" | ||||||
| #include "scene/lfrfid_app_scene_read.h" | #include "scene/lfrfid_app_scene_read.h" | ||||||
| #include "scene/lfrfid_app_scene_read_success.h" | #include "scene/lfrfid_app_scene_read_success.h" | ||||||
| @ -31,9 +34,11 @@ LfRfidApp::LfRfidApp() | |||||||
|     , storage{"storage"} |     , storage{"storage"} | ||||||
|     , dialogs{"dialogs"} |     , dialogs{"dialogs"} | ||||||
|     , text_store(40) { |     , text_store(40) { | ||||||
|  |     string_init_set_str(file_path, app_folder); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| LfRfidApp::~LfRfidApp() { | LfRfidApp::~LfRfidApp() { | ||||||
|  |     string_clear(file_path); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void LfRfidApp::run(void* _args) { | void LfRfidApp::run(void* _args) { | ||||||
| @ -42,7 +47,8 @@ void LfRfidApp::run(void* _args) { | |||||||
|     make_app_folder(); |     make_app_folder(); | ||||||
| 
 | 
 | ||||||
|     if(strlen(args)) { |     if(strlen(args)) { | ||||||
|         load_key_data(args, &worker.key); |         string_set_str(file_path, args); | ||||||
|  |         load_key_data(file_path, &worker.key); | ||||||
|         scene_controller.add_scene(SceneType::Emulate, new LfRfidAppSceneEmulate()); |         scene_controller.add_scene(SceneType::Emulate, new LfRfidAppSceneEmulate()); | ||||||
|         scene_controller.process(100, SceneType::Emulate); |         scene_controller.process(100, SceneType::Emulate); | ||||||
|     } else { |     } else { | ||||||
| @ -69,65 +75,49 @@ void LfRfidApp::run(void* _args) { | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| bool LfRfidApp::save_key(RfidKey* key) { | bool LfRfidApp::save_key(RfidKey* key) { | ||||||
|     string_t file_name; |  | ||||||
|     bool result = false; |     bool result = false; | ||||||
| 
 | 
 | ||||||
|     make_app_folder(); |     make_app_folder(); | ||||||
| 
 | 
 | ||||||
|     string_init_printf(file_name, "%s/%s%s", app_folder, key->get_name(), app_extension); |     if(string_end_with_str_p(file_path, app_extension)) { | ||||||
|     result = save_key_data(string_get_cstr(file_name), key); |         size_t filename_start = string_search_rchar(file_path, '/'); | ||||||
|     string_clear(file_name); |         string_left(file_path, filename_start); | ||||||
|  |     } | ||||||
| 
 | 
 | ||||||
|  |     string_cat_printf(file_path, "/%s%s", key->get_name(), app_extension); | ||||||
|  | 
 | ||||||
|  |     result = save_key_data(file_path, key); | ||||||
|     return result; |     return result; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| bool LfRfidApp::load_key_from_file_select(bool need_restore) { | bool LfRfidApp::load_key_from_file_select(bool need_restore) { | ||||||
|     TextStore* filename_ts = new TextStore(64); |     if(!need_restore) { | ||||||
|     bool result = false; |         string_set_str(file_path, app_folder); | ||||||
| 
 |  | ||||||
|     if(need_restore) { |  | ||||||
|         result = dialog_file_select_show( |  | ||||||
|             dialogs, |  | ||||||
|             app_folder, |  | ||||||
|             app_extension, |  | ||||||
|             filename_ts->text, |  | ||||||
|             filename_ts->text_size, |  | ||||||
|             worker.key.get_name()); |  | ||||||
|     } else { |  | ||||||
|         result = dialog_file_select_show( |  | ||||||
|             dialogs, app_folder, app_extension, filename_ts->text, filename_ts->text_size, NULL); |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     bool result = dialog_file_browser_show( | ||||||
|  |         dialogs, file_path, file_path, app_extension, true, &I_125_10px, true); | ||||||
|  | 
 | ||||||
|     if(result) { |     if(result) { | ||||||
|         string_t key_str; |         result = load_key_data(file_path, &worker.key); | ||||||
|         string_init_printf(key_str, "%s/%s%s", app_folder, filename_ts->text, app_extension); |  | ||||||
|         result = load_key_data(string_get_cstr(key_str), &worker.key); |  | ||||||
|         string_clear(key_str); |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     delete filename_ts; |  | ||||||
|     return result; |     return result; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| bool LfRfidApp::delete_key(RfidKey* key) { | bool LfRfidApp::delete_key(RfidKey* key) { | ||||||
|     string_t file_name; |     UNUSED(key); | ||||||
|     bool result = false; |     return storage_simply_remove(storage, string_get_cstr(file_path)); | ||||||
| 
 |  | ||||||
|     string_init_printf(file_name, "%s/%s%s", app_folder, key->get_name(), app_extension); |  | ||||||
|     result = storage_simply_remove(storage, string_get_cstr(file_name)); |  | ||||||
|     string_clear(file_name); |  | ||||||
| 
 |  | ||||||
|     return result; |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| bool LfRfidApp::load_key_data(const char* path, RfidKey* key) { | bool LfRfidApp::load_key_data(string_t path, RfidKey* key) { | ||||||
|     FlipperFormat* file = flipper_format_file_alloc(storage); |     FlipperFormat* file = flipper_format_file_alloc(storage); | ||||||
|     bool result = false; |     bool result = false; | ||||||
|     string_t str_result; |     string_t str_result; | ||||||
|     string_init(str_result); |     string_init(str_result); | ||||||
| 
 | 
 | ||||||
|     do { |     do { | ||||||
|         if(!flipper_format_file_open_existing(file, path)) break; |         if(!flipper_format_file_open_existing(file, string_get_cstr(path))) break; | ||||||
| 
 | 
 | ||||||
|         // header
 |         // header
 | ||||||
|         uint32_t version; |         uint32_t version; | ||||||
| @ -149,7 +139,7 @@ bool LfRfidApp::load_key_data(const char* path, RfidKey* key) { | |||||||
|             break; |             break; | ||||||
|         loaded_key.set_data(key_data, loaded_key.get_type_data_count()); |         loaded_key.set_data(key_data, loaded_key.get_type_data_count()); | ||||||
| 
 | 
 | ||||||
|         path_extract_filename_no_ext(path, str_result); |         path_extract_filename(path, str_result, true); | ||||||
|         loaded_key.set_name(string_get_cstr(str_result)); |         loaded_key.set_name(string_get_cstr(str_result)); | ||||||
| 
 | 
 | ||||||
|         *key = loaded_key; |         *key = loaded_key; | ||||||
| @ -166,12 +156,12 @@ bool LfRfidApp::load_key_data(const char* path, RfidKey* key) { | |||||||
|     return result; |     return result; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| bool LfRfidApp::save_key_data(const char* path, RfidKey* key) { | bool LfRfidApp::save_key_data(string_t path, RfidKey* key) { | ||||||
|     FlipperFormat* file = flipper_format_file_alloc(storage); |     FlipperFormat* file = flipper_format_file_alloc(storage); | ||||||
|     bool result = false; |     bool result = false; | ||||||
| 
 | 
 | ||||||
|     do { |     do { | ||||||
|         if(!flipper_format_file_open_always(file, path)) break; |         if(!flipper_format_file_open_always(file, string_get_cstr(path))) break; | ||||||
|         if(!flipper_format_write_header_cstr(file, app_filetype, 1)) break; |         if(!flipper_format_write_header_cstr(file, app_filetype, 1)) break; | ||||||
|         if(!flipper_format_write_comment_cstr(file, "Key type can be EM4100, H10301 or I40134")) |         if(!flipper_format_write_comment_cstr(file, "Key type can be EM4100, H10301 or I40134")) | ||||||
|             break; |             break; | ||||||
|  | |||||||
| @ -1,4 +1,5 @@ | |||||||
| #pragma once | #pragma once | ||||||
|  | #include "m-string.h" | ||||||
| #include <furi.h> | #include <furi.h> | ||||||
| #include <furi_hal.h> | #include <furi_hal.h> | ||||||
| 
 | 
 | ||||||
| @ -76,6 +77,8 @@ public: | |||||||
| 
 | 
 | ||||||
|     TextStore text_store; |     TextStore text_store; | ||||||
| 
 | 
 | ||||||
|  |     string_t file_path; | ||||||
|  | 
 | ||||||
|     void run(void* args); |     void run(void* args); | ||||||
| 
 | 
 | ||||||
|     static const char* app_folder; |     static const char* app_folder; | ||||||
| @ -86,8 +89,8 @@ public: | |||||||
|     bool load_key_from_file_select(bool need_restore); |     bool load_key_from_file_select(bool need_restore); | ||||||
|     bool delete_key(RfidKey* key); |     bool delete_key(RfidKey* key); | ||||||
| 
 | 
 | ||||||
|     bool load_key_data(const char* path, RfidKey* key); |     bool load_key_data(string_t path, RfidKey* key); | ||||||
|     bool save_key_data(const char* path, RfidKey* key); |     bool save_key_data(string_t path, RfidKey* key); | ||||||
| 
 | 
 | ||||||
|     void make_app_folder(); |     void make_app_folder(); | ||||||
| }; | }; | ||||||
|  | |||||||
| @ -1,11 +1,14 @@ | |||||||
| #include "lfrfid_app_scene_save_name.h" | #include "lfrfid_app_scene_save_name.h" | ||||||
|  | #include "m-string.h" | ||||||
| #include <lib/toolbox/random_name.h> | #include <lib/toolbox/random_name.h> | ||||||
|  | #include <lib/toolbox/path.h> | ||||||
| 
 | 
 | ||||||
| void LfRfidAppSceneSaveName::on_enter(LfRfidApp* app, bool /* need_restore */) { | void LfRfidAppSceneSaveName::on_enter(LfRfidApp* app, bool /* need_restore */) { | ||||||
|     const char* key_name = app->worker.key.get_name(); |     const char* key_name = app->worker.key.get_name(); | ||||||
| 
 | 
 | ||||||
|     bool key_name_empty = !strcmp(key_name, ""); |     bool key_name_empty = !strcmp(key_name, ""); | ||||||
|     if(key_name_empty) { |     if(key_name_empty) { | ||||||
|  |         string_set_str(app->file_path, app->app_folder); | ||||||
|         set_random_name(app->text_store.text, app->text_store.text_size); |         set_random_name(app->text_store.text, app->text_store.text_size); | ||||||
|     } else { |     } else { | ||||||
|         app->text_store.set("%s", key_name); |         app->text_store.set("%s", key_name); | ||||||
| @ -21,10 +24,17 @@ void LfRfidAppSceneSaveName::on_enter(LfRfidApp* app, bool /* need_restore */) { | |||||||
|         app->worker.key.get_name_length(), |         app->worker.key.get_name_length(), | ||||||
|         key_name_empty); |         key_name_empty); | ||||||
| 
 | 
 | ||||||
|  |     string_t folder_path; | ||||||
|  |     string_init(folder_path); | ||||||
|  | 
 | ||||||
|  |     path_extract_dirname(string_get_cstr(app->file_path), folder_path); | ||||||
|  | 
 | ||||||
|     ValidatorIsFile* validator_is_file = |     ValidatorIsFile* validator_is_file = | ||||||
|         validator_is_file_alloc_init(app->app_folder, app->app_extension, key_name); |         validator_is_file_alloc_init(string_get_cstr(folder_path), app->app_extension, key_name); | ||||||
|     text_input->set_validator(validator_is_file_callback, validator_is_file); |     text_input->set_validator(validator_is_file_callback, validator_is_file); | ||||||
| 
 | 
 | ||||||
|  |     string_clear(folder_path); | ||||||
|  | 
 | ||||||
|     app->view_controller.switch_to<TextInputVM>(); |     app->view_controller.switch_to<TextInputVM>(); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -1,3 +1,5 @@ | |||||||
|  | #include "assets_icons.h" | ||||||
|  | #include "m-string.h" | ||||||
| #include <furi.h> | #include <furi.h> | ||||||
| #include <furi_hal.h> | #include <furi_hal.h> | ||||||
| 
 | 
 | ||||||
| @ -298,23 +300,23 @@ int32_t music_player_app(void* p) { | |||||||
|         if(p) { |         if(p) { | ||||||
|             string_cat_str(file_path, p); |             string_cat_str(file_path, p); | ||||||
|         } else { |         } else { | ||||||
|             char file_name[256] = {0}; |             string_set_str(file_path, MUSIC_PLAYER_APP_PATH_FOLDER); | ||||||
|  | 
 | ||||||
|             DialogsApp* dialogs = furi_record_open("dialogs"); |             DialogsApp* dialogs = furi_record_open("dialogs"); | ||||||
|             bool res = dialog_file_select_show( |             bool res = dialog_file_browser_show( | ||||||
|                 dialogs, |                 dialogs, | ||||||
|                 MUSIC_PLAYER_APP_PATH_FOLDER, |                 file_path, | ||||||
|  |                 file_path, | ||||||
|                 MUSIC_PLAYER_APP_EXTENSION, |                 MUSIC_PLAYER_APP_EXTENSION, | ||||||
|                 file_name, |                 true, | ||||||
|                 255, |                 &I_music_10px, | ||||||
|                 NULL); |                 false); | ||||||
|  | 
 | ||||||
|             furi_record_close("dialogs"); |             furi_record_close("dialogs"); | ||||||
|             if(!res) { |             if(!res) { | ||||||
|                 FURI_LOG_E(TAG, "No file selected"); |                 FURI_LOG_E(TAG, "No file selected"); | ||||||
|                 break; |                 break; | ||||||
|             } |             } | ||||||
|             string_cat_str(file_path, MUSIC_PLAYER_APP_PATH_FOLDER); |  | ||||||
|             string_cat_str(file_path, "/"); |  | ||||||
|             string_cat_str(file_path, file_name); |  | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         if(!music_player_worker_load(music_player->worker, string_get_cstr(file_path))) { |         if(!music_player_worker_load(music_player->worker, string_get_cstr(file_path))) { | ||||||
|  | |||||||
| @ -1,4 +1,6 @@ | |||||||
| #include "nfc_device.h" | #include "nfc_device.h" | ||||||
|  | #include "assets_icons.h" | ||||||
|  | #include "m-string.h" | ||||||
| #include "nfc_types.h" | #include "nfc_types.h" | ||||||
| 
 | 
 | ||||||
| #include <toolbox/path.h> | #include <toolbox/path.h> | ||||||
| @ -14,6 +16,7 @@ NfcDevice* nfc_device_alloc() { | |||||||
|     NfcDevice* nfc_dev = malloc(sizeof(NfcDevice)); |     NfcDevice* nfc_dev = malloc(sizeof(NfcDevice)); | ||||||
|     nfc_dev->storage = furi_record_open("storage"); |     nfc_dev->storage = furi_record_open("storage"); | ||||||
|     nfc_dev->dialogs = furi_record_open("dialogs"); |     nfc_dev->dialogs = furi_record_open("dialogs"); | ||||||
|  |     string_init(nfc_dev->load_path); | ||||||
|     return nfc_dev; |     return nfc_dev; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @ -22,6 +25,7 @@ void nfc_device_free(NfcDevice* nfc_dev) { | |||||||
|     nfc_device_clear(nfc_dev); |     nfc_device_clear(nfc_dev); | ||||||
|     furi_record_close("storage"); |     furi_record_close("storage"); | ||||||
|     furi_record_close("dialogs"); |     furi_record_close("dialogs"); | ||||||
|  |     string_clear(nfc_dev->load_path); | ||||||
|     free(nfc_dev); |     free(nfc_dev); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @ -730,11 +734,24 @@ void nfc_device_set_name(NfcDevice* dev, const char* name) { | |||||||
|     strlcpy(dev->dev_name, name, NFC_DEV_NAME_MAX_LEN); |     strlcpy(dev->dev_name, name, NFC_DEV_NAME_MAX_LEN); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | static void nfc_device_get_path_without_ext(string_t orig_path, string_t shadow_path) { | ||||||
|  |     // TODO: this won't work if there is ".nfc" anywhere in the path other than
 | ||||||
|  |     // at the end
 | ||||||
|  |     size_t ext_start = string_search_str(orig_path, NFC_APP_EXTENSION); | ||||||
|  |     string_set_n(shadow_path, orig_path, 0, ext_start); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void nfc_device_get_shadow_path(string_t orig_path, string_t shadow_path) { | ||||||
|  |     nfc_device_get_path_without_ext(orig_path, shadow_path); | ||||||
|  |     string_cat_printf(shadow_path, "%s", NFC_APP_SHADOW_EXTENSION); | ||||||
|  | } | ||||||
|  | 
 | ||||||
| static bool nfc_device_save_file( | static bool nfc_device_save_file( | ||||||
|     NfcDevice* dev, |     NfcDevice* dev, | ||||||
|     const char* dev_name, |     const char* dev_name, | ||||||
|     const char* folder, |     const char* folder, | ||||||
|     const char* extension) { |     const char* extension, | ||||||
|  |     bool use_load_path) { | ||||||
|     furi_assert(dev); |     furi_assert(dev); | ||||||
| 
 | 
 | ||||||
|     bool saved = false; |     bool saved = false; | ||||||
| @ -744,10 +761,19 @@ static bool nfc_device_save_file( | |||||||
|     string_init(temp_str); |     string_init(temp_str); | ||||||
| 
 | 
 | ||||||
|     do { |     do { | ||||||
|  |         if(use_load_path && !string_empty_p(dev->load_path)) { | ||||||
|  |             // Get directory name
 | ||||||
|  |             path_extract_dirname(string_get_cstr(dev->load_path), temp_str); | ||||||
|  |             // Create nfc directory if necessary
 | ||||||
|  |             if(!storage_simply_mkdir(dev->storage, string_get_cstr(temp_str))) break; | ||||||
|  |             // Make path to file to save
 | ||||||
|  |             string_cat_printf(temp_str, "/%s%s", dev_name, extension); | ||||||
|  |         } else { | ||||||
|             // Create nfc directory if necessary
 |             // Create nfc directory if necessary
 | ||||||
|             if(!storage_simply_mkdir(dev->storage, NFC_APP_FOLDER)) break; |             if(!storage_simply_mkdir(dev->storage, NFC_APP_FOLDER)) break; | ||||||
|             // First remove nfc device file if it was saved
 |             // First remove nfc device file if it was saved
 | ||||||
|             string_printf(temp_str, "%s/%s%s", folder, dev_name, extension); |             string_printf(temp_str, "%s/%s%s", folder, dev_name, extension); | ||||||
|  |         } | ||||||
|         // Open file
 |         // Open file
 | ||||||
|         if(!flipper_format_file_open_always(file, string_get_cstr(temp_str))) break; |         if(!flipper_format_file_open_always(file, string_get_cstr(temp_str))) break; | ||||||
|         // Write header
 |         // Write header
 | ||||||
| @ -786,12 +812,12 @@ static bool nfc_device_save_file( | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| bool nfc_device_save(NfcDevice* dev, const char* dev_name) { | bool nfc_device_save(NfcDevice* dev, const char* dev_name) { | ||||||
|     return nfc_device_save_file(dev, dev_name, NFC_APP_FOLDER, NFC_APP_EXTENSION); |     return nfc_device_save_file(dev, dev_name, NFC_APP_FOLDER, NFC_APP_EXTENSION, true); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| bool nfc_device_save_shadow(NfcDevice* dev, const char* dev_name) { | bool nfc_device_save_shadow(NfcDevice* dev, const char* dev_name) { | ||||||
|     dev->shadow_file_exist = true; |     dev->shadow_file_exist = true; | ||||||
|     return nfc_device_save_file(dev, dev_name, NFC_APP_FOLDER, NFC_APP_SHADOW_EXTENSION); |     return nfc_device_save_file(dev, dev_name, NFC_APP_FOLDER, NFC_APP_SHADOW_EXTENSION, true); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static bool nfc_device_load_data(NfcDevice* dev, string_t path) { | static bool nfc_device_load_data(NfcDevice* dev, string_t path) { | ||||||
| @ -805,9 +831,7 @@ static bool nfc_device_load_data(NfcDevice* dev, string_t path) { | |||||||
| 
 | 
 | ||||||
|     do { |     do { | ||||||
|         // Check existance of shadow file
 |         // Check existance of shadow file
 | ||||||
|         size_t ext_start = string_search_str(path, NFC_APP_EXTENSION); |         nfc_device_get_shadow_path(path, temp_str); | ||||||
|         string_set_n(temp_str, path, 0, ext_start); |  | ||||||
|         string_cat_printf(temp_str, "%s", NFC_APP_SHADOW_EXTENSION); |  | ||||||
|         dev->shadow_file_exist = |         dev->shadow_file_exist = | ||||||
|             storage_common_stat(dev->storage, string_get_cstr(temp_str), NULL) == FSE_OK; |             storage_common_stat(dev->storage, string_get_cstr(temp_str), NULL) == FSE_OK; | ||||||
|         // Open shadow file if it exists. If not - open original
 |         // Open shadow file if it exists. If not - open original
 | ||||||
| @ -864,15 +888,16 @@ bool nfc_device_load(NfcDevice* dev, const char* file_path) { | |||||||
|     furi_assert(file_path); |     furi_assert(file_path); | ||||||
| 
 | 
 | ||||||
|     // Load device data
 |     // Load device data
 | ||||||
|     string_t path; |     string_set_str(dev->load_path, file_path); | ||||||
|     string_init_set_str(path, file_path); |     bool dev_load = nfc_device_load_data(dev, dev->load_path); | ||||||
|     bool dev_load = nfc_device_load_data(dev, path); |  | ||||||
|     if(dev_load) { |     if(dev_load) { | ||||||
|         // Set device name
 |         // Set device name
 | ||||||
|         path_extract_filename_no_ext(file_path, path); |         string_t filename; | ||||||
|         nfc_device_set_name(dev, string_get_cstr(path)); |         string_init(filename); | ||||||
|  |         path_extract_filename_no_ext(file_path, filename); | ||||||
|  |         nfc_device_set_name(dev, string_get_cstr(filename)); | ||||||
|  |         string_clear(filename); | ||||||
|     } |     } | ||||||
|     string_clear(path); |  | ||||||
| 
 | 
 | ||||||
|     return dev_load; |     return dev_load; | ||||||
| } | } | ||||||
| @ -880,23 +905,19 @@ bool nfc_device_load(NfcDevice* dev, const char* file_path) { | |||||||
| bool nfc_file_select(NfcDevice* dev) { | bool nfc_file_select(NfcDevice* dev) { | ||||||
|     furi_assert(dev); |     furi_assert(dev); | ||||||
| 
 | 
 | ||||||
|     // Input events and views are managed by file_select
 |     // Input events and views are managed by file_browser
 | ||||||
|     bool res = dialog_file_select_show( |     bool res = dialog_file_browser_show( | ||||||
|         dev->dialogs, |         dev->dialogs, dev->load_path, dev->load_path, NFC_APP_EXTENSION, true, &I_Nfc_10px, true); | ||||||
|         NFC_APP_FOLDER, |  | ||||||
|         NFC_APP_EXTENSION, |  | ||||||
|         dev->file_name, |  | ||||||
|         sizeof(dev->file_name), |  | ||||||
|         dev->dev_name); |  | ||||||
|     if(res) { |     if(res) { | ||||||
|         string_t dev_str; |         string_t filename; | ||||||
|         // Get key file path
 |         string_init(filename); | ||||||
|         string_init_printf(dev_str, "%s/%s%s", NFC_APP_FOLDER, dev->file_name, NFC_APP_EXTENSION); |         path_extract_filename(dev->load_path, filename, true); | ||||||
|         res = nfc_device_load_data(dev, dev_str); |         strncpy(dev->dev_name, string_get_cstr(filename), NFC_DEV_NAME_MAX_LEN); | ||||||
|  |         res = nfc_device_load_data(dev, dev->load_path); | ||||||
|         if(res) { |         if(res) { | ||||||
|             nfc_device_set_name(dev, dev->file_name); |             nfc_device_set_name(dev, dev->dev_name); | ||||||
|         } |         } | ||||||
|         string_clear(dev_str); |         string_clear(filename); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     return res; |     return res; | ||||||
| @ -914,9 +935,10 @@ void nfc_device_clear(NfcDevice* dev) { | |||||||
|     nfc_device_data_clear(&dev->dev_data); |     nfc_device_data_clear(&dev->dev_data); | ||||||
|     memset(&dev->dev_data, 0, sizeof(dev->dev_data)); |     memset(&dev->dev_data, 0, sizeof(dev->dev_data)); | ||||||
|     dev->format = NfcDeviceSaveFormatUid; |     dev->format = NfcDeviceSaveFormatUid; | ||||||
|  |     string_set_str(dev->load_path, NFC_APP_FOLDER); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| bool nfc_device_delete(NfcDevice* dev) { | bool nfc_device_delete(NfcDevice* dev, bool use_load_path) { | ||||||
|     furi_assert(dev); |     furi_assert(dev); | ||||||
| 
 | 
 | ||||||
|     bool deleted = false; |     bool deleted = false; | ||||||
| @ -925,12 +947,20 @@ bool nfc_device_delete(NfcDevice* dev) { | |||||||
| 
 | 
 | ||||||
|     do { |     do { | ||||||
|         // Delete original file
 |         // Delete original file
 | ||||||
|         string_init_printf(file_path, "%s/%s%s", NFC_APP_FOLDER, dev->dev_name, NFC_APP_EXTENSION); |         if(use_load_path && !string_empty_p(dev->load_path)) { | ||||||
|  |             string_set(file_path, dev->load_path); | ||||||
|  |         } else { | ||||||
|  |             string_printf(file_path, "%s/%s%s", NFC_APP_FOLDER, dev->dev_name, NFC_APP_EXTENSION); | ||||||
|  |         } | ||||||
|         if(!storage_simply_remove(dev->storage, string_get_cstr(file_path))) break; |         if(!storage_simply_remove(dev->storage, string_get_cstr(file_path))) break; | ||||||
|         // Delete shadow file if it exists
 |         // Delete shadow file if it exists
 | ||||||
|         if(dev->shadow_file_exist) { |         if(dev->shadow_file_exist) { | ||||||
|  |             if(use_load_path && !string_empty_p(dev->load_path)) { | ||||||
|  |                 nfc_device_get_shadow_path(dev->load_path, file_path); | ||||||
|  |             } else { | ||||||
|                 string_printf( |                 string_printf( | ||||||
|                     file_path, "%s/%s%s", NFC_APP_FOLDER, dev->dev_name, NFC_APP_SHADOW_EXTENSION); |                     file_path, "%s/%s%s", NFC_APP_FOLDER, dev->dev_name, NFC_APP_SHADOW_EXTENSION); | ||||||
|  |             } | ||||||
|             if(!storage_simply_remove(dev->storage, string_get_cstr(file_path))) break; |             if(!storage_simply_remove(dev->storage, string_get_cstr(file_path))) break; | ||||||
|         } |         } | ||||||
|         deleted = true; |         deleted = true; | ||||||
| @ -944,19 +974,29 @@ bool nfc_device_delete(NfcDevice* dev) { | |||||||
|     return deleted; |     return deleted; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| bool nfc_device_restore(NfcDevice* dev) { | bool nfc_device_restore(NfcDevice* dev, bool use_load_path) { | ||||||
|     furi_assert(dev); |     furi_assert(dev); | ||||||
|     furi_assert(dev->shadow_file_exist); |     furi_assert(dev->shadow_file_exist); | ||||||
| 
 | 
 | ||||||
|     bool restored = false; |     bool restored = false; | ||||||
|     string_t path; |     string_t path; | ||||||
| 
 | 
 | ||||||
|  |     string_init(path); | ||||||
|  | 
 | ||||||
|     do { |     do { | ||||||
|         string_init_printf( |         if(use_load_path && !string_empty_p(dev->load_path)) { | ||||||
|  |             nfc_device_get_shadow_path(dev->load_path, path); | ||||||
|  |         } else { | ||||||
|  |             string_printf( | ||||||
|                 path, "%s/%s%s", NFC_APP_FOLDER, dev->dev_name, NFC_APP_SHADOW_EXTENSION); |                 path, "%s/%s%s", NFC_APP_FOLDER, dev->dev_name, NFC_APP_SHADOW_EXTENSION); | ||||||
|  |         } | ||||||
|         if(!storage_simply_remove(dev->storage, string_get_cstr(path))) break; |         if(!storage_simply_remove(dev->storage, string_get_cstr(path))) break; | ||||||
|         dev->shadow_file_exist = false; |         dev->shadow_file_exist = false; | ||||||
|  |         if(use_load_path && !string_empty_p(dev->load_path)) { | ||||||
|  |             string_set(path, dev->load_path); | ||||||
|  |         } else { | ||||||
|             string_printf(path, "%s/%s%s", NFC_APP_FOLDER, dev->dev_name, NFC_APP_EXTENSION); |             string_printf(path, "%s/%s%s", NFC_APP_FOLDER, dev->dev_name, NFC_APP_EXTENSION); | ||||||
|  |         } | ||||||
|         if(!nfc_device_load_data(dev, path)) break; |         if(!nfc_device_load_data(dev, path)) break; | ||||||
|         restored = true; |         restored = true; | ||||||
|     } while(0); |     } while(0); | ||||||
|  | |||||||
| @ -12,7 +12,6 @@ | |||||||
| #include <lib/nfc_protocols/mifare_desfire.h> | #include <lib/nfc_protocols/mifare_desfire.h> | ||||||
| 
 | 
 | ||||||
| #define NFC_DEV_NAME_MAX_LEN 22 | #define NFC_DEV_NAME_MAX_LEN 22 | ||||||
| #define NFC_FILE_NAME_MAX_LEN 120 |  | ||||||
| #define NFC_READER_DATA_MAX_SIZE 64 | #define NFC_READER_DATA_MAX_SIZE 64 | ||||||
| 
 | 
 | ||||||
| #define NFC_APP_FOLDER "/any/nfc" | #define NFC_APP_FOLDER "/any/nfc" | ||||||
| @ -57,7 +56,7 @@ typedef struct { | |||||||
|     DialogsApp* dialogs; |     DialogsApp* dialogs; | ||||||
|     NfcDeviceData dev_data; |     NfcDeviceData dev_data; | ||||||
|     char dev_name[NFC_DEV_NAME_MAX_LEN + 1]; |     char dev_name[NFC_DEV_NAME_MAX_LEN + 1]; | ||||||
|     char file_name[NFC_FILE_NAME_MAX_LEN]; |     string_t load_path; | ||||||
|     NfcDeviceSaveFormat format; |     NfcDeviceSaveFormat format; | ||||||
|     bool shadow_file_exist; |     bool shadow_file_exist; | ||||||
| } NfcDevice; | } NfcDevice; | ||||||
| @ -80,6 +79,6 @@ void nfc_device_data_clear(NfcDeviceData* dev); | |||||||
| 
 | 
 | ||||||
| void nfc_device_clear(NfcDevice* dev); | void nfc_device_clear(NfcDevice* dev); | ||||||
| 
 | 
 | ||||||
| bool nfc_device_delete(NfcDevice* dev); | bool nfc_device_delete(NfcDevice* dev, bool use_load_path); | ||||||
| 
 | 
 | ||||||
| bool nfc_device_restore(NfcDevice* dev); | bool nfc_device_restore(NfcDevice* dev, bool use_load_path); | ||||||
|  | |||||||
| @ -73,7 +73,7 @@ bool nfc_scene_delete_on_event(void* context, SceneManagerEvent event) { | |||||||
|         if(event.event == GuiButtonTypeLeft) { |         if(event.event == GuiButtonTypeLeft) { | ||||||
|             return scene_manager_previous_scene(nfc->scene_manager); |             return scene_manager_previous_scene(nfc->scene_manager); | ||||||
|         } else if(event.event == GuiButtonTypeRight) { |         } else if(event.event == GuiButtonTypeRight) { | ||||||
|             if(nfc_device_delete(nfc->dev)) { |             if(nfc_device_delete(nfc->dev, true)) { | ||||||
|                 scene_manager_next_scene(nfc->scene_manager, NfcSceneDeleteSuccess); |                 scene_manager_next_scene(nfc->scene_manager, NfcSceneDeleteSuccess); | ||||||
|             } else { |             } else { | ||||||
|                 scene_manager_search_and_switch_to_previous_scene( |                 scene_manager_search_and_switch_to_previous_scene( | ||||||
|  | |||||||
							
								
								
									
										19
									
								
								applications/nfc/scenes/nfc_scene_save_name.c
									
									
									
									
									
										
										
										Executable file → Normal file
									
								
							
							
						
						
									
										19
									
								
								applications/nfc/scenes/nfc_scene_save_name.c
									
									
									
									
									
										
										
										Executable file → Normal file
									
								
							| @ -1,6 +1,8 @@ | |||||||
| #include "../nfc_i.h" | #include "../nfc_i.h" | ||||||
|  | #include "m-string.h" | ||||||
| #include <lib/toolbox/random_name.h> | #include <lib/toolbox/random_name.h> | ||||||
| #include <gui/modules/validators.h> | #include <gui/modules/validators.h> | ||||||
|  | #include <toolbox/path.h> | ||||||
| 
 | 
 | ||||||
| void nfc_scene_save_name_text_input_callback(void* context) { | void nfc_scene_save_name_text_input_callback(void* context) { | ||||||
|     Nfc* nfc = context; |     Nfc* nfc = context; | ||||||
| @ -29,11 +31,22 @@ void nfc_scene_save_name_on_enter(void* context) { | |||||||
|         NFC_DEV_NAME_MAX_LEN, |         NFC_DEV_NAME_MAX_LEN, | ||||||
|         dev_name_empty); |         dev_name_empty); | ||||||
| 
 | 
 | ||||||
|     ValidatorIsFile* validator_is_file = |     string_t folder_path; | ||||||
|         validator_is_file_alloc_init(NFC_APP_FOLDER, NFC_APP_EXTENSION, nfc->dev->dev_name); |     string_init(folder_path); | ||||||
|  | 
 | ||||||
|  |     if(string_end_with_str_p(nfc->dev->load_path, NFC_APP_EXTENSION)) { | ||||||
|  |         path_extract_dirname(string_get_cstr(nfc->dev->load_path), folder_path); | ||||||
|  |     } else { | ||||||
|  |         string_set_str(folder_path, NFC_APP_FOLDER); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     ValidatorIsFile* validator_is_file = validator_is_file_alloc_init( | ||||||
|  |         string_get_cstr(folder_path), NFC_APP_EXTENSION, nfc->dev->dev_name); | ||||||
|     text_input_set_validator(text_input, validator_is_file_callback, validator_is_file); |     text_input_set_validator(text_input, validator_is_file_callback, validator_is_file); | ||||||
| 
 | 
 | ||||||
|     view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewTextInput); |     view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewTextInput); | ||||||
|  | 
 | ||||||
|  |     string_clear(folder_path); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| bool nfc_scene_save_name_on_event(void* context, SceneManagerEvent event) { | bool nfc_scene_save_name_on_event(void* context, SceneManagerEvent event) { | ||||||
| @ -43,7 +56,7 @@ bool nfc_scene_save_name_on_event(void* context, SceneManagerEvent event) { | |||||||
|     if(event.type == SceneManagerEventTypeCustom) { |     if(event.type == SceneManagerEventTypeCustom) { | ||||||
|         if(event.event == NfcCustomEventTextInputDone) { |         if(event.event == NfcCustomEventTextInputDone) { | ||||||
|             if(strcmp(nfc->dev->dev_name, "")) { |             if(strcmp(nfc->dev->dev_name, "")) { | ||||||
|                 nfc_device_delete(nfc->dev); |                 nfc_device_delete(nfc->dev, true); | ||||||
|             } |             } | ||||||
|             if(scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneSetUid)) { |             if(scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneSetUid)) { | ||||||
|                 nfc->dev->dev_data.nfc_data = nfc->dev_edit_data; |                 nfc->dev->dev_data.nfc_data = nfc->dev_edit_data; | ||||||
|  | |||||||
| @ -30,9 +30,6 @@ bool nfc_scene_save_success_on_event(void* context, SceneManagerEvent event) { | |||||||
|             if(scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneCardMenu)) { |             if(scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneCardMenu)) { | ||||||
|                 consumed = scene_manager_search_and_switch_to_previous_scene( |                 consumed = scene_manager_search_and_switch_to_previous_scene( | ||||||
|                     nfc->scene_manager, NfcSceneCardMenu); |                     nfc->scene_manager, NfcSceneCardMenu); | ||||||
|             } else if(scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneSetType)) { |  | ||||||
|                 consumed = scene_manager_search_and_switch_to_another_scene( |  | ||||||
|                     nfc->scene_manager, NfcSceneFileSelect); |  | ||||||
|             } else if(scene_manager_has_previous_scene( |             } else if(scene_manager_has_previous_scene( | ||||||
|                           nfc->scene_manager, NfcSceneMifareDesfireMenu)) { |                           nfc->scene_manager, NfcSceneMifareDesfireMenu)) { | ||||||
|                 consumed = scene_manager_search_and_switch_to_previous_scene( |                 consumed = scene_manager_search_and_switch_to_previous_scene( | ||||||
|  | |||||||
| @ -78,7 +78,7 @@ bool nfc_scene_saved_menu_on_event(void* context, SceneManagerEvent event) { | |||||||
|             scene_manager_next_scene(nfc->scene_manager, NfcSceneDeviceInfo); |             scene_manager_next_scene(nfc->scene_manager, NfcSceneDeviceInfo); | ||||||
|             consumed = true; |             consumed = true; | ||||||
|         } else if(event.event == SubmenuIndexRestoreOriginal) { |         } else if(event.event == SubmenuIndexRestoreOriginal) { | ||||||
|             if(!nfc_device_restore(nfc->dev)) { |             if(!nfc_device_restore(nfc->dev, true)) { | ||||||
|                 scene_manager_search_and_switch_to_previous_scene( |                 scene_manager_search_and_switch_to_previous_scene( | ||||||
|                     nfc->scene_manager, NfcSceneStart); |                     nfc->scene_manager, NfcSceneStart); | ||||||
|             } else { |             } else { | ||||||
|  | |||||||
| @ -1,4 +1,5 @@ | |||||||
| #include "../nfc_i.h" | #include "../nfc_i.h" | ||||||
|  | #include "m-string.h" | ||||||
| 
 | 
 | ||||||
| enum SubmenuIndex { | enum SubmenuIndex { | ||||||
|     SubmenuIndexNFCA4, |     SubmenuIndexNFCA4, | ||||||
| @ -16,6 +17,7 @@ void nfc_scene_set_type_on_enter(void* context) { | |||||||
|     Submenu* submenu = nfc->submenu; |     Submenu* submenu = nfc->submenu; | ||||||
|     // Clear device name
 |     // Clear device name
 | ||||||
|     nfc_device_set_name(nfc->dev, ""); |     nfc_device_set_name(nfc->dev, ""); | ||||||
|  |     string_set_str(nfc->dev->load_path, ""); | ||||||
|     submenu_add_item( |     submenu_add_item( | ||||||
|         submenu, "NFC-A 7-bytes UID", SubmenuIndexNFCA7, nfc_scene_set_type_submenu_callback, nfc); |         submenu, "NFC-A 7-bytes UID", SubmenuIndexNFCA7, nfc_scene_set_type_submenu_callback, nfc); | ||||||
|     submenu_add_item( |     submenu_add_item( | ||||||
|  | |||||||
| @ -49,7 +49,7 @@ bool subghz_scene_delete_on_event(void* context, SceneManagerEvent event) { | |||||||
|     SubGhz* subghz = context; |     SubGhz* subghz = context; | ||||||
|     if(event.type == SceneManagerEventTypeCustom) { |     if(event.type == SceneManagerEventTypeCustom) { | ||||||
|         if(event.event == SubGhzCustomEventSceneDelete) { |         if(event.event == SubGhzCustomEventSceneDelete) { | ||||||
|             strncpy(subghz->file_path_tmp, subghz->file_path, SUBGHZ_MAX_LEN_NAME); |             string_set(subghz->file_path_tmp, subghz->file_path); | ||||||
|             if(subghz_delete_file(subghz)) { |             if(subghz_delete_file(subghz)) { | ||||||
|                 scene_manager_next_scene(subghz->scene_manager, SubGhzSceneDeleteSuccess); |                 scene_manager_next_scene(subghz->scene_manager, SubGhzSceneDeleteSuccess); | ||||||
|             } else { |             } else { | ||||||
|  | |||||||
| @ -24,7 +24,7 @@ void subghz_scene_delete_raw_on_enter(void* context) { | |||||||
|     char delete_str[SUBGHZ_MAX_LEN_NAME + 16]; |     char delete_str[SUBGHZ_MAX_LEN_NAME + 16]; | ||||||
|     string_t file_name; |     string_t file_name; | ||||||
|     string_init(file_name); |     string_init(file_name); | ||||||
|     path_extract_filename_no_ext(subghz->file_path, file_name); |     path_extract_filename(subghz->file_path, file_name, true); | ||||||
|     snprintf(delete_str, sizeof(delete_str), "\e#Delete %s?\e#", string_get_cstr(file_name)); |     snprintf(delete_str, sizeof(delete_str), "\e#Delete %s?\e#", string_get_cstr(file_name)); | ||||||
|     string_clear(file_name); |     string_clear(file_name); | ||||||
| 
 | 
 | ||||||
| @ -61,7 +61,7 @@ bool subghz_scene_delete_raw_on_event(void* context, SceneManagerEvent event) { | |||||||
|     SubGhz* subghz = context; |     SubGhz* subghz = context; | ||||||
|     if(event.type == SceneManagerEventTypeCustom) { |     if(event.type == SceneManagerEventTypeCustom) { | ||||||
|         if(event.event == SubGhzCustomEventSceneDeleteRAW) { |         if(event.event == SubGhzCustomEventSceneDeleteRAW) { | ||||||
|             strncpy(subghz->file_path_tmp, subghz->file_path, SUBGHZ_MAX_LEN_NAME); |             string_set(subghz->file_path_tmp, subghz->file_path); | ||||||
|             if(subghz_delete_file(subghz)) { |             if(subghz_delete_file(subghz)) { | ||||||
|                 scene_manager_next_scene(subghz->scene_manager, SubGhzSceneDeleteSuccess); |                 scene_manager_next_scene(subghz->scene_manager, SubGhzSceneDeleteSuccess); | ||||||
|             } else { |             } else { | ||||||
|  | |||||||
| @ -45,7 +45,7 @@ bool subghz_scene_more_raw_on_event(void* context, SceneManagerEvent event) { | |||||||
|             scene_manager_next_scene(subghz->scene_manager, SubGhzSceneDeleteRAW); |             scene_manager_next_scene(subghz->scene_manager, SubGhzSceneDeleteRAW); | ||||||
|             return true; |             return true; | ||||||
|         } else if(event.event == SubmenuIndexEdit) { |         } else if(event.event == SubmenuIndexEdit) { | ||||||
|             memset(subghz->file_path_tmp, 0, sizeof(subghz->file_path_tmp)); |             string_reset(subghz->file_path_tmp); | ||||||
|             scene_manager_set_scene_state( |             scene_manager_set_scene_state( | ||||||
|                 subghz->scene_manager, SubGhzSceneMoreRAW, SubmenuIndexEdit); |                 subghz->scene_manager, SubGhzSceneMoreRAW, SubmenuIndexEdit); | ||||||
|             scene_manager_next_scene(subghz->scene_manager, SubGhzSceneSaveName); |             scene_manager_next_scene(subghz->scene_manager, SubGhzSceneSaveName); | ||||||
|  | |||||||
| @ -23,7 +23,7 @@ bool subghz_scene_read_raw_update_filename(SubGhz* subghz) { | |||||||
|             break; |             break; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         strncpy(subghz->file_path, string_get_cstr(temp_str), SUBGHZ_MAX_LEN_NAME); |         string_set(subghz->file_path, temp_str); | ||||||
| 
 | 
 | ||||||
|         ret = true; |         ret = true; | ||||||
|     } while(false); |     } while(false); | ||||||
| @ -73,13 +73,13 @@ void subghz_scene_read_raw_on_enter(void* context) { | |||||||
|         subghz_read_raw_set_status(subghz->subghz_read_raw, SubGhzReadRAWStatusIDLE, ""); |         subghz_read_raw_set_status(subghz->subghz_read_raw, SubGhzReadRAWStatusIDLE, ""); | ||||||
|         break; |         break; | ||||||
|     case SubGhzRxKeyStateRAWLoad: |     case SubGhzRxKeyStateRAWLoad: | ||||||
|         path_extract_filename_no_ext(subghz->file_path, file_name); |         path_extract_filename(subghz->file_path, file_name, true); | ||||||
|         subghz_read_raw_set_status( |         subghz_read_raw_set_status( | ||||||
|             subghz->subghz_read_raw, SubGhzReadRAWStatusLoadKeyTX, string_get_cstr(file_name)); |             subghz->subghz_read_raw, SubGhzReadRAWStatusLoadKeyTX, string_get_cstr(file_name)); | ||||||
|         subghz->txrx->rx_key_state = SubGhzRxKeyStateIDLE; |         subghz->txrx->rx_key_state = SubGhzRxKeyStateIDLE; | ||||||
|         break; |         break; | ||||||
|     case SubGhzRxKeyStateRAWSave: |     case SubGhzRxKeyStateRAWSave: | ||||||
|         path_extract_filename_no_ext(subghz->file_path, file_name); |         path_extract_filename(subghz->file_path, file_name, true); | ||||||
|         subghz_read_raw_set_status( |         subghz_read_raw_set_status( | ||||||
|             subghz->subghz_read_raw, SubGhzReadRAWStatusSaveKey, string_get_cstr(file_name)); |             subghz->subghz_read_raw, SubGhzReadRAWStatusSaveKey, string_get_cstr(file_name)); | ||||||
|         subghz->txrx->rx_key_state = SubGhzRxKeyStateIDLE; |         subghz->txrx->rx_key_state = SubGhzRxKeyStateIDLE; | ||||||
| @ -273,7 +273,7 @@ bool subghz_scene_read_raw_on_event(void* context, SceneManagerEvent event) { | |||||||
|                     subghz->state_notifications = SubGhzNotificationStateRx; |                     subghz->state_notifications = SubGhzNotificationStateRx; | ||||||
|                     subghz->txrx->rx_key_state = SubGhzRxKeyStateAddKey; |                     subghz->txrx->rx_key_state = SubGhzRxKeyStateAddKey; | ||||||
|                 } else { |                 } else { | ||||||
|                     string_set(subghz->error_str, "Function requires\nan SD card."); |                     string_set_str(subghz->error_str, "Function requires\nan SD card."); | ||||||
|                     scene_manager_next_scene(subghz->scene_manager, SubGhzSceneShowError); |                     scene_manager_next_scene(subghz->scene_manager, SubGhzSceneShowError); | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|  | |||||||
| @ -1,4 +1,6 @@ | |||||||
| #include "../subghz_i.h" | #include "../subghz_i.h" | ||||||
|  | #include "m-string.h" | ||||||
|  | #include "subghz/types.h" | ||||||
| #include <lib/toolbox/random_name.h> | #include <lib/toolbox/random_name.h> | ||||||
| #include "../helpers/subghz_custom_event.h" | #include "../helpers/subghz_custom_event.h" | ||||||
| #include <lib/subghz/protocols/raw.h> | #include <lib/subghz/protocols/raw.h> | ||||||
| @ -20,45 +22,49 @@ void subghz_scene_save_name_on_enter(void* context) { | |||||||
|     bool dev_name_empty = false; |     bool dev_name_empty = false; | ||||||
| 
 | 
 | ||||||
|     string_t file_name; |     string_t file_name; | ||||||
|  |     string_t dir_name; | ||||||
|     string_init(file_name); |     string_init(file_name); | ||||||
|  |     string_init(dir_name); | ||||||
| 
 | 
 | ||||||
|     if(!strcmp(subghz->file_path, "")) { |     if(!subghz_path_is_file(subghz->file_path)) { | ||||||
|         char file_name_buf[SUBGHZ_MAX_LEN_NAME] = {0}; |         char file_name_buf[SUBGHZ_MAX_LEN_NAME] = {0}; | ||||||
|         set_random_name(file_name_buf, SUBGHZ_MAX_LEN_NAME); |         set_random_name(file_name_buf, SUBGHZ_MAX_LEN_NAME); | ||||||
|         string_set(file_name, file_name_buf); |         string_set_str(file_name, file_name_buf); | ||||||
|         strncpy(subghz->file_dir, SUBGHZ_APP_FOLDER, SUBGHZ_MAX_LEN_NAME); |         string_set_str(subghz->file_path, SUBGHZ_APP_FOLDER); | ||||||
|         //highlighting the entire filename by default
 |         //highlighting the entire filename by default
 | ||||||
|         dev_name_empty = true; |         dev_name_empty = true; | ||||||
|     } else { |     } else { | ||||||
|         strncpy(subghz->file_path_tmp, subghz->file_path, SUBGHZ_MAX_LEN_NAME); |         string_set(subghz->file_path_tmp, subghz->file_path); | ||||||
|         path_extract_dirname(subghz->file_path, file_name); |         path_extract_dirname(string_get_cstr(subghz->file_path), dir_name); | ||||||
|         strncpy(subghz->file_dir, string_get_cstr(file_name), SUBGHZ_MAX_LEN_NAME); |         path_extract_filename(subghz->file_path, file_name, true); | ||||||
|         path_extract_filename_no_ext(subghz->file_path, file_name); |  | ||||||
|         if(scene_manager_get_scene_state(subghz->scene_manager, SubGhzSceneReadRAW) != |         if(scene_manager_get_scene_state(subghz->scene_manager, SubGhzSceneReadRAW) != | ||||||
|            SubGhzCustomEventManagerNoSet) { |            SubGhzCustomEventManagerNoSet) { | ||||||
|             subghz_get_next_name_file(subghz, SUBGHZ_MAX_LEN_NAME); |             subghz_get_next_name_file(subghz, SUBGHZ_MAX_LEN_NAME); | ||||||
|             path_extract_filename_no_ext(subghz->file_path, file_name); |             path_extract_filename(subghz->file_path, file_name, true); | ||||||
|             if(scene_manager_get_scene_state(subghz->scene_manager, SubGhzSceneReadRAW) == |             if(scene_manager_get_scene_state(subghz->scene_manager, SubGhzSceneReadRAW) == | ||||||
|                SubGhzCustomEventManagerSetRAW) { |                SubGhzCustomEventManagerSetRAW) { | ||||||
|                 dev_name_empty = true; |                 dev_name_empty = true; | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|  |         string_set(subghz->file_path, dir_name); | ||||||
|     } |     } | ||||||
|     strncpy(subghz->file_path, string_get_cstr(file_name), SUBGHZ_MAX_LEN_NAME); | 
 | ||||||
|  |     strncpy(subghz->file_name_tmp, string_get_cstr(file_name), SUBGHZ_MAX_LEN_NAME); | ||||||
|     text_input_set_header_text(text_input, "Name signal"); |     text_input_set_header_text(text_input, "Name signal"); | ||||||
|     text_input_set_result_callback( |     text_input_set_result_callback( | ||||||
|         text_input, |         text_input, | ||||||
|         subghz_scene_save_name_text_input_callback, |         subghz_scene_save_name_text_input_callback, | ||||||
|         subghz, |         subghz, | ||||||
|         subghz->file_path, |         subghz->file_name_tmp, | ||||||
|         MAX_TEXT_INPUT_LEN, // buffer size
 |         MAX_TEXT_INPUT_LEN, // buffer size
 | ||||||
|         dev_name_empty); |         dev_name_empty); | ||||||
| 
 | 
 | ||||||
|     ValidatorIsFile* validator_is_file = |     ValidatorIsFile* validator_is_file = validator_is_file_alloc_init( | ||||||
|         validator_is_file_alloc_init(subghz->file_dir, SUBGHZ_APP_EXTENSION, NULL); |         string_get_cstr(subghz->file_path), SUBGHZ_APP_EXTENSION, NULL); | ||||||
|     text_input_set_validator(text_input, validator_is_file_callback, validator_is_file); |     text_input_set_validator(text_input, validator_is_file_callback, validator_is_file); | ||||||
| 
 | 
 | ||||||
|     string_clear(file_name); |     string_clear(file_name); | ||||||
|  |     string_clear(dir_name); | ||||||
| 
 | 
 | ||||||
|     view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewIdTextInput); |     view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewIdTextInput); | ||||||
| } | } | ||||||
| @ -66,18 +72,15 @@ void subghz_scene_save_name_on_enter(void* context) { | |||||||
| bool subghz_scene_save_name_on_event(void* context, SceneManagerEvent event) { | bool subghz_scene_save_name_on_event(void* context, SceneManagerEvent event) { | ||||||
|     SubGhz* subghz = context; |     SubGhz* subghz = context; | ||||||
|     if(event.type == SceneManagerEventTypeBack) { |     if(event.type == SceneManagerEventTypeBack) { | ||||||
|         strncpy(subghz->file_path, subghz->file_path_tmp, SUBGHZ_MAX_LEN_NAME); |         string_set(subghz->file_path, subghz->file_path_tmp); | ||||||
|         scene_manager_previous_scene(subghz->scene_manager); |         scene_manager_previous_scene(subghz->scene_manager); | ||||||
|         return true; |         return true; | ||||||
|     } else if(event.type == SceneManagerEventTypeCustom) { |     } else if(event.type == SceneManagerEventTypeCustom) { | ||||||
|         if(event.event == SubGhzCustomEventSceneSaveName) { |         if(event.event == SubGhzCustomEventSceneSaveName) { | ||||||
|             if(strcmp(subghz->file_path, "")) { |             if(strcmp(subghz->file_name_tmp, "")) { | ||||||
|                 string_t temp_str; |                 string_cat_printf( | ||||||
|                 string_init_printf( |                     subghz->file_path, "/%s%s", subghz->file_name_tmp, SUBGHZ_APP_EXTENSION); | ||||||
|                     temp_str, "%s/%s%s", subghz->file_dir, subghz->file_path, SUBGHZ_APP_EXTENSION); |                 if(subghz_path_is_file(subghz->file_path_tmp)) { | ||||||
|                 strncpy(subghz->file_path, string_get_cstr(temp_str), SUBGHZ_MAX_LEN_NAME); |  | ||||||
|                 string_clear(temp_str); |  | ||||||
|                 if(strcmp(subghz->file_path_tmp, "")) { |  | ||||||
|                     if(!subghz_rename_file(subghz)) { |                     if(!subghz_rename_file(subghz)) { | ||||||
|                         return false; |                         return false; | ||||||
|                     } |                     } | ||||||
| @ -85,7 +88,7 @@ bool subghz_scene_save_name_on_event(void* context, SceneManagerEvent event) { | |||||||
|                     if(scene_manager_get_scene_state(subghz->scene_manager, SubGhzSceneSetType) != |                     if(scene_manager_get_scene_state(subghz->scene_manager, SubGhzSceneSetType) != | ||||||
|                        SubGhzCustomEventManagerNoSet) { |                        SubGhzCustomEventManagerNoSet) { | ||||||
|                         subghz_save_protocol_to_file( |                         subghz_save_protocol_to_file( | ||||||
|                             subghz, subghz->txrx->fff_data, subghz->file_path); |                             subghz, subghz->txrx->fff_data, string_get_cstr(subghz->file_path)); | ||||||
|                         scene_manager_set_scene_state( |                         scene_manager_set_scene_state( | ||||||
|                             subghz->scene_manager, |                             subghz->scene_manager, | ||||||
|                             SubGhzSceneSetType, |                             SubGhzSceneSetType, | ||||||
| @ -95,13 +98,14 @@ bool subghz_scene_save_name_on_event(void* context, SceneManagerEvent event) { | |||||||
|                             subghz, |                             subghz, | ||||||
|                             subghz_history_get_raw_data( |                             subghz_history_get_raw_data( | ||||||
|                                 subghz->txrx->history, subghz->txrx->idx_menu_chosen), |                                 subghz->txrx->history, subghz->txrx->idx_menu_chosen), | ||||||
|                             subghz->file_path); |                             string_get_cstr(subghz->file_path)); | ||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
| 
 | 
 | ||||||
|                 if(scene_manager_get_scene_state(subghz->scene_manager, SubGhzSceneReadRAW) != |                 if(scene_manager_get_scene_state(subghz->scene_manager, SubGhzSceneReadRAW) != | ||||||
|                    SubGhzCustomEventManagerNoSet) { |                    SubGhzCustomEventManagerNoSet) { | ||||||
|                     subghz_protocol_raw_gen_fff_data(subghz->txrx->fff_data, subghz->file_path); |                     subghz_protocol_raw_gen_fff_data( | ||||||
|  |                         subghz->txrx->fff_data, string_get_cstr(subghz->file_path)); | ||||||
|                     scene_manager_set_scene_state( |                     scene_manager_set_scene_state( | ||||||
|                         subghz->scene_manager, SubGhzSceneReadRAW, SubGhzCustomEventManagerNoSet); |                         subghz->scene_manager, SubGhzSceneReadRAW, SubGhzCustomEventManagerNoSet); | ||||||
|                 } else { |                 } else { | ||||||
| @ -111,7 +115,7 @@ bool subghz_scene_save_name_on_event(void* context, SceneManagerEvent event) { | |||||||
|                 scene_manager_next_scene(subghz->scene_manager, SubGhzSceneSaveSuccess); |                 scene_manager_next_scene(subghz->scene_manager, SubGhzSceneSaveSuccess); | ||||||
|                 return true; |                 return true; | ||||||
|             } else { |             } else { | ||||||
|                 string_set(subghz->error_str, "No name file"); |                 string_set_str(subghz->error_str, "No name file"); | ||||||
|                 scene_manager_next_scene(subghz->scene_manager, SubGhzSceneShowErrorSub); |                 scene_manager_next_scene(subghz->scene_manager, SubGhzSceneShowErrorSub); | ||||||
|                 return true; |                 return true; | ||||||
|             } |             } | ||||||
|  | |||||||
| @ -39,7 +39,7 @@ bool subghz_scene_set_type_submenu_gen_data_protocol( | |||||||
|         subghz_receiver_search_decoder_base_by_name(subghz->txrx->receiver, protocol_name); |         subghz_receiver_search_decoder_base_by_name(subghz->txrx->receiver, protocol_name); | ||||||
| 
 | 
 | ||||||
|     if(subghz->txrx->decoder_result == NULL) { |     if(subghz->txrx->decoder_result == NULL) { | ||||||
|         string_set(subghz->error_str, "Protocol not found"); |         string_set_str(subghz->error_str, "Protocol not found"); | ||||||
|         scene_manager_next_scene(subghz->scene_manager, SubGhzSceneShowErrorSub); |         scene_manager_next_scene(subghz->scene_manager, SubGhzSceneShowErrorSub); | ||||||
|         return false; |         return false; | ||||||
|     } |     } | ||||||
| @ -282,7 +282,7 @@ bool subghz_scene_set_type_on_event(void* context, SceneManagerEvent event) { | |||||||
|             } |             } | ||||||
|             subghz_transmitter_free(subghz->txrx->transmitter); |             subghz_transmitter_free(subghz->txrx->transmitter); | ||||||
|             if(!generated_protocol) { |             if(!generated_protocol) { | ||||||
|                 string_set( |                 string_set_str( | ||||||
|                     subghz->error_str, "Function requires\nan SD card with\nfresh databases."); |                     subghz->error_str, "Function requires\nan SD card with\nfresh databases."); | ||||||
|                 scene_manager_next_scene(subghz->scene_manager, SubGhzSceneShowError); |                 scene_manager_next_scene(subghz->scene_manager, SubGhzSceneShowError); | ||||||
|             } |             } | ||||||
| @ -306,7 +306,7 @@ bool subghz_scene_set_type_on_event(void* context, SceneManagerEvent event) { | |||||||
|             } |             } | ||||||
|             subghz_transmitter_free(subghz->txrx->transmitter); |             subghz_transmitter_free(subghz->txrx->transmitter); | ||||||
|             if(!generated_protocol) { |             if(!generated_protocol) { | ||||||
|                 string_set( |                 string_set_str( | ||||||
|                     subghz->error_str, "Function requires\nan SD card with\nfresh databases."); |                     subghz->error_str, "Function requires\nan SD card with\nfresh databases."); | ||||||
|                 scene_manager_next_scene(subghz->scene_manager, SubGhzSceneShowError); |                 scene_manager_next_scene(subghz->scene_manager, SubGhzSceneShowError); | ||||||
|             } |             } | ||||||
|  | |||||||
| @ -94,7 +94,7 @@ bool subghz_scene_transmitter_on_event(void* context, SceneManagerEvent event) { | |||||||
|                 subghz->scene_manager, SubGhzSceneStart); |                 subghz->scene_manager, SubGhzSceneStart); | ||||||
|             return true; |             return true; | ||||||
|         } else if(event.event == SubGhzCustomEventViewTransmitterError) { |         } else if(event.event == SubGhzCustomEventViewTransmitterError) { | ||||||
|             string_set(subghz->error_str, "Protocol not found"); |             string_set_str(subghz->error_str, "Protocol not found"); | ||||||
|             scene_manager_next_scene(subghz->scene_manager, SubGhzSceneShowErrorSub); |             scene_manager_next_scene(subghz->scene_manager, SubGhzSceneShowErrorSub); | ||||||
|         } |         } | ||||||
|     } else if(event.type == SceneManagerEventTypeTick) { |     } else if(event.type == SceneManagerEventTypeTick) { | ||||||
|  | |||||||
| @ -1,5 +1,7 @@ | |||||||
| /* Abandon hope, all ye who enter here. */ | /* Abandon hope, all ye who enter here. */ | ||||||
| 
 | 
 | ||||||
|  | #include "m-string.h" | ||||||
|  | #include "subghz/types.h" | ||||||
| #include "subghz_i.h" | #include "subghz_i.h" | ||||||
| #include <lib/toolbox/path.h> | #include <lib/toolbox/path.h> | ||||||
| 
 | 
 | ||||||
| @ -24,6 +26,9 @@ void subghz_tick_event_callback(void* context) { | |||||||
| SubGhz* subghz_alloc() { | SubGhz* subghz_alloc() { | ||||||
|     SubGhz* subghz = malloc(sizeof(SubGhz)); |     SubGhz* subghz = malloc(sizeof(SubGhz)); | ||||||
| 
 | 
 | ||||||
|  |     string_init(subghz->file_path); | ||||||
|  |     string_init(subghz->file_path_tmp); | ||||||
|  | 
 | ||||||
|     // GUI
 |     // GUI
 | ||||||
|     subghz->gui = furi_record_open("gui"); |     subghz->gui = furi_record_open("gui"); | ||||||
| 
 | 
 | ||||||
| @ -241,9 +246,9 @@ void subghz_free(SubGhz* subghz) { | |||||||
|     furi_record_close("notification"); |     furi_record_close("notification"); | ||||||
|     subghz->notifications = NULL; |     subghz->notifications = NULL; | ||||||
| 
 | 
 | ||||||
|     // About birds
 |     // Path strings
 | ||||||
|     furi_assert(subghz->file_path[SUBGHZ_MAX_LEN_NAME] == 0); |     string_clear(subghz->file_path); | ||||||
|     furi_assert(subghz->file_path_tmp[SUBGHZ_MAX_LEN_NAME] == 0); |     string_clear(subghz->file_path_tmp); | ||||||
| 
 | 
 | ||||||
|     // The rest
 |     // The rest
 | ||||||
|     free(subghz); |     free(subghz); | ||||||
| @ -260,7 +265,7 @@ int32_t subghz_app(void* p) { | |||||||
|     // Check argument and run corresponding scene
 |     // Check argument and run corresponding scene
 | ||||||
|     if(p) { |     if(p) { | ||||||
|         if(subghz_key_load(subghz, p)) { |         if(subghz_key_load(subghz, p)) { | ||||||
|             strncpy(subghz->file_path, p, SUBGHZ_MAX_LEN_NAME); |             string_set_str(subghz->file_path, p); | ||||||
| 
 | 
 | ||||||
|             if((!strcmp(subghz->txrx->decoder_result->protocol->name, "RAW"))) { |             if((!strcmp(subghz->txrx->decoder_result->protocol->name, "RAW"))) { | ||||||
|                 //Load Raw TX
 |                 //Load Raw TX
 | ||||||
| @ -276,12 +281,13 @@ int32_t subghz_app(void* p) { | |||||||
|             view_dispatcher_stop(subghz->view_dispatcher); |             view_dispatcher_stop(subghz->view_dispatcher); | ||||||
|         } |         } | ||||||
|     } else { |     } else { | ||||||
|  |         string_set_str(subghz->file_path, SUBGHZ_APP_FOLDER); | ||||||
|         if(load_database) { |         if(load_database) { | ||||||
|             scene_manager_next_scene(subghz->scene_manager, SubGhzSceneStart); |             scene_manager_next_scene(subghz->scene_manager, SubGhzSceneStart); | ||||||
|         } else { |         } else { | ||||||
|             scene_manager_set_scene_state( |             scene_manager_set_scene_state( | ||||||
|                 subghz->scene_manager, SubGhzSceneShowError, SubGhzCustomEventManagerSet); |                 subghz->scene_manager, SubGhzSceneShowError, SubGhzCustomEventManagerSet); | ||||||
|             string_set( |             string_set_str( | ||||||
|                 subghz->error_str, |                 subghz->error_str, | ||||||
|                 "No SD card or\ndatabase found.\nSome app function\nmay be reduced."); |                 "No SD card or\ndatabase found.\nSome app function\nmay be reduced."); | ||||||
|             scene_manager_next_scene(subghz->scene_manager, SubGhzSceneShowError); |             scene_manager_next_scene(subghz->scene_manager, SubGhzSceneShowError); | ||||||
|  | |||||||
| @ -303,7 +303,7 @@ void subghz_cli_command_decode_raw(Cli* cli, string_t args, void* context) { | |||||||
|     UNUSED(context); |     UNUSED(context); | ||||||
|     string_t file_name; |     string_t file_name; | ||||||
|     string_init(file_name); |     string_init(file_name); | ||||||
|     string_set(file_name, "/any/subghz/test.sub"); |     string_set_str(file_name, "/any/subghz/test.sub"); | ||||||
| 
 | 
 | ||||||
|     Storage* storage = furi_record_open("storage"); |     Storage* storage = furi_record_open("storage"); | ||||||
|     FlipperFormat* fff_data_file = flipper_format_file_alloc(storage); |     FlipperFormat* fff_data_file = flipper_format_file_alloc(storage); | ||||||
|  | |||||||
| @ -169,14 +169,14 @@ bool subghz_history_add_to_history( | |||||||
|             break; |             break; | ||||||
|         } |         } | ||||||
|         if(!strcmp(string_get_cstr(instance->tmp_string), "KeeLoq")) { |         if(!strcmp(string_get_cstr(instance->tmp_string), "KeeLoq")) { | ||||||
|             string_set(instance->tmp_string, "KL "); |             string_set_str(instance->tmp_string, "KL "); | ||||||
|             if(!flipper_format_read_string(item->flipper_string, "Manufacture", text)) { |             if(!flipper_format_read_string(item->flipper_string, "Manufacture", text)) { | ||||||
|                 FURI_LOG_E(TAG, "Missing Protocol"); |                 FURI_LOG_E(TAG, "Missing Protocol"); | ||||||
|                 break; |                 break; | ||||||
|             } |             } | ||||||
|             string_cat(instance->tmp_string, text); |             string_cat(instance->tmp_string, text); | ||||||
|         } else if(!strcmp(string_get_cstr(instance->tmp_string), "Star Line")) { |         } else if(!strcmp(string_get_cstr(instance->tmp_string), "Star Line")) { | ||||||
|             string_set(instance->tmp_string, "SL "); |             string_set_str(instance->tmp_string, "SL "); | ||||||
|             if(!flipper_format_read_string(item->flipper_string, "Manufacture", text)) { |             if(!flipper_format_read_string(item->flipper_string, "Manufacture", text)) { | ||||||
|                 FURI_LOG_E(TAG, "Missing Protocol"); |                 FURI_LOG_E(TAG, "Missing Protocol"); | ||||||
|                 break; |                 break; | ||||||
|  | |||||||
| @ -1,5 +1,8 @@ | |||||||
| #include "subghz_i.h" | #include "subghz_i.h" | ||||||
| 
 | 
 | ||||||
|  | #include "assets_icons.h" | ||||||
|  | #include "m-string.h" | ||||||
|  | #include "subghz/types.h" | ||||||
| #include <math.h> | #include <math.h> | ||||||
| #include <furi.h> | #include <furi.h> | ||||||
| #include <furi_hal.h> | #include <furi_hal.h> | ||||||
| @ -45,11 +48,11 @@ void subghz_get_frequency_modulation(SubGhz* subghz, string_t frequency, string_ | |||||||
|     if(modulation != NULL) { |     if(modulation != NULL) { | ||||||
|         if(subghz->txrx->preset == FuriHalSubGhzPresetOok650Async || |         if(subghz->txrx->preset == FuriHalSubGhzPresetOok650Async || | ||||||
|            subghz->txrx->preset == FuriHalSubGhzPresetOok270Async) { |            subghz->txrx->preset == FuriHalSubGhzPresetOok270Async) { | ||||||
|             string_set(modulation, "AM"); |             string_set_str(modulation, "AM"); | ||||||
|         } else if( |         } else if( | ||||||
|             subghz->txrx->preset == FuriHalSubGhzPreset2FSKDev238Async || |             subghz->txrx->preset == FuriHalSubGhzPreset2FSKDev238Async || | ||||||
|             subghz->txrx->preset == FuriHalSubGhzPreset2FSKDev476Async) { |             subghz->txrx->preset == FuriHalSubGhzPreset2FSKDev476Async) { | ||||||
|             string_set(modulation, "FM"); |             string_set_str(modulation, "FM"); | ||||||
|         } else { |         } else { | ||||||
|             furi_crash("SugGhz: Modulation is incorrect."); |             furi_crash("SugGhz: Modulation is incorrect."); | ||||||
|         } |         } | ||||||
| @ -189,8 +192,9 @@ void subghz_tx_stop(SubGhz* subghz) { | |||||||
| 
 | 
 | ||||||
|     //if protocol dynamic then we save the last upload
 |     //if protocol dynamic then we save the last upload
 | ||||||
|     if((subghz->txrx->decoder_result->protocol->type == SubGhzProtocolTypeDynamic) && |     if((subghz->txrx->decoder_result->protocol->type == SubGhzProtocolTypeDynamic) && | ||||||
|        (strcmp(subghz->file_path, ""))) { |        (subghz_path_is_file(subghz->file_path))) { | ||||||
|         subghz_save_protocol_to_file(subghz, subghz->txrx->fff_data, subghz->file_path); |         subghz_save_protocol_to_file( | ||||||
|  |             subghz, subghz->txrx->fff_data, string_get_cstr(subghz->file_path)); | ||||||
|     } |     } | ||||||
|     subghz_idle(subghz); |     subghz_idle(subghz); | ||||||
|     notification_message(subghz->notifications, &sequence_reset_red); |     notification_message(subghz->notifications, &sequence_reset_red); | ||||||
| @ -332,10 +336,10 @@ bool subghz_get_next_name_file(SubGhz* subghz, uint8_t max_len) { | |||||||
| 
 | 
 | ||||||
|     bool res = false; |     bool res = false; | ||||||
| 
 | 
 | ||||||
|     if(strcmp(subghz->file_path, "")) { |     if(subghz_path_is_file(subghz->file_path)) { | ||||||
|         //get the name of the next free file
 |         //get the name of the next free file
 | ||||||
|         path_extract_filename_no_ext(subghz->file_path, file_name); |         path_extract_filename(subghz->file_path, file_name, true); | ||||||
|         path_extract_dirname(subghz->file_path, file_path); |         path_extract_dirname(string_get_cstr(subghz->file_path), file_path); | ||||||
| 
 | 
 | ||||||
|         storage_get_next_filename( |         storage_get_next_filename( | ||||||
|             storage, |             storage, | ||||||
| @ -351,7 +355,7 @@ bool subghz_get_next_name_file(SubGhz* subghz, uint8_t max_len) { | |||||||
|             string_get_cstr(file_path), |             string_get_cstr(file_path), | ||||||
|             string_get_cstr(file_name), |             string_get_cstr(file_name), | ||||||
|             SUBGHZ_APP_EXTENSION); |             SUBGHZ_APP_EXTENSION); | ||||||
|         strncpy(subghz->file_path, string_get_cstr(temp_str), SUBGHZ_MAX_LEN_NAME); |         string_set(subghz->file_path, temp_str); | ||||||
|         res = true; |         res = true; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| @ -411,19 +415,17 @@ bool subghz_load_protocol_from_file(SubGhz* subghz) { | |||||||
|     string_init(file_path); |     string_init(file_path); | ||||||
| 
 | 
 | ||||||
|     // Input events and views are managed by file_select
 |     // Input events and views are managed by file_select
 | ||||||
|     bool res = dialog_file_select_show( |     bool res = dialog_file_browser_show( | ||||||
|         subghz->dialogs, |         subghz->dialogs, | ||||||
|         SUBGHZ_APP_FOLDER, |  | ||||||
|         SUBGHZ_APP_EXTENSION, |  | ||||||
|         subghz->file_path, |         subghz->file_path, | ||||||
|         sizeof(subghz->file_path), |         subghz->file_path, | ||||||
|         NULL); |         SUBGHZ_APP_EXTENSION, | ||||||
|  |         true, | ||||||
|  |         &I_sub1_10px, | ||||||
|  |         true); | ||||||
| 
 | 
 | ||||||
|     if(res) { |     if(res) { | ||||||
|         string_printf( |         res = subghz_key_load(subghz, string_get_cstr(subghz->file_path)); | ||||||
|             file_path, "%s/%s%s", SUBGHZ_APP_FOLDER, subghz->file_path, SUBGHZ_APP_EXTENSION); |  | ||||||
|         strncpy(subghz->file_path, string_get_cstr(file_path), SUBGHZ_MAX_LEN_NAME); |  | ||||||
|         res = subghz_key_load(subghz, subghz->file_path); |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     string_clear(file_path); |     string_clear(file_path); | ||||||
| @ -437,9 +439,9 @@ bool subghz_rename_file(SubGhz* subghz) { | |||||||
| 
 | 
 | ||||||
|     Storage* storage = furi_record_open("storage"); |     Storage* storage = furi_record_open("storage"); | ||||||
| 
 | 
 | ||||||
|     if(strcmp(subghz->file_path_tmp, subghz->file_path)) { |     if(string_cmp(subghz->file_path_tmp, subghz->file_path)) { | ||||||
|         FS_Error fs_result = |         FS_Error fs_result = storage_common_rename( | ||||||
|             storage_common_rename(storage, subghz->file_path_tmp, subghz->file_path); |             storage, string_get_cstr(subghz->file_path_tmp), string_get_cstr(subghz->file_path)); | ||||||
| 
 | 
 | ||||||
|         if(fs_result != FSE_OK) { |         if(fs_result != FSE_OK) { | ||||||
|             dialog_message_show_storage_error(subghz->dialogs, "Cannot rename\n file/directory"); |             dialog_message_show_storage_error(subghz->dialogs, "Cannot rename\n file/directory"); | ||||||
| @ -455,7 +457,7 @@ bool subghz_delete_file(SubGhz* subghz) { | |||||||
|     furi_assert(subghz); |     furi_assert(subghz); | ||||||
| 
 | 
 | ||||||
|     Storage* storage = furi_record_open("storage"); |     Storage* storage = furi_record_open("storage"); | ||||||
|     bool result = storage_simply_remove(storage, subghz->file_path_tmp); |     bool result = storage_simply_remove(storage, string_get_cstr(subghz->file_path_tmp)); | ||||||
|     furi_record_close("storage"); |     furi_record_close("storage"); | ||||||
| 
 | 
 | ||||||
|     subghz_file_name_clear(subghz); |     subghz_file_name_clear(subghz); | ||||||
| @ -465,8 +467,12 @@ bool subghz_delete_file(SubGhz* subghz) { | |||||||
| 
 | 
 | ||||||
| void subghz_file_name_clear(SubGhz* subghz) { | void subghz_file_name_clear(SubGhz* subghz) { | ||||||
|     furi_assert(subghz); |     furi_assert(subghz); | ||||||
|     memset(subghz->file_path, 0, sizeof(subghz->file_path)); |     string_set_str(subghz->file_path, SUBGHZ_APP_FOLDER); | ||||||
|     memset(subghz->file_path_tmp, 0, sizeof(subghz->file_path_tmp)); |     string_reset(subghz->file_path_tmp); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool subghz_path_is_file(string_t path) { | ||||||
|  |     return string_end_with_str_p(path, SUBGHZ_APP_EXTENSION); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| uint32_t subghz_random_serial(void) { | uint32_t subghz_random_serial(void) { | ||||||
|  | |||||||
| @ -35,7 +35,7 @@ | |||||||
| #include <gui/modules/variable_item_list.h> | #include <gui/modules/variable_item_list.h> | ||||||
| #include <lib/toolbox/path.h> | #include <lib/toolbox/path.h> | ||||||
| 
 | 
 | ||||||
| #define SUBGHZ_MAX_LEN_NAME 250 | #define SUBGHZ_MAX_LEN_NAME 64 | ||||||
| 
 | 
 | ||||||
| /** SubGhzNotification state */ | /** SubGhzNotification state */ | ||||||
| typedef enum { | typedef enum { | ||||||
| @ -119,10 +119,9 @@ struct SubGhz { | |||||||
|     TextInput* text_input; |     TextInput* text_input; | ||||||
|     Widget* widget; |     Widget* widget; | ||||||
|     DialogsApp* dialogs; |     DialogsApp* dialogs; | ||||||
|     char file_path[SUBGHZ_MAX_LEN_NAME + 1]; |     string_t file_path; | ||||||
|     char file_path_tmp[SUBGHZ_MAX_LEN_NAME + 1]; |     string_t file_path_tmp; | ||||||
|     //ToDo you can get rid of it, you need to refactor text input to return the path to the folder
 |     char file_name_tmp[SUBGHZ_MAX_LEN_NAME]; | ||||||
|     char file_dir[SUBGHZ_MAX_LEN_NAME + 1]; |  | ||||||
|     SubGhzNotificationState state_notifications; |     SubGhzNotificationState state_notifications; | ||||||
| 
 | 
 | ||||||
|     SubGhzViewReceiver* subghz_receiver; |     SubGhzViewReceiver* subghz_receiver; | ||||||
| @ -173,5 +172,6 @@ bool subghz_load_protocol_from_file(SubGhz* subghz); | |||||||
| bool subghz_rename_file(SubGhz* subghz); | bool subghz_rename_file(SubGhz* subghz); | ||||||
| bool subghz_delete_file(SubGhz* subghz); | bool subghz_delete_file(SubGhz* subghz); | ||||||
| void subghz_file_name_clear(SubGhz* subghz); | void subghz_file_name_clear(SubGhz* subghz); | ||||||
|  | bool subghz_path_is_file(string_t path); | ||||||
| uint32_t subghz_random_serial(void); | uint32_t subghz_random_serial(void); | ||||||
| void subghz_hopper_update(SubGhz* subghz); | void subghz_hopper_update(SubGhz* subghz); | ||||||
|  | |||||||
| @ -111,9 +111,9 @@ void subghz_view_receiver_add_data_statusbar( | |||||||
|     furi_assert(subghz_receiver); |     furi_assert(subghz_receiver); | ||||||
|     with_view_model( |     with_view_model( | ||||||
|         subghz_receiver->view, (SubGhzViewReceiverModel * model) { |         subghz_receiver->view, (SubGhzViewReceiverModel * model) { | ||||||
|             string_set(model->frequency_str, frequency_str); |             string_set_str(model->frequency_str, frequency_str); | ||||||
|             string_set(model->preset_str, preset_str); |             string_set_str(model->preset_str, preset_str); | ||||||
|             string_set(model->history_stat_str, history_stat_str); |             string_set_str(model->history_stat_str, history_stat_str); | ||||||
|             return true; |             return true; | ||||||
|         }); |         }); | ||||||
| } | } | ||||||
|  | |||||||
| @ -46,8 +46,8 @@ void subghz_read_raw_add_data_statusbar( | |||||||
|     furi_assert(instance); |     furi_assert(instance); | ||||||
|     with_view_model( |     with_view_model( | ||||||
|         instance->view, (SubGhzReadRAWModel * model) { |         instance->view, (SubGhzReadRAWModel * model) { | ||||||
|             string_set(model->frequency_str, frequency_str); |             string_set_str(model->frequency_str, frequency_str); | ||||||
|             string_set(model->preset_str, preset_str); |             string_set_str(model->preset_str, preset_str); | ||||||
|             return true; |             return true; | ||||||
|         }); |         }); | ||||||
| } | } | ||||||
| @ -372,7 +372,7 @@ bool subghz_read_raw_input(InputEvent* event, void* context) { | |||||||
|                     model->satus = SubGhzReadRAWStatusStart; |                     model->satus = SubGhzReadRAWStatusStart; | ||||||
|                     model->rssi_history_end = false; |                     model->rssi_history_end = false; | ||||||
|                     model->ind_write = 0; |                     model->ind_write = 0; | ||||||
|                     string_set(model->sample_write, "0 spl."); |                     string_set_str(model->sample_write, "0 spl."); | ||||||
|                     string_reset(model->file_name); |                     string_reset(model->file_name); | ||||||
|                     instance->callback(SubGhzCustomEventViewReadRAWErase, instance->context); |                     instance->callback(SubGhzCustomEventViewReadRAWErase, instance->context); | ||||||
|                 } |                 } | ||||||
| @ -424,7 +424,7 @@ void subghz_read_raw_set_status( | |||||||
|                 model->rssi_history_end = false; |                 model->rssi_history_end = false; | ||||||
|                 model->ind_write = 0; |                 model->ind_write = 0; | ||||||
|                 string_reset(model->file_name); |                 string_reset(model->file_name); | ||||||
|                 string_set(model->sample_write, "0 spl."); |                 string_set_str(model->sample_write, "0 spl."); | ||||||
|                 return true; |                 return true; | ||||||
|             }); |             }); | ||||||
|         break; |         break; | ||||||
| @ -441,8 +441,8 @@ void subghz_read_raw_set_status( | |||||||
|                 model->satus = SubGhzReadRAWStatusLoadKeyIDLE; |                 model->satus = SubGhzReadRAWStatusLoadKeyIDLE; | ||||||
|                 model->rssi_history_end = false; |                 model->rssi_history_end = false; | ||||||
|                 model->ind_write = 0; |                 model->ind_write = 0; | ||||||
|                 string_set(model->file_name, file_name); |                 string_set_str(model->file_name, file_name); | ||||||
|                 string_set(model->sample_write, "RAW"); |                 string_set_str(model->sample_write, "RAW"); | ||||||
|                 return true; |                 return true; | ||||||
|             }); |             }); | ||||||
|         break; |         break; | ||||||
| @ -451,8 +451,8 @@ void subghz_read_raw_set_status( | |||||||
|             instance->view, (SubGhzReadRAWModel * model) { |             instance->view, (SubGhzReadRAWModel * model) { | ||||||
|                 model->satus = SubGhzReadRAWStatusLoadKeyIDLE; |                 model->satus = SubGhzReadRAWStatusLoadKeyIDLE; | ||||||
|                 if(!model->ind_write) { |                 if(!model->ind_write) { | ||||||
|                     string_set(model->file_name, file_name); |                     string_set_str(model->file_name, file_name); | ||||||
|                     string_set(model->sample_write, "RAW"); |                     string_set_str(model->sample_write, "RAW"); | ||||||
|                 } else { |                 } else { | ||||||
|                     string_reset(model->file_name); |                     string_reset(model->file_name); | ||||||
|                 } |                 } | ||||||
|  | |||||||
| @ -36,9 +36,9 @@ void subghz_view_transmitter_add_data_to_show( | |||||||
|     furi_assert(subghz_transmitter); |     furi_assert(subghz_transmitter); | ||||||
|     with_view_model( |     with_view_model( | ||||||
|         subghz_transmitter->view, (SubGhzViewTransmitterModel * model) { |         subghz_transmitter->view, (SubGhzViewTransmitterModel * model) { | ||||||
|             string_set(model->key_str, key_str); |             string_set_str(model->key_str, key_str); | ||||||
|             string_set(model->frequency_str, frequency_str); |             string_set_str(model->frequency_str, frequency_str); | ||||||
|             string_set(model->preset_str, preset_str); |             string_set_str(model->preset_str, preset_str); | ||||||
|             model->show_button = show_button; |             model->show_button = show_button; | ||||||
|             return true; |             return true; | ||||||
|         }); |         }); | ||||||
|  | |||||||
| @ -40,6 +40,9 @@ const uint8_t* const _I_125_10px[] = {_I_125_10px_0}; | |||||||
| const uint8_t _I_Nfc_10px_0[] = {0x00,0x80,0x00,0x00,0x01,0x22,0x02,0x43,0x02,0x45,0x02,0x49,0x02,0x31,0x02,0x22,0x02,0x00,0x01,0x80,0x00,}; | const uint8_t _I_Nfc_10px_0[] = {0x00,0x80,0x00,0x00,0x01,0x22,0x02,0x43,0x02,0x45,0x02,0x49,0x02,0x31,0x02,0x22,0x02,0x00,0x01,0x80,0x00,}; | ||||||
| const uint8_t* const _I_Nfc_10px[] = {_I_Nfc_10px_0}; | const uint8_t* const _I_Nfc_10px[] = {_I_Nfc_10px_0}; | ||||||
| 
 | 
 | ||||||
|  | const uint8_t _I_back_10px_0[] = {0x00,0x00,0x00,0x10,0x00,0x38,0x00,0x7C,0x00,0xFE,0x00,0x38,0x00,0x38,0x00,0xF8,0x01,0xF8,0x01,0x00,0x00,}; | ||||||
|  | const uint8_t* const _I_back_10px[] = {_I_back_10px_0}; | ||||||
|  | 
 | ||||||
| const uint8_t _I_badusb_10px_0[] = {0x01,0x00,0x11,0x00,0x00,0x0f,0xe2,0x01,0xfc,0x80,0xdd,0x20,0x32,0x48,0x08,0x14,0x40,0x23,0xa8,0x08,0xa0,}; | const uint8_t _I_badusb_10px_0[] = {0x01,0x00,0x11,0x00,0x00,0x0f,0xe2,0x01,0xfc,0x80,0xdd,0x20,0x32,0x48,0x08,0x14,0x40,0x23,0xa8,0x08,0xa0,}; | ||||||
| const uint8_t* const _I_badusb_10px[] = {_I_badusb_10px_0}; | const uint8_t* const _I_badusb_10px[] = {_I_badusb_10px_0}; | ||||||
| 
 | 
 | ||||||
| @ -55,6 +58,12 @@ const uint8_t* const _I_ibutt_10px[] = {_I_ibutt_10px_0}; | |||||||
| const uint8_t _I_ir_10px_0[] = {0x00,0xFC,0x00,0x02,0x01,0x79,0x02,0x84,0x00,0x30,0x00,0x00,0x00,0x30,0x00,0x58,0x00,0x78,0x00,0xFF,0x03,}; | const uint8_t _I_ir_10px_0[] = {0x00,0xFC,0x00,0x02,0x01,0x79,0x02,0x84,0x00,0x30,0x00,0x00,0x00,0x30,0x00,0x58,0x00,0x78,0x00,0xFF,0x03,}; | ||||||
| const uint8_t* const _I_ir_10px[] = {_I_ir_10px_0}; | const uint8_t* const _I_ir_10px[] = {_I_ir_10px_0}; | ||||||
| 
 | 
 | ||||||
|  | const uint8_t _I_loading_10px_0[] = {0x00,0xFE,0x00,0x82,0x00,0xBA,0x00,0x54,0x00,0x28,0x00,0x28,0x00,0x44,0x00,0x92,0x00,0xBA,0x00,0xFE,0x00,}; | ||||||
|  | const uint8_t* const _I_loading_10px[] = {_I_loading_10px_0}; | ||||||
|  | 
 | ||||||
|  | const uint8_t _I_music_10px_0[] = {0x01,0x00,0x10,0x00,0xf0,0x00,0x46,0x03,0x20,0x80,0x00,0x4e,0x7d,0x00,0x9f,0x80,0x4a,0x3c,0x13,0x20,}; | ||||||
|  | const uint8_t* const _I_music_10px[] = {_I_music_10px_0}; | ||||||
|  | 
 | ||||||
| const uint8_t _I_sub1_10px_0[] = {0x01,0x00,0x12,0x00,0x81,0x40,0x69,0x30,0x2c,0x2c,0x0b,0x6a,0x01,0x28,0x0c,0x0a,0x65,0x01,0x98,0x40,0x00,0x26,}; | const uint8_t _I_sub1_10px_0[] = {0x01,0x00,0x12,0x00,0x81,0x40,0x69,0x30,0x2c,0x2c,0x0b,0x6a,0x01,0x28,0x0c,0x0a,0x65,0x01,0x98,0x40,0x00,0x26,}; | ||||||
| const uint8_t* const _I_sub1_10px[] = {_I_sub1_10px_0}; | const uint8_t* const _I_sub1_10px[] = {_I_sub1_10px_0}; | ||||||
| 
 | 
 | ||||||
| @ -648,11 +657,14 @@ const Icon A_Levelup1_128x64 = {.width=128,.height=64,.frame_count=11,.frame_rat | |||||||
| const Icon A_Levelup2_128x64 = {.width=128,.height=64,.frame_count=11,.frame_rate=2,.frames=_A_Levelup2_128x64}; | const Icon A_Levelup2_128x64 = {.width=128,.height=64,.frame_count=11,.frame_rate=2,.frames=_A_Levelup2_128x64}; | ||||||
| const Icon I_125_10px = {.width=10,.height=10,.frame_count=1,.frame_rate=0,.frames=_I_125_10px}; | const Icon I_125_10px = {.width=10,.height=10,.frame_count=1,.frame_rate=0,.frames=_I_125_10px}; | ||||||
| const Icon I_Nfc_10px = {.width=10,.height=10,.frame_count=1,.frame_rate=0,.frames=_I_Nfc_10px}; | const Icon I_Nfc_10px = {.width=10,.height=10,.frame_count=1,.frame_rate=0,.frames=_I_Nfc_10px}; | ||||||
|  | const Icon I_back_10px = {.width=10,.height=10,.frame_count=1,.frame_rate=0,.frames=_I_back_10px}; | ||||||
| const Icon I_badusb_10px = {.width=10,.height=10,.frame_count=1,.frame_rate=0,.frames=_I_badusb_10px}; | const Icon I_badusb_10px = {.width=10,.height=10,.frame_count=1,.frame_rate=0,.frames=_I_badusb_10px}; | ||||||
| const Icon I_ble_10px = {.width=10,.height=10,.frame_count=1,.frame_rate=0,.frames=_I_ble_10px}; | const Icon I_ble_10px = {.width=10,.height=10,.frame_count=1,.frame_rate=0,.frames=_I_ble_10px}; | ||||||
| const Icon I_dir_10px = {.width=10,.height=10,.frame_count=1,.frame_rate=0,.frames=_I_dir_10px}; | const Icon I_dir_10px = {.width=10,.height=10,.frame_count=1,.frame_rate=0,.frames=_I_dir_10px}; | ||||||
| const Icon I_ibutt_10px = {.width=10,.height=10,.frame_count=1,.frame_rate=0,.frames=_I_ibutt_10px}; | const Icon I_ibutt_10px = {.width=10,.height=10,.frame_count=1,.frame_rate=0,.frames=_I_ibutt_10px}; | ||||||
| const Icon I_ir_10px = {.width=10,.height=10,.frame_count=1,.frame_rate=0,.frames=_I_ir_10px}; | const Icon I_ir_10px = {.width=10,.height=10,.frame_count=1,.frame_rate=0,.frames=_I_ir_10px}; | ||||||
|  | const Icon I_loading_10px = {.width=10,.height=10,.frame_count=1,.frame_rate=0,.frames=_I_loading_10px}; | ||||||
|  | const Icon I_music_10px = {.width=10,.height=10,.frame_count=1,.frame_rate=0,.frames=_I_music_10px}; | ||||||
| const Icon I_sub1_10px = {.width=10,.height=10,.frame_count=1,.frame_rate=0,.frames=_I_sub1_10px}; | const Icon I_sub1_10px = {.width=10,.height=10,.frame_count=1,.frame_rate=0,.frames=_I_sub1_10px}; | ||||||
| const Icon I_u2f_10px = {.width=10,.height=10,.frame_count=1,.frame_rate=0,.frames=_I_u2f_10px}; | const Icon I_u2f_10px = {.width=10,.height=10,.frame_count=1,.frame_rate=0,.frames=_I_u2f_10px}; | ||||||
| const Icon I_unknown_10px = {.width=10,.height=10,.frame_count=1,.frame_rate=0,.frames=_I_unknown_10px}; | const Icon I_unknown_10px = {.width=10,.height=10,.frame_count=1,.frame_rate=0,.frames=_I_unknown_10px}; | ||||||
|  | |||||||
| @ -7,11 +7,14 @@ extern const Icon A_Levelup1_128x64; | |||||||
| extern const Icon A_Levelup2_128x64; | extern const Icon A_Levelup2_128x64; | ||||||
| extern const Icon I_125_10px; | extern const Icon I_125_10px; | ||||||
| extern const Icon I_Nfc_10px; | extern const Icon I_Nfc_10px; | ||||||
|  | extern const Icon I_back_10px; | ||||||
| extern const Icon I_badusb_10px; | extern const Icon I_badusb_10px; | ||||||
| extern const Icon I_ble_10px; | extern const Icon I_ble_10px; | ||||||
| extern const Icon I_dir_10px; | extern const Icon I_dir_10px; | ||||||
| extern const Icon I_ibutt_10px; | extern const Icon I_ibutt_10px; | ||||||
| extern const Icon I_ir_10px; | extern const Icon I_ir_10px; | ||||||
|  | extern const Icon I_loading_10px; | ||||||
|  | extern const Icon I_music_10px; | ||||||
| extern const Icon I_sub1_10px; | extern const Icon I_sub1_10px; | ||||||
| extern const Icon I_u2f_10px; | extern const Icon I_u2f_10px; | ||||||
| extern const Icon I_unknown_10px; | extern const Icon I_unknown_10px; | ||||||
|  | |||||||
							
								
								
									
										
											BIN
										
									
								
								assets/icons/Archive/back_10px.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								assets/icons/Archive/back_10px.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 154 B | 
							
								
								
									
										
											BIN
										
									
								
								assets/icons/Archive/loading_10px.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								assets/icons/Archive/loading_10px.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 173 B | 
							
								
								
									
										
											BIN
										
									
								
								assets/icons/Archive/music_10px.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								assets/icons/Archive/music_10px.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 142 B | 
| @ -4,7 +4,6 @@ | |||||||
| 
 | 
 | ||||||
| struct iButtonKey { | struct iButtonKey { | ||||||
|     uint8_t data[IBUTTON_KEY_DATA_SIZE]; |     uint8_t data[IBUTTON_KEY_DATA_SIZE]; | ||||||
|     char name[IBUTTON_KEY_NAME_SIZE]; |  | ||||||
|     iButtonKeyType type; |     iButtonKeyType type; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| @ -42,14 +41,6 @@ uint8_t ibutton_key_get_data_size(iButtonKey* key) { | |||||||
|     return ibutton_key_get_size_by_type(key->type); |     return ibutton_key_get_size_by_type(key->type); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void ibutton_key_set_name(iButtonKey* key, const char* name) { |  | ||||||
|     strlcpy(key->name, name, IBUTTON_KEY_NAME_SIZE); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| const char* ibutton_key_get_name_p(iButtonKey* key) { |  | ||||||
|     return key->name; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void ibutton_key_set_type(iButtonKey* key, iButtonKeyType key_type) { | void ibutton_key_set_type(iButtonKey* key, iButtonKeyType key_type) { | ||||||
|     key->type = key_type; |     key->type = key_type; | ||||||
| } | } | ||||||
|  | |||||||
| @ -68,20 +68,6 @@ const uint8_t* ibutton_key_get_data_p(iButtonKey* key); | |||||||
|  */ |  */ | ||||||
| uint8_t ibutton_key_get_data_size(iButtonKey* key); | uint8_t ibutton_key_get_data_size(iButtonKey* key); | ||||||
| 
 | 
 | ||||||
| /**
 |  | ||||||
|  * Set key name |  | ||||||
|  * @param key  |  | ||||||
|  * @param name  |  | ||||||
|  */ |  | ||||||
| void ibutton_key_set_name(iButtonKey* key, const char* name); |  | ||||||
| 
 |  | ||||||
| /**
 |  | ||||||
|  * Get pointer to key name |  | ||||||
|  * @param key  |  | ||||||
|  * @return const char*  |  | ||||||
|  */ |  | ||||||
| const char* ibutton_key_get_name_p(iButtonKey* key); |  | ||||||
| 
 |  | ||||||
| /**
 | /**
 | ||||||
|  * Set key type |  * Set key type | ||||||
|  * @param key  |  * @param key  | ||||||
|  | |||||||
| @ -19,6 +19,20 @@ void path_extract_filename_no_ext(const char* path, string_t filename) { | |||||||
|     string_mid(filename, start_position, end_position - start_position); |     string_mid(filename, start_position, end_position - start_position); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | void path_extract_filename(string_t path, string_t name, bool trim_ext) { | ||||||
|  |     size_t filename_start = string_search_rchar(path, '/'); | ||||||
|  |     if(filename_start > 0) { | ||||||
|  |         filename_start++; | ||||||
|  |         string_set_n(name, path, filename_start, string_size(path) - filename_start); | ||||||
|  |     } | ||||||
|  |     if(trim_ext) { | ||||||
|  |         size_t dot = string_search_rchar(name, '.'); | ||||||
|  |         if(dot > 0) { | ||||||
|  |             string_left(name, dot); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
| static inline void path_cleanup(string_t path) { | static inline void path_cleanup(string_t path) { | ||||||
|     string_strim(path); |     string_strim(path); | ||||||
|     while(string_end_with_str_p(path, "/")) { |     while(string_end_with_str_p(path, "/")) { | ||||||
|  | |||||||
| @ -14,6 +14,15 @@ extern "C" { | |||||||
|  */ |  */ | ||||||
| void path_extract_filename_no_ext(const char* path, string_t filename); | void path_extract_filename_no_ext(const char* path, string_t filename); | ||||||
| 
 | 
 | ||||||
|  | /**
 | ||||||
|  |  * @brief Extract filename string from path. | ||||||
|  |  *  | ||||||
|  |  * @param path path string | ||||||
|  |  * @param filename output filename string. Must be initialized before. | ||||||
|  |  * @param trim_ext true - get filename without extension | ||||||
|  |  */ | ||||||
|  | void path_extract_filename(string_t path, string_t filename, bool trim_ext); | ||||||
|  | 
 | ||||||
| /**
 | /**
 | ||||||
|  * @brief Extract last path component |  * @brief Extract last path component | ||||||
|  *  |  *  | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 Nikolay Minaylov
						Nikolay Minaylov