[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:
		
							parent
							
								
									0e1922db4d
								
							
						
					
					
						commit
						4c05f67686
					
				| @ -31,10 +31,10 @@ ArchiveApp* archive_alloc() { | |||||||
|     view_dispatcher_set_navigation_event_callback( |     view_dispatcher_set_navigation_event_callback( | ||||||
|         archive->view_dispatcher, archive_back_event_callback); |         archive->view_dispatcher, archive_back_event_callback); | ||||||
| 
 | 
 | ||||||
|     archive->main_view = main_view_alloc(); |     archive->browser = browser_alloc(); | ||||||
| 
 | 
 | ||||||
|     view_dispatcher_add_view( |     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( |     view_dispatcher_add_view( | ||||||
|         archive->view_dispatcher, ArchiveViewTextInput, text_input_get_view(archive->text_input)); |         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_remove_view(archive->view_dispatcher, ArchiveViewTextInput); | ||||||
|     view_dispatcher_free(archive->view_dispatcher); |     view_dispatcher_free(archive->view_dispatcher); | ||||||
|     scene_manager_free(archive->scene_manager); |     scene_manager_free(archive->scene_manager); | ||||||
|     main_view_free(archive->main_view); |     browser_free(archive->browser); | ||||||
| 
 | 
 | ||||||
|     text_input_free(archive->text_input); |     text_input_free(archive->text_input); | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -9,72 +9,20 @@ | |||||||
| #include <gui/modules/text_input.h> | #include <gui/modules/text_input.h> | ||||||
| #include <loader/loader.h> | #include <loader/loader.h> | ||||||
| 
 | 
 | ||||||
| #include <m-string.h> | #include "views/archive_browser_view.h" | ||||||
| #include <m-array.h> |  | ||||||
| #include <storage/storage.h> |  | ||||||
| #include "applications.h" |  | ||||||
| #include "file-worker.h" |  | ||||||
| 
 |  | ||||||
| #include "views/archive_main_view.h" |  | ||||||
| #include "scenes/archive_scene.h" | #include "scenes/archive_scene.h" | ||||||
| 
 | 
 | ||||||
| #define MAX_FILE_SIZE 128 |  | ||||||
| 
 |  | ||||||
| typedef enum { | typedef enum { | ||||||
|     ArchiveViewBrowser, |     ArchiveViewBrowser, | ||||||
|     ArchiveViewTextInput, |     ArchiveViewTextInput, | ||||||
|     ArchiveViewTotal, |     ArchiveViewTotal, | ||||||
| } ArchiveViewEnum; | } 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 { | struct ArchiveApp { | ||||||
|     Gui* gui; |     Gui* gui; | ||||||
|     ViewDispatcher* view_dispatcher; |     ViewDispatcher* view_dispatcher; | ||||||
|     SceneManager* scene_manager; |     SceneManager* scene_manager; | ||||||
|     ArchiveMainView* main_view; |     ArchiveBrowserView* browser; | ||||||
|     TextInput* text_input; |     TextInput* text_input; | ||||||
|     char text_store[MAX_NAME_LEN]; |     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_favorites.h" | ||||||
| #include "archive_files.h" | #include "archive_browser.h" | ||||||
| #include "../views/archive_main_view.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) { | bool archive_favorites_read(void* context) { | ||||||
|     furi_assert(context); |     furi_assert(context); | ||||||
| 
 | 
 | ||||||
|     ArchiveMainView* archive_view = context; |     ArchiveBrowserView* archive_view = context; | ||||||
|     FileWorker* file_worker = file_worker_alloc(true); |     FileWorker* file_worker = file_worker_alloc(true); | ||||||
| 
 | 
 | ||||||
|     string_t buffer; |     string_t buffer; | ||||||
|     FileInfo file_info; |     FileInfo file_info; | ||||||
|     string_init(buffer); |     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) { |     if(result) { | ||||||
|         while(1) { |         while(1) { | ||||||
| @ -23,7 +52,7 @@ bool archive_favorites_read(void* context) { | |||||||
|                 break; |                 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); |             string_clean(buffer); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| @ -33,18 +62,19 @@ bool archive_favorites_read(void* context) { | |||||||
|     return result; |     return result; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| bool archive_favorites_delete(const char* file_path, const char* name) { | bool archive_favorites_delete(const char* format, ...) { | ||||||
|     furi_assert(file_path); |     va_list args; | ||||||
|     furi_assert(name); |     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); |     FileWorker* file_worker = file_worker_alloc(true); | ||||||
| 
 | 
 | ||||||
|     string_t path; |  | ||||||
|     string_t buffer; |     string_t buffer; | ||||||
|     string_init(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); |     bool result = file_worker_open(file_worker, ARCHIVE_FAV_PATH, FSAM_READ, FSOM_OPEN_EXISTING); | ||||||
|     if(result) { |     if(result) { | ||||||
|         while(1) { |         while(1) { | ||||||
| @ -55,17 +85,13 @@ bool archive_favorites_delete(const char* file_path, const char* name) { | |||||||
|                 break; |                 break; | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             if(string_search(buffer, path)) { |             if(string_search_str(buffer, filename)) { | ||||||
|                 string_t temp; |                 archive_file_append(ARCHIVE_FAV_TEMP_PATH, "%s\r\n", string_get_cstr(buffer)); | ||||||
|                 string_init_printf(temp, "%s\r\n", string_get_cstr(buffer)); |  | ||||||
|                 archive_file_append(ARCHIVE_FAV_TEMP_PATH, temp); |  | ||||||
|                 string_clear(temp); |  | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     string_clear(buffer); |     string_clear(buffer); | ||||||
|     string_clear(path); |  | ||||||
| 
 | 
 | ||||||
|     file_worker_close(file_worker); |     file_worker_close(file_worker); | ||||||
|     file_worker_remove(file_worker, ARCHIVE_FAV_PATH); |     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; |     return result; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| bool archive_is_favorite(const char* file_path, const char* name) { | bool archive_is_favorite(const char* format, ...) { | ||||||
|     furi_assert(file_path); |     va_list args; | ||||||
|     furi_assert(name); |     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); |     FileWorker* file_worker = file_worker_alloc(true); | ||||||
| 
 |  | ||||||
|     string_t path; |  | ||||||
|     string_t buffer; |     string_t buffer; | ||||||
|     string_init(buffer); |     string_init(buffer); | ||||||
|     bool found = false; |  | ||||||
| 
 | 
 | ||||||
|     string_init_printf(path, "%s/%s", file_path, name); |     bool found = false; | ||||||
|     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) { |     if(result) { | ||||||
|         while(1) { |         while(1) { | ||||||
| @ -98,7 +125,7 @@ bool archive_is_favorite(const char* file_path, const char* name) { | |||||||
|             if(!string_size(buffer)) { |             if(!string_size(buffer)) { | ||||||
|                 break; |                 break; | ||||||
|             } |             } | ||||||
|             if(!string_search(buffer, path)) { |             if(!string_search_str(buffer, filename)) { | ||||||
|                 found = true; |                 found = true; | ||||||
|                 break; |                 break; | ||||||
|             } |             } | ||||||
| @ -106,7 +133,6 @@ bool archive_is_favorite(const char* file_path, const char* name) { | |||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     string_clear(buffer); |     string_clear(buffer); | ||||||
|     string_clear(path); |  | ||||||
|     file_worker_close(file_worker); |     file_worker_close(file_worker); | ||||||
|     file_worker_free(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 path; | ||||||
|     string_t buffer; |     string_t buffer; | ||||||
|     string_t temp; |  | ||||||
| 
 | 
 | ||||||
|     string_init(buffer); |     string_init(buffer); | ||||||
|     string_init(temp); |  | ||||||
|     string_init(path); |     string_init(path); | ||||||
| 
 | 
 | ||||||
|     string_printf(path, "%s/%s", file_path, src); |     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)) { |             if(!string_size(buffer)) { | ||||||
|                 break; |                 break; | ||||||
|             } |             } | ||||||
|             string_printf( | 
 | ||||||
|                 temp, "%s\r\n", string_search(buffer, path) ? string_get_cstr(buffer) : dst); |             archive_file_append( | ||||||
|             archive_file_append(ARCHIVE_FAV_TEMP_PATH, temp); |                 ARCHIVE_FAV_TEMP_PATH, | ||||||
|             string_clean(temp); |                 "%s\r\n", | ||||||
|  |                 string_search(buffer, path) ? string_get_cstr(buffer) : dst); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     string_clear(temp); |  | ||||||
|     string_clear(buffer); |     string_clear(buffer); | ||||||
|     string_clear(path); |     string_clear(path); | ||||||
| 
 | 
 | ||||||
| @ -159,13 +183,8 @@ bool archive_favorites_rename(const char* file_path, const char* src, const char | |||||||
|     return result; |     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(file_path); | ||||||
|     furi_assert(name); |  | ||||||
| 
 | 
 | ||||||
|     string_t buffer_src; |     archive_file_append(ARCHIVE_FAV_PATH, "%s\r\n", file_path); | ||||||
| 
 |  | ||||||
|     string_init_printf(buffer_src, "%s/%s\r\n", file_path, name); |  | ||||||
|     archive_file_append(ARCHIVE_FAV_PATH, buffer_src); |  | ||||||
|     string_clear(buffer_src); |  | ||||||
| } | } | ||||||
|  | |||||||
| @ -4,8 +4,9 @@ | |||||||
| #define ARCHIVE_FAV_PATH "/any/favorites.txt" | #define ARCHIVE_FAV_PATH "/any/favorites.txt" | ||||||
| #define ARCHIVE_FAV_TEMP_PATH "/any/favorites.tmp" | #define ARCHIVE_FAV_TEMP_PATH "/any/favorites.tmp" | ||||||
| 
 | 
 | ||||||
|  | uint16_t archive_favorites_count(void* context); | ||||||
| bool archive_favorites_read(void* context); | bool archive_favorites_read(void* context); | ||||||
| bool archive_favorites_delete(const char* file_path, const char* name); | bool archive_favorites_delete(const char* format, ...); | ||||||
| bool archive_is_favorite(const char* file_path, const char* name); | bool archive_is_favorite(const char* format, ...); | ||||||
| bool archive_favorites_rename(const char* file_path, const char* src, const char* dst); | 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_files.h" | ||||||
| #include "archive_favorites.h" | #include "archive_browser.h" | ||||||
| #include "../archive_i.h" |  | ||||||
| 
 | 
 | ||||||
| bool filter_by_extension(FileInfo* file_info, const char* tab_ext, const char* name) { | bool filter_by_extension(FileInfo* file_info, const char* tab_ext, const char* name) { | ||||||
|     furi_assert(file_info); |     furi_assert(file_info); | ||||||
| @ -20,14 +19,12 @@ bool filter_by_extension(FileInfo* file_info, const char* tab_ext, const char* n | |||||||
|     return result; |     return result; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void archive_trim_file_ext(char* name) { | void archive_trim_file_path(char* name, bool ext) { | ||||||
|     size_t str_len = strlen(name); |     char* slash = strrchr(name, '/') + 1; | ||||||
|     char* end = name + str_len; |     if(strlen(slash)) strlcpy(name, slash, strlen(slash) + 1); | ||||||
|     while(end > name && *end != '.' && *end != '\\' && *end != '/') { |     if(ext) { | ||||||
|         --end; |         char* dot = strrchr(name, '.'); | ||||||
|     } |         if(strlen(dot)) *dot = '\0'; | ||||||
|     if((end > name && *end == '.') && (*(end - 1) != '\\' && *(end - 1) != '/')) { |  | ||||||
|         *end = '\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); |     furi_assert(context); | ||||||
| 
 | 
 | ||||||
|     ArchiveMainView* main_view = context; |     bool res; | ||||||
|     archive_file_array_clean(main_view); |     ArchiveBrowserView* browser = context; | ||||||
|  |     archive_file_array_rm_all(browser); | ||||||
| 
 | 
 | ||||||
|     if(tab_id != ArchiveTabFavorites) { |     if(archive_get_tab(browser) != ArchiveTabFavorites) { | ||||||
|         archive_read_dir(main_view, path); |         res = archive_read_dir(browser, path); | ||||||
|     } else { |     } 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); |     furi_assert(context); | ||||||
| 
 | 
 | ||||||
|     ArchiveMainView* main_view = context; |  | ||||||
|     FileInfo file_info; |     FileInfo file_info; | ||||||
|     Storage* fs_api = furi_record_open("storage"); |     Storage* fs_api = furi_record_open("storage"); | ||||||
|     File* directory = storage_file_alloc(fs_api); |     File* directory = storage_file_alloc(fs_api); | ||||||
| @ -78,17 +75,52 @@ bool archive_read_dir(void* context, const char* path) { | |||||||
|         return false; |         return false; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     bool files_found = false; | ||||||
|     while(1) { |     while(1) { | ||||||
|         if(!storage_dir_read(directory, &file_info, name, MAX_NAME_LEN)) { |         if(!storage_dir_read(directory, &file_info, name, MAX_NAME_LEN)) { | ||||||
|             break; |             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) { |         if(files_cnt > MAX_FILES) { | ||||||
|             break; |             break; | ||||||
|         } else if(storage_file_get_error(directory) == FSE_OK) { |         } 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 { |         } else { | ||||||
|             storage_dir_close(directory); |             storage_dir_close(directory); | ||||||
|             storage_file_free(directory); |             storage_file_free(directory); | ||||||
| @ -103,9 +135,15 @@ bool archive_read_dir(void* context, const char* path) { | |||||||
|     return true; |     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(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); |     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"); |         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"); |         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(context); | ||||||
|     furi_assert(path); |     furi_assert(path); | ||||||
|     furi_assert(name); |     furi_assert(name); | ||||||
|     ArchiveMainView* main_view = context; |     ArchiveBrowserView* browser = context; | ||||||
|     FileWorker* file_worker = file_worker_alloc(false); |     FileWorker* file_worker = file_worker_alloc(true); | ||||||
| 
 | 
 | ||||||
|     string_t full_path; |     string_t full_path; | ||||||
|     string_init(full_path); |     string_init_printf(full_path, "%s/%s", string_get_cstr(path), string_get_cstr(name)); | ||||||
|     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); |  | ||||||
| 
 | 
 | ||||||
|     if(archive_is_favorite(string_get_cstr(path), string_get_cstr(name))) { |     bool res = file_worker_remove(file_worker, string_get_cstr(full_path)); | ||||||
|         archive_favorites_delete(string_get_cstr(path), string_get_cstr(name)); |     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); | 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 set_file_type(ArchiveFile_t* file, FileInfo* file_info); | ||||||
| void archive_trim_file_ext(char* name); | void archive_trim_file_path(char* name, bool ext); | ||||||
| bool archive_get_filenames(void* context, uint8_t tab_id, const char* path); | 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); | 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); | void archive_delete_file(void* context, string_t path, string_t name); | ||||||
| @ -1,5 +1,35 @@ | |||||||
| #include "../archive_i.h" | #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) { | void archive_scene_browser_callback(ArchiveBrowserEvent event, void* context) { | ||||||
|     ArchiveApp* archive = (ArchiveApp*)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) { | void archive_scene_browser_on_enter(void* context) { | ||||||
|     ArchiveApp* archive = (ArchiveApp*)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_set_callback(browser, archive_scene_browser_callback, archive); | ||||||
|     archive_browser_update(main_view); |     archive_update_focus(browser, archive->text_store); | ||||||
|     view_dispatcher_switch_to_view(archive->view_dispatcher, ArchiveViewBrowser); |     view_dispatcher_switch_to_view(archive->view_dispatcher, ArchiveViewBrowser); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| bool archive_scene_browser_on_event(void* context, SceneManagerEvent event) { | bool archive_scene_browser_on_event(void* context, SceneManagerEvent event) { | ||||||
|     ArchiveApp* archive = (ArchiveApp*)context; |     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; |     bool consumed = false; | ||||||
| 
 | 
 | ||||||
|     if(event.type == SceneManagerEventTypeCustom) { |     if(event.type == SceneManagerEventTypeCustom) { | ||||||
|         switch(event.event) { |         switch(event.event) { | ||||||
|         case ArchiveBrowserEventRename: |         case ArchiveBrowserEventFileMenuOpen: | ||||||
|             scene_manager_next_scene(archive->scene_manager, ArchiveAppSceneRename); |             archive_show_file_menu(browser, true); | ||||||
|             consumed = true; |             consumed = true; | ||||||
|             break; |             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: |         case ArchiveBrowserEventExit: | ||||||
|  |             if(archive_get_depth(browser)) { | ||||||
|  |                 archive_leave_dir(browser); | ||||||
|  |             } else { | ||||||
|                 view_dispatcher_stop(archive->view_dispatcher); |                 view_dispatcher_stop(archive->view_dispatcher); | ||||||
|  |             } | ||||||
|             consumed = true; |             consumed = true; | ||||||
|             break; |             break; | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -1,6 +1,7 @@ | |||||||
| #include "../archive_i.h" | #include "../archive_i.h" | ||||||
| #include "../helpers/archive_favorites.h" | #include "../helpers/archive_favorites.h" | ||||||
| #include "../helpers/archive_files.h" | #include "../helpers/archive_files.h" | ||||||
|  | #include "../helpers/archive_browser.h" | ||||||
| 
 | 
 | ||||||
| #define SCENE_RENAME_CUSTOM_EVENT (0UL) | #define SCENE_RENAME_CUSTOM_EVENT (0UL) | ||||||
| 
 | 
 | ||||||
| @ -13,10 +14,10 @@ void archive_scene_rename_on_enter(void* context) { | |||||||
|     ArchiveApp* archive = (ArchiveApp*)context; |     ArchiveApp* archive = (ArchiveApp*)context; | ||||||
| 
 | 
 | ||||||
|     TextInput* text_input = archive->text_input; |     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); |     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:"); |     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_src; | ||||||
|             string_t buffer_dst; |             string_t buffer_dst; | ||||||
| 
 | 
 | ||||||
|             const char* path = archive_get_path(archive->main_view); |             const char* path = archive_get_path(archive->browser); | ||||||
|             const char* name = archive_get_name(archive->main_view); |             const char* name = archive_get_name(archive->browser); | ||||||
| 
 | 
 | ||||||
|             string_init_printf(buffer_src, "%s/%s", path, name); |             string_init_printf(buffer_src, "%s/%s", path, name); | ||||||
|             string_init_printf(buffer_dst, "%s/%s", path, archive->text_store); |             string_init_printf(buffer_dst, "%s/%s", path, archive->text_store); | ||||||
| 
 | 
 | ||||||
|             archive_set_name(archive->main_view, archive->text_store); |  | ||||||
| 
 |  | ||||||
|             // append extension
 |             // 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]); |             string_cat(buffer_dst, known_ext[file->type]); | ||||||
|             storage_common_rename( |             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); |  | ||||||
| } |  | ||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 its your bedtime
						its your bedtime