[FL-2491] File browser GUI module (#1237)
* File browser module and test app * nfc: Add support for saved files in subdirectories * nfc: Use helper function to get shadow path when loading data * File browser dialog integration pt.1 * File browser dialog integration pt.2 * Gui,Dialogs: drop file select * Correct use of dynamic string_t(string_ptr) Co-authored-by: Yukai Li <yukaili.geek@gmail.com> Co-authored-by: Aleksandr Kutuzov <alleteam@gmail.com>
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
#include "infrared_app.h"
|
||||
#include "m-string.h"
|
||||
#include <infrared_worker.h>
|
||||
#include <furi.h>
|
||||
#include <gui/gui.h>
|
||||
@@ -12,20 +13,18 @@ int32_t InfraredApp::run(void* args) {
|
||||
bool exit = false;
|
||||
|
||||
if(args) {
|
||||
std::string path = static_cast<const char*>(args);
|
||||
std::string remote_name(path, path.find_last_of('/') + 1, path.size());
|
||||
auto last_dot = remote_name.find_last_of('.');
|
||||
if(last_dot != std::string::npos) {
|
||||
remote_name.erase(last_dot);
|
||||
path.erase(path.find_last_of('/'));
|
||||
bool result = remote_manager.load(path, remote_name);
|
||||
string_t path;
|
||||
string_init_set_str(path, (char*)args);
|
||||
if(string_end_with_str_p(path, InfraredApp::infrared_extension)) {
|
||||
bool result = remote_manager.load(path);
|
||||
if(result) {
|
||||
current_scene = InfraredApp::Scene::Remote;
|
||||
} else {
|
||||
printf("Failed to load remote \'%s\'\r\n", remote_name.c_str());
|
||||
printf("Failed to load remote \'%s\'\r\n", string_get_cstr(path));
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
string_clear(path);
|
||||
}
|
||||
|
||||
scenes[current_scene]->on_enter(this);
|
||||
@@ -51,6 +50,7 @@ int32_t InfraredApp::run(void* args) {
|
||||
|
||||
InfraredApp::InfraredApp() {
|
||||
furi_check(InfraredAppRemoteManager::max_button_name_length < get_text_store_size());
|
||||
string_init_set_str(file_path, InfraredApp::infrared_directory);
|
||||
notification = static_cast<NotificationApp*>(furi_record_open("notification"));
|
||||
dialogs = static_cast<DialogsApp*>(furi_record_open("dialogs"));
|
||||
infrared_worker = infrared_worker_alloc();
|
||||
@@ -60,6 +60,7 @@ InfraredApp::~InfraredApp() {
|
||||
infrared_worker_free(infrared_worker);
|
||||
furi_record_close("notification");
|
||||
furi_record_close("dialogs");
|
||||
string_clear(file_path);
|
||||
for(auto& [key, scene] : scenes) delete scene;
|
||||
}
|
||||
|
||||
|
@@ -251,6 +251,8 @@ public:
|
||||
/** Main class destructor, deinitializes all critical objects */
|
||||
~InfraredApp();
|
||||
|
||||
string_t file_path;
|
||||
|
||||
/** Path to Infrared directory */
|
||||
static constexpr const char* infrared_directory = "/any/infrared";
|
||||
/** Infrared files extension (remote files and universal databases) */
|
||||
|
@@ -1,3 +1,5 @@
|
||||
#include "m-string.h"
|
||||
#include "storage/filesystem_api_defines.h"
|
||||
#include <flipper_format/flipper_format.h>
|
||||
#include "infrared_app_remote_manager.h"
|
||||
#include "infrared/helpers/infrared_parser.h"
|
||||
@@ -11,44 +13,58 @@
|
||||
#include <gui/modules/button_menu.h>
|
||||
#include <storage/storage.h>
|
||||
#include "infrared_app.h"
|
||||
#include <toolbox/path.h>
|
||||
|
||||
static const char* default_remote_name = "remote";
|
||||
|
||||
std::string InfraredAppRemoteManager::make_full_name(
|
||||
const std::string& path,
|
||||
const std::string& remote_name) const {
|
||||
return std::string("") + path + "/" + remote_name + InfraredApp::infrared_extension;
|
||||
}
|
||||
|
||||
std::string InfraredAppRemoteManager::find_vacant_remote_name(const std::string& name) {
|
||||
std::string result_name;
|
||||
void InfraredAppRemoteManager::find_vacant_remote_name(string_t name, string_t path) {
|
||||
Storage* storage = static_cast<Storage*>(furi_record_open("storage"));
|
||||
|
||||
FS_Error error = storage_common_stat(
|
||||
storage, make_full_name(InfraredApp::infrared_directory, name).c_str(), NULL);
|
||||
string_t base_path;
|
||||
string_init_set(base_path, path);
|
||||
|
||||
if(error == FSE_NOT_EXIST) {
|
||||
result_name = name;
|
||||
} else if(error != FSE_OK) {
|
||||
result_name = std::string();
|
||||
} else {
|
||||
if(string_end_with_str_p(base_path, InfraredApp::infrared_extension)) {
|
||||
size_t filename_start = string_search_rchar(base_path, '/');
|
||||
string_left(base_path, filename_start);
|
||||
}
|
||||
|
||||
string_printf(
|
||||
base_path,
|
||||
"%s/%s%s",
|
||||
string_get_cstr(path),
|
||||
string_get_cstr(name),
|
||||
InfraredApp::infrared_extension);
|
||||
|
||||
FS_Error error = storage_common_stat(storage, string_get_cstr(base_path), NULL);
|
||||
|
||||
if(error == FSE_OK) {
|
||||
/* if suggested name is occupied, try another one (name2, name3, etc) */
|
||||
size_t dot = string_search_rchar(base_path, '.');
|
||||
string_left(base_path, dot);
|
||||
|
||||
string_t path_temp;
|
||||
string_init(path_temp);
|
||||
|
||||
uint32_t i = 1;
|
||||
std::string new_name;
|
||||
do {
|
||||
new_name = make_full_name(InfraredApp::infrared_directory, name + std::to_string(++i));
|
||||
error = storage_common_stat(storage, new_name.c_str(), NULL);
|
||||
string_printf(
|
||||
path_temp,
|
||||
"%s%u%s",
|
||||
string_get_cstr(base_path),
|
||||
++i,
|
||||
InfraredApp::infrared_extension);
|
||||
error = storage_common_stat(storage, string_get_cstr(path_temp), NULL);
|
||||
} while(error == FSE_OK);
|
||||
|
||||
string_clear(path_temp);
|
||||
|
||||
if(error == FSE_NOT_EXIST) {
|
||||
result_name = name + std::to_string(i);
|
||||
} else {
|
||||
result_name = std::string();
|
||||
string_cat_printf(name, "%u", i);
|
||||
}
|
||||
}
|
||||
|
||||
string_clear(base_path);
|
||||
furi_record_close("storage");
|
||||
return result_name;
|
||||
}
|
||||
|
||||
bool InfraredAppRemoteManager::add_button(const char* button_name, const InfraredAppSignal& signal) {
|
||||
@@ -61,12 +77,23 @@ bool InfraredAppRemoteManager::add_remote_with_button(
|
||||
const InfraredAppSignal& signal) {
|
||||
furi_check(button_name != nullptr);
|
||||
|
||||
auto new_name = find_vacant_remote_name(default_remote_name);
|
||||
if(new_name.empty()) {
|
||||
return false;
|
||||
}
|
||||
string_t new_name;
|
||||
string_init_set_str(new_name, default_remote_name);
|
||||
|
||||
string_t new_path;
|
||||
string_init_set_str(new_path, InfraredApp::infrared_directory);
|
||||
|
||||
find_vacant_remote_name(new_name, new_path);
|
||||
|
||||
string_cat_printf(
|
||||
new_path, "/%s%s", string_get_cstr(new_name), InfraredApp::infrared_extension);
|
||||
|
||||
remote = std::make_unique<InfraredAppRemote>(new_path);
|
||||
remote->name = std::string(string_get_cstr(new_name));
|
||||
|
||||
string_clear(new_path);
|
||||
string_clear(new_name);
|
||||
|
||||
remote = std::make_unique<InfraredAppRemote>(InfraredApp::infrared_directory, new_name);
|
||||
return add_button(button_name, signal);
|
||||
}
|
||||
|
||||
@@ -93,8 +120,7 @@ const InfraredAppSignal& InfraredAppRemoteManager::get_button_data(size_t index)
|
||||
bool InfraredAppRemoteManager::delete_remote() {
|
||||
Storage* storage = static_cast<Storage*>(furi_record_open("storage"));
|
||||
|
||||
FS_Error error =
|
||||
storage_common_remove(storage, make_full_name(remote->path, remote->name).c_str());
|
||||
FS_Error error = storage_common_remove(storage, string_get_cstr(remote->path));
|
||||
reset_remote();
|
||||
|
||||
furi_record_close("storage");
|
||||
@@ -128,22 +154,33 @@ std::string InfraredAppRemoteManager::get_remote_name() {
|
||||
bool InfraredAppRemoteManager::rename_remote(const char* str) {
|
||||
furi_check(str != nullptr);
|
||||
furi_check(remote.get() != nullptr);
|
||||
furi_check(!string_empty_p(remote->path));
|
||||
|
||||
if(!remote->name.compare(str)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
auto new_name = find_vacant_remote_name(str);
|
||||
if(new_name.empty()) {
|
||||
return false;
|
||||
string_t new_name;
|
||||
string_init_set_str(new_name, str);
|
||||
find_vacant_remote_name(new_name, remote->path);
|
||||
|
||||
string_t new_path;
|
||||
string_init_set(new_path, remote->path);
|
||||
if(string_end_with_str_p(new_path, InfraredApp::infrared_extension)) {
|
||||
size_t filename_start = string_search_rchar(new_path, '/');
|
||||
string_left(new_path, filename_start);
|
||||
}
|
||||
string_cat_printf(
|
||||
new_path, "/%s%s", string_get_cstr(new_name), InfraredApp::infrared_extension);
|
||||
|
||||
Storage* storage = static_cast<Storage*>(furi_record_open("storage"));
|
||||
|
||||
std::string old_filename = make_full_name(remote->path, remote->name);
|
||||
std::string new_filename = make_full_name(remote->path, new_name);
|
||||
FS_Error error = storage_common_rename(storage, old_filename.c_str(), new_filename.c_str());
|
||||
remote->name = new_name;
|
||||
FS_Error error =
|
||||
storage_common_rename(storage, string_get_cstr(remote->path), string_get_cstr(new_path));
|
||||
remote->name = std::string(string_get_cstr(new_name));
|
||||
|
||||
string_clear(new_name);
|
||||
string_clear(new_path);
|
||||
|
||||
furi_record_close("storage");
|
||||
return (error == FSE_OK || error == FSE_EXIST);
|
||||
@@ -171,10 +208,8 @@ bool InfraredAppRemoteManager::store(void) {
|
||||
|
||||
FlipperFormat* ff = flipper_format_file_alloc(storage);
|
||||
|
||||
FURI_LOG_I(
|
||||
"RemoteManager", "store file: \'%s\'", make_full_name(remote->path, remote->name).c_str());
|
||||
result =
|
||||
flipper_format_file_open_always(ff, make_full_name(remote->path, remote->name).c_str());
|
||||
FURI_LOG_I("RemoteManager", "store file: \'%s\'", string_get_cstr(remote->path));
|
||||
result = flipper_format_file_open_always(ff, string_get_cstr(remote->path));
|
||||
if(result) {
|
||||
result = flipper_format_write_header_cstr(ff, "IR signals file", 1);
|
||||
}
|
||||
@@ -192,13 +227,13 @@ bool InfraredAppRemoteManager::store(void) {
|
||||
return result;
|
||||
}
|
||||
|
||||
bool InfraredAppRemoteManager::load(const std::string& path, const std::string& remote_name) {
|
||||
bool InfraredAppRemoteManager::load(string_t path) {
|
||||
bool result = false;
|
||||
Storage* storage = static_cast<Storage*>(furi_record_open("storage"));
|
||||
FlipperFormat* ff = flipper_format_file_alloc(storage);
|
||||
|
||||
FURI_LOG_I("RemoteManager", "load file: \'%s\'", make_full_name(path, remote_name).c_str());
|
||||
result = flipper_format_file_open_existing(ff, make_full_name(path, remote_name).c_str());
|
||||
FURI_LOG_I("RemoteManager", "load file: \'%s\'", string_get_cstr(path));
|
||||
result = flipper_format_file_open_existing(ff, string_get_cstr(path));
|
||||
if(result) {
|
||||
string_t header;
|
||||
string_init(header);
|
||||
@@ -210,7 +245,14 @@ bool InfraredAppRemoteManager::load(const std::string& path, const std::string&
|
||||
string_clear(header);
|
||||
}
|
||||
if(result) {
|
||||
remote = std::make_unique<InfraredAppRemote>(path, remote_name);
|
||||
string_t new_name;
|
||||
string_init(new_name);
|
||||
|
||||
remote = std::make_unique<InfraredAppRemote>(path);
|
||||
path_extract_filename(path, new_name, true);
|
||||
remote->name = std::string(string_get_cstr(new_name));
|
||||
|
||||
string_clear(new_name);
|
||||
InfraredAppSignal signal;
|
||||
std::string signal_name;
|
||||
while(infrared_parser_read_signal(ff, signal, signal_name)) {
|
||||
|
@@ -8,6 +8,7 @@
|
||||
|
||||
#include "infrared_app_signal.h"
|
||||
|
||||
#include "m-string.h"
|
||||
#include <infrared_worker.h>
|
||||
#include <infrared.h>
|
||||
|
||||
@@ -60,17 +61,19 @@ class InfraredAppRemote {
|
||||
/** Name of remote */
|
||||
std::string name;
|
||||
/** Path to remote file */
|
||||
std::string path;
|
||||
string_t path;
|
||||
|
||||
public:
|
||||
/** Initialize new remote
|
||||
*
|
||||
* @param path - remote file path
|
||||
* @param name - new remote name
|
||||
*/
|
||||
InfraredAppRemote(const std::string& path, const std::string& name)
|
||||
: name(name)
|
||||
, path(path) {
|
||||
InfraredAppRemote(string_t file_path) {
|
||||
string_init_set(path, file_path);
|
||||
}
|
||||
|
||||
~InfraredAppRemote() {
|
||||
string_clear(path);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -78,12 +81,6 @@ public:
|
||||
class InfraredAppRemoteManager {
|
||||
/** Remote instance. There can be 1 remote loaded at a time. */
|
||||
std::unique_ptr<InfraredAppRemote> remote;
|
||||
/** Make full name from remote name
|
||||
*
|
||||
* @param remote_name name of remote
|
||||
* @retval full name of remote on disk
|
||||
*/
|
||||
std::string make_full_name(const std::string& path, const std::string& remote_name) const;
|
||||
|
||||
public:
|
||||
/** Restriction to button name length. Buttons larger are ignored. */
|
||||
@@ -125,9 +122,9 @@ public:
|
||||
* incremented digit(2,3,4,etc) added to name and check repeated.
|
||||
*
|
||||
* @param name - suggested remote name
|
||||
* @retval garanteed free remote name, prefixed with suggested
|
||||
* @param path - remote file path
|
||||
*/
|
||||
std::string find_vacant_remote_name(const std::string& name);
|
||||
void find_vacant_remote_name(string_t name, string_t path);
|
||||
|
||||
/** Get button list
|
||||
*
|
||||
@@ -185,8 +182,8 @@ public:
|
||||
|
||||
/** Load data from disk into current remote
|
||||
*
|
||||
* @param name - name of remote to load
|
||||
* @param path - path to remote file
|
||||
* @retval true if success, false otherwise
|
||||
*/
|
||||
bool load(const std::string& path, const std::string& name);
|
||||
bool load(string_t path);
|
||||
};
|
||||
|
@@ -1,4 +1,6 @@
|
||||
#include "../infrared_app.h"
|
||||
#include "m-string.h"
|
||||
#include "toolbox/path.h"
|
||||
|
||||
void InfraredAppSceneEditRename::on_enter(InfraredApp* app) {
|
||||
InfraredAppViewManager* view_manager = app->get_view_manager();
|
||||
@@ -21,9 +23,18 @@ void InfraredAppSceneEditRename::on_enter(InfraredApp* app) {
|
||||
enter_name_length = InfraredAppRemoteManager::max_remote_name_length;
|
||||
text_input_set_header_text(text_input, "Name the remote");
|
||||
|
||||
string_t folder_path;
|
||||
string_init(folder_path);
|
||||
|
||||
if(string_end_with_str_p(app->file_path, InfraredApp::infrared_extension)) {
|
||||
path_extract_dirname(string_get_cstr(app->file_path), folder_path);
|
||||
}
|
||||
|
||||
ValidatorIsFile* validator_is_file = validator_is_file_alloc_init(
|
||||
app->infrared_directory, app->infrared_extension, remote_name.c_str());
|
||||
string_get_cstr(folder_path), app->infrared_extension, remote_name.c_str());
|
||||
text_input_set_validator(text_input, validator_is_file_callback, validator_is_file);
|
||||
|
||||
string_clear(folder_path);
|
||||
}
|
||||
|
||||
text_input_set_result_callback(
|
||||
|
@@ -1,4 +1,5 @@
|
||||
#include "../infrared_app.h"
|
||||
#include "assets_icons.h"
|
||||
#include "infrared/infrared_app_event.h"
|
||||
#include <text_store.h>
|
||||
|
||||
@@ -8,11 +9,6 @@ void InfraredAppSceneRemoteList::on_enter(InfraredApp* app) {
|
||||
bool result = false;
|
||||
bool file_select_result;
|
||||
auto remote_manager = app->get_remote_manager();
|
||||
auto last_selected_remote = remote_manager->get_remote_name();
|
||||
const char* last_selected_remote_name =
|
||||
last_selected_remote.size() ? last_selected_remote.c_str() : nullptr;
|
||||
auto filename_ts =
|
||||
std::make_unique<TextStore>(InfraredAppRemoteManager::max_remote_name_length);
|
||||
DialogsApp* dialogs = app->get_dialogs();
|
||||
|
||||
InfraredAppViewManager* view_manager = app->get_view_manager();
|
||||
@@ -20,16 +16,17 @@ void InfraredAppSceneRemoteList::on_enter(InfraredApp* app) {
|
||||
button_menu_reset(button_menu);
|
||||
view_manager->switch_to(InfraredAppViewManager::ViewId::ButtonMenu);
|
||||
|
||||
file_select_result = dialog_file_select_show(
|
||||
file_select_result = dialog_file_browser_show(
|
||||
dialogs,
|
||||
InfraredApp::infrared_directory,
|
||||
app->file_path,
|
||||
app->file_path,
|
||||
InfraredApp::infrared_extension,
|
||||
filename_ts->text,
|
||||
filename_ts->text_size,
|
||||
last_selected_remote_name);
|
||||
true,
|
||||
&I_ir_10px,
|
||||
true);
|
||||
|
||||
if(file_select_result) {
|
||||
if(remote_manager->load(InfraredApp::infrared_directory, std::string(filename_ts->text))) {
|
||||
if(remote_manager->load(app->file_path)) {
|
||||
app->switch_to_next_scene(InfraredApp::Scene::Remote);
|
||||
result = true;
|
||||
}
|
||||
|
@@ -26,6 +26,8 @@ void InfraredAppSceneStart::on_enter(InfraredApp* app) {
|
||||
submenu, "Learn New Remote", SubmenuIndexLearnNewRemote, submenu_callback, app);
|
||||
submenu_add_item(submenu, "Saved Remotes", SubmenuIndexSavedRemotes, submenu_callback, app);
|
||||
submenu_set_selected_item(submenu, submenu_item_selected);
|
||||
|
||||
string_set_str(app->file_path, InfraredApp::infrared_directory);
|
||||
submenu_item_selected = 0;
|
||||
|
||||
view_manager->switch_to(InfraredAppViewManager::ViewId::Submenu);
|
||||
|
Reference in New Issue
Block a user