فهرست منبع

improved IR file import

rdefeo 1 سال پیش
والد
کامیت
036d6d03d0

+ 8 - 0
actions/action.h

@@ -4,4 +4,12 @@
 
 struct Item;
 
+/** Transmits the selected item
+ * 
+ * @param   context     The App
+ * @param   item        Selected item to transmit
+ * @param   error       Error message if unsuccessful
+*/
 void action_tx(void* context, Item* item, FuriString* error);
+
+bool action_ir_list_commands(const FuriString* ir_file, FuriString* command);

+ 23 - 143
actions/action_ir.c

@@ -1,166 +1,36 @@
 // Methods for IR transmission
 
-// infrared
-#include <infrared.h>
-#include <infrared/encoder_decoder/infrared.h>
-#include <infrared/worker/infrared_transmit.h>
-#include <infrared/worker/infrared_worker.h>
-
-#include <flipper_format/flipper_format.h>
-
-#include "action_i.h"
 #include "quac.h"
-
-#define INFRARED_FILE_TYPE "IR signals file"
-#define INFRARED_FILE_VERSION 1
-
-typedef struct {
-    size_t timings_size; /**< Number of elements in the timings array. */
-    uint32_t* timings; /**< Pointer to an array of timings describing the signal. */
-    uint32_t frequency; /**< Carrier frequency of the signal. */
-    float duty_cycle; /**< Duty cycle of the signal. */
-} InfraredRawSignal;
-
-typedef struct InfraredSignal {
-    bool is_raw;
-    union {
-        InfraredMessage message; // protocol, address, command, repeat
-        InfraredRawSignal raw;
-    } payload;
-} InfraredSignal;
-
-InfraredSignal* infrared_signal_alloc() {
-    InfraredSignal* signal = malloc(sizeof(InfraredSignal));
-    signal->is_raw = false;
-    signal->payload.message.protocol = InfraredProtocolUnknown;
-    return signal;
-}
-
-void infrared_signal_free(InfraredSignal* signal) {
-    if(signal->is_raw) {
-        free(signal->payload.raw.timings);
-        signal->payload.raw.timings = NULL;
-    }
-    free(signal);
-}
+#include "action_i.h"
+#include "action_ir_utils.h"
 
 void action_ir_tx(void* context, const FuriString* action_path, FuriString* error) {
     UNUSED(error);
     App* app = context;
     const char* file_name = furi_string_get_cstr(action_path);
-    InfraredSignal* signal = infrared_signal_alloc();
+    InfraredSignal* signal = infrared_utils_signal_alloc();
 
     FlipperFormat* fff_data_file = flipper_format_file_alloc(app->storage);
     FuriString* temp_str;
     temp_str = furi_string_alloc();
-    uint32_t temp_data32;
+    // uint32_t temp_data32;
 
     // https://developer.flipper.net/flipperzero/doxygen/infrared_file_format.html
     // TODO: Right now we only read the first signal found in the file. Add support
-    // for reading any signal by 'name'
+    // for reading any signal by 'name'?
     do {
         if(!flipper_format_file_open_existing(fff_data_file, file_name)) {
             ACTION_SET_ERROR("IR: Error opening %s", file_name);
             break;
         }
-        if(!flipper_format_read_header(fff_data_file, temp_str, &temp_data32)) {
-            ACTION_SET_ERROR("IR: Missing or incorrect header");
-            break;
-        }
-        if(!furi_string_cmp_str(temp_str, INFRARED_FILE_TYPE) &&
-           temp_data32 == INFRARED_FILE_VERSION) {
-        } else {
-            ACTION_SET_ERROR("IR: File type or version mismatch");
-            break;
-        }
-        if(!flipper_format_read_string(fff_data_file, "name", temp_str)) {
-            ACTION_SET_ERROR("IR: Invalid or missing name");
-            break;
-        }
-        // FURI_LOG_I(TAG, "Reading signal %s", furi_string_get_cstr(temp_str));
-        if(!flipper_format_read_string(fff_data_file, "type", temp_str)) {
-            ACTION_SET_ERROR("IR: Type missing");
+        uint32_t index = 0;
+        if(!infrared_utils_read_signal_at_index(fff_data_file, index, signal, temp_str)) {
+            ACTION_SET_ERROR("IR: Failed to read from file");
             break;
         }
-        if(!furi_string_cmp_str(temp_str, "parsed")) {
-            // FURI_LOG_I(TAG, "IR File is PARSED");
-            signal->is_raw = false;
-
-            if(!flipper_format_read_string(fff_data_file, "protocol", temp_str)) {
-                ACTION_SET_ERROR("IR: Invalid or missing protocol");
-                break;
-            }
-            signal->payload.message.protocol =
-                infrared_get_protocol_by_name(furi_string_get_cstr(temp_str));
-            if(!infrared_is_protocol_valid(signal->payload.message.protocol)) {
-                ACTION_SET_ERROR("IR: Invalid or unknown protocol");
-                break;
-            }
-
-            // Why do these methods exist, when the spec says the address and command
-            // lengths MUST be 4 bytes?
-
-            // uint8_t address_len;
-            // address_len = infrared_get_protocol_address_length(signal->payload.message.protocol);
-
-            // uint8_t command_len;
-            // command_len = infrared_get_protocol_command_length(signal->payload.message.protocol);
-
-            if(!flipper_format_read_hex(
-                   fff_data_file, "address", (uint8_t*)&signal->payload.message.address, 4)) {
-                ACTION_SET_ERROR("IR: Failed to read address");
-                break;
-            }
-            if(!flipper_format_read_hex(
-                   fff_data_file, "command", (uint8_t*)&signal->payload.message.command, 4)) {
-                ACTION_SET_ERROR("IR: Failed to read command");
-                break;
-            }
-
-            FURI_LOG_I(
-                TAG,
-                "IR: Sending (%s) type=parsed => %s %lu %lu",
-                file_name,
-                infrared_get_protocol_name(signal->payload.message.protocol),
-                signal->payload.message.address,
-                signal->payload.message.command);
-
-            infrared_send(&signal->payload.message, 1);
-
-            FURI_LOG_I(TAG, "IR: Send complete");
-
-        } else if(!furi_string_cmp_str(temp_str, "raw")) {
-            // FURI_LOG_I(TAG, "IR File is RAW");
-            signal->is_raw = true;
-
-            if(!flipper_format_read_uint32(
-                   fff_data_file, "frequency", &signal->payload.raw.frequency, 1)) {
-                ACTION_SET_ERROR("IR: Failed to read frequency");
-                break;
-            }
-            if(!flipper_format_read_float(
-                   fff_data_file, "duty_cycle", &signal->payload.raw.duty_cycle, 1)) {
-                ACTION_SET_ERROR("IR: Failed to read duty cycle");
-                break;
-            }
-            if(!flipper_format_get_value_count(fff_data_file, "data", &temp_data32)) {
-                ACTION_SET_ERROR("IR: Failed to get size of data");
-                break;
-            }
-            if(temp_data32 > MAX_TIMINGS_AMOUNT) {
-                ACTION_SET_ERROR("IR: Data size exceeds limit");
-                break;
-            }
-            signal->payload.raw.timings_size = temp_data32;
-
-            signal->payload.raw.timings =
-                malloc(sizeof(uint32_t) * signal->payload.raw.timings_size);
-            if(!flipper_format_read_uint32(
-                   fff_data_file, "data", signal->payload.raw.timings, temp_data32)) {
-                ACTION_SET_ERROR("IR: Failed to read data");
-                break;
-            }
 
+        if(signal->is_raw) {
+            // raw
             FURI_LOG_I(
                 TAG,
                 "IR: Sending (%s) type=raw => %d timings, %lu Hz, %f",
@@ -178,13 +48,23 @@ void action_ir_tx(void* context, const FuriString* action_path, FuriString* erro
 
             FURI_LOG_I(TAG, "IR: Send complete");
         } else {
-            ACTION_SET_ERROR("IR: Unknown type: %s", furi_string_get_cstr(temp_str));
-            break;
+            //parsed
+            FURI_LOG_I(
+                TAG,
+                "IR: Sending (%s) type=parsed => %s %lu %lu",
+                file_name,
+                infrared_get_protocol_name(signal->payload.message.protocol),
+                signal->payload.message.address,
+                signal->payload.message.command);
+
+            infrared_send(&signal->payload.message, 1);
+
+            FURI_LOG_I(TAG, "IR: Send complete");
         }
 
     } while(false);
 
     furi_string_free(temp_str);
     flipper_format_free(fff_data_file);
-    infrared_signal_free(signal);
+    infrared_utils_signal_free(signal);
 }

+ 207 - 0
actions/action_ir_utils.c

@@ -0,0 +1,207 @@
+// Utility methods for IR transmission
+#include <furi.h>
+#include "../quac.h"
+
+#include <flipper_format/flipper_format.h>
+
+#include "action_ir_utils.h"
+
+InfraredSignal* infrared_utils_signal_alloc() {
+    InfraredSignal* signal = malloc(sizeof(InfraredSignal));
+    signal->is_raw = false;
+    signal->payload.message.protocol = InfraredProtocolUnknown;
+    return signal;
+}
+
+void infrared_utils_signal_free(InfraredSignal* signal) {
+    if(signal->is_raw) {
+        free(signal->payload.raw.timings);
+        signal->payload.raw.timings = NULL;
+    }
+    free(signal);
+}
+
+bool infrared_utils_read_signal_at_index(
+    FlipperFormat* fff_data_file,
+    uint32_t index,
+    InfraredSignal* signal,
+    FuriString* name) {
+    //
+    FuriString* temp_str;
+    temp_str = furi_string_alloc();
+    uint32_t temp_data32;
+    bool success = false;
+
+    do {
+        if(!flipper_format_read_header(fff_data_file, temp_str, &temp_data32)) {
+            FURI_LOG_E(TAG, "IR: Missing or incorrect header");
+            break;
+        }
+
+        if(!furi_string_cmp_str(temp_str, INFRARED_FILE_TYPE) &&
+           temp_data32 == INFRARED_FILE_VERSION) {
+        } else {
+            // ACTION_SET_ERROR("IR: File type or version mismatch");
+            FURI_LOG_E(TAG, "IR: File type or version mismatch");
+            break;
+        }
+
+        // Read the file until we find the signal we want
+        uint32_t i = 0;
+        bool found = false;
+        while(flipper_format_read_string(fff_data_file, "name", name)) {
+            if(i == index) {
+                found = true;
+                break;
+            }
+            ++i;
+        }
+        if(!found) {
+            // ACTION_SET_ERROR("IR: Could not find command %lu!", index);
+            FURI_LOG_E(TAG, "Requested IR command %lu not found", index);
+            furi_string_reset(name);
+            break;
+        }
+
+        FURI_LOG_I(TAG, "Reading signal %s", furi_string_get_cstr(temp_str));
+        if(!flipper_format_read_string(fff_data_file, "type", temp_str)) {
+            // ACTION_SET_ERROR("IR: Type missing");
+            break;
+        }
+        if(furi_string_equal(temp_str, "parsed")) {
+            FURI_LOG_I(TAG, "IR File is PARSED");
+            signal->is_raw = false;
+
+            if(!flipper_format_read_string(fff_data_file, "protocol", temp_str)) {
+                // ACTION_SET_ERROR("IR: Invalid or missing protocol");
+                break;
+            }
+            signal->payload.message.protocol =
+                infrared_get_protocol_by_name(furi_string_get_cstr(temp_str));
+            if(!infrared_is_protocol_valid(signal->payload.message.protocol)) {
+                // ACTION_SET_ERROR("IR: Invalid or unknown protocol");
+                break;
+            }
+
+            if(!flipper_format_read_hex(
+                   fff_data_file, "address", (uint8_t*)&signal->payload.message.address, 4)) {
+                // ACTION_SET_ERROR("IR: Failed to read address");
+                break;
+            }
+            if(!flipper_format_read_hex(
+                   fff_data_file, "command", (uint8_t*)&signal->payload.message.command, 4)) {
+                // ACTION_SET_ERROR("IR: Failed to read command");
+                break;
+            }
+            success = true;
+        } else if(furi_string_equal(temp_str, "raw")) {
+            FURI_LOG_I(TAG, "IR File is RAW");
+            signal->is_raw = true;
+
+            if(!flipper_format_read_uint32(
+                   fff_data_file, "frequency", &signal->payload.raw.frequency, 1)) {
+                // ACTION_SET_ERROR("IR: Failed to read frequency");
+                break;
+            }
+            if(!flipper_format_read_float(
+                   fff_data_file, "duty_cycle", &signal->payload.raw.duty_cycle, 1)) {
+                // ACTION_SET_ERROR("IR: Failed to read duty cycle");
+                break;
+            }
+            if(!flipper_format_get_value_count(fff_data_file, "data", &temp_data32)) {
+                // ACTION_SET_ERROR("IR: Failed to get size of data");
+                break;
+            }
+            if(temp_data32 > MAX_TIMINGS_AMOUNT) {
+                // ACTION_SET_ERROR("IR: Data size exceeds limit");
+                break;
+            }
+            signal->payload.raw.timings_size = temp_data32;
+
+            signal->payload.raw.timings =
+                malloc(sizeof(uint32_t) * signal->payload.raw.timings_size);
+            if(!flipper_format_read_uint32(
+                   fff_data_file, "data", signal->payload.raw.timings, temp_data32)) {
+                // ACTION_SET_ERROR("IR: Failed to read data");
+                free(signal->payload.raw.timings);
+                break;
+            }
+            success = true;
+        }
+    } while(false);
+
+    return success;
+}
+
+bool infrared_utils_write_signal(
+    FlipperFormat* fff_data_file,
+    InfraredSignal* signal,
+    FuriString* name) {
+    //
+    bool success = false;
+
+    do {
+        if(!flipper_format_write_header_cstr(
+               fff_data_file, INFRARED_FILE_TYPE, INFRARED_FILE_VERSION)) {
+            FURI_LOG_E(TAG, "Error writing header");
+            break;
+        }
+        if(!flipper_format_write_comment_cstr(fff_data_file, "")) {
+            FURI_LOG_E(TAG, "Error writing blank comment");
+            break;
+        }
+        if(!flipper_format_write_string(fff_data_file, "name", name)) {
+            FURI_LOG_E(TAG, "Error writing name");
+            break;
+        }
+        if(!flipper_format_write_string_cstr(
+               fff_data_file, "type", signal->is_raw ? "raw" : "parsed")) {
+            FURI_LOG_E(TAG, "Error writing type");
+            break;
+        }
+        if(signal->is_raw) {
+            // raw
+            if(!flipper_format_write_uint32(
+                   fff_data_file, "frequency", &signal->payload.raw.frequency, 1)) {
+                FURI_LOG_E(TAG, "Error writing frequency");
+                break;
+            }
+            if(!flipper_format_write_float(
+                   fff_data_file, "duty_cycle", &signal->payload.raw.duty_cycle, 1)) {
+                FURI_LOG_E(TAG, "Error writing duty_cycle");
+                break;
+            }
+            if(!flipper_format_write_uint32(
+                   fff_data_file,
+                   "data",
+                   signal->payload.raw.timings,
+                   signal->payload.raw.timings_size)) {
+                FURI_LOG_E(TAG, "Error writing data");
+                break;
+            }
+            success = true;
+        } else {
+            // parsed
+            if(!flipper_format_write_string_cstr(
+                   fff_data_file,
+                   "protocol",
+                   infrared_get_protocol_name(signal->payload.message.protocol))) {
+                FURI_LOG_E(TAG, "Error writing protocol");
+                break;
+            }
+            if(!flipper_format_write_hex(
+                   fff_data_file, "address", (uint8_t*)&signal->payload.message.address, 4)) {
+                FURI_LOG_E(TAG, "Error writing address");
+                break;
+            }
+            if(!flipper_format_write_hex(
+                   fff_data_file, "command", (uint8_t*)&signal->payload.message.command, 4)) {
+                FURI_LOG_E(TAG, "Error writing command");
+                break;
+            }
+            success = true;
+        }
+    } while(false);
+
+    return success;
+}

+ 38 - 0
actions/action_ir_utils.h

@@ -0,0 +1,38 @@
+#include <furi.h>
+// infrared
+#include <infrared.h>
+#include <infrared/encoder_decoder/infrared.h>
+#include <infrared/worker/infrared_transmit.h>
+#include <infrared/worker/infrared_worker.h>
+
+#include <flipper_format/flipper_format.h>
+
+#define INFRARED_FILE_TYPE "IR signals file"
+#define INFRARED_FILE_VERSION 1
+
+typedef struct {
+    size_t timings_size; /**< Number of elements in the timings array. */
+    uint32_t* timings; /**< Pointer to an array of timings describing the signal. */
+    uint32_t frequency; /**< Carrier frequency of the signal. */
+    float duty_cycle; /**< Duty cycle of the signal. */
+} InfraredRawSignal;
+
+typedef struct InfraredSignal {
+    bool is_raw;
+    union {
+        InfraredMessage message; // protocol, address, command, repeat
+        InfraredRawSignal raw;
+    } payload;
+} InfraredSignal;
+
+InfraredSignal* infrared_utils_signal_alloc();
+
+void infrared_utils_signal_free(InfraredSignal* signal);
+
+bool infrared_utils_read_signal_at_index(
+    FlipperFormat* fffile,
+    uint32_t index,
+    InfraredSignal* signal,
+    FuriString* name);
+
+bool infrared_utils_write_signal(FlipperFormat* fffile, InfraredSignal* signal, FuriString* name);

+ 4 - 5
quac.c

@@ -32,12 +32,11 @@ App* app_alloc() {
 
     // Misc interfaces
     app->sub_menu = submenu_alloc();
-    view_dispatcher_add_view(
-        app->view_dispatcher, QView_ActionSettings, submenu_get_view(app->sub_menu));
+    view_dispatcher_add_view(app->view_dispatcher, QView_SubMenu, submenu_get_view(app->sub_menu));
 
     app->text_input = text_input_alloc();
     view_dispatcher_add_view(
-        app->view_dispatcher, QView_ActionTextInput, text_input_get_view(app->text_input));
+        app->view_dispatcher, QView_TextInput, text_input_get_view(app->text_input));
 
     app->popup = popup_alloc();
     view_dispatcher_add_view(app->view_dispatcher, QView_Popup, popup_get_view(app->popup));
@@ -66,8 +65,8 @@ void app_free(App* app) {
 
     view_dispatcher_remove_view(app->view_dispatcher, QView_ActionMenu);
     view_dispatcher_remove_view(app->view_dispatcher, QView_Settings);
-    view_dispatcher_remove_view(app->view_dispatcher, QView_ActionSettings);
-    view_dispatcher_remove_view(app->view_dispatcher, QView_ActionTextInput);
+    view_dispatcher_remove_view(app->view_dispatcher, QView_SubMenu);
+    view_dispatcher_remove_view(app->view_dispatcher, QView_TextInput);
     view_dispatcher_remove_view(app->view_dispatcher, QView_Popup);
 
     action_menu_free(app->action_menu);

+ 1 - 1
scenes/scene_action_create_group.c

@@ -33,7 +33,7 @@ void scene_action_create_group_on_enter(void* context) {
     // TextInputValidatorCallback
     // text_input_set_validator(text, validator_callback, context)
 
-    view_dispatcher_switch_to_view(app->view_dispatcher, QView_ActionTextInput);
+    view_dispatcher_switch_to_view(app->view_dispatcher, QView_TextInput);
 }
 
 bool scene_action_create_group_on_event(void* context, SceneManagerEvent event) {

+ 116 - 0
scenes/scene_action_ir_list.c

@@ -0,0 +1,116 @@
+#include <furi.h>
+
+#include <gui/view_dispatcher.h>
+#include <gui/scene_manager.h>
+#include <gui/modules/submenu.h>
+#include <lib/toolbox/path.h>
+
+#include "quac.h"
+#include "scenes.h"
+#include "scene_action_ir_list.h"
+#include "../actions/action.h"
+#include "../actions/action_ir_utils.h"
+#include "quac_icons.h"
+
+#include <flipper_format/flipper_format.h>
+
+void scene_action_ir_list_callback(void* context, uint32_t index) {
+    App* app = context;
+    view_dispatcher_send_custom_event(app->view_dispatcher, index);
+}
+
+void scene_action_ir_list_on_enter(void* context) {
+    App* app = context;
+
+    Submenu* menu = app->sub_menu;
+    submenu_reset(menu);
+
+    // Our selected IR File is app->temp_str
+    submenu_set_header(menu, "Select IR Command");
+
+    // read the IR file and load the names of all of the commands
+    FuriString* name = furi_string_alloc();
+
+    uint32_t index = 0;
+    FlipperFormat* fff_data_file = flipper_format_file_alloc(app->storage);
+    if(flipper_format_file_open_existing(fff_data_file, furi_string_get_cstr(app->temp_str))) {
+        while(flipper_format_read_string(fff_data_file, "name", name)) {
+            submenu_add_item(
+                menu, furi_string_get_cstr(name), index, scene_action_ir_list_callback, app);
+            index++;
+        }
+    }
+
+    if(index == 0) {
+        FURI_LOG_E(TAG, "Failed to get commands from %s", furi_string_get_cstr(app->temp_str));
+    }
+
+    flipper_format_file_close(fff_data_file);
+    flipper_format_free(fff_data_file);
+    furi_string_free(name);
+
+    view_dispatcher_switch_to_view(app->view_dispatcher, QView_SubMenu);
+}
+
+bool scene_action_ir_list_on_event(void* context, SceneManagerEvent event) {
+    App* app = context;
+    bool consumed = false;
+    if(event.type == SceneManagerEventTypeCustom) {
+        consumed = true;
+        uint32_t index = event.event;
+        InfraredSignal* signal = infrared_utils_signal_alloc();
+
+        // extract that item as it's own file and place it "here", as defined by
+        // the currently selected_item
+        FuriString* name = furi_string_alloc(); // IR command name
+        FlipperFormat* fff_data_file = flipper_format_file_alloc(app->storage);
+        FuriString* file_name = furi_string_alloc(); // new IR file name
+
+        do {
+            if(!flipper_format_file_open_existing(
+                   fff_data_file, furi_string_get_cstr(app->temp_str))) {
+                FURI_LOG_E(TAG, "Failed to open %s", furi_string_get_cstr(app->temp_str));
+                break;
+            }
+            if(!infrared_utils_read_signal_at_index(fff_data_file, index, signal, name)) {
+                FURI_LOG_E(TAG, "Failed to read signal at %lu", index);
+                break;
+            }
+            FURI_LOG_I(TAG, "Read IR signal: %s", furi_string_get_cstr(name));
+            flipper_format_file_close(fff_data_file);
+
+            // generate the new path, based on current item's dir and new command name
+            Item* item = ItemArray_get(app->items_view->items, app->selected_item);
+            path_extract_dirname(furi_string_get_cstr(item->path), file_name);
+            furi_string_cat_printf(file_name, "/%s.ir", furi_string_get_cstr(name));
+
+            FURI_LOG_I(TAG, "Writing new IR file: %s", furi_string_get_cstr(file_name));
+            if(!flipper_format_file_open_new(fff_data_file, furi_string_get_cstr(file_name))) {
+                FURI_LOG_E(TAG, "Error creating new file: %s", furi_string_get_cstr(file_name));
+                break;
+            }
+            if(!infrared_utils_write_signal(fff_data_file, signal, name)) {
+                FURI_LOG_E(TAG, "Failed to write signal!");
+                break;
+            }
+
+            // Import successful
+            // TODO: give user an OK prompt and leave them on this scene
+            // in case they want to import more from this IR file
+
+        } while(false);
+
+        // cleanup
+        flipper_format_file_close(fff_data_file);
+        flipper_format_free(fff_data_file);
+        furi_string_free(name);
+        furi_string_free(file_name);
+        infrared_utils_signal_free(signal);
+    }
+    return consumed;
+}
+
+void scene_action_ir_list_on_exit(void* context) {
+    App* app = context;
+    submenu_reset(app->sub_menu);
+}

+ 8 - 0
scenes/scene_action_ir_list.h

@@ -0,0 +1,8 @@
+#pragma once
+
+#include <gui/scene_manager.h>
+
+// For each scene, implement handler callbacks
+void scene_action_ir_list_on_enter(void* context);
+bool scene_action_ir_list_on_event(void* context, SceneManagerEvent event);
+void scene_action_ir_list_on_exit(void* context);

+ 1 - 1
scenes/scene_action_rename.c

@@ -36,7 +36,7 @@ void scene_action_rename_on_enter(void* context) {
         text, scene_action_rename_callback, app, app->temp_cstr, MAX_NAME_LEN, false);
 
     furi_string_free(file_name);
-    view_dispatcher_switch_to_view(app->view_dispatcher, QView_ActionTextInput);
+    view_dispatcher_switch_to_view(app->view_dispatcher, QView_TextInput);
 }
 
 bool scene_action_rename_on_event(void* context, SceneManagerEvent event) {

+ 89 - 2
scenes/scene_action_settings.c

@@ -91,6 +91,42 @@ static bool scene_action_settings_import_file_browser_callback(
     return true;
 }
 
+// Ask user for file to import from elsewhere on the SD card
+FuriString* scene_action_get_file_to_import_alloc(App* app) {
+    FuriString* current_path = furi_string_alloc();
+    if(app->selected_item != EMPTY_ACTION_INDEX) {
+        Item* item = ItemArray_get(app->items_view->items, app->selected_item);
+        path_extract_dirname(furi_string_get_cstr(item->path), current_path);
+    } else {
+        furi_string_set(current_path, app->items_view->path);
+    }
+
+    // Setup our file browser options
+    DialogsFileBrowserOptions fb_options;
+    dialog_file_browser_set_basic_options(&fb_options, "", NULL);
+    fb_options.base_path = furi_string_get_cstr(current_path);
+    fb_options.skip_assets = true;
+    furi_string_set_str(app->temp_str, fb_options.base_path);
+    fb_options.item_loader_callback = scene_action_settings_import_file_browser_callback;
+    fb_options.item_loader_context = app;
+
+    FuriString* full_path = NULL;
+    if(dialog_file_browser_show(app->dialog, app->temp_str, app->temp_str, &fb_options)) {
+        // FURI_LOG_I(TAG, "Selected file is %s", furi_string_get_cstr(app->temp_str));
+        FuriString* file_name = furi_string_alloc();
+        path_extract_filename(app->temp_str, file_name, false);
+        // FURI_LOG_I(TAG, "Importing file %s", furi_string_get_cstr(file_name));
+        full_path = furi_string_alloc_printf(
+            "%s/%s", furi_string_get_cstr(current_path), furi_string_get_cstr(file_name));
+        // FURI_LOG_I(TAG, "New path is %s", furi_string_get_cstr(full_path));
+        furi_string_free(file_name);
+    } else {
+        // FURI_LOG_I(TAG, "User cancelled");
+    }
+    furi_string_free(current_path);
+    return full_path;
+}
+
 // Import a file from elsewhere on the SD card
 // Update items_view list before returning so that UI is updated and correct
 bool scene_action_settings_import(App* app) {
@@ -114,6 +150,7 @@ bool scene_action_settings_import(App* app) {
 
     if(dialog_file_browser_show(app->dialog, app->temp_str, app->temp_str, &fb_options)) {
         // FURI_LOG_I(TAG, "Selected file is %s", furi_string_get_cstr(app->temp_str));
+        // TODO: this should be a method
         FuriString* file_name = furi_string_alloc();
         path_extract_filename(app->temp_str, file_name, false);
         // FURI_LOG_I(TAG, "Importing file %s", furi_string_get_cstr(file_name));
@@ -182,7 +219,7 @@ void scene_action_settings_on_enter(void* context) {
     submenu_add_item(
         menu, "Create Group", ActionSettingsCreateGroup, scene_action_settings_callback, app);
 
-    view_dispatcher_switch_to_view(app->view_dispatcher, QView_ActionSettings);
+    view_dispatcher_switch_to_view(app->view_dispatcher, QView_SubMenu);
 }
 
 bool scene_action_settings_on_event(void* context, SceneManagerEvent event) {
@@ -202,9 +239,59 @@ bool scene_action_settings_on_event(void* context, SceneManagerEvent event) {
             break;
         case ActionSettingsImport:
             consumed = true;
-            if(scene_action_settings_import(app)) {
+            // get the filename to import
+            FuriString* import_file = scene_action_get_file_to_import_alloc(app);
+            FURI_LOG_I(TAG, "Importing %s", furi_string_get_cstr(import_file));
+            if(import_file) {
+                // if it's a .ir file, switch to a scene that lets user pick the command from the file
+                // only if there's more than one command in the file. then copy that relevant chunk
+                // to the local directory
+                char ext[MAX_EXT_LEN] = {0};
+
+                path_extract_extension(import_file, ext, MAX_EXT_LEN);
+                if(!strcmp(ext, ".ir")) {
+                    FURI_LOG_I(TAG, "Loading ir file %s", furi_string_get_cstr(app->temp_str));
+                    // load scene that takes filename and lists all commands
+                    // the scene should write the new file, eh?
+                    scene_manager_next_scene(app->scene_manager, QScene_ActionIRList);
+                } else {
+                    // just copy the file here
+                    FuriString* current_path = furi_string_alloc();
+                    if(app->selected_item != EMPTY_ACTION_INDEX) {
+                        Item* item = ItemArray_get(app->items_view->items, app->selected_item);
+                        path_extract_dirname(furi_string_get_cstr(item->path), current_path);
+                    } else {
+                        furi_string_set(current_path, app->items_view->path);
+                    }
+                    // TODO: this should be a method
+                    FuriString* file_name = furi_string_alloc();
+                    path_extract_filename(import_file, file_name, false);
+                    // FURI_LOG_I(TAG, "Importing file %s", furi_string_get_cstr(file_name));
+                    FuriString* full_path;
+                    full_path = furi_string_alloc_printf(
+                        "%s/%s",
+                        furi_string_get_cstr(current_path),
+                        furi_string_get_cstr(file_name));
+                    // FURI_LOG_I(TAG, "New path is %s", furi_string_get_cstr(full_path));
+
+                    FS_Error fs_result = storage_common_copy(
+                        app->storage,
+                        furi_string_get_cstr(import_file),
+                        furi_string_get_cstr(full_path));
+                    if(fs_result == FSE_OK) {
+                    } else {
+                    }
+                    furi_string_free(file_name);
+                    furi_string_free(full_path);
+                }
+                furi_string_free(import_file);
+            } else {
                 scene_manager_previous_scene(app->scene_manager);
             }
+
+            // if(scene_action_settings_import(app)) {
+            //     scene_manager_previous_scene(app->scene_manager);
+            // }
             break;
         case ActionSettingsCreateGroup:
             consumed = true;

+ 4 - 0
scenes/scenes.c

@@ -7,6 +7,7 @@
 #include "scene_action_settings.h"
 #include "scene_action_rename.h"
 #include "scene_action_create_group.h"
+#include "scene_action_ir_list.h"
 #include "scene_about.h"
 
 // define handler callbacks - order must match appScenes enum!
@@ -16,6 +17,7 @@ void (*const app_on_enter_handlers[])(void* context) = {
     scene_action_settings_on_enter,
     scene_action_rename_on_enter,
     scene_action_create_group_on_enter,
+    scene_action_ir_list_on_enter,
     scene_about_on_enter,
 };
 bool (*const app_on_event_handlers[])(void* context, SceneManagerEvent event) = {
@@ -24,6 +26,7 @@ bool (*const app_on_event_handlers[])(void* context, SceneManagerEvent event) =
     scene_action_settings_on_event,
     scene_action_rename_on_event,
     scene_action_create_group_on_event,
+    scene_action_ir_list_on_event,
     scene_about_on_event,
 };
 void (*const app_on_exit_handlers[])(void* context) = {
@@ -32,6 +35,7 @@ void (*const app_on_exit_handlers[])(void* context) = {
     scene_action_settings_on_exit,
     scene_action_rename_on_exit,
     scene_action_create_group_on_exit,
+    scene_action_ir_list_on_exit,
     scene_about_on_exit,
 };
 

+ 3 - 2
scenes/scenes.h

@@ -6,6 +6,7 @@ typedef enum {
     QScene_ActionSettings,
     QScene_ActionRename,
     QScene_ActionCreateGroup,
+    QScene_ActionIRList,
     QScene_About,
     QScene_count
 } appScenes;
@@ -13,8 +14,8 @@ typedef enum {
 typedef enum {
     QView_ActionMenu, // new UI,
     QView_Settings, // Variable Item List for settings
-    QView_ActionSettings, // [SubMenu] Action: Rename, Delete, Import (copies from elsewhere)
-    QView_ActionTextInput, // Action: Rename, Create Group
+    QView_SubMenu, // [SubMenu] Action: Rename, Delete, Import, IR List
+    QView_TextInput, // Action: Rename, Create Group
     QView_Popup, // About screen
 } appView;