From 4c05f6768646b99768402dcc664693a1434b150f Mon Sep 17 00:00:00 2001 From: its your bedtime <23366927+itsyourbedtime@users.noreply.github.com> Date: Tue, 21 Sep 2021 13:56:33 +0300 Subject: [PATCH] [FL-1823, FL-1824] Archive app: refactoring and UI improvements (#711) * Archive app: skip empty app folders, file menu in favorites tab, looped tab switching * refactoring * cleanup * better filepath trim * fix excessive view updates, various small optimizations * better list_offset calculation, favorites vargs) * revert poor fix Co-authored-by: Aleksandr Kutuzov --- applications/archive/archive.c | 6 +- applications/archive/archive_i.h | 56 +- .../archive/helpers/archive_browser.c | 255 +++++++ .../archive/helpers/archive_browser.h | 68 ++ .../archive/helpers/archive_favorites.c | 101 +-- .../archive/helpers/archive_favorites.h | 7 +- applications/archive/helpers/archive_files.c | 109 ++- applications/archive/helpers/archive_files.h | 7 +- .../archive/scenes/archive_scene_browser.c | 96 ++- .../archive/scenes/archive_scene_rename.c | 13 +- .../archive/views/archive_browser_view.c | 294 ++++++++ .../archive/views/archive_browser_view.h | 88 +++ .../archive/views/archive_main_view.c | 641 ------------------ .../archive/views/archive_main_view.h | 117 ---- 14 files changed, 948 insertions(+), 910 deletions(-) create mode 100644 applications/archive/helpers/archive_browser.c create mode 100644 applications/archive/helpers/archive_browser.h create mode 100644 applications/archive/views/archive_browser_view.c create mode 100644 applications/archive/views/archive_browser_view.h delete mode 100644 applications/archive/views/archive_main_view.c delete mode 100644 applications/archive/views/archive_main_view.h diff --git a/applications/archive/archive.c b/applications/archive/archive.c index 68fc7287..8b4918a5 100644 --- a/applications/archive/archive.c +++ b/applications/archive/archive.c @@ -31,10 +31,10 @@ ArchiveApp* archive_alloc() { view_dispatcher_set_navigation_event_callback( archive->view_dispatcher, archive_back_event_callback); - archive->main_view = main_view_alloc(); + archive->browser = browser_alloc(); view_dispatcher_add_view( - archive->view_dispatcher, ArchiveViewBrowser, archive_main_get_view(archive->main_view)); + archive->view_dispatcher, ArchiveViewBrowser, archive_browser_get_view(archive->browser)); view_dispatcher_add_view( archive->view_dispatcher, ArchiveViewTextInput, text_input_get_view(archive->text_input)); @@ -49,7 +49,7 @@ void archive_free(ArchiveApp* archive) { view_dispatcher_remove_view(archive->view_dispatcher, ArchiveViewTextInput); view_dispatcher_free(archive->view_dispatcher); scene_manager_free(archive->scene_manager); - main_view_free(archive->main_view); + browser_free(archive->browser); text_input_free(archive->text_input); diff --git a/applications/archive/archive_i.h b/applications/archive/archive_i.h index 474b18e5..ba95ac4b 100644 --- a/applications/archive/archive_i.h +++ b/applications/archive/archive_i.h @@ -9,72 +9,20 @@ #include #include -#include -#include -#include -#include "applications.h" -#include "file-worker.h" - -#include "views/archive_main_view.h" +#include "views/archive_browser_view.h" #include "scenes/archive_scene.h" -#define MAX_FILE_SIZE 128 - typedef enum { ArchiveViewBrowser, ArchiveViewTextInput, ArchiveViewTotal, } ArchiveViewEnum; -static const char* tab_default_paths[] = { - [ArchiveTabFavorites] = "/any/favorites", - [ArchiveTabIButton] = "/any/ibutton", - [ArchiveTabNFC] = "/any/nfc", - [ArchiveTabSubGhz] = "/any/subghz/saved", - [ArchiveTabLFRFID] = "/any/lfrfid", - [ArchiveTabIrda] = "/any/irda", - [ArchiveTabBrowser] = "/any", -}; - -static inline const char* get_default_path(ArchiveFileTypeEnum type) { - switch(type) { - case ArchiveFileTypeIButton: - return tab_default_paths[ArchiveTabIButton]; - case ArchiveFileTypeNFC: - return tab_default_paths[ArchiveTabNFC]; - case ArchiveFileTypeSubGhz: - return tab_default_paths[ArchiveTabSubGhz]; - case ArchiveFileTypeLFRFID: - return tab_default_paths[ArchiveTabLFRFID]; - case ArchiveFileTypeIrda: - return tab_default_paths[ArchiveTabIrda]; - default: - return false; - } -} - -static inline const char* get_favorites_path() { - return tab_default_paths[ArchiveTabFavorites]; -} - -typedef enum { - EventTypeTick, - EventTypeKey, - EventTypeExit, -} EventType; - -typedef struct { - union { - InputEvent input; - } value; - EventType type; -} AppEvent; - struct ArchiveApp { Gui* gui; ViewDispatcher* view_dispatcher; SceneManager* scene_manager; - ArchiveMainView* main_view; + ArchiveBrowserView* browser; TextInput* text_input; char text_store[MAX_NAME_LEN]; }; diff --git a/applications/archive/helpers/archive_browser.c b/applications/archive/helpers/archive_browser.c new file mode 100644 index 00000000..da8a54d1 --- /dev/null +++ b/applications/archive/helpers/archive_browser.c @@ -0,0 +1,255 @@ +#include "archive_browser.h" +#include "math.h" + +void archive_update_offset(ArchiveBrowserView* browser) { + furi_assert(browser); + with_view_model( + browser->view, (ArchiveBrowserViewModel * model) { + size_t array_size = files_array_size(model->files); + uint16_t bounds = array_size > 3 ? 2 : array_size; + + if(array_size > 3 && model->idx >= array_size - 1) { + model->list_offset = model->idx - 3; + } else if(model->list_offset < model->idx - bounds) { + model->list_offset = CLAMP(model->idx - 2, array_size - bounds, 0); + } else if(model->list_offset > model->idx - bounds) { + model->list_offset = CLAMP(model->idx - 1, array_size - bounds, 0); + } + + return true; + }); +} + +void archive_update_focus(ArchiveBrowserView* browser, const char* target) { + furi_assert(browser); + furi_assert(target); + + archive_get_filenames(browser, string_get_cstr(browser->path)); + + if(!archive_file_array_size(browser) && !archive_get_depth(browser)) { + archive_switch_tab(browser, DEFAULT_TAB_DIR); + } else { + with_view_model( + browser->view, (ArchiveBrowserViewModel * model) { + uint16_t idx = 0; + while(idx < files_array_size(model->files)) { + ArchiveFile_t* current = files_array_get(model->files, idx); + if(!string_search(current->name, target)) { + model->idx = idx; + break; + } + ++idx; + } + return false; + }); + + archive_update_offset(browser); + } +} + +size_t archive_file_array_size(ArchiveBrowserView* browser) { + uint16_t size = 0; + with_view_model( + browser->view, (ArchiveBrowserViewModel * model) { + size = files_array_size(model->files); + return false; + }); + return size; +} + +void archive_file_array_rm_selected(ArchiveBrowserView* browser) { + with_view_model( + browser->view, (ArchiveBrowserViewModel * model) { + files_array_remove_v(model->files, model->idx, model->idx + 1); + model->idx = CLAMP(model->idx, files_array_size(model->files) - 1, 0); + return false; + }); + + if(!archive_file_array_size(browser) && !archive_get_depth(browser)) { + archive_switch_tab(browser, DEFAULT_TAB_DIR); + } + + archive_update_offset(browser); +} + +void archive_file_array_rm_all(ArchiveBrowserView* browser) { + with_view_model( + browser->view, (ArchiveBrowserViewModel * model) { + files_array_clean(model->files); + return false; + }); +} + +ArchiveFile_t* archive_get_current_file(ArchiveBrowserView* browser) { + ArchiveFile_t* selected; + with_view_model( + browser->view, (ArchiveBrowserViewModel * model) { + selected = files_array_size(model->files) ? files_array_get(model->files, model->idx) : + NULL; + return false; + }); + return selected; +} + +ArchiveTabEnum archive_get_tab(ArchiveBrowserView* browser) { + ArchiveTabEnum tab_id; + with_view_model( + browser->view, (ArchiveBrowserViewModel * model) { + tab_id = model->tab_idx; + return false; + }); + return tab_id; +} + +uint8_t archive_get_depth(ArchiveBrowserView* browser) { + uint8_t depth; + with_view_model( + browser->view, (ArchiveBrowserViewModel * model) { + depth = model->depth; + return false; + }); + + return depth; +} + +const char* archive_get_path(ArchiveBrowserView* browser) { + return string_get_cstr(browser->path); +} + +const char* archive_get_name(ArchiveBrowserView* browser) { + ArchiveFile_t* selected = archive_get_current_file(browser); + return string_get_cstr(selected->name); +} + +void archive_set_tab(ArchiveBrowserView* browser, ArchiveTabEnum tab) { + with_view_model( + browser->view, (ArchiveBrowserViewModel * model) { + model->tab_idx = tab; + return false; + }); +} +void archive_set_last_tab(ArchiveBrowserView* browser, ArchiveTabEnum tab) { + with_view_model( + browser->view, (ArchiveBrowserViewModel * model) { + model->last_tab = model->tab_idx; + return false; + }); +} + +void archive_add_item(ArchiveBrowserView* browser, FileInfo* file_info, const char* name) { + furi_assert(browser); + furi_assert(file_info); + furi_assert(name); + + ArchiveFile_t item; + + if(filter_by_extension(file_info, get_tab_ext(archive_get_tab(browser)), name)) { + ArchiveFile_t_init(&item); + string_init_set_str(item.name, name); + set_file_type(&item, file_info); + + with_view_model( + browser->view, (ArchiveBrowserViewModel * model) { + files_array_push_back(model->files, item); + return false; + }); + + ArchiveFile_t_clear(&item); + } +} + +void archive_show_file_menu(ArchiveBrowserView* browser, bool show) { + furi_assert(browser); + with_view_model( + browser->view, (ArchiveBrowserViewModel * model) { + model->menu = show; + model->menu_idx = 0; + + if(show) { + ArchiveFile_t* selected = files_array_get(model->files, model->idx); + selected->fav = archive_is_favorite( + "%s/%s", string_get_cstr(browser->path), string_get_cstr(selected->name)); + } + + return true; + }); +} + +void archive_switch_dir(ArchiveBrowserView* browser, const char* path) { + furi_assert(browser); + furi_assert(path); + + string_set(browser->path, path); + archive_get_filenames(browser, string_get_cstr(browser->path)); + archive_update_offset(browser); +} + +void archive_switch_tab(ArchiveBrowserView* browser, InputKey key) { + furi_assert(browser); + ArchiveTabEnum tab = archive_get_tab(browser); + + if(key == InputKeyLeft) { + tab = ((tab - 1) + ArchiveTabTotal) % ArchiveTabTotal; + } else if(key == InputKeyRight) { + tab = (tab + 1) % ArchiveTabTotal; + } + + archive_set_tab(browser, tab); + + if((tab != ArchiveTabFavorites && + !archive_dir_empty(browser, archive_get_default_path(tab))) || + (tab == ArchiveTabFavorites && !archive_favorites_count(browser))) { + archive_switch_tab(browser, key); + } else { + with_view_model( + browser->view, (ArchiveBrowserViewModel * model) { + if(model->last_tab != model->tab_idx) { + model->idx = 0; + model->depth = 0; + } + return false; + }); + archive_switch_dir(browser, archive_get_default_path(tab)); + } + archive_set_last_tab(browser, tab); +} + +void archive_enter_dir(ArchiveBrowserView* browser, string_t name) { + furi_assert(browser); + furi_assert(name); + // update last index + with_view_model( + browser->view, (ArchiveBrowserViewModel * model) { + model->last_idx[model->depth] = + CLAMP(model->idx, files_array_size(model->files) - 1, 0); + model->idx = 0; + model->depth = CLAMP(model->depth + 1, MAX_DEPTH, 0); + return false; + }); + + string_cat(browser->path, "/"); + string_cat(browser->path, name); + + archive_switch_dir(browser, string_get_cstr(browser->path)); +} + +void archive_leave_dir(ArchiveBrowserView* browser) { + furi_assert(browser); + + const char* path = archive_get_path(browser); + char* last_char_ptr = strrchr(path, '/'); + + if(last_char_ptr) { + size_t pos = last_char_ptr - path; + string_left(browser->path, pos); + } + + with_view_model( + browser->view, (ArchiveBrowserViewModel * model) { + model->depth = CLAMP(model->depth - 1, MAX_DEPTH, 0); + model->idx = model->last_idx[model->depth]; + return false; + }); + + archive_switch_dir(browser, path); +} diff --git a/applications/archive/helpers/archive_browser.h b/applications/archive/helpers/archive_browser.h new file mode 100644 index 00000000..23872e17 --- /dev/null +++ b/applications/archive/helpers/archive_browser.h @@ -0,0 +1,68 @@ +#pragma once + +#include "../archive_i.h" + +#define DEFAULT_TAB_DIR InputKeyRight //default tab swith direction + +static const char* tab_default_paths[] = { + [ArchiveTabFavorites] = "/any/favorites", + [ArchiveTabIButton] = "/any/ibutton", + [ArchiveTabNFC] = "/any/nfc", + [ArchiveTabSubGhz] = "/any/subghz/saved", + [ArchiveTabLFRFID] = "/any/lfrfid", + [ArchiveTabIrda] = "/any/irda", + [ArchiveTabBrowser] = "/any", +}; + +static const char* known_ext[] = { + [ArchiveFileTypeIButton] = ".ibtn", + [ArchiveFileTypeNFC] = ".nfc", + [ArchiveFileTypeSubGhz] = ".sub", + [ArchiveFileTypeLFRFID] = ".rfid", + [ArchiveFileTypeIrda] = ".ir", +}; + +static inline const char* get_tab_ext(ArchiveTabEnum tab) { + switch(tab) { + case ArchiveTabIButton: + return known_ext[ArchiveFileTypeIButton]; + case ArchiveTabNFC: + return known_ext[ArchiveFileTypeNFC]; + case ArchiveTabSubGhz: + return known_ext[ArchiveFileTypeSubGhz]; + case ArchiveTabLFRFID: + return known_ext[ArchiveFileTypeLFRFID]; + case ArchiveTabIrda: + return known_ext[ArchiveFileTypeIrda]; + default: + return "*"; + } +} + +static inline const char* archive_get_default_path(ArchiveTabEnum tab) { + return tab_default_paths[tab]; +} + +inline bool is_known_app(ArchiveFileTypeEnum type) { + return (type != ArchiveFileTypeFolder && type != ArchiveFileTypeUnknown); +} + +void archive_update_offset(ArchiveBrowserView* browser); +void archive_update_focus(ArchiveBrowserView* browser, const char* target); + +size_t archive_file_array_size(ArchiveBrowserView* browser); +void archive_file_array_rm_selected(ArchiveBrowserView* browser); +void archive_file_array_rm_all(ArchiveBrowserView* browser); + +ArchiveFile_t* archive_get_current_file(ArchiveBrowserView* browser); +ArchiveTabEnum archive_get_tab(ArchiveBrowserView* browser); +uint8_t archive_get_depth(ArchiveBrowserView* browser); +const char* archive_get_path(ArchiveBrowserView* browser); +const char* archive_get_name(ArchiveBrowserView* browser); + +void archive_add_item(ArchiveBrowserView* browser, FileInfo* file_info, const char* name); +void archive_show_file_menu(ArchiveBrowserView* browser, bool show); + +void archive_switch_tab(ArchiveBrowserView* browser, InputKey key); +void archive_enter_dir(ArchiveBrowserView* browser, string_t name); +void archive_leave_dir(ArchiveBrowserView* browser); diff --git a/applications/archive/helpers/archive_favorites.c b/applications/archive/helpers/archive_favorites.c index 6eeefdbb..7f419890 100644 --- a/applications/archive/helpers/archive_favorites.c +++ b/applications/archive/helpers/archive_favorites.c @@ -1,18 +1,47 @@ + #include "archive_favorites.h" -#include "archive_files.h" -#include "../views/archive_main_view.h" +#include "archive_browser.h" + +uint16_t archive_favorites_count(void* context) { + furi_assert(context); + + FileWorker* file_worker = file_worker_alloc(true); + + string_t buffer; + string_init(buffer); + + bool result = file_worker_open(file_worker, ARCHIVE_FAV_PATH, FSAM_READ, FSOM_OPEN_EXISTING); + uint16_t lines = 0; + + if(result) { + while(1) { + if(!file_worker_read_until(file_worker, buffer, '\n')) { + break; + } + if(!string_size(buffer)) { + break; + } + ++lines; + } + } + + string_clear(buffer); + file_worker_close(file_worker); + file_worker_free(file_worker); + return lines; +} bool archive_favorites_read(void* context) { furi_assert(context); - ArchiveMainView* archive_view = context; + ArchiveBrowserView* archive_view = context; FileWorker* file_worker = file_worker_alloc(true); string_t buffer; FileInfo file_info; string_init(buffer); - bool result = file_worker_open(file_worker, ARCHIVE_FAV_PATH, FSAM_READ, FSOM_OPEN_ALWAYS); + bool result = file_worker_open(file_worker, ARCHIVE_FAV_PATH, FSAM_READ, FSOM_OPEN_EXISTING); if(result) { while(1) { @@ -23,7 +52,7 @@ bool archive_favorites_read(void* context) { break; } - archive_view_add_item(archive_view, &file_info, string_get_cstr(buffer)); + archive_add_item(archive_view, &file_info, string_get_cstr(buffer)); string_clean(buffer); } } @@ -33,18 +62,19 @@ bool archive_favorites_read(void* context) { return result; } -bool archive_favorites_delete(const char* file_path, const char* name) { - furi_assert(file_path); - furi_assert(name); +bool archive_favorites_delete(const char* format, ...) { + va_list args; + va_start(args, format); + uint8_t len = vsnprintf(NULL, 0, format, args); + char filename[len + 1]; + vsnprintf(filename, len + 1, format, args); + va_end(args); FileWorker* file_worker = file_worker_alloc(true); - string_t path; string_t buffer; string_init(buffer); - string_init_printf(path, "%s/%s", file_path, name); - bool result = file_worker_open(file_worker, ARCHIVE_FAV_PATH, FSAM_READ, FSOM_OPEN_EXISTING); if(result) { while(1) { @@ -55,17 +85,13 @@ bool archive_favorites_delete(const char* file_path, const char* name) { break; } - if(string_search(buffer, path)) { - string_t temp; - string_init_printf(temp, "%s\r\n", string_get_cstr(buffer)); - archive_file_append(ARCHIVE_FAV_TEMP_PATH, temp); - string_clear(temp); + if(string_search_str(buffer, filename)) { + archive_file_append(ARCHIVE_FAV_TEMP_PATH, "%s\r\n", string_get_cstr(buffer)); } } } string_clear(buffer); - string_clear(path); file_worker_close(file_worker); file_worker_remove(file_worker, ARCHIVE_FAV_PATH); @@ -76,19 +102,20 @@ bool archive_favorites_delete(const char* file_path, const char* name) { return result; } -bool archive_is_favorite(const char* file_path, const char* name) { - furi_assert(file_path); - furi_assert(name); +bool archive_is_favorite(const char* format, ...) { + va_list args; + va_start(args, format); + uint8_t len = vsnprintf(NULL, 0, format, args); + char filename[len + 1]; + vsnprintf(filename, len + 1, format, args); + va_end(args); FileWorker* file_worker = file_worker_alloc(true); - - string_t path; string_t buffer; string_init(buffer); - bool found = false; - string_init_printf(path, "%s/%s", file_path, name); - bool result = file_worker_open(file_worker, ARCHIVE_FAV_PATH, FSAM_READ, FSOM_OPEN_ALWAYS); + bool found = false; + bool result = file_worker_open(file_worker, ARCHIVE_FAV_PATH, FSAM_READ, FSOM_OPEN_EXISTING); if(result) { while(1) { @@ -98,7 +125,7 @@ bool archive_is_favorite(const char* file_path, const char* name) { if(!string_size(buffer)) { break; } - if(!string_search(buffer, path)) { + if(!string_search_str(buffer, filename)) { found = true; break; } @@ -106,7 +133,6 @@ bool archive_is_favorite(const char* file_path, const char* name) { } string_clear(buffer); - string_clear(path); file_worker_close(file_worker); file_worker_free(file_worker); @@ -122,10 +148,8 @@ bool archive_favorites_rename(const char* file_path, const char* src, const char string_t path; string_t buffer; - string_t temp; string_init(buffer); - string_init(temp); string_init(path); string_printf(path, "%s/%s", file_path, src); @@ -139,14 +163,14 @@ bool archive_favorites_rename(const char* file_path, const char* src, const char if(!string_size(buffer)) { break; } - string_printf( - temp, "%s\r\n", string_search(buffer, path) ? string_get_cstr(buffer) : dst); - archive_file_append(ARCHIVE_FAV_TEMP_PATH, temp); - string_clean(temp); + + archive_file_append( + ARCHIVE_FAV_TEMP_PATH, + "%s\r\n", + string_search(buffer, path) ? string_get_cstr(buffer) : dst); } } - string_clear(temp); string_clear(buffer); string_clear(path); @@ -159,13 +183,8 @@ bool archive_favorites_rename(const char* file_path, const char* src, const char return result; } -void archive_add_to_favorites(const char* file_path, const char* name) { +void archive_add_to_favorites(const char* file_path) { furi_assert(file_path); - furi_assert(name); - string_t buffer_src; - - string_init_printf(buffer_src, "%s/%s\r\n", file_path, name); - archive_file_append(ARCHIVE_FAV_PATH, buffer_src); - string_clear(buffer_src); + archive_file_append(ARCHIVE_FAV_PATH, "%s\r\n", file_path); } diff --git a/applications/archive/helpers/archive_favorites.h b/applications/archive/helpers/archive_favorites.h index 7a8fce00..b07d7f87 100644 --- a/applications/archive/helpers/archive_favorites.h +++ b/applications/archive/helpers/archive_favorites.h @@ -4,8 +4,9 @@ #define ARCHIVE_FAV_PATH "/any/favorites.txt" #define ARCHIVE_FAV_TEMP_PATH "/any/favorites.tmp" +uint16_t archive_favorites_count(void* context); bool archive_favorites_read(void* context); -bool archive_favorites_delete(const char* file_path, const char* name); -bool archive_is_favorite(const char* file_path, const char* name); +bool archive_favorites_delete(const char* format, ...); +bool archive_is_favorite(const char* format, ...); bool archive_favorites_rename(const char* file_path, const char* src, const char* dst); -void archive_add_to_favorites(const char* file_path, const char* name); +void archive_add_to_favorites(const char* file_path); \ No newline at end of file diff --git a/applications/archive/helpers/archive_files.c b/applications/archive/helpers/archive_files.c index 036df166..127b2fd3 100644 --- a/applications/archive/helpers/archive_files.c +++ b/applications/archive/helpers/archive_files.c @@ -1,6 +1,5 @@ #include "archive_files.h" -#include "archive_favorites.h" -#include "../archive_i.h" +#include "archive_browser.h" bool filter_by_extension(FileInfo* file_info, const char* tab_ext, const char* name) { furi_assert(file_info); @@ -20,14 +19,12 @@ bool filter_by_extension(FileInfo* file_info, const char* tab_ext, const char* n return result; } -void archive_trim_file_ext(char* name) { - size_t str_len = strlen(name); - char* end = name + str_len; - while(end > name && *end != '.' && *end != '\\' && *end != '/') { - --end; - } - if((end > name && *end == '.') && (*(end - 1) != '\\' && *(end - 1) != '/')) { - *end = '\0'; +void archive_trim_file_path(char* name, bool ext) { + char* slash = strrchr(name, '/') + 1; + if(strlen(slash)) strlcpy(name, slash, strlen(slash) + 1); + if(ext) { + char* dot = strrchr(name, '.'); + if(strlen(dot)) *dot = '\0'; } } @@ -49,24 +46,24 @@ void set_file_type(ArchiveFile_t* file, FileInfo* file_info) { } } -bool archive_get_filenames(void* context, uint8_t tab_id, const char* path) { +bool archive_get_filenames(void* context, const char* path) { furi_assert(context); - ArchiveMainView* main_view = context; - archive_file_array_clean(main_view); + bool res; + ArchiveBrowserView* browser = context; + archive_file_array_rm_all(browser); - if(tab_id != ArchiveTabFavorites) { - archive_read_dir(main_view, path); + if(archive_get_tab(browser) != ArchiveTabFavorites) { + res = archive_read_dir(browser, path); } else { - archive_favorites_read(main_view); + res = archive_favorites_read(browser); } - return true; + return res; } -bool archive_read_dir(void* context, const char* path) { +bool archive_dir_empty(void* context, const char* path) { // can be simpler? furi_assert(context); - ArchiveMainView* main_view = context; FileInfo file_info; Storage* fs_api = furi_record_open("storage"); File* directory = storage_file_alloc(fs_api); @@ -78,17 +75,52 @@ bool archive_read_dir(void* context, const char* path) { return false; } + bool files_found = false; while(1) { if(!storage_dir_read(directory, &file_info, name, MAX_NAME_LEN)) { break; } + if(files_found) { + break; + } else if(storage_file_get_error(directory) == FSE_OK) { + files_found = name[0]; + } else { + return false; + } + } + storage_dir_close(directory); + storage_file_free(directory); - uint16_t files_cnt = archive_file_array_size(main_view); + furi_record_close("storage"); + return files_found; +} + +bool archive_read_dir(void* context, const char* path) { + furi_assert(context); + + ArchiveBrowserView* browser = context; + FileInfo file_info; + Storage* fs_api = furi_record_open("storage"); + File* directory = storage_file_alloc(fs_api); + char name[MAX_NAME_LEN]; + size_t files_cnt = 0; + + if(!storage_dir_open(directory, path)) { + storage_dir_close(directory); + storage_file_free(directory); + return false; + } + + while(1) { + if(!storage_dir_read(directory, &file_info, name, MAX_NAME_LEN)) { + break; + } if(files_cnt > MAX_FILES) { break; } else if(storage_file_get_error(directory) == FSE_OK) { - archive_view_add_item(main_view, &file_info, name); + archive_add_item(browser, &file_info, name); + ++files_cnt; } else { storage_dir_close(directory); storage_file_free(directory); @@ -103,9 +135,15 @@ bool archive_read_dir(void* context, const char* path) { return true; } -void archive_file_append(const char* path, string_t string) { +void archive_file_append(const char* path, const char* format, ...) { furi_assert(path); - furi_assert(string); + + va_list args; + va_start(args, format); + uint8_t len = vsnprintf(NULL, 0, format, args); + char cstr_buff[len + 1]; + vsnprintf(cstr_buff, len + 1, format, args); + va_end(args); FileWorker* file_worker = file_worker_alloc(false); @@ -113,7 +151,7 @@ void archive_file_append(const char* path, string_t string) { FURI_LOG_E("Archive", "Append open error"); } - if(!file_worker_write(file_worker, string_get_cstr(string), string_size(string))) { + if(!file_worker_write(file_worker, cstr_buff, strlen(cstr_buff))) { FURI_LOG_E("Archive", "Append write error"); } @@ -125,19 +163,22 @@ void archive_delete_file(void* context, string_t path, string_t name) { furi_assert(context); furi_assert(path); furi_assert(name); - ArchiveMainView* main_view = context; - FileWorker* file_worker = file_worker_alloc(false); + ArchiveBrowserView* browser = context; + FileWorker* file_worker = file_worker_alloc(true); string_t full_path; - string_init(full_path); - string_printf(full_path, "%s/%s", string_get_cstr(path), string_get_cstr(name)); - file_worker_remove(file_worker, string_get_cstr(full_path)); - file_worker_free(file_worker); - string_clear(full_path); + string_init_printf(full_path, "%s/%s", string_get_cstr(path), string_get_cstr(name)); - if(archive_is_favorite(string_get_cstr(path), string_get_cstr(name))) { - archive_favorites_delete(string_get_cstr(path), string_get_cstr(name)); + bool res = file_worker_remove(file_worker, string_get_cstr(full_path)); + file_worker_free(file_worker); + + if(archive_is_favorite(string_get_cstr(full_path))) { + archive_favorites_delete(string_get_cstr(full_path)); } - archive_file_array_remove_selected(main_view); + if(res) { + archive_file_array_rm_selected(browser); + } + + string_clear(full_path); } diff --git a/applications/archive/helpers/archive_files.h b/applications/archive/helpers/archive_files.h index 71864fe9..92fb7d20 100644 --- a/applications/archive/helpers/archive_files.h +++ b/applications/archive/helpers/archive_files.h @@ -49,8 +49,9 @@ ARRAY_DEF( bool filter_by_extension(FileInfo* file_info, const char* tab_ext, const char* name); void set_file_type(ArchiveFile_t* file, FileInfo* file_info); -void archive_trim_file_ext(char* name); -bool archive_get_filenames(void* context, uint8_t tab_id, const char* path); +void archive_trim_file_path(char* name, bool ext); +bool archive_get_filenames(void* context, const char* path); +bool archive_dir_empty(void* context, const char* path); bool archive_read_dir(void* context, const char* path); -void archive_file_append(const char* path, string_t string); +void archive_file_append(const char* path, const char* format, ...); void archive_delete_file(void* context, string_t path, string_t name); \ No newline at end of file diff --git a/applications/archive/scenes/archive_scene_browser.c b/applications/archive/scenes/archive_scene_browser.c index 000408c3..03c78fae 100644 --- a/applications/archive/scenes/archive_scene_browser.c +++ b/applications/archive/scenes/archive_scene_browser.c @@ -1,5 +1,35 @@ #include "../archive_i.h" -#include "../views/archive_main_view.h" +#include "../helpers/archive_files.h" +#include "../helpers/archive_favorites.h" +#include "../helpers/archive_browser.h" +#include "../views/archive_browser_view.h" + +static const char* flipper_app_name[] = { + [ArchiveFileTypeIButton] = "iButton", + [ArchiveFileTypeNFC] = "NFC", + [ArchiveFileTypeSubGhz] = "Sub-GHz", + [ArchiveFileTypeLFRFID] = "125 kHz RFID", + [ArchiveFileTypeIrda] = "Infrared", +}; + +static void archive_run_in_app( + ArchiveBrowserView* browser, + ArchiveFile_t* selected, + bool full_path_provided) { + Loader* loader = furi_record_open("loader"); + + string_t full_path; + if(!full_path_provided) { + string_init_printf( + full_path, "%s/%s", string_get_cstr(browser->path), string_get_cstr(selected->name)); + } else { + string_init_set(full_path, selected->name); + } + loader_start(loader, flipper_app_name[selected->type], string_get_cstr(full_path)); + + string_clear(full_path); + furi_record_close("loader"); +} void archive_scene_browser_callback(ArchiveBrowserEvent event, void* context) { ArchiveApp* archive = (ArchiveApp*)context; @@ -8,25 +38,77 @@ void archive_scene_browser_callback(ArchiveBrowserEvent event, void* context) { void archive_scene_browser_on_enter(void* context) { ArchiveApp* archive = (ArchiveApp*)context; - ArchiveMainView* main_view = archive->main_view; + ArchiveBrowserView* browser = archive->browser; - archive_browser_set_callback(main_view, archive_scene_browser_callback, archive); - archive_browser_update(main_view); + archive_browser_set_callback(browser, archive_scene_browser_callback, archive); + archive_update_focus(browser, archive->text_store); view_dispatcher_switch_to_view(archive->view_dispatcher, ArchiveViewBrowser); } bool archive_scene_browser_on_event(void* context, SceneManagerEvent event) { ArchiveApp* archive = (ArchiveApp*)context; + ArchiveBrowserView* browser = archive->browser; + ArchiveFile_t* selected = archive_get_current_file(browser); + + const char* path = archive_get_path(browser); + const char* name = archive_get_name(browser); + bool known_app = is_known_app(selected->type); + bool favorites = archive_get_tab(browser) == ArchiveTabFavorites; bool consumed = false; if(event.type == SceneManagerEventTypeCustom) { switch(event.event) { - case ArchiveBrowserEventRename: - scene_manager_next_scene(archive->scene_manager, ArchiveAppSceneRename); + case ArchiveBrowserEventFileMenuOpen: + archive_show_file_menu(browser, true); consumed = true; break; + case ArchiveBrowserEventFileMenuClose: + archive_show_file_menu(browser, false); + consumed = true; + break; + case ArchiveBrowserEventFileMenuRun: + if(known_app) { + archive_run_in_app(browser, selected, favorites); + } + consumed = true; + break; + case ArchiveBrowserEventFileMenuPin: + if(favorites) { + archive_favorites_delete(name); + archive_file_array_rm_selected(browser); + } else if(known_app) { + if(archive_is_favorite("%s/%s", path, name)) { + archive_favorites_delete("%s/%s", path, name); + } else { + archive_file_append(ARCHIVE_FAV_PATH, "%s/%s\r\n", path, name); + } + } + archive_show_file_menu(browser, false); + consumed = true; + break; + + case ArchiveBrowserEventFileMenuRename: + if(known_app && !favorites) { + scene_manager_next_scene(archive->scene_manager, ArchiveAppSceneRename); + } + consumed = true; + break; + case ArchiveBrowserEventFileMenuDelete: + archive_delete_file(browser, browser->path, selected->name); + archive_show_file_menu(browser, false); + consumed = true; + break; + case ArchiveBrowserEventEnterDir: + archive_enter_dir(browser, selected->name); + consumed = true; + break; + case ArchiveBrowserEventExit: - view_dispatcher_stop(archive->view_dispatcher); + if(archive_get_depth(browser)) { + archive_leave_dir(browser); + } else { + view_dispatcher_stop(archive->view_dispatcher); + } consumed = true; break; diff --git a/applications/archive/scenes/archive_scene_rename.c b/applications/archive/scenes/archive_scene_rename.c index 478eaf71..a5c70fac 100644 --- a/applications/archive/scenes/archive_scene_rename.c +++ b/applications/archive/scenes/archive_scene_rename.c @@ -1,6 +1,7 @@ #include "../archive_i.h" #include "../helpers/archive_favorites.h" #include "../helpers/archive_files.h" +#include "../helpers/archive_browser.h" #define SCENE_RENAME_CUSTOM_EVENT (0UL) @@ -13,10 +14,10 @@ void archive_scene_rename_on_enter(void* context) { ArchiveApp* archive = (ArchiveApp*)context; TextInput* text_input = archive->text_input; - ArchiveFile_t* current = archive_get_current_file(archive->main_view); + ArchiveFile_t* current = archive_get_current_file(archive->browser); strlcpy(archive->text_store, string_get_cstr(current->name), MAX_NAME_LEN); - archive_trim_file_ext(archive->text_store); + archive_trim_file_path(archive->text_store, true); text_input_set_header_text(text_input, "Rename:"); @@ -42,16 +43,14 @@ bool archive_scene_rename_on_event(void* context, SceneManagerEvent event) { string_t buffer_src; string_t buffer_dst; - const char* path = archive_get_path(archive->main_view); - const char* name = archive_get_name(archive->main_view); + const char* path = archive_get_path(archive->browser); + const char* name = archive_get_name(archive->browser); string_init_printf(buffer_src, "%s/%s", path, name); string_init_printf(buffer_dst, "%s/%s", path, archive->text_store); - archive_set_name(archive->main_view, archive->text_store); - // append extension - ArchiveFile_t* file = archive_get_current_file(archive->main_view); + ArchiveFile_t* file = archive_get_current_file(archive->browser); string_cat(buffer_dst, known_ext[file->type]); storage_common_rename( diff --git a/applications/archive/views/archive_browser_view.c b/applications/archive/views/archive_browser_view.c new file mode 100644 index 00000000..7b0a912f --- /dev/null +++ b/applications/archive/views/archive_browser_view.c @@ -0,0 +1,294 @@ +#include +#include "../archive_i.h" +#include "archive_browser_view.h" +#include "../helpers/archive_browser.h" + +static const char* ArchiveTabNames[] = { + [ArchiveTabFavorites] = "Favorites", + [ArchiveTabIButton] = "iButton", + [ArchiveTabNFC] = "NFC", + [ArchiveTabSubGhz] = "Sub-GHz", + [ArchiveTabLFRFID] = "RFID LF", + [ArchiveTabIrda] = "Infrared", + [ArchiveTabBrowser] = "Browser"}; + +static const Icon* ArchiveItemIcons[] = { + [ArchiveFileTypeIButton] = &I_ibutt_10px, + [ArchiveFileTypeNFC] = &I_Nfc_10px, + [ArchiveFileTypeSubGhz] = &I_sub1_10px, + [ArchiveFileTypeLFRFID] = &I_125_10px, + [ArchiveFileTypeIrda] = &I_ir_10px, + [ArchiveFileTypeFolder] = &I_dir_10px, + [ArchiveFileTypeUnknown] = &I_unknown_10px, +}; + +void archive_browser_set_callback( + ArchiveBrowserView* browser, + ArchiveBrowserViewCallback callback, + void* context) { + furi_assert(browser); + furi_assert(callback); + browser->callback = callback; + browser->context = context; +} + +static void render_item_menu(Canvas* canvas, ArchiveBrowserViewModel* model) { + canvas_set_color(canvas, ColorWhite); + canvas_draw_box(canvas, 71, 17, 57, 46); + canvas_set_color(canvas, ColorBlack); + elements_slightly_rounded_frame(canvas, 70, 16, 58, 48); + + string_t menu[MENU_ITEMS]; + + string_init_set_str(menu[0], "Run in app"); + string_init_set_str(menu[1], "Pin"); + string_init_set_str(menu[2], "Rename"); + string_init_set_str(menu[3], "Delete"); + + ArchiveFile_t* selected = files_array_get(model->files, model->idx); + + if(!is_known_app(selected->type)) { + string_set_str(menu[0], "---"); + string_set_str(menu[1], "---"); + string_set_str(menu[2], "---"); + } else if(selected->fav) { + string_set_str(menu[1], "Unpin"); + } else if(model->tab_idx == ArchiveTabFavorites) { + string_set_str(menu[1], "Unpin"); + string_set_str(menu[2], "---"); + } + + for(size_t i = 0; i < MENU_ITEMS; i++) { + canvas_draw_str(canvas, 82, 27 + i * 11, string_get_cstr(menu[i])); + string_clear(menu[i]); + } + + canvas_draw_icon(canvas, 74, 20 + model->menu_idx * 11, &I_ButtonRight_4x7); +} + +static void archive_draw_frame(Canvas* canvas, uint16_t idx, bool scrollbar) { + canvas_set_color(canvas, ColorBlack); + canvas_draw_box(canvas, 0, 15 + idx * FRAME_HEIGHT, scrollbar ? 122 : 127, FRAME_HEIGHT); + + canvas_set_color(canvas, ColorWhite); + canvas_draw_dot(canvas, 0, 15 + idx * FRAME_HEIGHT); + canvas_draw_dot(canvas, 1, 15 + idx * FRAME_HEIGHT); + canvas_draw_dot(canvas, 0, (15 + idx * FRAME_HEIGHT) + 1); + + canvas_draw_dot(canvas, 0, (15 + idx * FRAME_HEIGHT) + 11); + canvas_draw_dot(canvas, scrollbar ? 121 : 126, 15 + idx * FRAME_HEIGHT); + canvas_draw_dot(canvas, scrollbar ? 121 : 126, (15 + idx * FRAME_HEIGHT) + 11); +} + +static void draw_list(Canvas* canvas, ArchiveBrowserViewModel* model) { + furi_assert(model); + + size_t array_size = files_array_size(model->files); + bool scrollbar = array_size > 4; + + for(size_t i = 0; i < MIN(array_size, MENU_ITEMS); ++i) { + string_t str_buff; + char cstr_buff[MAX_NAME_LEN]; + + size_t idx = CLAMP(i + model->list_offset, array_size, 0); + ArchiveFile_t* file = files_array_get(model->files, CLAMP(idx, array_size - 1, 0)); + + strlcpy(cstr_buff, string_get_cstr(file->name), string_size(file->name) + 1); + archive_trim_file_path(cstr_buff, is_known_app(file->type)); + string_init_set_str(str_buff, cstr_buff); + elements_string_fit_width(canvas, str_buff, scrollbar ? MAX_LEN_PX - 6 : MAX_LEN_PX); + + if(model->idx == idx) { + archive_draw_frame(canvas, i, scrollbar); + } else { + canvas_set_color(canvas, ColorBlack); + } + + canvas_draw_icon(canvas, 2, 16 + i * FRAME_HEIGHT, ArchiveItemIcons[file->type]); + canvas_draw_str(canvas, 15, 24 + i * FRAME_HEIGHT, string_get_cstr(str_buff)); + string_clear(str_buff); + } + + if(scrollbar) { + elements_scrollbar_pos(canvas, 126, 15, 49, model->idx, array_size); + } + + if(model->menu) { + render_item_menu(canvas, model); + } +} + +static void archive_render_status_bar(Canvas* canvas, ArchiveBrowserViewModel* model) { + furi_assert(model); + + const char* tab_name = ArchiveTabNames[model->tab_idx]; + + canvas_draw_icon(canvas, 0, 0, &I_Background_128x11); + + canvas_set_color(canvas, ColorWhite); + canvas_draw_box(canvas, 0, 0, 50, 13); + canvas_draw_box(canvas, 107, 0, 20, 13); + + canvas_set_color(canvas, ColorBlack); + canvas_draw_frame(canvas, 1, 0, 50, 12); + canvas_draw_line(canvas, 0, 1, 0, 11); + canvas_draw_line(canvas, 1, 12, 49, 12); + canvas_draw_str_aligned(canvas, 26, 9, AlignCenter, AlignBottom, tab_name); + + canvas_draw_frame(canvas, 108, 0, 20, 12); + canvas_draw_line(canvas, 107, 1, 107, 11); + canvas_draw_line(canvas, 108, 12, 126, 12); + + canvas_draw_icon(canvas, 112, 2, &I_ButtonLeft_4x7); + canvas_draw_icon(canvas, 120, 2, &I_ButtonRight_4x7); + + canvas_set_color(canvas, ColorWhite); + canvas_draw_dot(canvas, 50, 0); + canvas_draw_dot(canvas, 127, 0); + + canvas_set_color(canvas, ColorBlack); +} + +void archive_view_render(Canvas* canvas, void* model) { + ArchiveBrowserViewModel* m = model; + + archive_render_status_bar(canvas, model); + + if(files_array_size(m->files)) { + draw_list(canvas, m); + } else { + canvas_draw_str_aligned( + canvas, GUI_DISPLAY_WIDTH / 2, 40, AlignCenter, AlignCenter, "Empty"); + } +} + +View* archive_browser_get_view(ArchiveBrowserView* browser) { + furi_assert(browser); + return browser->view; +} + +bool archive_view_input(InputEvent* event, void* context) { + furi_assert(event); + furi_assert(context); + + ArchiveBrowserView* browser = context; + + bool in_menu; + with_view_model( + browser->view, (ArchiveBrowserViewModel * model) { + in_menu = model->menu; + return false; + }); + + if(in_menu) { + if(event->type == InputTypeShort) { + if(event->key == InputKeyUp || event->key == InputKeyDown) { + with_view_model( + browser->view, (ArchiveBrowserViewModel * model) { + if(event->key == InputKeyUp) { + model->menu_idx = ((model->menu_idx - 1) + MENU_ITEMS) % MENU_ITEMS; + } else if(event->key == InputKeyDown) { + model->menu_idx = (model->menu_idx + 1) % MENU_ITEMS; + } + return true; + }); + } + + if(event->key == InputKeyOk) { + uint8_t idx; + with_view_model( + browser->view, (ArchiveBrowserViewModel * model) { + idx = model->menu_idx; + return false; + }); + browser->callback(file_menu_actions[idx], browser->context); + } else if(event->key == InputKeyBack) { + browser->callback(ArchiveBrowserEventFileMenuClose, browser->context); + } + } + + } else { + if(event->type == InputTypeShort) { + if(event->key == InputKeyLeft || event->key == InputKeyRight) { + archive_switch_tab(browser, event->key); + } else if(event->key == InputKeyBack) { + browser->callback(ArchiveBrowserEventExit, browser->context); + } + } + if(event->key == InputKeyUp || event->key == InputKeyDown) { + with_view_model( + browser->view, (ArchiveBrowserViewModel * model) { + uint16_t num_elements = (uint16_t)files_array_size(model->files); + if((event->type == InputTypeShort || event->type == InputTypeRepeat)) { + if(event->key == InputKeyUp) { + model->idx = ((model->idx - 1) + num_elements) % num_elements; + } else if(event->key == InputKeyDown) { + model->idx = (model->idx + 1) % num_elements; + } + } + + return true; + }); + archive_update_offset(browser); + } + + if(event->key == InputKeyOk) { + ArchiveFile_t* selected = archive_get_current_file(browser); + + if(selected) { + bool favorites = archive_get_tab(browser) == ArchiveTabFavorites; + bool folder = selected->type == ArchiveFileTypeFolder; + + if(event->type == InputTypeShort) { + if(favorites) { + browser->callback(ArchiveBrowserEventFileMenuRun, browser->context); + } else if(folder) { + browser->callback(ArchiveBrowserEventEnterDir, browser->context); + } else { + browser->callback(ArchiveBrowserEventFileMenuOpen, browser->context); + } + } else if(event->type == InputTypeLong) { + if(folder || favorites) { + browser->callback(ArchiveBrowserEventFileMenuOpen, browser->context); + } + } + } + } + } + + return true; +} + +ArchiveBrowserView* browser_alloc() { + ArchiveBrowserView* browser = furi_alloc(sizeof(ArchiveBrowserView)); + browser->view = view_alloc(); + view_allocate_model(browser->view, ViewModelTypeLocking, sizeof(ArchiveBrowserViewModel)); + view_set_context(browser->view, browser); + view_set_draw_callback(browser->view, (ViewDrawCallback)archive_view_render); + view_set_input_callback(browser->view, archive_view_input); + + string_init(browser->path); + + with_view_model( + browser->view, (ArchiveBrowserViewModel * model) { + files_array_init(model->files); + return true; + }); + + return browser; +} + +void browser_free(ArchiveBrowserView* browser) { + furi_assert(browser); + + with_view_model( + browser->view, (ArchiveBrowserViewModel * model) { + files_array_clear(model->files); + return false; + }); + + string_clear(browser->path); + + view_free(browser->view); + free(browser); +} diff --git a/applications/archive/views/archive_browser_view.h b/applications/archive/views/archive_browser_view.h new file mode 100644 index 00000000..d79a2c76 --- /dev/null +++ b/applications/archive/views/archive_browser_view.h @@ -0,0 +1,88 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include "../helpers/archive_files.h" +#include "../helpers/archive_favorites.h" + +#define MAX_LEN_PX 110 +#define MAX_NAME_LEN 255 +#define FRAME_HEIGHT 12 +#define MENU_ITEMS 4 +#define MAX_DEPTH 32 + +typedef enum { + ArchiveTabFavorites, + ArchiveTabLFRFID, + ArchiveTabSubGhz, + ArchiveTabNFC, + ArchiveTabIButton, + ArchiveTabIrda, + ArchiveTabBrowser, + ArchiveTabTotal, +} ArchiveTabEnum; + +typedef enum { + ArchiveBrowserEventFileMenuOpen, + ArchiveBrowserEventFileMenuClose, + ArchiveBrowserEventFileMenuRun, + ArchiveBrowserEventFileMenuPin, + ArchiveBrowserEventFileMenuRename, + ArchiveBrowserEventFileMenuDelete, + ArchiveBrowserEventEnterDir, + ArchiveBrowserEventExit, +} ArchiveBrowserEvent; + +static const uint8_t file_menu_actions[MENU_ITEMS] = { + [0] = ArchiveBrowserEventFileMenuRun, + [1] = ArchiveBrowserEventFileMenuPin, + [2] = ArchiveBrowserEventFileMenuRename, + [3] = ArchiveBrowserEventFileMenuDelete, +}; + +typedef struct ArchiveBrowserView ArchiveBrowserView; + +typedef void (*ArchiveBrowserViewCallback)(ArchiveBrowserEvent event, void* context); + +typedef enum { + BrowserActionBrowse, + BrowserActionItemMenu, + BrowserActionTotal, +} BrowserActionEnum; + +struct ArchiveBrowserView { + View* view; + ArchiveBrowserViewCallback callback; + void* context; + + string_t path; +}; + +typedef struct { + ArchiveTabEnum tab_idx; + ArchiveTabEnum last_tab; + files_array_t files; + + uint8_t depth; + uint8_t menu_idx; + bool menu; + + uint16_t idx; + uint16_t last_idx[MAX_DEPTH]; + uint16_t list_offset; + +} ArchiveBrowserViewModel; + +void archive_browser_set_callback( + ArchiveBrowserView* browser, + ArchiveBrowserViewCallback callback, + void* context); + +View* archive_browser_get_view(ArchiveBrowserView* browser); + +ArchiveBrowserView* browser_alloc(); +void browser_free(ArchiveBrowserView* browser); diff --git a/applications/archive/views/archive_main_view.c b/applications/archive/views/archive_main_view.c deleted file mode 100644 index ab2e11d7..00000000 --- a/applications/archive/views/archive_main_view.c +++ /dev/null @@ -1,641 +0,0 @@ -#include -#include "../archive_i.h" -#include "archive_main_view.h" - -static const char* flipper_app_name[] = { - [ArchiveFileTypeIButton] = "iButton", - [ArchiveFileTypeNFC] = "NFC", - [ArchiveFileTypeSubGhz] = "Sub-GHz", - [ArchiveFileTypeLFRFID] = "125 kHz RFID", - [ArchiveFileTypeIrda] = "Infrared", -}; - -static const char* ArchiveTabNames[] = { - [ArchiveTabFavorites] = "Favorites", - [ArchiveTabIButton] = "iButton", - [ArchiveTabNFC] = "NFC", - [ArchiveTabSubGhz] = "Sub-GHz", - [ArchiveTabLFRFID] = "RFID LF", - [ArchiveTabIrda] = "Infrared", - [ArchiveTabBrowser] = "Browser"}; - -static const Icon* ArchiveItemIcons[] = { - [ArchiveFileTypeIButton] = &I_ibutt_10px, - [ArchiveFileTypeNFC] = &I_Nfc_10px, - [ArchiveFileTypeSubGhz] = &I_sub1_10px, - [ArchiveFileTypeLFRFID] = &I_125_10px, - [ArchiveFileTypeIrda] = &I_ir_10px, - [ArchiveFileTypeFolder] = &I_dir_10px, - [ArchiveFileTypeUnknown] = &I_unknown_10px, -}; - -void archive_browser_set_callback( - ArchiveMainView* main_view, - ArchiveMainViewCallback callback, - void* context) { - furi_assert(main_view); - furi_assert(callback); - main_view->callback = callback; - main_view->context = context; -} - -void update_offset(ArchiveMainView* main_view) { - furi_assert(main_view); - with_view_model( - main_view->view, (ArchiveMainViewModel * model) { - size_t array_size = files_array_size(model->files); - uint16_t bounds = array_size > 3 ? 2 : array_size; - - if(array_size > 3 && model->idx >= array_size - 1) { - model->list_offset = model->idx - 3; - } else if(model->list_offset < model->idx - bounds) { - model->list_offset = CLAMP(model->idx - 2, array_size - bounds, 0); - } else if(model->list_offset > model->idx - bounds) { - model->list_offset = CLAMP(model->idx - 1, array_size - bounds, 0); - } - return true; - }); -} - -size_t archive_file_array_size(ArchiveMainView* main_view) { - uint16_t size = 0; - with_view_model( - main_view->view, (ArchiveMainViewModel * model) { - size = files_array_size(model->files); - return true; - }); - return size; -} - -void archive_file_array_remove_selected(ArchiveMainView* main_view) { - with_view_model( - main_view->view, (ArchiveMainViewModel * model) { - files_array_remove_v(model->files, model->idx, model->idx + 1); - model->idx = CLAMP(model->idx, files_array_size(model->files) - 1, 0); - return true; - }); - - update_offset(main_view); -} - -void archive_file_array_clean(ArchiveMainView* main_view) { - with_view_model( - main_view->view, (ArchiveMainViewModel * model) { - files_array_clean(model->files); - return true; - }); -} - -ArchiveFile_t* archive_get_current_file(ArchiveMainView* main_view) { - ArchiveFile_t* selected; - with_view_model( - main_view->view, (ArchiveMainViewModel * model) { - selected = files_array_size(model->files) > 0 ? - files_array_get(model->files, model->idx) : - NULL; - return true; - }); - return selected; -} - -ArchiveTabEnum archive_get_tab(ArchiveMainView* main_view) { - ArchiveTabEnum tab_id; - with_view_model( - main_view->view, (ArchiveMainViewModel * model) { - tab_id = model->tab_idx; - return true; - }); - return tab_id; -} - -void archive_set_tab(ArchiveMainView* main_view, ArchiveTabEnum tab) { - with_view_model( - main_view->view, (ArchiveMainViewModel * model) { - model->tab_idx = tab; - return true; - }); -} - -uint8_t archive_get_depth(ArchiveMainView* main_view) { - uint8_t depth; - with_view_model( - main_view->view, (ArchiveMainViewModel * model) { - depth = model->depth; - return true; - }); - - return depth; -} - -const char* archive_get_path(ArchiveMainView* main_view) { - return string_get_cstr(main_view->path); -} -const char* archive_get_name(ArchiveMainView* main_view) { - ArchiveFile_t* selected = archive_get_current_file(main_view); - return string_get_cstr(selected->name); -} - -void archive_set_name(ArchiveMainView* main_view, const char* name) { - furi_assert(main_view); - furi_assert(name); - - string_set(main_view->name, name); -} - -void archive_browser_update(ArchiveMainView* main_view) { - furi_assert(main_view); - - archive_get_filenames(main_view, archive_get_tab(main_view), string_get_cstr(main_view->path)); - - with_view_model( - main_view->view, (ArchiveMainViewModel * model) { - uint16_t idx = 0; - while(idx < files_array_size(model->files)) { - ArchiveFile_t* current = files_array_get(model->files, idx); - if(!string_search(current->name, string_get_cstr(main_view->name))) { - model->idx = idx; - break; - } - ++idx; - } - return true; - }); - - update_offset(main_view); -} - -void archive_view_add_item(ArchiveMainView* main_view, FileInfo* file_info, const char* name) { - furi_assert(main_view); - furi_assert(file_info); - furi_assert(name); - - ArchiveFile_t item; - - if(filter_by_extension(file_info, get_tab_ext(archive_get_tab(main_view)), name)) { - ArchiveFile_t_init(&item); - string_init_set_str(item.name, name); - set_file_type(&item, file_info); - - with_view_model( - main_view->view, (ArchiveMainViewModel * model) { - files_array_push_back(model->files, item); - return true; - }); - - ArchiveFile_t_clear(&item); - } -} - -static void render_item_menu(Canvas* canvas, ArchiveMainViewModel* model) { - canvas_set_color(canvas, ColorWhite); - canvas_draw_box(canvas, 71, 17, 57, 46); - canvas_set_color(canvas, ColorBlack); - elements_slightly_rounded_frame(canvas, 70, 16, 58, 48); - - string_t menu[MENU_ITEMS]; - - string_init_set_str(menu[0], "Run in app"); - string_init_set_str(menu[1], "Pin"); - string_init_set_str(menu[2], "Rename"); - string_init_set_str(menu[3], "Delete"); - - ArchiveFile_t* selected = files_array_get(model->files, model->idx); - - if(!is_known_app(selected->type)) { - string_set_str(menu[0], "---"); - string_set_str(menu[1], "---"); - string_set_str(menu[2], "---"); - } else if(selected->fav) { - string_set_str(menu[1], "Unpin"); - } - - for(size_t i = 0; i < MENU_ITEMS; i++) { - canvas_draw_str(canvas, 82, 27 + i * 11, string_get_cstr(menu[i])); - string_clear(menu[i]); - } - - canvas_draw_icon(canvas, 74, 20 + model->menu_idx * 11, &I_ButtonRight_4x7); -} - -static void archive_draw_frame(Canvas* canvas, uint16_t idx, bool scrollbar) { - canvas_set_color(canvas, ColorBlack); - canvas_draw_box(canvas, 0, 15 + idx * FRAME_HEIGHT, scrollbar ? 122 : 127, FRAME_HEIGHT); - - canvas_set_color(canvas, ColorWhite); - canvas_draw_dot(canvas, 0, 15 + idx * FRAME_HEIGHT); - canvas_draw_dot(canvas, 1, 15 + idx * FRAME_HEIGHT); - canvas_draw_dot(canvas, 0, (15 + idx * FRAME_HEIGHT) + 1); - - canvas_draw_dot(canvas, 0, (15 + idx * FRAME_HEIGHT) + 11); - canvas_draw_dot(canvas, scrollbar ? 121 : 126, 15 + idx * FRAME_HEIGHT); - canvas_draw_dot(canvas, scrollbar ? 121 : 126, (15 + idx * FRAME_HEIGHT) + 11); -} - -static void draw_list(Canvas* canvas, ArchiveMainViewModel* model) { - furi_assert(model); - - size_t array_size = files_array_size(model->files); - bool scrollbar = array_size > 4; - - for(size_t i = 0; i < MIN(array_size, MENU_ITEMS); ++i) { - string_t str_buff; - char cstr_buff[MAX_NAME_LEN]; - - size_t idx = CLAMP(i + model->list_offset, array_size, 0); - ArchiveFile_t* file = files_array_get(model->files, CLAMP(idx, array_size - 1, 0)); - - string_init_set(str_buff, file->name); - string_right(str_buff, string_search_rchar(str_buff, '/') + 1); - strlcpy(cstr_buff, string_get_cstr(str_buff), string_size(str_buff) + 1); - - if(is_known_app(file->type)) archive_trim_file_ext(cstr_buff); - - string_clean(str_buff); - string_set_str(str_buff, cstr_buff); - - elements_string_fit_width(canvas, str_buff, scrollbar ? MAX_LEN_PX - 6 : MAX_LEN_PX); - - if(model->idx == idx) { - archive_draw_frame(canvas, i, scrollbar); - } else { - canvas_set_color(canvas, ColorBlack); - } - - canvas_draw_icon(canvas, 2, 16 + i * FRAME_HEIGHT, ArchiveItemIcons[file->type]); - canvas_draw_str(canvas, 15, 24 + i * FRAME_HEIGHT, string_get_cstr(str_buff)); - string_clear(str_buff); - } - - if(scrollbar) { - elements_scrollbar_pos(canvas, 126, 15, 49, model->idx, array_size); - } - - if(model->action == BrowserActionItemMenu) { - render_item_menu(canvas, model); - } -} - -static void archive_render_status_bar(Canvas* canvas, ArchiveMainViewModel* model) { - furi_assert(model); - - const char* tab_name = ArchiveTabNames[model->tab_idx]; - - canvas_draw_icon(canvas, 0, 0, &I_Background_128x11); - - canvas_set_color(canvas, ColorWhite); - canvas_draw_box(canvas, 0, 0, 50, 13); - canvas_draw_box(canvas, 107, 0, 20, 13); - - canvas_set_color(canvas, ColorBlack); - canvas_draw_frame(canvas, 1, 0, 50, 12); - canvas_draw_line(canvas, 0, 1, 0, 11); - canvas_draw_line(canvas, 1, 12, 49, 12); - canvas_draw_str_aligned(canvas, 26, 9, AlignCenter, AlignBottom, tab_name); - - canvas_draw_frame(canvas, 108, 0, 20, 12); - canvas_draw_line(canvas, 107, 1, 107, 11); - canvas_draw_line(canvas, 108, 12, 126, 12); - - if(model->tab_idx > 0) { - canvas_draw_icon(canvas, 112, 2, &I_ButtonLeft_4x7); - } - if(model->tab_idx < SIZEOF_ARRAY(ArchiveTabNames) - 1) { - canvas_draw_icon(canvas, 120, 2, &I_ButtonRight_4x7); - } - - canvas_set_color(canvas, ColorWhite); - canvas_draw_dot(canvas, 50, 0); - canvas_draw_dot(canvas, 127, 0); - - canvas_set_color(canvas, ColorBlack); -} - -void archive_view_render(Canvas* canvas, void* model) { - ArchiveMainViewModel* m = model; - - archive_render_status_bar(canvas, model); - - if(files_array_size(m->files) > 0) { - draw_list(canvas, m); - } else { - canvas_draw_str_aligned( - canvas, GUI_DISPLAY_WIDTH / 2, 40, AlignCenter, AlignCenter, "Empty"); - } -} - -View* archive_main_get_view(ArchiveMainView* main_view) { - furi_assert(main_view); - return main_view->view; -} - -static void archive_show_file_menu(ArchiveMainView* main_view) { - furi_assert(main_view); - with_view_model( - main_view->view, (ArchiveMainViewModel * model) { - ArchiveFile_t* selected; - selected = files_array_get(model->files, model->idx); - model->action = BrowserActionItemMenu; - model->menu_idx = 0; - selected->fav = is_known_app(selected->type) ? archive_is_favorite( - string_get_cstr(main_view->path), - string_get_cstr(selected->name)) : - false; - - return true; - }); -} - -static void archive_close_file_menu(ArchiveMainView* main_view) { - furi_assert(main_view); - - with_view_model( - main_view->view, (ArchiveMainViewModel * model) { - model->action = BrowserActionBrowse; - model->menu_idx = 0; - return true; - }); -} - -static void archive_run_in_app( - ArchiveMainView* main_view, - ArchiveFile_t* selected, - bool full_path_provided) { - Loader* loader = furi_record_open("loader"); - - string_t full_path; - - if(!full_path_provided) { - string_init_printf( - full_path, "%s/%s", string_get_cstr(main_view->path), string_get_cstr(selected->name)); - } else { - string_init_set(full_path, selected->name); - } - loader_start(loader, flipper_app_name[selected->type], string_get_cstr(full_path)); - - string_clear(full_path); - furi_record_close("loader"); -} - -static void archive_file_menu_callback(ArchiveMainView* main_view) { - furi_assert(main_view); - - ArchiveFile_t* selected = archive_get_current_file(main_view); - const char* path = archive_get_path(main_view); - const char* name = archive_get_name(main_view); - - uint8_t idx; - with_view_model( - main_view->view, (ArchiveMainViewModel * model) { - idx = model->menu_idx; - return true; - }); - - switch(idx) { - case 0: - if(is_known_app(selected->type)) { - archive_run_in_app(main_view, selected, false); - } - break; - case 1: - if(is_known_app(selected->type)) { - if(!archive_is_favorite(path, name)) { - archive_set_name(main_view, string_get_cstr(selected->name)); - archive_add_to_favorites(path, name); - } else { - // delete from favorites - archive_favorites_delete(path, name); - } - archive_close_file_menu(main_view); - } - break; - case 2: - // open rename view - if(is_known_app(selected->type)) { - main_view->callback(ArchiveBrowserEventRename, main_view->context); - } - break; - case 3: - // confirmation? - archive_delete_file(main_view, main_view->path, selected->name); - archive_close_file_menu(main_view); - break; - - default: - archive_close_file_menu(main_view); - break; - } - selected = NULL; -} - -static void archive_switch_dir(ArchiveMainView* main_view, const char* path) { - furi_assert(main_view); - furi_assert(path); - - string_set(main_view->path, path); - archive_get_filenames(main_view, archive_get_tab(main_view), string_get_cstr(main_view->path)); - update_offset(main_view); -} - -void archive_switch_tab(ArchiveMainView* main_view) { - furi_assert(main_view); - - with_view_model( - main_view->view, (ArchiveMainViewModel * model) { - model->idx = 0; - model->depth = 0; - return true; - }); - - archive_switch_dir(main_view, tab_default_paths[archive_get_tab(main_view)]); -} - -static void archive_enter_dir(ArchiveMainView* main_view, string_t name) { - furi_assert(main_view); - furi_assert(name); - - // update last index - with_view_model( - main_view->view, (ArchiveMainViewModel * model) { - model->last_idx[model->depth] = - CLAMP(model->idx, files_array_size(model->files) - 1, 0); - model->idx = 0; - model->depth = CLAMP(model->depth + 1, MAX_DEPTH, 0); - return true; - }); - - string_cat(main_view->path, "/"); - string_cat(main_view->path, main_view->name); - - archive_switch_dir(main_view, string_get_cstr(main_view->path)); -} - -static void archive_leave_dir(ArchiveMainView* main_view) { - furi_assert(main_view); - - char* last_char_ptr = strrchr(string_get_cstr(main_view->path), '/'); - - if(last_char_ptr) { - size_t pos = last_char_ptr - string_get_cstr(main_view->path); - string_left(main_view->path, pos); - } - - with_view_model( - main_view->view, (ArchiveMainViewModel * model) { - model->depth = CLAMP(model->depth - 1, MAX_DEPTH, 0); - model->idx = model->last_idx[model->depth]; - return true; - }); - - archive_switch_dir(main_view, string_get_cstr(main_view->path)); -} - -bool archive_view_input(InputEvent* event, void* context) { - furi_assert(event); - furi_assert(context); - - ArchiveMainView* main_view = context; - - BrowserActionEnum action; - with_view_model( - main_view->view, (ArchiveMainViewModel * model) { - action = model->action; - return true; - }); - - switch(action) { - case BrowserActionItemMenu: - - if(event->type == InputTypeShort) { - if(event->key == InputKeyUp || event->key == InputKeyDown) { - with_view_model( - main_view->view, (ArchiveMainViewModel * model) { - if(event->key == InputKeyUp) { - model->menu_idx = ((model->menu_idx - 1) + MENU_ITEMS) % MENU_ITEMS; - } else if(event->key == InputKeyDown) { - model->menu_idx = (model->menu_idx + 1) % MENU_ITEMS; - } - return true; - }); - } - - if(event->key == InputKeyOk) { - archive_file_menu_callback(main_view); - } else if(event->key == InputKeyBack) { - archive_close_file_menu(main_view); - } - } - break; - - case BrowserActionBrowse: - - if(event->type == InputTypeShort) { - if(event->key == InputKeyLeft) { - ArchiveTabEnum tab = archive_get_tab(main_view); - if(tab) { - archive_set_tab(main_view, CLAMP(tab - 1, ArchiveTabTotal, 0)); - archive_switch_tab(main_view); - return true; - } - } else if(event->key == InputKeyRight) { - ArchiveTabEnum tab = archive_get_tab(main_view); - - if(tab < ArchiveTabTotal - 1) { - archive_set_tab(main_view, CLAMP(tab + 1, ArchiveTabTotal - 1, 0)); - archive_switch_tab(main_view); - return true; - } - - } else if(event->key == InputKeyBack) { - if(!archive_get_depth(main_view)) { - main_view->callback(ArchiveBrowserEventExit, main_view->context); - } else { - archive_leave_dir(main_view); - } - - return true; - } - } - if(event->key == InputKeyUp || event->key == InputKeyDown) { - with_view_model( - main_view->view, (ArchiveMainViewModel * model) { - uint16_t num_elements = (uint16_t)files_array_size(model->files); - if((event->type == InputTypeShort || event->type == InputTypeRepeat)) { - if(event->key == InputKeyUp) { - model->idx = ((model->idx - 1) + num_elements) % num_elements; - } else if(event->key == InputKeyDown) { - model->idx = (model->idx + 1) % num_elements; - } - } - - return true; - }); - update_offset(main_view); - } - - if(event->key == InputKeyOk) { - ArchiveFile_t* selected = archive_get_current_file(main_view); - - if(selected) { - archive_set_name(main_view, string_get_cstr(selected->name)); - if(selected->type == ArchiveFileTypeFolder) { - if(event->type == InputTypeShort) { - archive_enter_dir(main_view, main_view->name); - } else if(event->type == InputTypeLong) { - archive_show_file_menu(main_view); - } - } else { - if(event->type == InputTypeShort) { - if(archive_get_tab(main_view) == ArchiveTabFavorites) { - if(is_known_app(selected->type)) { - archive_run_in_app(main_view, selected, true); - } - } else { - archive_show_file_menu(main_view); - } - } - } - } - } - break; - default: - break; - } - - return true; -} - -ArchiveMainView* main_view_alloc() { - ArchiveMainView* main_view = furi_alloc(sizeof(ArchiveMainView)); - main_view->view = view_alloc(); - view_allocate_model(main_view->view, ViewModelTypeLocking, sizeof(ArchiveMainViewModel)); - view_set_context(main_view->view, main_view); - view_set_draw_callback(main_view->view, (ViewDrawCallback)archive_view_render); - view_set_input_callback(main_view->view, archive_view_input); - - string_init(main_view->name); - string_init(main_view->path); - - with_view_model( - main_view->view, (ArchiveMainViewModel * model) { - files_array_init(model->files); - return true; - }); - - return main_view; -} - -void main_view_free(ArchiveMainView* main_view) { - furi_assert(main_view); - - with_view_model( - main_view->view, (ArchiveMainViewModel * model) { - files_array_clear(model->files); - return false; - }); - - string_clear(main_view->name); - string_clear(main_view->path); - - view_free(main_view->view); - free(main_view); -} diff --git a/applications/archive/views/archive_main_view.h b/applications/archive/views/archive_main_view.h deleted file mode 100644 index b3d07879..00000000 --- a/applications/archive/views/archive_main_view.h +++ /dev/null @@ -1,117 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include -#include -#include "../helpers/archive_files.h" -#include "../helpers/archive_favorites.h" - -#define MAX_LEN_PX 110 -#define MAX_NAME_LEN 255 -#define FRAME_HEIGHT 12 -#define MENU_ITEMS 4 -#define MAX_DEPTH 32 - -typedef enum { - ArchiveTabFavorites, - ArchiveTabLFRFID, - ArchiveTabSubGhz, - ArchiveTabNFC, - ArchiveTabIButton, - ArchiveTabIrda, - ArchiveTabBrowser, - ArchiveTabTotal, -} ArchiveTabEnum; - -static const char* known_ext[] = { - [ArchiveFileTypeIButton] = ".ibtn", - [ArchiveFileTypeNFC] = ".nfc", - [ArchiveFileTypeSubGhz] = ".sub", - [ArchiveFileTypeLFRFID] = ".rfid", - [ArchiveFileTypeIrda] = ".ir", -}; - -static inline const char* get_tab_ext(ArchiveTabEnum tab) { - switch(tab) { - case ArchiveTabIButton: - return known_ext[ArchiveFileTypeIButton]; - case ArchiveTabNFC: - return known_ext[ArchiveFileTypeNFC]; - case ArchiveTabSubGhz: - return known_ext[ArchiveFileTypeSubGhz]; - case ArchiveTabLFRFID: - return known_ext[ArchiveFileTypeLFRFID]; - case ArchiveTabIrda: - return known_ext[ArchiveFileTypeIrda]; - default: - return "*"; - } -} - -typedef enum { - ArchiveBrowserEventRename, - ArchiveBrowserEventExit, - ArchiveBrowserEventLeaveDir, -} ArchiveBrowserEvent; - -typedef struct ArchiveMainView ArchiveMainView; - -typedef void (*ArchiveMainViewCallback)(ArchiveBrowserEvent event, void* context); - -typedef enum { - BrowserActionBrowse, - BrowserActionItemMenu, - BrowserActionTotal, -} BrowserActionEnum; - -struct ArchiveMainView { - View* view; - ArchiveMainViewCallback callback; - void* context; - - string_t name; - string_t path; -}; - -typedef struct { - ArchiveTabEnum tab_idx; - BrowserActionEnum action; - files_array_t files; - - uint8_t depth; - uint8_t menu_idx; - - uint16_t idx; - uint16_t last_idx[MAX_DEPTH]; - uint16_t list_offset; - -} ArchiveMainViewModel; - -void archive_browser_set_callback( - ArchiveMainView* main_view, - ArchiveMainViewCallback callback, - void* context); - -View* archive_main_get_view(ArchiveMainView* main_view); - -ArchiveMainView* main_view_alloc(); -void main_view_free(ArchiveMainView* main_view); - -void archive_file_array_remove_selected(ArchiveMainView* main_view); -void archive_file_array_clean(ArchiveMainView* main_view); - -void archive_view_add_item(ArchiveMainView* main_view, FileInfo* file_info, const char* name); -void archive_browser_update(ArchiveMainView* main_view); - -size_t archive_file_array_size(ArchiveMainView* main_view); -ArchiveFile_t* archive_get_current_file(ArchiveMainView* main_view); -const char* archive_get_path(ArchiveMainView* main_view); -const char* archive_get_name(ArchiveMainView* main_view); -void archive_set_name(ArchiveMainView* main_view, const char* name); - -static inline bool is_known_app(ArchiveFileTypeEnum type) { - return (type != ArchiveFileTypeFolder && type != ArchiveFileTypeUnknown); -}