Archive refactoring WIP (#688)
* view_dispatcher queue * refactoring, all works * scenes Co-authored-by: あく <alleteam@gmail.com>
This commit is contained in:
		
							parent
							
								
									9c39290f12
								
							
						
					
					
						commit
						b6579d66d8
					
				| @ -1,831 +1,69 @@ | |||||||
| #include "archive_i.h" | #include "archive_i.h" | ||||||
| 
 | 
 | ||||||
| static bool archive_get_filenames(ArchiveApp* archive); | bool archive_custom_event_callback(void* context, uint32_t event) { | ||||||
| 
 |  | ||||||
| static void update_offset(ArchiveApp* archive) { |  | ||||||
|     furi_assert(archive); |  | ||||||
| 
 |  | ||||||
|     with_view_model( |  | ||||||
|         archive->view_archive_main, (ArchiveViewModel * 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->list_offset + 1, 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; |  | ||||||
|         }); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| static void archive_update_last_idx(ArchiveApp* archive) { |  | ||||||
|     furi_assert(archive); |  | ||||||
| 
 |  | ||||||
|     with_view_model( |  | ||||||
|         archive->view_archive_main, (ArchiveViewModel * model) { |  | ||||||
|             archive->browser.last_idx[archive->browser.depth] = |  | ||||||
|                 CLAMP(model->idx, files_array_size(model->files) - 1, 0); |  | ||||||
|             model->idx = 0; |  | ||||||
|             return true; |  | ||||||
|         }); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| static void archive_switch_dir(ArchiveApp* archive, const char* path) { |  | ||||||
|     furi_assert(archive); |  | ||||||
|     furi_assert(path); |  | ||||||
|     string_set(archive->browser.path, path); |  | ||||||
|     archive_get_filenames(archive); |  | ||||||
|     update_offset(archive); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| static void archive_switch_tab(ArchiveApp* archive) { |  | ||||||
|     furi_assert(archive); |  | ||||||
| 
 |  | ||||||
|     with_view_model( |  | ||||||
|         archive->view_archive_main, (ArchiveViewModel * model) { |  | ||||||
|             model->tab_idx = archive->browser.tab_id; |  | ||||||
|             model->idx = 0; |  | ||||||
| 
 |  | ||||||
|             return true; |  | ||||||
|         }); |  | ||||||
| 
 |  | ||||||
|     archive->browser.depth = 0; |  | ||||||
|     archive_switch_dir(archive, tab_default_paths[archive->browser.tab_id]); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| static void archive_leave_dir(ArchiveApp* archive) { |  | ||||||
|     furi_assert(archive); |  | ||||||
| 
 |  | ||||||
|     char* last_char_ptr = strrchr(string_get_cstr(archive->browser.path), '/'); |  | ||||||
| 
 |  | ||||||
|     if(last_char_ptr) { |  | ||||||
|         size_t pos = last_char_ptr - string_get_cstr(archive->browser.path); |  | ||||||
|         string_left(archive->browser.path, pos); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     archive->browser.depth = CLAMP(archive->browser.depth - 1, MAX_DEPTH, 0); |  | ||||||
| 
 |  | ||||||
|     with_view_model( |  | ||||||
|         archive->view_archive_main, (ArchiveViewModel * model) { |  | ||||||
|             model->idx = archive->browser.last_idx[archive->browser.depth]; |  | ||||||
|             model->list_offset = |  | ||||||
|                 model->idx - |  | ||||||
|                 (files_array_size(model->files) > 3 ? 3 : files_array_size(model->files)); |  | ||||||
|             return true; |  | ||||||
|         }); |  | ||||||
| 
 |  | ||||||
|     archive_switch_dir(archive, string_get_cstr(archive->browser.path)); |  | ||||||
|     update_offset(archive); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| static void archive_enter_dir(ArchiveApp* archive, string_t name) { |  | ||||||
|     furi_assert(archive); |  | ||||||
|     furi_assert(name); |  | ||||||
| 
 |  | ||||||
|     archive_update_last_idx(archive); |  | ||||||
|     archive->browser.depth = CLAMP(archive->browser.depth + 1, MAX_DEPTH, 0); |  | ||||||
| 
 |  | ||||||
|     string_cat(archive->browser.path, "/"); |  | ||||||
|     string_cat(archive->browser.path, archive->browser.name); |  | ||||||
| 
 |  | ||||||
|     archive_switch_dir(archive, string_get_cstr(archive->browser.path)); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| static bool filter_by_extension(ArchiveApp* archive, FileInfo* file_info, const char* name) { |  | ||||||
|     furi_assert(archive); |  | ||||||
|     furi_assert(file_info); |  | ||||||
|     furi_assert(name); |  | ||||||
| 
 |  | ||||||
|     bool result = false; |  | ||||||
|     const char* filter_ext_ptr = get_tab_ext(archive->browser.tab_id); |  | ||||||
| 
 |  | ||||||
|     if(strcmp(filter_ext_ptr, "*") == 0) { |  | ||||||
|         result = true; |  | ||||||
|     } else if(strstr(name, filter_ext_ptr) != NULL) { |  | ||||||
|         result = true; |  | ||||||
|     } else if(file_info->flags & FSF_DIRECTORY) { |  | ||||||
|         result = true; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     return result; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| static void set_file_type(ArchiveFile_t* file, FileInfo* file_info) { |  | ||||||
|     furi_assert(file); |  | ||||||
|     furi_assert(file_info); |  | ||||||
| 
 |  | ||||||
|     for(size_t i = 0; i < SIZEOF_ARRAY(known_ext); i++) { |  | ||||||
|         if(string_search_str(file->name, known_ext[i], 0) != STRING_FAILURE) { |  | ||||||
|             file->type = i; |  | ||||||
|             return; |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     if(file_info->flags & FSF_DIRECTORY) { |  | ||||||
|         file->type = ArchiveFileTypeFolder; |  | ||||||
|     } else { |  | ||||||
|         file->type = ArchiveFileTypeUnknown; |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| static void archive_file_append(ArchiveApp* archive, const char* path, string_t string) { |  | ||||||
|     furi_assert(archive); |  | ||||||
|     furi_assert(path); |  | ||||||
|     furi_assert(string); |  | ||||||
| 
 |  | ||||||
|     FileWorker* file_worker = file_worker_alloc(false); |  | ||||||
| 
 |  | ||||||
|     if(!file_worker_open(file_worker, path, FSAM_WRITE, FSOM_OPEN_APPEND)) { |  | ||||||
|         FURI_LOG_E("Archive", "Append open error"); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     if(!file_worker_write(file_worker, string_get_cstr(string), string_size(string))) { |  | ||||||
|         FURI_LOG_E("Archive", "Append write error"); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     file_worker_close(file_worker); |  | ||||||
|     file_worker_free(file_worker); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| static void archive_view_add_item(ArchiveApp* archive, FileInfo* file_info, const char* name) { |  | ||||||
|     furi_assert(archive); |  | ||||||
|     furi_assert(file_info); |  | ||||||
|     furi_assert(name); |  | ||||||
| 
 |  | ||||||
|     ArchiveFile_t item; |  | ||||||
| 
 |  | ||||||
|     if(filter_by_extension(archive, file_info, name)) { |  | ||||||
|         ArchiveFile_t_init(&item); |  | ||||||
|         string_init_set_str(item.name, name); |  | ||||||
|         set_file_type(&item, file_info); |  | ||||||
| 
 |  | ||||||
|         with_view_model( |  | ||||||
|             archive->view_archive_main, (ArchiveViewModel * model) { |  | ||||||
|                 files_array_push_back(model->files, item); |  | ||||||
|                 return true; |  | ||||||
|             }); |  | ||||||
| 
 |  | ||||||
|         ArchiveFile_t_clear(&item); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| static bool archive_is_favorite(ArchiveApp* archive, ArchiveFile_t* selected) { |  | ||||||
|     furi_assert(selected); |  | ||||||
|     string_t path; |  | ||||||
|     string_t buffer; |  | ||||||
|     string_init(buffer); |  | ||||||
|     bool found = false; |  | ||||||
| 
 |  | ||||||
|     string_init_printf( |  | ||||||
|         path, "%s/%s", string_get_cstr(archive->browser.path), string_get_cstr(selected->name)); |  | ||||||
| 
 |  | ||||||
|     bool load_result = |  | ||||||
|         file_worker_open(archive->file_worker, ARCHIVE_FAV_PATH, FSAM_READ, FSOM_OPEN_ALWAYS); |  | ||||||
| 
 |  | ||||||
|     if(load_result) { |  | ||||||
|         while(1) { |  | ||||||
|             if(!file_worker_read_until(archive->file_worker, buffer, '\n')) { |  | ||||||
|                 break; |  | ||||||
|             } |  | ||||||
|             if(!string_size(buffer)) { |  | ||||||
|                 break; |  | ||||||
|             } |  | ||||||
|             if(!string_search(buffer, path)) { |  | ||||||
|                 found = true; |  | ||||||
|                 break; |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     string_clear(buffer); |  | ||||||
|     string_clear(path); |  | ||||||
|     file_worker_close(archive->file_worker); |  | ||||||
| 
 |  | ||||||
|     return found; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| static bool archive_favorites_read(ArchiveApp* archive) { |  | ||||||
|     string_t buffer; |  | ||||||
|     FileInfo file_info; |  | ||||||
|     string_init(buffer); |  | ||||||
| 
 |  | ||||||
|     bool load_result = |  | ||||||
|         file_worker_open(archive->file_worker, ARCHIVE_FAV_PATH, FSAM_READ, FSOM_OPEN_EXISTING); |  | ||||||
| 
 |  | ||||||
|     if(load_result) { |  | ||||||
|         while(1) { |  | ||||||
|             if(!file_worker_read_until(archive->file_worker, buffer, '\n')) { |  | ||||||
|                 break; |  | ||||||
|             } |  | ||||||
|             if(!string_size(buffer)) { |  | ||||||
|                 break; |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             archive_view_add_item(archive, &file_info, string_get_cstr(buffer)); |  | ||||||
|             string_clean(buffer); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|     string_clear(buffer); |  | ||||||
|     file_worker_close(archive->file_worker); |  | ||||||
| 
 |  | ||||||
|     return load_result; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| static bool |  | ||||||
|     archive_favorites_rename(ArchiveApp* archive, ArchiveFile_t* selected, const char* dst) { |  | ||||||
|     furi_assert(selected); |  | ||||||
|     string_t path; |  | ||||||
|     string_t buffer; |  | ||||||
|     string_t temp; |  | ||||||
| 
 |  | ||||||
|     string_init(buffer); |  | ||||||
|     string_init(temp); |  | ||||||
| 
 |  | ||||||
|     string_init_printf( |  | ||||||
|         path, "%s/%s", string_get_cstr(archive->browser.path), string_get_cstr(selected->name)); |  | ||||||
|     bool load_result = |  | ||||||
|         file_worker_open(archive->file_worker, ARCHIVE_FAV_PATH, FSAM_READ, FSOM_OPEN_EXISTING); |  | ||||||
| 
 |  | ||||||
|     if(load_result) { |  | ||||||
|         while(1) { |  | ||||||
|             if(!file_worker_read_until(archive->file_worker, buffer, '\n')) { |  | ||||||
|                 break; |  | ||||||
|             } |  | ||||||
|             if(!string_size(buffer)) { |  | ||||||
|                 break; |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             string_printf( |  | ||||||
|                 temp, "%s\r\n", string_search(buffer, path) ? string_get_cstr(buffer) : dst); |  | ||||||
|             archive_file_append(archive, ARCHIVE_FAV_TEMP_PATH, temp); |  | ||||||
|             string_clean(temp); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     string_clear(temp); |  | ||||||
|     string_clear(buffer); |  | ||||||
|     string_clear(path); |  | ||||||
| 
 |  | ||||||
|     file_worker_close(archive->file_worker); |  | ||||||
|     file_worker_remove(archive->file_worker, ARCHIVE_FAV_PATH); |  | ||||||
|     file_worker_rename(archive->file_worker, ARCHIVE_FAV_TEMP_PATH, ARCHIVE_FAV_PATH); |  | ||||||
| 
 |  | ||||||
|     return load_result; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| static bool archive_favorites_delete(ArchiveApp* archive, ArchiveFile_t* selected) { |  | ||||||
|     furi_assert(selected); |  | ||||||
|     string_t path; |  | ||||||
|     string_t buffer; |  | ||||||
|     string_init(buffer); |  | ||||||
| 
 |  | ||||||
|     string_init_printf( |  | ||||||
|         path, "%s/%s", string_get_cstr(archive->browser.path), string_get_cstr(selected->name)); |  | ||||||
| 
 |  | ||||||
|     bool load_result = |  | ||||||
|         file_worker_open(archive->file_worker, ARCHIVE_FAV_PATH, FSAM_READ, FSOM_OPEN_EXISTING); |  | ||||||
|     if(load_result) { |  | ||||||
|         while(1) { |  | ||||||
|             if(!file_worker_read_until(archive->file_worker, buffer, '\n')) { |  | ||||||
|                 break; |  | ||||||
|             } |  | ||||||
|             if(!string_size(buffer)) { |  | ||||||
|                 break; |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             if(string_search(buffer, path)) { |  | ||||||
|                 string_t temp; |  | ||||||
|                 string_init_printf(temp, "%s\r\n", string_get_cstr(buffer)); |  | ||||||
|                 archive_file_append(archive, ARCHIVE_FAV_TEMP_PATH, temp); |  | ||||||
|                 string_clear(temp); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     string_clear(buffer); |  | ||||||
|     string_clear(path); |  | ||||||
| 
 |  | ||||||
|     file_worker_close(archive->file_worker); |  | ||||||
|     file_worker_remove(archive->file_worker, ARCHIVE_FAV_PATH); |  | ||||||
|     file_worker_rename(archive->file_worker, ARCHIVE_FAV_TEMP_PATH, ARCHIVE_FAV_PATH); |  | ||||||
| 
 |  | ||||||
|     return load_result; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| static bool archive_read_dir(ArchiveApp* archive) { |  | ||||||
|     FileInfo file_info; |  | ||||||
|     File* directory = storage_file_alloc(archive->api); |  | ||||||
|     char name[MAX_NAME_LEN]; |  | ||||||
| 
 |  | ||||||
|     if(!storage_dir_open(directory, string_get_cstr(archive->browser.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; |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         uint16_t files_cnt; |  | ||||||
|         with_view_model( |  | ||||||
|             archive->view_archive_main, (ArchiveViewModel * model) { |  | ||||||
|                 files_cnt = files_array_size(model->files); |  | ||||||
| 
 |  | ||||||
|                 return true; |  | ||||||
|             }); |  | ||||||
| 
 |  | ||||||
|         if(files_cnt > MAX_FILES) { |  | ||||||
|             break; |  | ||||||
|         } else if(storage_file_get_error(directory) == FSE_OK) { |  | ||||||
|             archive_view_add_item(archive, &file_info, name); |  | ||||||
|         } else { |  | ||||||
|             storage_dir_close(directory); |  | ||||||
|             storage_file_free(directory); |  | ||||||
|             return false; |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|     storage_dir_close(directory); |  | ||||||
|     storage_file_free(directory); |  | ||||||
| 
 |  | ||||||
|     return true; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| static bool archive_get_filenames(ArchiveApp* archive) { |  | ||||||
|     furi_assert(archive); |  | ||||||
|     with_view_model( |  | ||||||
|         archive->view_archive_main, (ArchiveViewModel * model) { |  | ||||||
|             files_array_clean(model->files); |  | ||||||
|             return true; |  | ||||||
|         }); |  | ||||||
| 
 |  | ||||||
|     if(archive->browser.tab_id != ArchiveTabFavorites) { |  | ||||||
|         archive_read_dir(archive); |  | ||||||
|     } else { |  | ||||||
|         archive_favorites_read(archive); |  | ||||||
|     } |  | ||||||
|     return true; |  | ||||||
| } |  | ||||||
| static void archive_exit_callback(ArchiveApp* archive) { |  | ||||||
|     furi_assert(archive); |  | ||||||
| 
 |  | ||||||
|     AppEvent event; |  | ||||||
|     event.type = EventTypeExit; |  | ||||||
|     furi_check(osMessageQueuePut(archive->event_queue, &event, 0, osWaitForever) == osOK); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| static uint32_t archive_previous_callback(void* context) { |  | ||||||
|     return ArchiveViewMain; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| /* file menu */ |  | ||||||
| static void archive_add_to_favorites(ArchiveApp* archive) { |  | ||||||
|     furi_assert(archive); |  | ||||||
|     string_t buffer_src; |  | ||||||
| 
 |  | ||||||
|     string_init_printf( |  | ||||||
|         buffer_src, |  | ||||||
|         "%s/%s\r\n", |  | ||||||
|         string_get_cstr(archive->browser.path), |  | ||||||
|         string_get_cstr(archive->browser.name)); |  | ||||||
| 
 |  | ||||||
|     archive_file_append(archive, ARCHIVE_FAV_PATH, buffer_src); |  | ||||||
| 
 |  | ||||||
|     string_clear(buffer_src); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| static void archive_text_input_callback(void* context) { |  | ||||||
|     furi_assert(context); |     furi_assert(context); | ||||||
| 
 |  | ||||||
|     ArchiveApp* archive = (ArchiveApp*)context; |     ArchiveApp* archive = (ArchiveApp*)context; | ||||||
| 
 |     return scene_manager_handle_custom_event(archive->scene_manager, event); | ||||||
|     string_t buffer_src; |  | ||||||
|     string_t buffer_dst; |  | ||||||
| 
 |  | ||||||
|     string_init_printf( |  | ||||||
|         buffer_src, |  | ||||||
|         "%s/%s", |  | ||||||
|         string_get_cstr(archive->browser.path), |  | ||||||
|         string_get_cstr(archive->browser.name)); |  | ||||||
|     string_init_printf( |  | ||||||
|         buffer_dst, |  | ||||||
|         "%s/%s", |  | ||||||
|         string_get_cstr(archive->browser.path), |  | ||||||
|         archive->browser.text_input_buffer); |  | ||||||
| 
 |  | ||||||
|     string_set(archive->browser.name, archive->browser.text_input_buffer); |  | ||||||
|     // append extension
 |  | ||||||
| 
 |  | ||||||
|     ArchiveFile_t* file; |  | ||||||
| 
 |  | ||||||
|     with_view_model( |  | ||||||
|         archive->view_archive_main, (ArchiveViewModel * model) { |  | ||||||
|             file = files_array_get( |  | ||||||
|                 model->files, CLAMP(model->idx, files_array_size(model->files) - 1, 0)); |  | ||||||
|             file->fav = archive_is_favorite(archive, file); |  | ||||||
| 
 |  | ||||||
|             return true; |  | ||||||
|         }); |  | ||||||
| 
 |  | ||||||
|     string_cat(buffer_dst, known_ext[file->type]); |  | ||||||
|     storage_common_rename(archive->api, string_get_cstr(buffer_src), string_get_cstr(buffer_dst)); |  | ||||||
| 
 |  | ||||||
|     if(file->fav) { |  | ||||||
|         archive_favorites_rename(archive, file, string_get_cstr(buffer_dst)); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     view_dispatcher_switch_to_view(archive->view_dispatcher, ArchiveViewMain); |  | ||||||
|     archive_get_filenames(archive); |  | ||||||
| 
 |  | ||||||
|     with_view_model( |  | ||||||
|         archive->view_archive_main, (ArchiveViewModel * model) { |  | ||||||
|             model->idx = 0; |  | ||||||
|             while(model->idx < files_array_size(model->files)) { |  | ||||||
|                 ArchiveFile_t* current = files_array_get(model->files, model->idx); |  | ||||||
|                 if(!string_search(current->name, archive->browser.text_input_buffer)) { |  | ||||||
|                     break; |  | ||||||
|                 } |  | ||||||
|                 ++model->idx; |  | ||||||
|             } |  | ||||||
|             return true; |  | ||||||
|         }); |  | ||||||
| 
 |  | ||||||
|     update_offset(archive); |  | ||||||
| 
 |  | ||||||
|     string_clear(buffer_src); |  | ||||||
|     string_clear(buffer_dst); |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static void archive_enter_text_input(ArchiveApp* archive) { | bool archive_back_event_callback(void* context) { | ||||||
|     furi_assert(archive); |  | ||||||
|     *archive->browser.text_input_buffer = '\0'; |  | ||||||
| 
 |  | ||||||
|     strlcpy( |  | ||||||
|         archive->browser.text_input_buffer, string_get_cstr(archive->browser.name), MAX_NAME_LEN); |  | ||||||
| 
 |  | ||||||
|     archive_trim_file_ext(archive->browser.text_input_buffer); |  | ||||||
| 
 |  | ||||||
|     text_input_set_header_text(archive->text_input, "Rename:"); |  | ||||||
| 
 |  | ||||||
|     text_input_set_result_callback( |  | ||||||
|         archive->text_input, |  | ||||||
|         archive_text_input_callback, |  | ||||||
|         archive, |  | ||||||
|         archive->browser.text_input_buffer, |  | ||||||
|         MAX_NAME_LEN, |  | ||||||
|         false); |  | ||||||
| 
 |  | ||||||
|     view_dispatcher_switch_to_view(archive->view_dispatcher, ArchiveViewTextInput); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| static void archive_show_file_menu(ArchiveApp* archive) { |  | ||||||
|     furi_assert(archive); |  | ||||||
| 
 |  | ||||||
|     archive->browser.menu = true; |  | ||||||
| 
 |  | ||||||
|     with_view_model( |  | ||||||
|         archive->view_archive_main, (ArchiveViewModel * model) { |  | ||||||
|             ArchiveFile_t* selected; |  | ||||||
|             selected = files_array_get(model->files, model->idx); |  | ||||||
|             model->menu = true; |  | ||||||
|             model->menu_idx = 0; |  | ||||||
|             selected->fav = is_known_app(selected->type) ? archive_is_favorite(archive, selected) : |  | ||||||
|                                                            false; |  | ||||||
| 
 |  | ||||||
|             return true; |  | ||||||
|         }); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| static void archive_close_file_menu(ArchiveApp* archive) { |  | ||||||
|     furi_assert(archive); |  | ||||||
| 
 |  | ||||||
|     archive->browser.menu = false; |  | ||||||
| 
 |  | ||||||
|     with_view_model( |  | ||||||
|         archive->view_archive_main, (ArchiveViewModel * model) { |  | ||||||
|             model->menu = false; |  | ||||||
|             model->menu_idx = 0; |  | ||||||
|             return true; |  | ||||||
|         }); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| static void archive_open_app(ArchiveApp* archive, const char* app_name, const char* args) { |  | ||||||
|     furi_assert(archive); |  | ||||||
|     furi_assert(app_name); |  | ||||||
| 
 |  | ||||||
|     loader_start(archive->loader, app_name, args); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| static void archive_delete_file(ArchiveApp* archive, ArchiveFile_t* file) { |  | ||||||
|     furi_assert(archive); |  | ||||||
|     furi_assert(file); |  | ||||||
| 
 |  | ||||||
|     string_t path; |  | ||||||
|     string_init(path); |  | ||||||
| 
 |  | ||||||
|     string_printf( |  | ||||||
|         path, "%s/%s", string_get_cstr(archive->browser.path), string_get_cstr(file->name)); |  | ||||||
| 
 |  | ||||||
|     if(archive_is_favorite(archive, file)) { // remove from favorites
 |  | ||||||
|         archive_favorites_delete(archive, file); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     file_worker_remove(archive->file_worker, string_get_cstr(path)); |  | ||||||
| 
 |  | ||||||
|     string_clear(path); |  | ||||||
|     archive_get_filenames(archive); |  | ||||||
| 
 |  | ||||||
|     with_view_model( |  | ||||||
|         archive->view_archive_main, (ArchiveViewModel * model) { |  | ||||||
|             model->idx = CLAMP(model->idx, files_array_size(model->files) - 1, 0); |  | ||||||
|             return true; |  | ||||||
|         }); |  | ||||||
| 
 |  | ||||||
|     update_offset(archive); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| static void |  | ||||||
|     archive_run_in_app(ArchiveApp* archive, ArchiveFile_t* selected, bool full_path_provided) { |  | ||||||
|     string_t full_path; |  | ||||||
| 
 |  | ||||||
|     if(!full_path_provided) { |  | ||||||
|         string_init_printf( |  | ||||||
|             full_path, |  | ||||||
|             "%s/%s", |  | ||||||
|             string_get_cstr(archive->browser.path), |  | ||||||
|             string_get_cstr(selected->name)); |  | ||||||
|     } else { |  | ||||||
|         string_init_set(full_path, selected->name); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     archive_open_app(archive, flipper_app_name[selected->type], string_get_cstr(full_path)); |  | ||||||
|     string_clear(full_path); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| static void archive_file_menu_callback(ArchiveApp* archive) { |  | ||||||
|     furi_assert(archive); |  | ||||||
| 
 |  | ||||||
|     ArchiveFile_t* selected; |  | ||||||
|     uint8_t idx = 0; |  | ||||||
| 
 |  | ||||||
|     with_view_model( |  | ||||||
|         archive->view_archive_main, (ArchiveViewModel * model) { |  | ||||||
|             selected = files_array_get(model->files, model->idx); |  | ||||||
|             idx = model->menu_idx; |  | ||||||
|             return true; |  | ||||||
|         }); |  | ||||||
| 
 |  | ||||||
|     switch(idx) { |  | ||||||
|     case 0: |  | ||||||
|         if(is_known_app(selected->type)) { |  | ||||||
|             archive_run_in_app(archive, selected, false); |  | ||||||
|         } |  | ||||||
|         break; |  | ||||||
|     case 1: |  | ||||||
|         if(is_known_app(selected->type)) { |  | ||||||
|             if(!archive_is_favorite(archive, selected)) { |  | ||||||
|                 string_set(archive->browser.name, selected->name); |  | ||||||
|                 archive_add_to_favorites(archive); |  | ||||||
|             } else { |  | ||||||
|                 // delete from favorites
 |  | ||||||
|                 archive_favorites_delete(archive, selected); |  | ||||||
|             } |  | ||||||
|             archive_close_file_menu(archive); |  | ||||||
|         } |  | ||||||
|         break; |  | ||||||
|     case 2: |  | ||||||
|         // open rename view
 |  | ||||||
|         if(is_known_app(selected->type)) { |  | ||||||
|             archive_enter_text_input(archive); |  | ||||||
|         } |  | ||||||
|         break; |  | ||||||
|     case 3: |  | ||||||
|         // confirmation?
 |  | ||||||
|         archive_delete_file(archive, selected); |  | ||||||
|         archive_close_file_menu(archive); |  | ||||||
| 
 |  | ||||||
|         break; |  | ||||||
| 
 |  | ||||||
|     default: |  | ||||||
|         archive_close_file_menu(archive); |  | ||||||
|         break; |  | ||||||
|     } |  | ||||||
|     selected = NULL; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| static void menu_input_handler(ArchiveApp* archive, InputEvent* event) { |  | ||||||
|     furi_assert(archive); |  | ||||||
|     furi_assert(archive); |  | ||||||
| 
 |  | ||||||
|     if(event->type == InputTypeShort) { |  | ||||||
|         if(event->key == InputKeyUp || event->key == InputKeyDown) { |  | ||||||
|             with_view_model( |  | ||||||
|                 archive->view_archive_main, (ArchiveViewModel * 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(archive); |  | ||||||
|         } else if(event->key == InputKeyBack) { |  | ||||||
|             archive_close_file_menu(archive); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| /* main controls */ |  | ||||||
| 
 |  | ||||||
| static bool archive_view_input(InputEvent* event, void* context) { |  | ||||||
|     furi_assert(event); |  | ||||||
|     furi_assert(context); |     furi_assert(context); | ||||||
| 
 |     ArchiveApp* archive = (ArchiveApp*)context; | ||||||
|     ArchiveApp* archive = context; |     return scene_manager_handle_back_event(archive->scene_manager); | ||||||
|     bool in_menu = archive->browser.menu; |  | ||||||
| 
 |  | ||||||
|     if(in_menu) { |  | ||||||
|         menu_input_handler(archive, event); |  | ||||||
|         return true; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     if(event->type == InputTypeShort) { |  | ||||||
|         if(event->key == InputKeyLeft) { |  | ||||||
|             if(archive->browser.tab_id > 0) { |  | ||||||
|                 archive->browser.tab_id = CLAMP(archive->browser.tab_id - 1, ArchiveTabTotal, 0); |  | ||||||
|                 archive_switch_tab(archive); |  | ||||||
|                 return true; |  | ||||||
|             } |  | ||||||
|         } else if(event->key == InputKeyRight) { |  | ||||||
|             if(archive->browser.tab_id < ArchiveTabTotal - 1) { |  | ||||||
|                 archive->browser.tab_id = |  | ||||||
|                     CLAMP(archive->browser.tab_id + 1, ArchiveTabTotal - 1, 0); |  | ||||||
|                 archive_switch_tab(archive); |  | ||||||
|                 return true; |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|         } else if(event->key == InputKeyBack) { |  | ||||||
|             if(archive->browser.depth == 0) { |  | ||||||
|                 archive_exit_callback(archive); |  | ||||||
|             } else { |  | ||||||
|                 archive_leave_dir(archive); |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             return true; |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|     if(event->key == InputKeyUp || event->key == InputKeyDown) { |  | ||||||
|         with_view_model( |  | ||||||
|             archive->view_archive_main, (ArchiveViewModel * 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(archive); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     if(event->key == InputKeyOk) { |  | ||||||
|         ArchiveFile_t* selected; |  | ||||||
| 
 |  | ||||||
|         with_view_model( |  | ||||||
|             archive->view_archive_main, (ArchiveViewModel * model) { |  | ||||||
|                 selected = files_array_size(model->files) > 0 ? |  | ||||||
|                                files_array_get(model->files, model->idx) : |  | ||||||
|                                NULL; |  | ||||||
|                 return true; |  | ||||||
|             }); |  | ||||||
| 
 |  | ||||||
|         if(selected) { |  | ||||||
|             string_set(archive->browser.name, selected->name); |  | ||||||
| 
 |  | ||||||
|             if(selected->type == ArchiveFileTypeFolder) { |  | ||||||
|                 if(event->type == InputTypeShort) { |  | ||||||
|                     archive_enter_dir(archive, archive->browser.name); |  | ||||||
|                 } else if(event->type == InputTypeLong) { |  | ||||||
|                     archive_show_file_menu(archive); |  | ||||||
|                 } |  | ||||||
|             } else { |  | ||||||
|                 if(event->type == InputTypeShort) { |  | ||||||
|                     if(archive->browser.tab_id == ArchiveTabFavorites) { |  | ||||||
|                         if(is_known_app(selected->type)) { |  | ||||||
|                             archive_run_in_app(archive, selected, true); |  | ||||||
|                         } |  | ||||||
|                     } else { |  | ||||||
|                         archive_show_file_menu(archive); |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     update_offset(archive); |  | ||||||
| 
 |  | ||||||
|     return true; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void archive_free(ArchiveApp* archive) { |  | ||||||
|     furi_assert(archive); |  | ||||||
| 
 |  | ||||||
|     file_worker_free(archive->file_worker); |  | ||||||
| 
 |  | ||||||
|     view_dispatcher_remove_view(archive->view_dispatcher, ArchiveViewMain); |  | ||||||
|     view_dispatcher_remove_view(archive->view_dispatcher, ArchiveViewTextInput); |  | ||||||
|     view_dispatcher_free(archive->view_dispatcher); |  | ||||||
| 
 |  | ||||||
|     with_view_model( |  | ||||||
|         archive->view_archive_main, (ArchiveViewModel * model) { |  | ||||||
|             files_array_clear(model->files); |  | ||||||
|             return false; |  | ||||||
|         }); |  | ||||||
| 
 |  | ||||||
|     view_free(archive->view_archive_main); |  | ||||||
| 
 |  | ||||||
|     string_clear(archive->browser.name); |  | ||||||
|     string_clear(archive->browser.path); |  | ||||||
| 
 |  | ||||||
|     text_input_free(archive->text_input); |  | ||||||
| 
 |  | ||||||
|     furi_record_close("storage"); |  | ||||||
|     archive->api = NULL; |  | ||||||
|     furi_record_close("gui"); |  | ||||||
|     archive->gui = NULL; |  | ||||||
|     furi_record_close("loader"); |  | ||||||
|     archive->loader = NULL; |  | ||||||
|     furi_thread_free(archive->app_thread); |  | ||||||
|     furi_check(osMessageQueueDelete(archive->event_queue) == osOK); |  | ||||||
| 
 |  | ||||||
|     free(archive); |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| ArchiveApp* archive_alloc() { | ArchiveApp* archive_alloc() { | ||||||
|     ArchiveApp* archive = furi_alloc(sizeof(ArchiveApp)); |     ArchiveApp* archive = furi_alloc(sizeof(ArchiveApp)); | ||||||
| 
 | 
 | ||||||
|     archive->event_queue = osMessageQueueNew(8, sizeof(AppEvent), NULL); |  | ||||||
|     archive->app_thread = furi_thread_alloc(); |  | ||||||
|     archive->gui = furi_record_open("gui"); |     archive->gui = furi_record_open("gui"); | ||||||
|     archive->loader = furi_record_open("loader"); |  | ||||||
|     archive->api = furi_record_open("storage"); |  | ||||||
|     archive->text_input = text_input_alloc(); |     archive->text_input = text_input_alloc(); | ||||||
|     archive->view_archive_main = view_alloc(); |  | ||||||
|     archive->file_worker = file_worker_alloc(true); |  | ||||||
| 
 | 
 | ||||||
|     furi_check(archive->event_queue); |  | ||||||
| 
 |  | ||||||
|     view_allocate_model( |  | ||||||
|         archive->view_archive_main, ViewModelTypeLocking, sizeof(ArchiveViewModel)); |  | ||||||
|     with_view_model( |  | ||||||
|         archive->view_archive_main, (ArchiveViewModel * model) { |  | ||||||
|             files_array_init(model->files); |  | ||||||
|             return false; |  | ||||||
|         }); |  | ||||||
| 
 |  | ||||||
|     view_set_context(archive->view_archive_main, archive); |  | ||||||
|     view_set_draw_callback(archive->view_archive_main, archive_view_render); |  | ||||||
|     view_set_input_callback(archive->view_archive_main, archive_view_input); |  | ||||||
|     view_set_previous_callback( |  | ||||||
|         text_input_get_view(archive->text_input), archive_previous_callback); |  | ||||||
| 
 |  | ||||||
|     // View Dispatcher
 |  | ||||||
|     archive->view_dispatcher = view_dispatcher_alloc(); |     archive->view_dispatcher = view_dispatcher_alloc(); | ||||||
|     view_dispatcher_add_view( |     archive->scene_manager = scene_manager_alloc(&archive_scene_handlers, archive); | ||||||
|         archive->view_dispatcher, ArchiveViewMain, archive->view_archive_main); | 
 | ||||||
|     view_dispatcher_add_view( |     view_dispatcher_enable_queue(archive->view_dispatcher); | ||||||
|         archive->view_dispatcher, ArchiveViewTextInput, text_input_get_view(archive->text_input)); |  | ||||||
|     view_dispatcher_attach_to_gui( |     view_dispatcher_attach_to_gui( | ||||||
|         archive->view_dispatcher, archive->gui, ViewDispatcherTypeFullscreen); |         archive->view_dispatcher, archive->gui, ViewDispatcherTypeFullscreen); | ||||||
| 
 | 
 | ||||||
|     view_dispatcher_switch_to_view(archive->view_dispatcher, ArchiveTabFavorites); |     view_dispatcher_set_event_callback_context(archive->view_dispatcher, archive); | ||||||
|  |     view_dispatcher_set_custom_event_callback( | ||||||
|  |         archive->view_dispatcher, archive_custom_event_callback); | ||||||
|  |     view_dispatcher_set_navigation_event_callback( | ||||||
|  |         archive->view_dispatcher, archive_back_event_callback); | ||||||
|  | 
 | ||||||
|  |     archive->main_view = main_view_alloc(); | ||||||
|  | 
 | ||||||
|  |     view_dispatcher_add_view( | ||||||
|  |         archive->view_dispatcher, ArchiveViewBrowser, archive_main_get_view(archive->main_view)); | ||||||
|  | 
 | ||||||
|  |     view_dispatcher_add_view( | ||||||
|  |         archive->view_dispatcher, ArchiveViewTextInput, text_input_get_view(archive->text_input)); | ||||||
| 
 | 
 | ||||||
|     return archive; |     return archive; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | void archive_free(ArchiveApp* archive) { | ||||||
|  |     furi_assert(archive); | ||||||
|  | 
 | ||||||
|  |     view_dispatcher_remove_view(archive->view_dispatcher, ArchiveViewBrowser); | ||||||
|  |     view_dispatcher_remove_view(archive->view_dispatcher, ArchiveViewTextInput); | ||||||
|  |     view_dispatcher_free(archive->view_dispatcher); | ||||||
|  |     scene_manager_free(archive->scene_manager); | ||||||
|  |     main_view_free(archive->main_view); | ||||||
|  | 
 | ||||||
|  |     text_input_free(archive->text_input); | ||||||
|  | 
 | ||||||
|  |     furi_record_close("gui"); | ||||||
|  |     archive->gui = NULL; | ||||||
|  | 
 | ||||||
|  |     free(archive); | ||||||
|  | } | ||||||
|  | 
 | ||||||
| int32_t archive_app(void* p) { | int32_t archive_app(void* p) { | ||||||
|     ArchiveApp* archive = archive_alloc(); |     ArchiveApp* archive = archive_alloc(); | ||||||
| 
 |     scene_manager_next_scene(archive->scene_manager, ArchiveAppSceneBrowser); | ||||||
|     // default tab
 |     view_dispatcher_run(archive->view_dispatcher); | ||||||
|     archive_switch_tab(archive); |  | ||||||
| 
 |  | ||||||
|     AppEvent event; |  | ||||||
|     while(1) { |  | ||||||
|         furi_check(osMessageQueueGet(archive->event_queue, &event, NULL, osWaitForever) == osOK); |  | ||||||
|         if(event.type == EventTypeExit) { |  | ||||||
|             break; |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     archive_free(archive); |     archive_free(archive); | ||||||
|  | 
 | ||||||
|     return 0; |     return 0; | ||||||
| } | } | ||||||
|  | |||||||
| @ -5,44 +5,27 @@ | |||||||
| #include <furi.h> | #include <furi.h> | ||||||
| #include <gui/gui_i.h> | #include <gui/gui_i.h> | ||||||
| #include <gui/view_dispatcher.h> | #include <gui/view_dispatcher.h> | ||||||
|  | #include <gui/scene_manager.h> | ||||||
| #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 <m-string.h> | ||||||
| #include <m-array.h> | #include <m-array.h> | ||||||
| #include <storage/storage.h> | #include <storage/storage.h> | ||||||
| #include "archive_views.h" |  | ||||||
| #include "applications.h" | #include "applications.h" | ||||||
| #include "file-worker.h" | #include "file-worker.h" | ||||||
| 
 | 
 | ||||||
| #define MAX_DEPTH 32 | #include "views/archive_main_view.h" | ||||||
| #define MAX_FILES 100 //temp
 | #include "scenes/archive_scene.h" | ||||||
|  | 
 | ||||||
| #define MAX_FILE_SIZE 128 | #define MAX_FILE_SIZE 128 | ||||||
| #define ARCHIVE_FAV_PATH "/any/favorites.txt" |  | ||||||
| #define ARCHIVE_FAV_TEMP_PATH "/any/favorites.tmp" |  | ||||||
| 
 | 
 | ||||||
| typedef enum { | typedef enum { | ||||||
|     ArchiveViewMain, |     ArchiveViewBrowser, | ||||||
|     ArchiveViewTextInput, |     ArchiveViewTextInput, | ||||||
|     ArchiveViewTotal, |     ArchiveViewTotal, | ||||||
| } ArchiveViewEnum; | } ArchiveViewEnum; | ||||||
| 
 | 
 | ||||||
| static const char* flipper_app_name[] = { |  | ||||||
|     [ArchiveFileTypeIButton] = "iButton", |  | ||||||
|     [ArchiveFileTypeNFC] = "NFC", |  | ||||||
|     [ArchiveFileTypeSubGhz] = "Sub-GHz", |  | ||||||
|     [ArchiveFileTypeLFRFID] = "125 kHz RFID", |  | ||||||
|     [ArchiveFileTypeIrda] = "Infrared", |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| static const char* known_ext[] = { |  | ||||||
|     [ArchiveFileTypeIButton] = ".ibtn", |  | ||||||
|     [ArchiveFileTypeNFC] = ".nfc", |  | ||||||
|     [ArchiveFileTypeSubGhz] = ".sub", |  | ||||||
|     [ArchiveFileTypeLFRFID] = ".rfid", |  | ||||||
|     [ArchiveFileTypeIrda] = ".ir", |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| static const char* tab_default_paths[] = { | static const char* tab_default_paths[] = { | ||||||
|     [ArchiveTabFavorites] = "/any/favorites", |     [ArchiveTabFavorites] = "/any/favorites", | ||||||
|     [ArchiveTabIButton] = "/any/ibutton", |     [ArchiveTabIButton] = "/any/ibutton", | ||||||
| @ -53,23 +36,6 @@ static const char* tab_default_paths[] = { | |||||||
|     [ArchiveTabBrowser] = "/any", |     [ArchiveTabBrowser] = "/any", | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| 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* get_default_path(ArchiveFileTypeEnum type) { | static inline const char* get_default_path(ArchiveFileTypeEnum type) { | ||||||
|     switch(type) { |     switch(type) { | ||||||
|     case ArchiveFileTypeIButton: |     case ArchiveFileTypeIButton: | ||||||
| @ -104,35 +70,11 @@ typedef struct { | |||||||
|     EventType type; |     EventType type; | ||||||
| } AppEvent; | } AppEvent; | ||||||
| 
 | 
 | ||||||
| typedef enum { |  | ||||||
|     FavoritesCheck, |  | ||||||
|     FavoritesRead, |  | ||||||
|     FavoritesDelete, |  | ||||||
|     FavoritesRename, |  | ||||||
| } FavActionsEnum; |  | ||||||
| 
 |  | ||||||
| typedef struct { |  | ||||||
|     ArchiveTabEnum tab_id; |  | ||||||
|     string_t name; |  | ||||||
|     string_t path; |  | ||||||
|     char text_input_buffer[MAX_NAME_LEN]; |  | ||||||
| 
 |  | ||||||
|     uint8_t depth; |  | ||||||
|     uint16_t last_idx[MAX_DEPTH]; |  | ||||||
| 
 |  | ||||||
|     bool menu; |  | ||||||
| } ArchiveBrowser; |  | ||||||
| 
 |  | ||||||
| struct ArchiveApp { | struct ArchiveApp { | ||||||
|     osMessageQueueId_t event_queue; |  | ||||||
|     FuriThread* app_thread; |  | ||||||
|     Loader* loader; |  | ||||||
|     Gui* gui; |     Gui* gui; | ||||||
|     ViewDispatcher* view_dispatcher; |     ViewDispatcher* view_dispatcher; | ||||||
|     View* view_archive_main; |     SceneManager* scene_manager; | ||||||
|  |     ArchiveMainView* main_view; | ||||||
|     TextInput* text_input; |     TextInput* text_input; | ||||||
| 
 |     char text_store[MAX_NAME_LEN]; | ||||||
|     Storage* api; |  | ||||||
|     FileWorker* file_worker; |  | ||||||
|     ArchiveBrowser browser; |  | ||||||
| }; | }; | ||||||
|  | |||||||
| @ -1,168 +0,0 @@ | |||||||
| #include "archive_views.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, |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| static void render_item_menu(Canvas* canvas, ArchiveViewModel* 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(model->tab_idx == 0 || 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); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void archive_trim_file_ext(char* name) { |  | ||||||
|     size_t str_len = strlen(name); |  | ||||||
|     char* end = name + str_len; |  | ||||||
|     while(end > name && *end != '.' && *end != '\\' && *end != '/') { |  | ||||||
|         --end; |  | ||||||
|     } |  | ||||||
|     if((end > name && *end == '.') && (*(end - 1) != '\\' && *(end - 1) != '/')) { |  | ||||||
|         *end = '\0'; |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| 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, ArchiveViewModel* 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->menu) { |  | ||||||
|         render_item_menu(canvas, model); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| static void archive_render_status_bar(Canvas* canvas, ArchiveViewModel* 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) { |  | ||||||
|     ArchiveViewModel* 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"); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
							
								
								
									
										171
									
								
								applications/archive/helpers/archive_favorites.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										171
									
								
								applications/archive/helpers/archive_favorites.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,171 @@ | |||||||
|  | #include "archive_favorites.h" | ||||||
|  | #include "archive_files.h" | ||||||
|  | #include "../views/archive_main_view.h" | ||||||
|  | 
 | ||||||
|  | bool archive_favorites_read(void* context) { | ||||||
|  |     furi_assert(context); | ||||||
|  | 
 | ||||||
|  |     ArchiveMainView* archive_view = context; | ||||||
|  |     FileWorker* file_worker = file_worker_alloc(true); | ||||||
|  | 
 | ||||||
|  |     string_t buffer; | ||||||
|  |     FileInfo file_info; | ||||||
|  |     string_init(buffer); | ||||||
|  | 
 | ||||||
|  |     bool result = file_worker_open(file_worker, ARCHIVE_FAV_PATH, FSAM_READ, FSOM_OPEN_ALWAYS); | ||||||
|  | 
 | ||||||
|  |     if(result) { | ||||||
|  |         while(1) { | ||||||
|  |             if(!file_worker_read_until(file_worker, buffer, '\n')) { | ||||||
|  |                 break; | ||||||
|  |             } | ||||||
|  |             if(!string_size(buffer)) { | ||||||
|  |                 break; | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             archive_view_add_item(archive_view, &file_info, string_get_cstr(buffer)); | ||||||
|  |             string_clean(buffer); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     string_clear(buffer); | ||||||
|  |     file_worker_close(file_worker); | ||||||
|  |     file_worker_free(file_worker); | ||||||
|  |     return result; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool archive_favorites_delete(const char* file_path, const char* name) { | ||||||
|  |     furi_assert(file_path); | ||||||
|  |     furi_assert(name); | ||||||
|  | 
 | ||||||
|  |     FileWorker* file_worker = file_worker_alloc(true); | ||||||
|  | 
 | ||||||
|  |     string_t path; | ||||||
|  |     string_t buffer; | ||||||
|  |     string_init(buffer); | ||||||
|  | 
 | ||||||
|  |     string_init_printf(path, "%s/%s", file_path, name); | ||||||
|  | 
 | ||||||
|  |     bool result = file_worker_open(file_worker, ARCHIVE_FAV_PATH, FSAM_READ, FSOM_OPEN_EXISTING); | ||||||
|  |     if(result) { | ||||||
|  |         while(1) { | ||||||
|  |             if(!file_worker_read_until(file_worker, buffer, '\n')) { | ||||||
|  |                 break; | ||||||
|  |             } | ||||||
|  |             if(!string_size(buffer)) { | ||||||
|  |                 break; | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             if(string_search(buffer, path)) { | ||||||
|  |                 string_t temp; | ||||||
|  |                 string_init_printf(temp, "%s\r\n", string_get_cstr(buffer)); | ||||||
|  |                 archive_file_append(ARCHIVE_FAV_TEMP_PATH, temp); | ||||||
|  |                 string_clear(temp); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     string_clear(buffer); | ||||||
|  |     string_clear(path); | ||||||
|  | 
 | ||||||
|  |     file_worker_close(file_worker); | ||||||
|  |     file_worker_remove(file_worker, ARCHIVE_FAV_PATH); | ||||||
|  |     file_worker_rename(file_worker, ARCHIVE_FAV_TEMP_PATH, ARCHIVE_FAV_PATH); | ||||||
|  | 
 | ||||||
|  |     file_worker_free(file_worker); | ||||||
|  | 
 | ||||||
|  |     return result; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool archive_is_favorite(const char* file_path, const char* name) { | ||||||
|  |     furi_assert(file_path); | ||||||
|  |     furi_assert(name); | ||||||
|  | 
 | ||||||
|  |     FileWorker* file_worker = file_worker_alloc(true); | ||||||
|  | 
 | ||||||
|  |     string_t path; | ||||||
|  |     string_t buffer; | ||||||
|  |     string_init(buffer); | ||||||
|  |     bool found = false; | ||||||
|  | 
 | ||||||
|  |     string_init_printf(path, "%s/%s", file_path, name); | ||||||
|  |     bool result = file_worker_open(file_worker, ARCHIVE_FAV_PATH, FSAM_READ, FSOM_OPEN_ALWAYS); | ||||||
|  | 
 | ||||||
|  |     if(result) { | ||||||
|  |         while(1) { | ||||||
|  |             if(!file_worker_read_until(file_worker, buffer, '\n')) { | ||||||
|  |                 break; | ||||||
|  |             } | ||||||
|  |             if(!string_size(buffer)) { | ||||||
|  |                 break; | ||||||
|  |             } | ||||||
|  |             if(!string_search(buffer, path)) { | ||||||
|  |                 found = true; | ||||||
|  |                 break; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     string_clear(buffer); | ||||||
|  |     string_clear(path); | ||||||
|  |     file_worker_close(file_worker); | ||||||
|  |     file_worker_free(file_worker); | ||||||
|  | 
 | ||||||
|  |     return found; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool archive_favorites_rename(const char* file_path, const char* src, const char* dst) { | ||||||
|  |     furi_assert(file_path); | ||||||
|  |     furi_assert(src); | ||||||
|  |     furi_assert(dst); | ||||||
|  | 
 | ||||||
|  |     FileWorker* file_worker = file_worker_alloc(true); | ||||||
|  | 
 | ||||||
|  |     string_t path; | ||||||
|  |     string_t buffer; | ||||||
|  |     string_t temp; | ||||||
|  | 
 | ||||||
|  |     string_init(buffer); | ||||||
|  |     string_init(temp); | ||||||
|  |     string_init(path); | ||||||
|  | 
 | ||||||
|  |     string_printf(path, "%s/%s", file_path, src); | ||||||
|  |     bool result = file_worker_open(file_worker, ARCHIVE_FAV_PATH, FSAM_READ, FSOM_OPEN_EXISTING); | ||||||
|  | 
 | ||||||
|  |     if(result) { | ||||||
|  |         while(1) { | ||||||
|  |             if(!file_worker_read_until(file_worker, buffer, '\n')) { | ||||||
|  |                 break; | ||||||
|  |             } | ||||||
|  |             if(!string_size(buffer)) { | ||||||
|  |                 break; | ||||||
|  |             } | ||||||
|  |             string_printf( | ||||||
|  |                 temp, "%s\r\n", string_search(buffer, path) ? string_get_cstr(buffer) : dst); | ||||||
|  |             archive_file_append(ARCHIVE_FAV_TEMP_PATH, temp); | ||||||
|  |             string_clean(temp); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     string_clear(temp); | ||||||
|  |     string_clear(buffer); | ||||||
|  |     string_clear(path); | ||||||
|  | 
 | ||||||
|  |     file_worker_close(file_worker); | ||||||
|  |     file_worker_remove(file_worker, ARCHIVE_FAV_PATH); | ||||||
|  |     file_worker_rename(file_worker, ARCHIVE_FAV_TEMP_PATH, ARCHIVE_FAV_PATH); | ||||||
|  | 
 | ||||||
|  |     file_worker_free(file_worker); | ||||||
|  | 
 | ||||||
|  |     return result; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void archive_add_to_favorites(const char* file_path, const char* name) { | ||||||
|  |     furi_assert(file_path); | ||||||
|  |     furi_assert(name); | ||||||
|  | 
 | ||||||
|  |     string_t buffer_src; | ||||||
|  | 
 | ||||||
|  |     string_init_printf(buffer_src, "%s/%s\r\n", file_path, name); | ||||||
|  |     archive_file_append(ARCHIVE_FAV_PATH, buffer_src); | ||||||
|  |     string_clear(buffer_src); | ||||||
|  | } | ||||||
							
								
								
									
										11
									
								
								applications/archive/helpers/archive_favorites.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								applications/archive/helpers/archive_favorites.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,11 @@ | |||||||
|  | #pragma once | ||||||
|  | #include "file-worker.h" | ||||||
|  | 
 | ||||||
|  | #define ARCHIVE_FAV_PATH "/any/favorites.txt" | ||||||
|  | #define ARCHIVE_FAV_TEMP_PATH "/any/favorites.tmp" | ||||||
|  | 
 | ||||||
|  | bool archive_favorites_read(void* context); | ||||||
|  | bool archive_favorites_delete(const char* file_path, const char* name); | ||||||
|  | bool archive_is_favorite(const char* file_path, const char* name); | ||||||
|  | bool archive_favorites_rename(const char* file_path, const char* src, const char* dst); | ||||||
|  | void archive_add_to_favorites(const char* file_path, const char* name); | ||||||
							
								
								
									
										143
									
								
								applications/archive/helpers/archive_files.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										143
									
								
								applications/archive/helpers/archive_files.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,143 @@ | |||||||
|  | #include "archive_files.h" | ||||||
|  | #include "archive_favorites.h" | ||||||
|  | #include "../archive_i.h" | ||||||
|  | 
 | ||||||
|  | bool filter_by_extension(FileInfo* file_info, const char* tab_ext, const char* name) { | ||||||
|  |     furi_assert(file_info); | ||||||
|  |     furi_assert(tab_ext); | ||||||
|  |     furi_assert(name); | ||||||
|  | 
 | ||||||
|  |     bool result = false; | ||||||
|  | 
 | ||||||
|  |     if(strcmp(tab_ext, "*") == 0) { | ||||||
|  |         result = true; | ||||||
|  |     } else if(strstr(name, tab_ext) != NULL) { | ||||||
|  |         result = true; | ||||||
|  |     } else if(file_info->flags & FSF_DIRECTORY) { | ||||||
|  |         result = true; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return result; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void archive_trim_file_ext(char* name) { | ||||||
|  |     size_t str_len = strlen(name); | ||||||
|  |     char* end = name + str_len; | ||||||
|  |     while(end > name && *end != '.' && *end != '\\' && *end != '/') { | ||||||
|  |         --end; | ||||||
|  |     } | ||||||
|  |     if((end > name && *end == '.') && (*(end - 1) != '\\' && *(end - 1) != '/')) { | ||||||
|  |         *end = '\0'; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void set_file_type(ArchiveFile_t* file, FileInfo* file_info) { | ||||||
|  |     furi_assert(file); | ||||||
|  |     furi_assert(file_info); | ||||||
|  | 
 | ||||||
|  |     for(size_t i = 0; i < SIZEOF_ARRAY(known_ext); i++) { | ||||||
|  |         if(string_search_str(file->name, known_ext[i], 0) != STRING_FAILURE) { | ||||||
|  |             file->type = i; | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if(file_info->flags & FSF_DIRECTORY) { | ||||||
|  |         file->type = ArchiveFileTypeFolder; | ||||||
|  |     } else { | ||||||
|  |         file->type = ArchiveFileTypeUnknown; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool archive_get_filenames(void* context, uint8_t tab_id, const char* path) { | ||||||
|  |     furi_assert(context); | ||||||
|  | 
 | ||||||
|  |     ArchiveMainView* main_view = context; | ||||||
|  |     archive_file_array_clean(main_view); | ||||||
|  | 
 | ||||||
|  |     if(tab_id != ArchiveTabFavorites) { | ||||||
|  |         archive_read_dir(main_view, path); | ||||||
|  |     } else { | ||||||
|  |         archive_favorites_read(main_view); | ||||||
|  |     } | ||||||
|  |     return true; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool archive_read_dir(void* context, const char* path) { | ||||||
|  |     furi_assert(context); | ||||||
|  | 
 | ||||||
|  |     ArchiveMainView* main_view = context; | ||||||
|  |     FileInfo file_info; | ||||||
|  |     Storage* fs_api = furi_record_open("storage"); | ||||||
|  |     File* directory = storage_file_alloc(fs_api); | ||||||
|  |     char name[MAX_NAME_LEN]; | ||||||
|  | 
 | ||||||
|  |     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; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         uint16_t files_cnt = archive_file_array_size(main_view); | ||||||
|  | 
 | ||||||
|  |         if(files_cnt > MAX_FILES) { | ||||||
|  |             break; | ||||||
|  |         } else if(storage_file_get_error(directory) == FSE_OK) { | ||||||
|  |             archive_view_add_item(main_view, &file_info, name); | ||||||
|  |         } else { | ||||||
|  |             storage_dir_close(directory); | ||||||
|  |             storage_file_free(directory); | ||||||
|  |             return false; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     storage_dir_close(directory); | ||||||
|  |     storage_file_free(directory); | ||||||
|  | 
 | ||||||
|  |     furi_record_close("storage"); | ||||||
|  | 
 | ||||||
|  |     return true; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void archive_file_append(const char* path, string_t string) { | ||||||
|  |     furi_assert(path); | ||||||
|  |     furi_assert(string); | ||||||
|  | 
 | ||||||
|  |     FileWorker* file_worker = file_worker_alloc(false); | ||||||
|  | 
 | ||||||
|  |     if(!file_worker_open(file_worker, path, FSAM_WRITE, FSOM_OPEN_APPEND)) { | ||||||
|  |         FURI_LOG_E("Archive", "Append open error"); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if(!file_worker_write(file_worker, string_get_cstr(string), string_size(string))) { | ||||||
|  |         FURI_LOG_E("Archive", "Append write error"); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     file_worker_close(file_worker); | ||||||
|  |     file_worker_free(file_worker); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void archive_delete_file(void* context, string_t path, string_t name) { | ||||||
|  |     furi_assert(context); | ||||||
|  |     furi_assert(path); | ||||||
|  |     furi_assert(name); | ||||||
|  |     ArchiveMainView* main_view = context; | ||||||
|  |     FileWorker* file_worker = file_worker_alloc(false); | ||||||
|  | 
 | ||||||
|  |     string_t full_path; | ||||||
|  |     string_init(full_path); | ||||||
|  |     string_printf(full_path, "%s/%s", string_get_cstr(path), string_get_cstr(name)); | ||||||
|  |     file_worker_remove(file_worker, string_get_cstr(full_path)); | ||||||
|  |     file_worker_free(file_worker); | ||||||
|  |     string_clear(full_path); | ||||||
|  | 
 | ||||||
|  |     if(archive_is_favorite(string_get_cstr(path), string_get_cstr(name))) { | ||||||
|  |         archive_favorites_delete(string_get_cstr(path), string_get_cstr(name)); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     archive_file_array_remove_selected(main_view); | ||||||
|  | } | ||||||
| @ -1,15 +1,7 @@ | |||||||
| #pragma once | #pragma once | ||||||
|  | #include "file-worker.h" | ||||||
| 
 | 
 | ||||||
| #include <gui/gui_i.h> | #define MAX_FILES 100 //temp
 | ||||||
| #include <gui/canvas.h> |  | ||||||
| #include <gui/elements.h> |  | ||||||
| #include <furi.h> |  | ||||||
| #include <storage/storage.h> |  | ||||||
| 
 |  | ||||||
| #define MAX_LEN_PX 100 |  | ||||||
| #define MAX_NAME_LEN 255 |  | ||||||
| #define FRAME_HEIGHT 12 |  | ||||||
| #define MENU_ITEMS 4 |  | ||||||
| 
 | 
 | ||||||
| typedef enum { | typedef enum { | ||||||
|     ArchiveFileTypeIButton, |     ArchiveFileTypeIButton, | ||||||
| @ -22,17 +14,6 @@ typedef enum { | |||||||
|     AppIdTotal, |     AppIdTotal, | ||||||
| } ArchiveFileTypeEnum; | } ArchiveFileTypeEnum; | ||||||
| 
 | 
 | ||||||
| typedef enum { |  | ||||||
|     ArchiveTabFavorites, |  | ||||||
|     ArchiveTabLFRFID, |  | ||||||
|     ArchiveTabSubGhz, |  | ||||||
|     ArchiveTabNFC, |  | ||||||
|     ArchiveTabIButton, |  | ||||||
|     ArchiveTabIrda, |  | ||||||
|     ArchiveTabBrowser, |  | ||||||
|     ArchiveTabTotal, |  | ||||||
| } ArchiveTabEnum; |  | ||||||
| 
 |  | ||||||
| typedef struct { | typedef struct { | ||||||
|     string_t name; |     string_t name; | ||||||
|     ArchiveFileTypeEnum type; |     ArchiveFileTypeEnum type; | ||||||
| @ -66,18 +47,10 @@ ARRAY_DEF( | |||||||
|      INIT_SET(API_6(ArchiveFile_t_init_set)), |      INIT_SET(API_6(ArchiveFile_t_init_set)), | ||||||
|      CLEAR(API_2(ArchiveFile_t_clear)))) |      CLEAR(API_2(ArchiveFile_t_clear)))) | ||||||
| 
 | 
 | ||||||
| typedef struct { | bool filter_by_extension(FileInfo* file_info, const char* tab_ext, const char* name); | ||||||
|     uint8_t tab_idx; | void set_file_type(ArchiveFile_t* file, FileInfo* file_info); | ||||||
|     uint8_t menu_idx; |  | ||||||
|     uint16_t idx; |  | ||||||
|     uint16_t list_offset; |  | ||||||
|     files_array_t files; |  | ||||||
|     bool menu; |  | ||||||
| } ArchiveViewModel; |  | ||||||
| 
 |  | ||||||
| void archive_view_render(Canvas* canvas, void* model); |  | ||||||
| void archive_trim_file_ext(char* name); | void archive_trim_file_ext(char* name); | ||||||
| 
 | bool archive_get_filenames(void* context, uint8_t tab_id, const char* path); | ||||||
| static inline bool is_known_app(ArchiveFileTypeEnum type) { | bool archive_read_dir(void* context, const char* path); | ||||||
|     return (type != ArchiveFileTypeFolder && type != ArchiveFileTypeUnknown); | void archive_file_append(const char* path, string_t string); | ||||||
| } | void archive_delete_file(void* context, string_t path, string_t name); | ||||||
							
								
								
									
										30
									
								
								applications/archive/scenes/archive_scene.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								applications/archive/scenes/archive_scene.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,30 @@ | |||||||
|  | #include "archive_scene.h" | ||||||
|  | 
 | ||||||
|  | // Generate scene on_enter handlers array
 | ||||||
|  | #define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_enter, | ||||||
|  | void (*const archive_on_enter_handlers[])(void*) = { | ||||||
|  | #include "archive_scene_config.h" | ||||||
|  | }; | ||||||
|  | #undef ADD_SCENE | ||||||
|  | 
 | ||||||
|  | // Generate scene on_event handlers array
 | ||||||
|  | #define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_event, | ||||||
|  | bool (*const archive_on_event_handlers[])(void* context, SceneManagerEvent event) = { | ||||||
|  | #include "archive_scene_config.h" | ||||||
|  | }; | ||||||
|  | #undef ADD_SCENE | ||||||
|  | 
 | ||||||
|  | // Generate scene on_exit handlers array
 | ||||||
|  | #define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_exit, | ||||||
|  | void (*const archive_on_exit_handlers[])(void* context) = { | ||||||
|  | #include "archive_scene_config.h" | ||||||
|  | }; | ||||||
|  | #undef ADD_SCENE | ||||||
|  | 
 | ||||||
|  | // Initialize scene handlers configuration structure
 | ||||||
|  | const SceneManagerHandlers archive_scene_handlers = { | ||||||
|  |     .on_enter_handlers = archive_on_enter_handlers, | ||||||
|  |     .on_event_handlers = archive_on_event_handlers, | ||||||
|  |     .on_exit_handlers = archive_on_exit_handlers, | ||||||
|  |     .scene_num = ArchiveAppSceneNum, | ||||||
|  | }; | ||||||
							
								
								
									
										29
									
								
								applications/archive/scenes/archive_scene.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								applications/archive/scenes/archive_scene.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,29 @@ | |||||||
|  | #pragma once | ||||||
|  | 
 | ||||||
|  | #include <gui/scene_manager.h> | ||||||
|  | 
 | ||||||
|  | // Generate scene id and total number
 | ||||||
|  | #define ADD_SCENE(prefix, name, id) ArchiveAppScene##id, | ||||||
|  | typedef enum { | ||||||
|  | #include "archive_scene_config.h" | ||||||
|  |     ArchiveAppSceneNum, | ||||||
|  | } ArchiveAppScene; | ||||||
|  | #undef ADD_SCENE | ||||||
|  | 
 | ||||||
|  | extern const SceneManagerHandlers archive_scene_handlers; | ||||||
|  | 
 | ||||||
|  | // Generate scene on_enter handlers declaration
 | ||||||
|  | #define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_enter(void*); | ||||||
|  | #include "archive_scene_config.h" | ||||||
|  | #undef ADD_SCENE | ||||||
|  | 
 | ||||||
|  | // Generate scene on_event handlers declaration
 | ||||||
|  | #define ADD_SCENE(prefix, name, id) \ | ||||||
|  |     bool prefix##_scene_##name##_on_event(void* context, SceneManagerEvent event); | ||||||
|  | #include "archive_scene_config.h" | ||||||
|  | #undef ADD_SCENE | ||||||
|  | 
 | ||||||
|  | // Generate scene on_exit handlers declaration
 | ||||||
|  | #define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_exit(void* context); | ||||||
|  | #include "archive_scene_config.h" | ||||||
|  | #undef ADD_SCENE | ||||||
							
								
								
									
										42
									
								
								applications/archive/scenes/archive_scene_browser.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								applications/archive/scenes/archive_scene_browser.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,42 @@ | |||||||
|  | #include "../archive_i.h" | ||||||
|  | #include "../views/archive_main_view.h" | ||||||
|  | 
 | ||||||
|  | void archive_scene_browser_callback(ArchiveBrowserEvent event, void* context) { | ||||||
|  |     ArchiveApp* archive = (ArchiveApp*)context; | ||||||
|  |     view_dispatcher_send_custom_event(archive->view_dispatcher, event); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | const void archive_scene_browser_on_enter(void* context) { | ||||||
|  |     ArchiveApp* archive = (ArchiveApp*)context; | ||||||
|  |     ArchiveMainView* main_view = archive->main_view; | ||||||
|  | 
 | ||||||
|  |     archive_browser_set_callback(main_view, archive_scene_browser_callback, archive); | ||||||
|  |     archive_browser_update(main_view); | ||||||
|  |     view_dispatcher_switch_to_view(archive->view_dispatcher, ArchiveViewBrowser); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | const bool archive_scene_browser_on_event(void* context, SceneManagerEvent event) { | ||||||
|  |     ArchiveApp* archive = (ArchiveApp*)context; | ||||||
|  |     bool consumed; | ||||||
|  | 
 | ||||||
|  |     if(event.type == SceneManagerEventTypeCustom) { | ||||||
|  |         switch(event.event) { | ||||||
|  |         case ArchiveBrowserEventRename: | ||||||
|  |             scene_manager_next_scene(archive->scene_manager, ArchiveAppSceneRename); | ||||||
|  |             consumed = true; | ||||||
|  |             break; | ||||||
|  |         case ArchiveBrowserEventExit: | ||||||
|  |             view_dispatcher_stop(archive->view_dispatcher); | ||||||
|  |             consumed = true; | ||||||
|  |             break; | ||||||
|  | 
 | ||||||
|  |         default: | ||||||
|  |             break; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     return consumed; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | const void archive_scene_browser_on_exit(void* context) { | ||||||
|  |     // ArchiveApp* archive = (ArchiveApp*)context;
 | ||||||
|  | } | ||||||
							
								
								
									
										2
									
								
								applications/archive/scenes/archive_scene_config.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								applications/archive/scenes/archive_scene_config.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,2 @@ | |||||||
|  | ADD_SCENE(archive, browser, Browser) | ||||||
|  | ADD_SCENE(archive, rename, Rename) | ||||||
							
								
								
									
										80
									
								
								applications/archive/scenes/archive_scene_rename.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										80
									
								
								applications/archive/scenes/archive_scene_rename.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,80 @@ | |||||||
|  | #include "../archive_i.h" | ||||||
|  | #include "../helpers/archive_favorites.h" | ||||||
|  | #include "../helpers/archive_files.h" | ||||||
|  | 
 | ||||||
|  | #define SCENE_RENAME_CUSTOM_EVENT (0UL) | ||||||
|  | 
 | ||||||
|  | void archive_scene_rename_text_input_callback(void* context) { | ||||||
|  |     ArchiveApp* archive = (ArchiveApp*)context; | ||||||
|  |     view_dispatcher_send_custom_event(archive->view_dispatcher, SCENE_RENAME_CUSTOM_EVENT); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | const void archive_scene_rename_on_enter(void* context) { | ||||||
|  |     ArchiveApp* archive = (ArchiveApp*)context; | ||||||
|  | 
 | ||||||
|  |     TextInput* text_input = archive->text_input; | ||||||
|  |     ArchiveFile_t* current = archive_get_current_file(archive->main_view); | ||||||
|  |     strlcpy(archive->text_store, string_get_cstr(current->name), MAX_NAME_LEN); | ||||||
|  | 
 | ||||||
|  |     archive_trim_file_ext(archive->text_store); | ||||||
|  | 
 | ||||||
|  |     text_input_set_header_text(text_input, "Rename:"); | ||||||
|  | 
 | ||||||
|  |     text_input_set_result_callback( | ||||||
|  |         text_input, | ||||||
|  |         archive_scene_rename_text_input_callback, | ||||||
|  |         archive, | ||||||
|  |         archive->text_store, | ||||||
|  |         MAX_NAME_LEN, | ||||||
|  |         false); | ||||||
|  | 
 | ||||||
|  |     view_dispatcher_switch_to_view(archive->view_dispatcher, ArchiveViewTextInput); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | const bool archive_scene_rename_on_event(void* context, SceneManagerEvent event) { | ||||||
|  |     ArchiveApp* archive = (ArchiveApp*)context; | ||||||
|  |     bool consumed = false; | ||||||
|  | 
 | ||||||
|  |     if(event.type == SceneManagerEventTypeCustom) { | ||||||
|  |         if(event.event == SCENE_RENAME_CUSTOM_EVENT) { | ||||||
|  |             Storage* fs_api = furi_record_open("storage"); | ||||||
|  | 
 | ||||||
|  |             string_t buffer_src; | ||||||
|  |             string_t buffer_dst; | ||||||
|  | 
 | ||||||
|  |             const char* path = archive_get_path(archive->main_view); | ||||||
|  |             const char* name = archive_get_name(archive->main_view); | ||||||
|  | 
 | ||||||
|  |             string_init_printf(buffer_src, "%s/%s", path, name); | ||||||
|  |             string_init_printf(buffer_dst, "%s/%s", path, archive->text_store); | ||||||
|  | 
 | ||||||
|  |             archive_set_name(archive->main_view, archive->text_store); | ||||||
|  | 
 | ||||||
|  |             // append extension
 | ||||||
|  |             ArchiveFile_t* file = archive_get_current_file(archive->main_view); | ||||||
|  | 
 | ||||||
|  |             string_cat(buffer_dst, known_ext[file->type]); | ||||||
|  |             storage_common_rename( | ||||||
|  |                 fs_api, string_get_cstr(buffer_src), string_get_cstr(buffer_dst)); | ||||||
|  |             furi_record_close("storage"); | ||||||
|  | 
 | ||||||
|  |             if(file->fav) { | ||||||
|  |                 archive_favorites_rename(path, name, string_get_cstr(buffer_dst)); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             string_clear(buffer_src); | ||||||
|  |             string_clear(buffer_dst); | ||||||
|  | 
 | ||||||
|  |             scene_manager_next_scene(archive->scene_manager, ArchiveAppSceneBrowser); | ||||||
|  |             consumed = true; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     return consumed; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | const void archive_scene_rename_on_exit(void* context) { | ||||||
|  |     ArchiveApp* archive = (ArchiveApp*)context; | ||||||
|  |     // Clear view
 | ||||||
|  |     text_input_set_header_text(archive->text_input, NULL); | ||||||
|  |     text_input_set_result_callback(archive->text_input, NULL, NULL, NULL, 0, false); | ||||||
|  | } | ||||||
							
								
								
									
										642
									
								
								applications/archive/views/archive_main_view.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										642
									
								
								applications/archive/views/archive_main_view.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,642 @@ | |||||||
|  | #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->list_offset + 1, 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); | ||||||
|  | } | ||||||
							
								
								
									
										117
									
								
								applications/archive/views/archive_main_view.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										117
									
								
								applications/archive/views/archive_main_view.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,117 @@ | |||||||
|  | #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