Просмотр исходного кода

mag_device overhaul, file loading

File loading working
Some mag.c functions destined for deprecation
Misc small changes
Under construction placeholder scene
Zachary Weiss 3 лет назад
Родитель
Сommit
8f642dfaa6

+ 2 - 2
application.fam

@@ -13,7 +13,7 @@ App(
     provides=[],
     stack_size=2 * 1024,
     order=20,
-    fap_icon="mag_10px.png",
+    fap_icon="icons/mag_10px.png",
     fap_category="Tools",
     fap_icon_assets="icons",
-)
+)

+ 39 - 13
mag.c

@@ -1,5 +1,7 @@
 #include "mag_i.h"
 
+#define TAG "Mag"
+
 static bool mag_debug_custom_event_callback(void* context, uint32_t event) {
     furi_assert(context);
     Mag* mag = context;
@@ -30,6 +32,8 @@ static Mag* mag_alloc() {
     view_dispatcher_set_navigation_event_callback(
         mag->view_dispatcher, mag_debug_back_event_callback);
 
+    mag->mag_dev = mag_device_alloc();
+
     // Open GUI record
     mag->gui = furi_record_open(RECORD_GUI);
 
@@ -49,6 +53,10 @@ static Mag* mag_alloc() {
     mag->popup = popup_alloc();
     view_dispatcher_add_view(mag->view_dispatcher, MagViewPopup, popup_get_view(mag->popup));
 
+    // Loading
+    mag->loading = loading_alloc();
+    view_dispatcher_add_view(mag->view_dispatcher, MagViewLoading, loading_get_view(mag->loading));
+
     // Widget
     mag->widget = widget_alloc();
     view_dispatcher_add_view(mag->view_dispatcher, MagViewWidget, widget_get_view(mag->widget));
@@ -58,11 +66,6 @@ static Mag* mag_alloc() {
     view_dispatcher_add_view(
         mag->view_dispatcher, MagViewTextInput, text_input_get_view(mag->text_input));
 
-    // Byte Input
-    mag->byte_input = byte_input_alloc();
-    view_dispatcher_add_view(
-        mag->view_dispatcher, MagViewByteInput, byte_input_get_view(mag->byte_input));
-
     return mag;
 }
 
@@ -72,6 +75,10 @@ static void mag_free(Mag* mag) {
     furi_string_free(mag->file_name);
     furi_string_free(mag->file_path);
 
+    // Mag device
+    mag_device_free(mag->mag_dev);
+    mag->mag_dev = NULL;
+
     // Submenu
     view_dispatcher_remove_view(mag->view_dispatcher, MagViewSubmenu);
     submenu_free(mag->submenu);
@@ -84,6 +91,10 @@ static void mag_free(Mag* mag) {
     view_dispatcher_remove_view(mag->view_dispatcher, MagViewPopup);
     popup_free(mag->popup);
 
+    // Loading
+    view_dispatcher_remove_view(mag->view_dispatcher, MagViewLoading);
+    loading_free(mag->loading);
+
     // Widget
     view_dispatcher_remove_view(mag->view_dispatcher, MagViewWidget);
     widget_free(mag->widget);
@@ -92,10 +103,6 @@ static void mag_free(Mag* mag) {
     view_dispatcher_remove_view(mag->view_dispatcher, MagViewTextInput);
     text_input_free(mag->text_input);
 
-    // ByteInput
-    view_dispatcher_remove_view(mag->view_dispatcher, MagViewByteInput);
-    byte_input_free(mag->byte_input);
-
     // View Dispatcher
     view_dispatcher_free(mag->view_dispatcher);
 
@@ -118,18 +125,24 @@ static void mag_free(Mag* mag) {
 
 // entry point for app
 int32_t mag_app(void* p) {
+    FURI_LOG_D(TAG, "Start");
     Mag* mag = mag_alloc();
-    char* args = p;
-    UNUSED(args);
+    FURI_LOG_D(TAG, "Mag alloc-ed");
+    UNUSED(p);
 
     mag_make_app_folder(mag);
+    FURI_LOG_D(TAG, "mag_make_app_folder done");
 
     view_dispatcher_attach_to_gui(mag->view_dispatcher, mag->gui, ViewDispatcherTypeFullscreen);
+    FURI_LOG_D(TAG, "view_dispatcher_attach... done");
     scene_manager_next_scene(mag->scene_manager, MagSceneStart);
+    FURI_LOG_D(TAG, "scene manager next scene done");
 
     view_dispatcher_run(mag->view_dispatcher);
+    FURI_LOG_D(TAG, "view dispatcher run done");
 
     mag_free(mag);
+    FURI_LOG_D(TAG, "mag free done");
 
     return 0;
 }
@@ -157,8 +170,7 @@ bool mag_load_key_from_file_select(Mag* mag) {
     furi_assert(mag);
 
     DialogsFileBrowserOptions browser_options;
-    // TODO: Fix icon reference / definition! Temporarily importing asset_icons.h in mag_i.h to let it compile. Remove when fixed!
-    dialog_file_browser_set_basic_options(&browser_options, MAG_APP_EXTENSION, &I_125_10px);
+    dialog_file_browser_set_basic_options(&browser_options, MAG_APP_EXTENSION, &I_mag_10px);
     browser_options.base_path = MAG_APP_FOLDER;
 
     // Input events and views are managed by file_browser
@@ -241,3 +253,17 @@ void mag_text_input_callback(void* context) {
     Mag* mag = context;
     view_dispatcher_send_custom_event(mag->view_dispatcher, MagEventNext);
 }
+
+void mag_show_loading_popup(void* context, bool show) {
+    Mag* mag = context;
+    TaskHandle_t timer_task = xTaskGetHandle(configTIMER_SERVICE_TASK_NAME);
+
+    if(show) {
+        // Raise timer priority so that animations can play
+        vTaskPrioritySet(timer_task, configMAX_PRIORITIES - 1);
+        view_dispatcher_switch_to_view(mag->view_dispatcher, MagViewLoading);
+    } else {
+        // Restore default timer priority
+        vTaskPrioritySet(timer_task, configTIMER_TASK_PRIORITY);
+    }
+}


+ 216 - 2
mag_device.c

@@ -3,5 +3,219 @@
 #include <toolbox/path.h>
 #include <flipper_format/flipper_format.h>
 
-//static const char* mag_file_header = "Flipper Mag device";
-//static const uint32_t mag_file_version = 1;
+#define TAG "MagDevice"
+
+static const char* mag_file_header = "Flipper Mag device";
+static const uint32_t mag_file_version = 0;
+
+MagDevice* mag_device_alloc() {
+    MagDevice* mag_dev = malloc(sizeof(MagDevice));
+    mag_dev->dev_data = furi_string_alloc();
+    mag_dev->storage = furi_record_open(RECORD_STORAGE);
+    mag_dev->dialogs = furi_record_open(RECORD_DIALOGS);
+    mag_dev->load_path = furi_string_alloc();
+    return mag_dev;
+}
+
+void mag_device_data_clear(FuriString* dev_data) {
+    furi_string_reset(dev_data);
+}
+
+void mag_device_clear(MagDevice* mag_dev) {
+    furi_assert(mag_dev);
+
+    mag_device_data_clear(mag_dev->dev_data);
+    memset(&mag_dev->dev_data, 0, sizeof(mag_dev->dev_data));
+    furi_string_reset(mag_dev->load_path);
+}
+
+void mag_device_free(MagDevice* mag_dev) {
+    furi_assert(mag_dev);
+
+    mag_device_clear(mag_dev);
+    furi_record_close(RECORD_STORAGE);
+    furi_record_close(RECORD_DIALOGS);
+    furi_string_free(mag_dev->load_path);
+    free(mag_dev);
+}
+
+void mag_device_set_name(MagDevice* mag_dev, const char* name) {
+    furi_assert(mag_dev);
+
+    strlcpy(mag_dev->dev_name, name, MAG_DEV_NAME_MAX_LEN);
+}
+
+static bool mag_device_save_file(
+    MagDevice* mag_dev,
+    const char* dev_name,
+    const char* folder,
+    const char* extension,
+    bool use_load_path) {
+    furi_assert(mag_dev);
+
+    bool saved = false;
+    FlipperFormat* file = flipper_format_file_alloc(mag_dev->storage);
+    FuriString* temp_str;
+    temp_str = furi_string_alloc();
+
+    do {
+        if(use_load_path && !furi_string_empty(mag_dev->load_path)) {
+            // Get dir name
+            path_extract_dirname(furi_string_get_cstr(mag_dev->load_path), temp_str);
+            // Create mag directory if necessary
+            if(!storage_simply_mkdir((mag_dev->storage), furi_string_get_cstr(temp_str))) break;
+            // Make path to file to be saved
+            furi_string_cat_printf(temp_str, "/%s%s", dev_name, extension);
+        } else {
+            // Create mag directory if necessary
+            if(!storage_simply_mkdir((mag_dev->storage), MAG_APP_FOLDER)) break;
+            // First remove mag device file if it was saved
+            furi_string_printf(temp_str, "%s/%s%s", folder, dev_name, extension);
+        }
+        // Open file
+        if(!flipper_format_file_open_always(file, furi_string_get_cstr(temp_str))) break;
+
+        // Write header
+        if(!flipper_format_write_header_cstr(file, mag_file_header, mag_file_version)) break;
+
+        // Write comment
+        if(!flipper_format_write_comment_cstr(file, "Mag device track data")) break;
+
+        // Write data
+        if(!flipper_format_write_string_cstr(file, "Data", furi_string_get_cstr(mag_dev->dev_data)))
+            break;
+
+        saved = true;
+    } while(0);
+
+    if(!saved) {
+        dialog_message_show_storage_error(mag_dev->dialogs, "Cannot save\nfile");
+    }
+
+    furi_string_free(temp_str);
+    flipper_format_free(file);
+
+    // TODO. Extrapolating from the picopass app
+    return saved;
+}
+
+bool mag_device_save(MagDevice* mag_dev, const char* dev_name) {
+    // wrapping function in the event we have multiple formats
+    return mag_device_save_file(mag_dev, dev_name, MAG_APP_FOLDER, MAG_APP_EXTENSION, true);
+}
+
+static bool mag_device_load_data(MagDevice* mag_dev, FuriString* path, bool show_dialog) {
+    bool parsed = false;
+
+    FlipperFormat* file = flipper_format_file_alloc(mag_dev->storage);
+    FuriString* temp_str;
+    temp_str = furi_string_alloc();
+    bool deprecated_version = false;
+    bool data_read = true;
+
+    if(mag_dev->loading_cb) {
+        mag_dev->loading_cb(mag_dev->loading_cb_ctx, true);
+    }
+
+    do {
+        if(!flipper_format_file_open_existing(file, furi_string_get_cstr(path))) break;
+
+        // Read and verify header, check file version
+        uint32_t version;
+        if(!flipper_format_read_header(file, temp_str, &version)) break;
+        if(furi_string_cmp_str(temp_str, mag_file_header) || (version != mag_file_version)) {
+            deprecated_version = true;
+            break;
+        }
+
+        // Parse data
+        if(!flipper_format_read_string(file, "Data", mag_dev->dev_data)) {
+            data_read = false;
+            break;
+        }
+
+        parsed = true;
+    } while(false);
+
+    if((!parsed) && (show_dialog)) {
+        if(deprecated_version) {
+            dialog_message_show_storage_error(mag_dev->dialogs, "File format\ndeprecated");
+        } else if(!data_read) {
+            dialog_message_show_storage_error(mag_dev->dialogs, "Cannot read\ndata");
+        } else {
+            dialog_message_show_storage_error(mag_dev->dialogs, "Cannot parse\nfile");
+        }
+    }
+
+    furi_string_free(temp_str);
+    flipper_format_free(file);
+
+    return parsed;
+}
+
+bool mag_file_select(MagDevice* mag_dev) {
+    furi_assert(mag_dev);
+
+    // Input events and views are managed by file_browser
+    FuriString* mag_app_folder;
+    mag_app_folder = furi_string_alloc_set(MAG_APP_FOLDER);
+
+    DialogsFileBrowserOptions browser_options;
+    dialog_file_browser_set_basic_options(&browser_options, MAG_APP_EXTENSION, &I_mag_10px);
+    browser_options.base_path = MAG_APP_FOLDER;
+
+    bool res = dialog_file_browser_show(
+        mag_dev->dialogs, mag_dev->load_path, mag_app_folder, &browser_options);
+
+    furi_string_free(mag_app_folder);
+    if(res) {
+        FuriString* filename;
+        filename = furi_string_alloc();
+        path_extract_filename(mag_dev->load_path, filename, true);
+        strncpy(mag_dev->dev_name, furi_string_get_cstr(filename), MAG_DEV_NAME_MAX_LEN);
+        res = mag_device_load_data(mag_dev, mag_dev->load_path, true);
+        if(res) {
+            mag_device_set_name(mag_dev, mag_dev->dev_name);
+        }
+        furi_string_free(filename);
+    }
+
+    return res;
+}
+
+bool mag_device_delete(MagDevice* mag_dev, bool use_load_path) {
+    furi_assert(mag_dev);
+
+    bool deleted = false;
+    FuriString* file_path;
+    file_path = furi_string_alloc();
+
+    do {
+        // Delete original file
+        if(use_load_path && !furi_string_empty(mag_dev->load_path)) {
+            furi_string_set(file_path, mag_dev->load_path);
+        } else {
+            furi_string_printf(
+                file_path, "%s/%s%s", MAG_APP_FOLDER, mag_dev->dev_name, MAG_APP_EXTENSION);
+        }
+        if(!storage_simply_remove(mag_dev->storage, furi_string_get_cstr(file_path))) break;
+        deleted = true;
+    } while(false);
+
+    if(!deleted) {
+        dialog_message_show_storage_error(mag_dev->dialogs, "Cannot remove\nfile");
+    }
+
+    furi_string_free(file_path);
+    return deleted;
+}
+
+void mag_device_set_loading_callback(
+    MagDevice* mag_dev,
+    MagLoadingCallback callback,
+    void* context) {
+    furi_assert(mag_dev);
+
+    mag_dev->loading_cb = callback;
+    mag_dev->loading_cb_ctx = context;
+}

+ 40 - 0
mag_device.h

@@ -6,3 +6,43 @@
 #include <dialogs/dialogs.h>
 
 #include "mag_icons.h"
+
+#define MAG_DEV_NAME_MAX_LEN 22
+
+#define MAG_APP_FOLDER ANY_PATH("mag")
+#define MAG_APP_EXTENSION ".mag"
+
+typedef void (*MagLoadingCallback)(void* context, bool state);
+
+typedef struct MagDevice MagDevice;
+
+struct MagDevice {
+    Storage* storage;
+    DialogsApp* dialogs;
+    FuriString* dev_data;
+    char dev_name[MAG_DEV_NAME_MAX_LEN + 1];
+    FuriString* load_path;
+    MagLoadingCallback loading_cb;
+    void* loading_cb_ctx;
+};
+
+MagDevice* mag_device_alloc();
+
+void mag_device_free(MagDevice* mag_dev);
+
+void mag_device_set_name(MagDevice* mag_dev, const char* name);
+
+bool mag_device_save(MagDevice* mag_dev, const char* dev_name);
+
+bool mag_file_select(MagDevice* mag_dev);
+
+void mag_device_data_clear(FuriString* dev_data);
+
+void mag_device_clear(MagDevice* mag_dev);
+
+bool mag_device_delete(MagDevice* mag_dev, bool use_load_path);
+
+void mag_device_set_loading_callback(
+    MagDevice* mag_dev,
+    MagLoadingCallback callback,
+    void* context);

+ 16 - 16
mag_i.h

@@ -1,11 +1,12 @@
 #pragma once
 
+#include "mag_device.h"
+
 #include <furi.h>
 #include <furi_hal.h>
 
 #include <gui/gui.h>
 #include <gui/view.h>
-#include <assets_icons.h>
 #include <gui/view_dispatcher.h>
 #include <gui/scene_manager.h>
 #include <notification/notification_messages.h>
@@ -13,8 +14,8 @@
 #include <gui/modules/submenu.h>
 #include <gui/modules/dialog_ex.h>
 #include <gui/modules/popup.h>
+#include <gui/modules/loading.h>
 #include <gui/modules/text_input.h>
-#include <gui/modules/byte_input.h>
 #include <gui/modules/widget.h>
 
 #include <notification/notification_messages.h>
@@ -26,13 +27,7 @@
 
 #include "scenes/mag_scene.h"
 
-#define MAG_KEY_NAME_SIZE 22
-#define MAG_TEXT_STORE_SIZE 40
-
-#define MAG_APP_FOLDER ANY_PATH("mag")
-#define MAG_SD_FOLDER EXT_PATH("mag")
-#define MAG_APP_EXTENSION ".mag"
-#define MAG_APP_SHADOW_EXTENSION ".shd"
+#define MAG_TEXT_STORE_SIZE 128
 
 enum MagCustomEvent {
     MagEventNext = 100,
@@ -49,7 +44,7 @@ struct Mag {
     SceneManager* scene_manager;
     Storage* storage;
     DialogsApp* dialogs;
-    Widget* widget;
+    MagDevice* mag_dev;
 
     char text_store[MAG_TEXT_STORE_SIZE + 1];
     FuriString* file_path;
@@ -59,8 +54,9 @@ struct Mag {
     Submenu* submenu;
     DialogEx* dialog_ex;
     Popup* popup;
+    Loading* loading;
     TextInput* text_input;
-    ByteInput* byte_input;
+    Widget* widget;
 
     // Custom views?
 };
@@ -69,11 +65,19 @@ typedef enum {
     MagViewSubmenu,
     MagViewDialogEx,
     MagViewPopup,
+    MagViewLoading,
     MagViewWidget,
     MagViewTextInput,
-    MagViewByteInput,
 } MagView;
 
+void mag_text_store_set(Mag* mag, const char* text, ...);
+
+void mag_text_store_clear(Mag* mag);
+
+void mag_show_loading_popup(void* context, bool show);
+
+// all below this comment are destined for deprecation (now handled by mag_device)
+
 bool mag_save_key(Mag* mag);
 
 bool mag_load_key_from_file_select(Mag* mag);
@@ -86,10 +90,6 @@ bool mag_save_key_data(Mag* mag, FuriString* path);
 
 void mag_make_app_folder(Mag* mag);
 
-void mag_text_store_set(Mag* mag, const char* text, ...);
-
-void mag_text_store_clear(Mag* mag);
-
 void mag_popup_timeout_callback(void* context);
 
 void mag_widget_callback(GuiButtonType result, InputType type, void* context);

+ 3 - 2
scenes/mag_scene_config.h

@@ -1,7 +1,7 @@
 ADD_SCENE(mag, start, Start)
 ADD_SCENE(mag, emulate_test, EmulateTest)
-ADD_SCENE(mag, select_key, SelectKey)
-ADD_SCENE(mag, saved_key_menu, SavedKeyMenu)
+ADD_SCENE(mag, file_select, FileSelect)
+ADD_SCENE(mag, saved_menu, SavedMenu)
 ADD_SCENE(mag, saved_info, SavedInfo)
 ADD_SCENE(mag, save_data, SaveData)
 ADD_SCENE(mag, save_name, SaveName)
@@ -9,3 +9,4 @@ ADD_SCENE(mag, save_success, SaveSuccess)
 ADD_SCENE(mag, delete_success, DeleteSuccess)
 ADD_SCENE(mag, delete_confirm, DeleteConfirm)
 ADD_SCENE(mag, exit_confirm, ExitConfirm)
+ADD_SCENE(mag, under_construction, UnderConstruction)

+ 1 - 1
scenes/mag_scene_emulate_test.c

@@ -186,7 +186,7 @@ bool mag_scene_emulate_test_on_event(void *context, SceneManagerEvent event) {
             furi_string_set_str(v, test_str);
 
             // blink led while spoofing
-            notification_message(mag->notifications, &sequence_blink_start_magenta);
+            notification_message(mag->notifications, &sequence_blink_start_blue);
             mag_spoof(v, TEST_TRACK);
             notification_message(mag->notifications, &sequence_blink_stop);
 

+ 24 - 0
scenes/mag_scene_file_select.c

@@ -0,0 +1,24 @@
+#include "../mag_i.h"
+#include "../mag_device.h"
+
+void mag_scene_file_select_on_enter(void* context) {
+    Mag* mag = context;
+    //UNUSED(mag);
+    mag_device_set_loading_callback(mag->mag_dev, mag_show_loading_popup, mag);
+    if(mag_file_select(mag->mag_dev)) {
+        scene_manager_next_scene(mag->scene_manager, MagSceneSavedMenu);
+    } else {
+        scene_manager_search_and_switch_to_previous_scene(mag->scene_manager, MagSceneStart);
+    }
+    mag_device_set_loading_callback(mag->mag_dev, NULL, mag);
+}
+
+bool mag_scene_file_select_on_event(void* context, SceneManagerEvent event) {
+    UNUSED(context);
+    UNUSED(event);
+    return false;
+}
+
+void mag_scene_file_select_on_exit(void* context) {
+    UNUSED(context);
+}

+ 2 - 2
scenes/mag_scene_save_name.c

@@ -24,7 +24,7 @@ void mag_scene_save_name_on_enter(void* context) {
         mag_text_input_callback,
         mag,
         mag->text_store,
-        MAG_KEY_NAME_SIZE,
+        MAG_DEV_NAME_MAX_LEN,
         key_name_is_empty);
 
     FURI_LOG_I("", "%s %s", furi_string_get_cstr(folder_path), mag->text_store);
@@ -56,7 +56,7 @@ bool mag_scene_save_name_on_event(void* context, SceneManagerEvent event) {
 
             if(mag_save_key(mag)) {
                 scene_manager_next_scene(scene_manager, MagSceneSaveSuccess);
-                if(scene_manager_has_previous_scene(scene_manager, MagSceneSavedKeyMenu)) {
+                if(scene_manager_has_previous_scene(scene_manager, MagSceneSavedMenu)) {
                     // Nothing, do not count editing as saving
                     //} else if(scene_manager_has_previous_scene(scene_manager, MagSceneSaveType)) {
                     //DOLPHIN_DEED(DolphinDeedRfidAdd);

+ 24 - 4
scenes/mag_scene_saved_info.c

@@ -2,19 +2,39 @@
 
 void mag_scene_saved_info_on_enter(void* context) {
     Mag* mag = context;
-    UNUSED(mag);
+    Widget* widget = mag->widget;
+
+    widget_add_string_element(
+        widget,
+        64,
+        12,
+        AlignCenter,
+        AlignCenter,
+        FontSecondary,
+        furi_string_get_cstr(mag->mag_dev->dev_data));
+
+    widget_add_button_element(widget, GuiButtonTypeLeft, "Back", mag_widget_callback, mag);
+
+    view_dispatcher_switch_to_view(mag->view_dispatcher, MagViewWidget);
 }
 
 bool mag_scene_saved_info_on_event(void* context, SceneManagerEvent event) {
     Mag* mag = context;
-    UNUSED(mag);
-    UNUSED(event);
+    SceneManager* scene_manager = mag->scene_manager;
     bool consumed = false;
 
+    if(event.type == SceneManagerEventTypeCustom) {
+        if(event.event == GuiButtonTypeLeft) {
+            consumed = true;
+
+            scene_manager_previous_scene(scene_manager);
+        }
+    }
+
     return consumed;
 }
 
 void mag_scene_saved_info_on_exit(void* context) {
     Mag* mag = context;
-    UNUSED(mag);
+    widget_reset(mag->widget);
 }

+ 0 - 20
scenes/mag_scene_saved_key_menu.c

@@ -1,20 +0,0 @@
-#include "../mag_i.h"
-
-void mag_scene_saved_key_menu_on_enter(void* context) {
-    Mag* mag = context;
-    UNUSED(mag);
-}
-
-bool mag_scene_saved_key_menu_on_event(void* context, SceneManagerEvent event) {
-    Mag* mag = context;
-    UNUSED(mag);
-    UNUSED(event);
-    bool consumed = false;
-
-    return consumed;
-}
-
-void mag_scene_saved_key_menu_on_exit(void* context) {
-    Mag* mag = context;
-    UNUSED(mag);
-}

+ 65 - 0
scenes/mag_scene_saved_menu.c

@@ -0,0 +1,65 @@
+#include "../mag_i.h"
+
+enum SubmenuIndex {
+    SubmenuIndexEmulate,
+    SubmenuIndexEdit,
+    SubmenuIndexDelete,
+    SubmenuIndexInfo,
+};
+
+void mag_scene_saved_menu_submenu_callback(void* context, uint32_t index) {
+    Mag* mag = context;
+
+    view_dispatcher_send_custom_event(mag->view_dispatcher, index);
+}
+
+void mag_scene_saved_menu_on_enter(void* context) {
+    Mag* mag = context;
+    Submenu* submenu = mag->submenu;
+
+    submenu_add_item(
+        submenu, "Emulate", SubmenuIndexEmulate, mag_scene_saved_menu_submenu_callback, mag);
+    submenu_add_item(
+        submenu, "Edit", SubmenuIndexEdit, mag_scene_saved_menu_submenu_callback, mag);
+    submenu_add_item(
+        submenu, "Delete", SubmenuIndexDelete, mag_scene_saved_menu_submenu_callback, mag);
+    submenu_add_item(
+        submenu, "Info", SubmenuIndexInfo, mag_scene_saved_menu_submenu_callback, mag);
+
+    submenu_set_selected_item(
+        mag->submenu, scene_manager_get_scene_state(mag->scene_manager, MagSceneSavedMenu));
+
+    view_dispatcher_switch_to_view(mag->view_dispatcher, MagViewSubmenu);
+}
+
+bool mag_scene_saved_menu_on_event(void* context, SceneManagerEvent event) {
+    Mag* mag = context;
+    bool consumed = false;
+
+    if(event.type == SceneManagerEventTypeCustom) {
+        scene_manager_set_scene_state(mag->scene_manager, MagSceneSavedMenu, event.event);
+
+        // TODO: replace with actual next scenes once built
+        if(event.event == SubmenuIndexEmulate) {
+            scene_manager_next_scene(mag->scene_manager, MagSceneUnderConstruction);
+            consumed = true;
+        } else if(event.event == SubmenuIndexEdit) {
+            scene_manager_next_scene(mag->scene_manager, MagSceneUnderConstruction);
+            consumed = true;
+        } else if(event.event == SubmenuIndexDelete) {
+            scene_manager_next_scene(mag->scene_manager, MagSceneUnderConstruction);
+            consumed = true;
+        } else if(event.event == SubmenuIndexInfo) {
+            scene_manager_next_scene(mag->scene_manager, MagSceneSavedInfo);
+            consumed = true;
+        }
+    }
+
+    return consumed;
+}
+
+void mag_scene_saved_menu_on_exit(void* context) {
+    Mag* mag = context;
+
+    submenu_reset(mag->submenu);
+}

+ 0 - 20
scenes/mag_scene_select_key.c

@@ -1,20 +0,0 @@
-#include "../mag_i.h"
-
-void mag_scene_select_key_on_enter(void* context) {
-    Mag* mag = context;
-    UNUSED(mag);
-}
-
-bool mag_scene_select_key_on_event(void* context, SceneManagerEvent event) {
-    Mag* mag = context;
-    UNUSED(mag);
-    UNUSED(event);
-    bool consumed = false;
-
-    return consumed;
-}
-
-void mag_scene_select_key_on_exit(void* context) {
-    Mag* mag = context;
-    UNUSED(mag);
-}

+ 2 - 2
scenes/mag_scene_start.c

@@ -45,10 +45,10 @@ bool mag_scene_start_on_event(void* context, SceneManagerEvent event) {
             consumed = true;
         } else if(event.event == SubmenuIndexSaved) {
             furi_string_set(mag->file_path, MAG_APP_FOLDER);
-            scene_manager_next_scene(mag->scene_manager, MagSceneSelectKey);
+            scene_manager_next_scene(mag->scene_manager, MagSceneFileSelect);
             consumed = true;
         } else if(event.event == SubmenuIndexAddManually) {
-            scene_manager_next_scene(mag->scene_manager, MagSceneSaveData);
+            scene_manager_next_scene(mag->scene_manager, MagSceneUnderConstruction);
             consumed = true;
         }
         scene_manager_set_scene_state(mag->scene_manager, MagSceneStart, event.event);

+ 40 - 0
scenes/mag_scene_under_construction.c

@@ -0,0 +1,40 @@
+#include "../mag_i.h"
+
+void mag_scene_under_construction_on_enter(void* context) {
+    Mag* mag = context;
+    Widget* widget = mag->widget;
+
+    FuriString* tmp_str;
+    tmp_str = furi_string_alloc();
+
+    widget_add_button_element(widget, GuiButtonTypeLeft, "Back", mag_widget_callback, mag);
+
+    furi_string_printf(tmp_str, "Under construction!");
+    widget_add_string_element(
+        widget, 64, 4, AlignCenter, AlignTop, FontPrimary, furi_string_get_cstr(tmp_str));
+    furi_string_reset(tmp_str);
+
+    view_dispatcher_switch_to_view(mag->view_dispatcher, MagViewWidget);
+    furi_string_free(tmp_str);
+}
+
+bool mag_scene_under_construction_on_event(void* context, SceneManagerEvent event) {
+    Mag* mag = context;
+    SceneManager* scene_manager = mag->scene_manager;
+    bool consumed = false;
+
+    if(event.type == SceneManagerEventTypeCustom) {
+        if(event.event == GuiButtonTypeLeft) {
+            consumed = true;
+
+            scene_manager_previous_scene(scene_manager);
+        }
+    }
+
+    return consumed;
+}
+
+void mag_scene_under_construction_on_exit(void* context) {
+    Mag* mag = context;
+    widget_reset(mag->widget);
+}