diff --git a/applications/main/archive/helpers/archive_browser.c b/applications/main/archive/helpers/archive_browser.c index 68ca630c2..e550d665e 100644 --- a/applications/main/archive/helpers/archive_browser.c +++ b/applications/main/archive/helpers/archive_browser.c @@ -733,3 +733,18 @@ void archive_clear_selection(ArchiveBrowserViewModel* model) { file->selected = false; } } + +void archive_deselect_children(ArchiveBrowserViewModel* model, const char* parent) { + size_t write_idx = 0; + for(size_t i = 0; i < model->selected_count; i++) { + if(!furi_string_start_with(model->selected_files[i], parent)) { + if(write_idx != i) { + model->selected_files[write_idx] = model->selected_files[i]; + } + write_idx++; + } else { + furi_string_free(model->selected_files[i]); + } + } + model->selected_count = write_idx; +} diff --git a/applications/main/archive/helpers/archive_browser.h b/applications/main/archive/helpers/archive_browser.h index 6d520745a..0b30ac0a4 100644 --- a/applications/main/archive/helpers/archive_browser.h +++ b/applications/main/archive/helpers/archive_browser.h @@ -112,3 +112,4 @@ void archive_leave_dir(ArchiveBrowserView* browser); void archive_refresh_dir(ArchiveBrowserView* browser); void archive_clear_selection(ArchiveBrowserViewModel* model); +void archive_deselect_children(ArchiveBrowserViewModel* model, const char* parent); diff --git a/applications/main/archive/scenes/archive_scene_browser.c b/applications/main/archive/scenes/archive_scene_browser.c index 9d58db262..cf93ebe1b 100644 --- a/applications/main/archive/scenes/archive_scene_browser.c +++ b/applications/main/archive/scenes/archive_scene_browser.c @@ -307,20 +307,7 @@ bool archive_scene_browser_on_event(void* context, SceneManagerEvent event) { if(!current->selected) { // If current file type is a folder, deselect all files that start with the same path to not have a conflict. if(current->type == ArchiveFileTypeFolder) { - size_t write_idx = 0; - for(size_t i = 0; i < model->selected_count; i++) { - if(!furi_string_start_with( - model->selected_files[i], current->path)) { - if(write_idx != i) { - model->selected_files[write_idx] = - model->selected_files[i]; - } - write_idx++; - } else { - furi_string_free(model->selected_files[i]); - } - } - model->selected_count = write_idx; + archive_deselect_children(model, furi_string_get_cstr(current->path)); } model->selected_files[model->selected_count] = furi_string_alloc_set(current->path); diff --git a/applications/main/archive/scenes/archive_scene_delete.c b/applications/main/archive/scenes/archive_scene_delete.c index 81e78cb97..535776d28 100644 --- a/applications/main/archive/scenes/archive_scene_delete.c +++ b/applications/main/archive/scenes/archive_scene_delete.c @@ -28,12 +28,14 @@ void archive_scene_delete_on_enter(void* context) { browser->view, ArchiveBrowserViewModel * model, { - if(model->select_mode && model->selected_count > 0) { + if(model->select_mode && model->selected_count > 1) { snprintf( delete_str, sizeof(delete_str), "\e#Delete %d files?\e#", model->selected_count); + widget_add_file_list_element( + app->widget, 0, 23, 3, model->selected_files, model->selected_count); } else { ArchiveFile_t* current = archive_get_current_file(browser); FuriString* filename = furi_string_alloc(); diff --git a/applications/main/archive/views/archive_browser_view.c b/applications/main/archive/views/archive_browser_view.c index cfe50b10a..56672fde7 100644 --- a/applications/main/archive/views/archive_browser_view.c +++ b/applications/main/archive/views/archive_browser_view.c @@ -125,10 +125,12 @@ static void render_item_menu(Canvas* canvas, ArchiveBrowserViewModel* model) { menu_array_push_raw(model->context_menu), "Info", ArchiveBrowserEventFileMenuInfo); - archive_menu_add_item( - menu_array_push_raw(model->context_menu), - model->select_mode ? "Deselect" : "Select", - ArchiveBrowserEventFileMenuSelectMode); + if(!favorites) { + archive_menu_add_item( + menu_array_push_raw(model->context_menu), + model->select_mode ? "Deselect" : "Select", + ArchiveBrowserEventFileMenuSelectMode); + } if(selected->type != ArchiveFileTypeFolder) { archive_menu_add_item( menu_array_push_raw(model->context_menu), diff --git a/applications/services/gui/modules/widget.c b/applications/services/gui/modules/widget.c index 8efb9601d..32eb48f9a 100644 --- a/applications/services/gui/modules/widget.c +++ b/applications/services/gui/modules/widget.c @@ -119,6 +119,20 @@ static void widget_add_element(Widget* widget, WidgetElement* element) { true); } +WidgetElement* widget_add_file_list_element( + Widget* widget, + uint8_t x, + uint8_t y, + uint8_t lines, + FuriString** files, + size_t count) { + furi_assert(widget); + WidgetElement* file_list_element = + widget_element_file_list_create(widget, x, y, lines, files, count); + widget_add_element(widget, file_list_element); + return file_list_element; +} + WidgetElement* widget_add_string_multiline_element( Widget* widget, uint8_t x, diff --git a/applications/services/gui/modules/widget.h b/applications/services/gui/modules/widget.h index 51ce2785f..38b5706be 100644 --- a/applications/services/gui/modules/widget.h +++ b/applications/services/gui/modules/widget.h @@ -41,6 +41,23 @@ void widget_reset(Widget* widget); */ View* widget_get_view(Widget* widget); +/** Add File List Element + * + * @param widget Widget instance + * @param x x coordinate + * @param y y coordinate + * @param lines Number of lines visible + * @param files Array of FuriString pointers + * @param count Number of files + */ +WidgetElement* widget_add_file_list_element( + Widget* widget, + uint8_t x, + uint8_t y, + uint8_t lines, + FuriString** files, + size_t count); + /** Add Multi String Element * * @param widget Widget instance diff --git a/applications/services/gui/modules/widget_elements/widget_element_file_list.c b/applications/services/gui/modules/widget_elements/widget_element_file_list.c new file mode 100644 index 000000000..9337d15cc --- /dev/null +++ b/applications/services/gui/modules/widget_elements/widget_element_file_list.c @@ -0,0 +1,121 @@ +#include "assets_icons.h" +#include "path.h" +#include "widget_element_i.h" +#include +#include +#include "archive/archive_i.h" + +typedef struct { + FuriString* name; + bool is_dir; +} FileListItem; + +typedef struct { + uint8_t x; + uint8_t y; + uint8_t lines; + FileListItem* files; + size_t count; + size_t offset; +} FileListModel; + +static void widget_element_file_list_draw(Canvas* canvas, WidgetElement* element) { + furi_assert(canvas); + furi_assert(element); + FileListModel* model = element->model; + size_t items_visible = MIN(model->lines, model->count); + + canvas_set_font(canvas, FontSecondary); + for(size_t i = 0; i < items_visible; i++) { + size_t idx = model->offset + i; + if(idx < model->count) { + canvas_draw_icon( + canvas, + model->x + 2, + model->y + (i * FRAME_HEIGHT) - 9, + model->files[idx].is_dir ? &I_dir_10px : &I_unknown_10px); + canvas_draw_str( + canvas, + model->x + 15, + model->y + (i * FRAME_HEIGHT), + furi_string_get_cstr(model->files[idx].name)); + } + } + + if(model->count > model->lines) { + elements_scrollbar_pos( + canvas, + 128, + model->y - 9, + model->lines * FRAME_HEIGHT, + model->offset, + model->count - 2); + } +} + +static bool widget_element_file_list_input(InputEvent* event, WidgetElement* element) { + furi_assert(element); + FileListModel* model = element->model; + bool consumed = false; + + if(event->type == InputTypeShort || event->type == InputTypeRepeat) { + if(event->key == InputKeyUp) { + model->offset = (model->offset > 0) ? model->offset - 1 : model->count - 3; + consumed = true; + } else if(event->key == InputKeyDown) { + model->offset = ((model->offset + 3) < model->count) ? model->offset + 1 : 0; + consumed = true; + } + } + + return consumed; +} + +static void widget_element_file_list_free(WidgetElement* element) { + furi_assert(element); + FileListModel* model = element->model; + for(size_t i = 0; i < model->count; i++) { + furi_string_free(model->files[i].name); + } + free(model->files); + free(model); + free(element); +} + +WidgetElement* widget_element_file_list_create( + Widget* widget, + uint8_t x, + uint8_t y, + uint8_t lines, + FuriString** files, + size_t count) { + // Allocate and init model + FileListModel* model = malloc(sizeof(FileListModel)); + model->x = x; + model->y = y; + model->lines = lines; + model->count = count; + model->offset = 0; + model->files = malloc(sizeof(FileListItem) * count); + Storage* storage = furi_record_open(RECORD_STORAGE); + for(size_t i = 0; i < count; i++) { + FileInfo fileinfo; + model->files[i].name = furi_string_alloc(); + path_extract_filename(files[i], model->files[i].name, false); + model->files[i].is_dir = false; + + if(storage_common_stat(storage, furi_string_get_cstr(files[i]), &fileinfo) == FSE_OK) { + model->files[i].is_dir = file_info_is_dir(&fileinfo); + } + } + furi_record_close(RECORD_STORAGE); + + // Allocate and init Element + WidgetElement* element = malloc(sizeof(WidgetElement)); + element->draw = widget_element_file_list_draw; + element->input = widget_element_file_list_input; + element->free = widget_element_file_list_free; + element->parent = widget; + element->model = model; + return element; +} diff --git a/applications/services/gui/modules/widget_elements/widget_element_i.h b/applications/services/gui/modules/widget_elements/widget_element_i.h index 9a873fef7..ef415ce9c 100644 --- a/applications/services/gui/modules/widget_elements/widget_element_i.h +++ b/applications/services/gui/modules/widget_elements/widget_element_i.h @@ -34,6 +34,15 @@ struct WidgetElement { Widget* parent; }; +/** Create file list element */ +WidgetElement* widget_element_file_list_create( + Widget* widget, + uint8_t x, + uint8_t y, + uint8_t lines, + FuriString** files, + size_t count); + /** Create multi string element */ WidgetElement* widget_element_string_multiline_create( uint8_t x, diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index f5e79d7ee..37d3eb238 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -3836,6 +3836,7 @@ Function,-,vsscanf,int,"const char*, const char*, __gnuc_va_list" Function,-,wcstombs,size_t,"char*, const wchar_t*, size_t" Function,-,wctomb,int,"char*, wchar_t" Function,+,widget_add_button_element,WidgetElement*,"Widget*, GuiButtonType, const char*, ButtonCallback, void*" +Function,+,widget_add_file_list_element,WidgetElement*,"Widget*, uint8_t, uint8_t, uint8_t, FuriString**, size_t" Function,+,widget_add_frame_element,WidgetElement*,"Widget*, uint8_t, uint8_t, uint8_t, uint8_t, uint8_t" Function,+,widget_add_icon_element,WidgetElement*,"Widget*, uint8_t, uint8_t, const Icon*" Function,+,widget_add_string_element,WidgetElement*,"Widget*, uint8_t, uint8_t, Align, Align, Font, const char*"