[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 <alleteam@gmail.com>
This commit is contained in:
		| @@ -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); | ||||
|  | ||||
|   | ||||
| @@ -9,72 +9,20 @@ | ||||
| #include <gui/modules/text_input.h> | ||||
| #include <loader/loader.h> | ||||
|  | ||||
| #include <m-string.h> | ||||
| #include <m-array.h> | ||||
| #include <storage/storage.h> | ||||
| #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]; | ||||
| }; | ||||
|   | ||||
							
								
								
									
										255
									
								
								applications/archive/helpers/archive_browser.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										255
									
								
								applications/archive/helpers/archive_browser.c
									
									
									
									
									
										Normal file
									
								
							| @@ -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); | ||||
| } | ||||
							
								
								
									
										68
									
								
								applications/archive/helpers/archive_browser.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										68
									
								
								applications/archive/helpers/archive_browser.h
									
									
									
									
									
										Normal file
									
								
							| @@ -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); | ||||
| @@ -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); | ||||
| } | ||||
|   | ||||
| @@ -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); | ||||
| @@ -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); | ||||
| } | ||||
|   | ||||
| @@ -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); | ||||
| @@ -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; | ||||
|  | ||||
|   | ||||
| @@ -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( | ||||
|   | ||||
							
								
								
									
										294
									
								
								applications/archive/views/archive_browser_view.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										294
									
								
								applications/archive/views/archive_browser_view.c
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,294 @@ | ||||
| #include <furi.h> | ||||
| #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); | ||||
| } | ||||
							
								
								
									
										88
									
								
								applications/archive/views/archive_browser_view.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										88
									
								
								applications/archive/views/archive_browser_view.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,88 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include <gui/gui_i.h> | ||||
| #include <gui/view.h> | ||||
| #include <gui/canvas.h> | ||||
| #include <gui/elements.h> | ||||
| #include <furi.h> | ||||
| #include <storage/storage.h> | ||||
| #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); | ||||
| @@ -1,641 +0,0 @@ | ||||
| #include <furi.h> | ||||
| #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); | ||||
| } | ||||
| @@ -1,117 +0,0 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include <gui/gui_i.h> | ||||
| #include <gui/view.h> | ||||
| #include <gui/canvas.h> | ||||
| #include <gui/elements.h> | ||||
| #include <furi.h> | ||||
| #include <storage/storage.h> | ||||
| #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); | ||||
| } | ||||
		Reference in New Issue
	
	Block a user