Explorar el Código

Split scenes

- Simplifies the files by splitting the UI's
- Adds default variables to the main .h file to make adjusting the settings easy
acegoal07 hace 2 años
padre
commit
ee805eaa7b
Se han modificado 7 ficheros con 330 adiciones y 333 borrados
  1. 60 333
      nfc_playlist.c
  2. 35 0
      nfc_playlist.h
  3. 4 0
      nfc_playlist_i.h
  4. 98 0
      scences/emulation.c
  5. 18 0
      scences/emulation.h
  6. 101 0
      scences/main_menu.c
  7. 14 0
      scences/main_menu.h

+ 60 - 333
nfc_playlist.c

@@ -1,363 +1,90 @@
-#include <furi.h>
-#include <string.h>
-#include <storage/storage.h>
-#include <toolbox/stream/stream.h>
-#include <toolbox/stream/file_stream.h>
-#include <nfc_playlist_worker.h>
-#include <gui/gui.h>
-#include <gui/view_dispatcher.h>
-#include <gui/scene_manager.h>
-#include <gui/modules/popup.h>
-#include <gui/modules/variable_item_list.h>
-
-// Define log tag
-#define TAG "NfcPlaylist"
-
-// IDs for all scenes used by the app
-typedef enum {
-   NfcPlaylistScene_MainMenu,
-   NfcPlaylistScene_EmulatingPopup,
-   NfcPlaylistScene_count
-} NfcPlaylistScene;
-
-// The app context struct
-typedef struct {
-   SceneManager* scene_manager;
-   ViewDispatcher* view_dispatcher;
-   VariableItemList* variable_item_list;
-   Popup* popup;
-   uint8_t emulate_timeout;
-   uint8_t emulate_delay;
-} NfcPlaylist;
-
-// All options for the emulate timeout and delay
-const int options_emulate_timeout[] = { 1000, 2000, 3000, 4000, 5000, 6000, 7000, 8000, 9000, 10000 };
-const int options_emulate_delay[] = { 0000, 1000, 2000, 3000, 4000, 5000 };
-
-/* main menu scene */
-// Indices for menu items
-typedef enum { NfcPlaylistSettings_Timeout, NfcPlaylistSettings_Delay, NfcPlaylistMenuSelection_Start } NfcPlaylistMenuSelection;
-// All custom events
-typedef enum { NfcPlaylistEvent_ShowEmulatingPopup } NfcPlaylistEvent;
-// IDs for the view used by the app
-typedef enum { NfcPlaylistView_Menu, NfcPlaylistView_Popup } NfcPlaylistView;
-
-// Main menu callback - sends a custom event to the scene manager based on the menu selection
-void nfc_playlist_menu_callback_main_menu(void* context, uint32_t index) {
-   FURI_LOG_T(TAG, "nfc_playlist_menu_callback_main_menu");
-   NfcPlaylist* app = context;
-   switch(index) {
-      case NfcPlaylistMenuSelection_Start:
-         scene_manager_handle_custom_event(app->scene_manager, NfcPlaylistEvent_ShowEmulatingPopup);
-         break;
-      default:
-         break;
-   }
+#include "nfc_playlist.h"
+#include "nfc_playlist_i.h"
+
+static void (*const nfc_playlist_scene_on_enter_handlers[])(void*) = {
+    nfc_playlist_main_menu_scene_on_enter,
+    nfc_playlist_emulation_scene_on_enter,
+};
+
+static bool (*const nfc_playlist_scene_on_event_handlers[])(void*, SceneManagerEvent) = {
+    nfc_playlist_main_menu_scene_on_event,
+    nfc_playlist_emulation_scene_on_event,
+};
+
+static void (*const nfc_playlist_scene_on_exit_handlers[])(void*) = {
+    nfc_playlist_main_menu_scene_on_exit,
+    nfc_playlist_emulation_scene_on_exit,
+};
+
+static const SceneManagerHandlers nfc_playlist_scene_manager_handlers = {
+    .on_enter_handlers = nfc_playlist_scene_on_enter_handlers,
+    .on_event_handlers = nfc_playlist_scene_on_event_handlers,
+    .on_exit_handlers = nfc_playlist_scene_on_exit_handlers,
+    .scene_num = NfcPlaylistScene_count,
+};
+
+static bool nfc_playlist_custom_callback(void* context, uint32_t custom_event) {
+    furi_assert(context);
+    NfcPlaylist* app = context;
+    return scene_manager_handle_custom_event(app->scene_manager, custom_event);
 }
 
-static void nfc_playlist_settings_change_callback(VariableItem* item) {
-   NfcPlaylist* app = variable_item_get_context(item);
-
-   uint8_t current_option = variable_item_list_get_selected_item_index(app->variable_item_list);
-   uint8_t option_value_index = variable_item_get_current_value_index(item);
-
-   switch(current_option) {
-      case NfcPlaylistSettings_Timeout: ;
-         app->emulate_timeout = option_value_index;
-         char emulate_timeout_text[10];
-         snprintf(emulate_timeout_text, 10, "%ds", (options_emulate_timeout[option_value_index]/1000));
-         variable_item_set_current_value_text(item, (char*)emulate_timeout_text);
-         break;
-      case NfcPlaylistSettings_Delay: ;
-         app->emulate_delay = option_value_index;
-         char emulate_delay_text[10];
-         snprintf(emulate_delay_text, 10, "%ds", (options_emulate_delay[option_value_index]/1000));
-         variable_item_set_current_value_text(item, (char*)emulate_delay_text);
-         break;
-   }
+static bool nfc_playlist_back_event_callback(void* context) {
+    furi_assert(context);
+    NfcPlaylist* app = context;
+    return scene_manager_handle_back_event(app->scene_manager);
 }
 
-// Resets the menu, gives it content, callbacks and selection enums
-void nfc_playlist_scene_on_enter_main_menu(void* context) {
-   FURI_LOG_T(TAG, "nfc_playlist_scene_on_enter_main_menu");
-   NfcPlaylist* app = context;
-   variable_item_list_set_header(app->variable_item_list, "NFC Playlist");
-   VariableItem* emulation_timeout_settings = variable_item_list_add(
-      app->variable_item_list,
-      "Timeout",
-      10,
-      nfc_playlist_settings_change_callback,
-      app);
-   variable_item_set_current_value_index(emulation_timeout_settings, app->emulate_timeout);
-   char emulation_timeout_settings_text[10];
-   snprintf(emulation_timeout_settings_text, 10, "%ds", (options_emulate_timeout[app->emulate_timeout]/1000));
-   variable_item_set_current_value_text(emulation_timeout_settings, (char*)emulation_timeout_settings_text);
-   VariableItem* emulation_delay_settings = variable_item_list_add(
-      app->variable_item_list,
-      "Delay",
-      6,
-      nfc_playlist_settings_change_callback,
-      app);
-   variable_item_set_current_value_index(emulation_delay_settings, app->emulate_delay);
-   char emulation_delay_settings_text[10];
-   snprintf(emulation_delay_settings_text, 10, "%ds", (options_emulate_delay[app->emulate_delay]/1000));
-   variable_item_set_current_value_text(emulation_delay_settings, (char*)emulation_delay_settings_text);
-   variable_item_list_add(app->variable_item_list, "Start", 0, NULL, NULL);
-   variable_item_list_set_enter_callback(app->variable_item_list, nfc_playlist_menu_callback_main_menu, app);
-   view_dispatcher_switch_to_view(app->view_dispatcher, NfcPlaylistView_Menu);
-}
+static NfcPlaylist* nfc_playlist_alloc() {
 
-// Main menu event handler - switches scene based on the event
-bool nfc_playlist_scene_on_event_main_menu(void* context, SceneManagerEvent event) {
-   FURI_LOG_T(TAG, "nfc_playlist_scene_on_event_main_menu");
-   NfcPlaylist* app = context;
-   bool consumed = false;
-   switch(event.type) {
-      case SceneManagerEventTypeCustom:
-         switch(event.event) {
-            case NfcPlaylistEvent_ShowEmulatingPopup:
-               scene_manager_next_scene(app->scene_manager, NfcPlaylistScene_EmulatingPopup);
-               consumed = true;
-               break;
-         }
-         break;
-      default: // eg. SceneManagerEventTypeBack, SceneManagerEventTypeTick
-         consumed = false;
-         break;
-   }
-   return consumed;
-}
+    NfcPlaylist* app = malloc(sizeof(NfcPlaylist));
+    furi_assert(app);
+    app->scene_manager = scene_manager_alloc(&nfc_playlist_scene_manager_handlers, app);
 
-void nfc_playlist_scene_on_exit_main_menu(void* context) {
-   FURI_LOG_T(TAG, "nfc_playlist_scene_on_exit_main_menu");
-   NfcPlaylist* app = context;
-   variable_item_list_reset(app->variable_item_list);
-}
+    app->view_dispatcher = view_dispatcher_alloc();
+    view_dispatcher_enable_queue(app->view_dispatcher);
 
-// Emulating scene
-void nfc_playlist_scene_on_enter_popup_emulating(void* context) {
-   FURI_LOG_T(TAG, "nfc_playlist_scene_on_enter_popup_emulating");
-   NfcPlaylist* app = context;
+    app->variable_item_list = variable_item_list_alloc();
+    app->popup = popup_alloc();
+    app->emulate_timeout = default_emulate_timeout;
+    app->emulate_delay = default_emulate_delay;
 
-   // open/alloc resources
-   Storage* storage = furi_record_open(RECORD_STORAGE);
-   Stream* stream = file_stream_alloc(storage);
-   FuriString* line = furi_string_alloc();
-   NfcPlaylistWorker* nfc_worker = nfc_playlist_worker_alloc();
-   // Read file
-   if(file_stream_open(stream, APP_DATA_PATH("playlist.txt"), FSAM_READ, FSOM_OPEN_EXISTING)) {
-      popup_reset(app->popup);
-      popup_set_context(app->popup, app);
-      popup_set_header(app->popup, "Emulating:", 64, 10, AlignCenter, AlignTop);
-      view_dispatcher_switch_to_view(app->view_dispatcher, NfcPlaylistView_Popup);
+    view_dispatcher_set_event_callback_context(app->view_dispatcher, app);
+    view_dispatcher_set_custom_event_callback( app->view_dispatcher, nfc_playlist_custom_callback);
+    view_dispatcher_set_navigation_event_callback(app->view_dispatcher, nfc_playlist_back_event_callback);
 
-      int file_position = 0;
-      // read the file line by line and print the text
-      while(stream_read_line(stream, line)) {
-         if (options_emulate_delay[app->emulate_delay] > 0) {
-            if (file_position > 0) {
-               int time_counter_delay_ms = options_emulate_timeout[app->emulate_delay];
-               do {
-                  char display_text[30];
-                  snprintf(display_text, 30, "%s\n\n%ds", "Delaying...", (time_counter_delay_ms/1000));
-                  popup_set_text(app->popup, display_text, 64, 25, AlignCenter, AlignTop);
-                  furi_delay_ms(500);
-                  time_counter_delay_ms -= 500;
-               } while(time_counter_delay_ms > 0);
-            } else {
-               file_position++;
-            }
-         }
+    view_dispatcher_add_view(app->view_dispatcher, NfcPlaylistView_Menu, variable_item_list_get_view(app->variable_item_list));
 
-         char* file_path = (char*)furi_string_get_cstr(line);
-         char* file_name = &strrchr(file_path, '/')[1];
-         int time_counter_ms = options_emulate_timeout[app->emulate_timeout];
+    view_dispatcher_add_view(app->view_dispatcher, NfcPlaylistView_Popup, popup_get_view(app->popup));
 
-         if (storage_file_exists(storage, file_path) == false) {
-            char* popup_text_unformatted = strcat(file_name, "\nnot found");
-            int popup_text_size = (strlen(popup_text_unformatted) + 4);
-            char popup_text[popup_text_size];
-
-            do {
-               snprintf(popup_text, popup_text_size, "%s\n%ds", file_name, (time_counter_ms/1000));
-               popup_set_text(app->popup, popup_text, 64, 25, AlignCenter, AlignTop);
-               furi_delay_ms(500);
-               time_counter_ms -= 500;
-            } while(time_counter_ms > 0);
-         } else {
-            nfc_playlist_worker_set_nfc_data(nfc_worker, file_path);
-            nfc_playlist_worker_start(nfc_worker);
-
-            int popup_text_size = (strlen(file_name) + 4);
-            char popup_text[popup_text_size];
-
-            do {
-               snprintf(popup_text, popup_text_size, "%s\n%ds", file_name, (time_counter_ms/1000));
-               popup_set_text(app->popup, popup_text, 64, 25, AlignCenter, AlignTop);
-               furi_delay_ms(500);
-               time_counter_ms -= 500;
-            } while(nfc_playlist_worker_is_emulating(nfc_worker) && time_counter_ms > 0);
-
-            if (nfc_playlist_worker_is_emulating(nfc_worker)) {
-               nfc_playlist_worker_stop(nfc_worker);
-            }
-         }
-      }
-      popup_reset(app->popup);
-      scene_manager_previous_scene(app->scene_manager);
-   } else {
-      popup_reset(app->popup);
-      popup_set_context(app->popup, app);
-      popup_set_header(app->popup, "Error:", 64, 10, AlignCenter, AlignTop);
-      popup_set_text(app->popup, "Failed to open file\n/ext/apps_data/nfc_playlist/playlist.txt", 64, 25, AlignCenter, AlignTop);
-      view_dispatcher_switch_to_view(app->view_dispatcher, NfcPlaylistView_Popup);
-      FURI_LOG_E(TAG, "Failed to open file");
-   }
-   // Free/close resources
-   furi_string_free(line);
-   file_stream_close(stream);
-   stream_free(stream);
-   nfc_playlist_worker_free(nfc_worker);
-   // Close storage
-   furi_record_close(RECORD_STORAGE);
-}
-
-bool nfc_playlist_scene_on_event_popup_emulating(void* context, SceneManagerEvent event) {
-   FURI_LOG_T(TAG, "nfc_playlist_scene_on_event_popup_emulating");
-   UNUSED(context);
-   UNUSED(event);
-   return false;
+    return app;
 }
 
-void nfc_playlist_scene_on_exit_popup_emulating(void* context) {
-   FURI_LOG_T(TAG, "nfc_playlist_scene_on_exit_popup_emulating");
-   NfcPlaylist* app = context;
-   popup_reset(app->popup);
+static void nfc_playlist_free(NfcPlaylist* app) {
+    furi_assert(app);
+    scene_manager_free(app->scene_manager);
+    view_dispatcher_remove_view(app->view_dispatcher, NfcPlaylistView_Menu);
+    view_dispatcher_remove_view(app->view_dispatcher, NfcPlaylistView_Popup);
+    view_dispatcher_free(app->view_dispatcher);
+    variable_item_list_free(app->variable_item_list);
+    popup_free(app->popup);
+    free(app);
 }
 
-// Collection of all scene on_enter handlers - in the same order as their enum
-void (*const nfc_playlist_scene_on_enter_handlers[])(void*) = {
-   nfc_playlist_scene_on_enter_main_menu,
-   nfc_playlist_scene_on_enter_popup_emulating};
-
-// Collection of all scene on event handlers - in the same order as their enum
-bool (*const nfc_playlist_scene_on_event_handlers[])(void*, SceneManagerEvent) = {
-   nfc_playlist_scene_on_event_main_menu,
-   nfc_playlist_scene_on_event_popup_emulating};
-
-// Collection of all scene on exit handlers - in the same order as their enum
-void (*const nfc_playlist_scene_on_exit_handlers[])(void*) = {
-   nfc_playlist_scene_on_exit_main_menu,
-   nfc_playlist_scene_on_exit_popup_emulating};
-
-// Collection of all on_enter, on_event, on_exit handlers */
-const SceneManagerHandlers nfc_playlist_scene_event_handlers = {
-   .on_enter_handlers = nfc_playlist_scene_on_enter_handlers,
-   .on_event_handlers = nfc_playlist_scene_on_event_handlers,
-   .on_exit_handlers = nfc_playlist_scene_on_exit_handlers,
-   .scene_num = NfcPlaylistScene_count};
-
-// Custom event handler - passes the event to the scene manager
-bool nfc_playlist_scene_manager_custom_event_callback(void* context, uint32_t custom_event) {
-   FURI_LOG_T(TAG, "nfc_playlist_scene_manager_custom_event_callback");
-   furi_assert(context);
-   NfcPlaylist* app = context;
-   return scene_manager_handle_custom_event(app->scene_manager, custom_event);
-}
-
-// Navigation event handler - passes the event to the scene manager
-bool nfc_playlist_scene_manager_navigation_event_callback(void* context) {
-   FURI_LOG_T(TAG, "nfc_playlist_scene_manager_navigation_event_callback");
-   furi_assert(context);
-   NfcPlaylist* app = context;
-   return scene_manager_handle_back_event(app->scene_manager);
-}
-
-// Initialise the scene manager with all handlers
-void nfc_playlist_scene_manager_init(NfcPlaylist* app) {
-   FURI_LOG_T(TAG, "nfc_playlist_scene_manager_init");
-   app->scene_manager = scene_manager_alloc(&nfc_playlist_scene_event_handlers, app);
-}
-
-// Initialise the views, and initialise the view dispatcher with all views
-void nfc_playlist_view_dispatcher_init(NfcPlaylist* app) {
-   FURI_LOG_T(TAG, "nfc_playlist_view_dispatcher_init");
-   app->view_dispatcher = view_dispatcher_alloc();
-   view_dispatcher_enable_queue(app->view_dispatcher);
-
-   // allocate each view
-   FURI_LOG_D(TAG, "nfc_playlist_view_dispatcher_init allocating views");
-   app->variable_item_list = variable_item_list_alloc();
-   app->popup = popup_alloc();
-   app->emulate_timeout = 4;
-   app->emulate_delay = 0;
-
-   // assign callback that pass events from views to the scene manager
-   FURI_LOG_D(TAG, "nfc_playlist_view_dispatcher_init setting callbacks");
-   view_dispatcher_set_event_callback_context(app->view_dispatcher, app);
-   view_dispatcher_set_custom_event_callback( app->view_dispatcher, nfc_playlist_scene_manager_custom_event_callback);
-   view_dispatcher_set_navigation_event_callback(app->view_dispatcher, nfc_playlist_scene_manager_navigation_event_callback);
-
-   // add views to the dispatcher, indexed by their enum value
-   FURI_LOG_D(TAG, "nfc_playlist_view_dispatcher_init adding view menu");
-   view_dispatcher_add_view(app->view_dispatcher, NfcPlaylistView_Menu, variable_item_list_get_view(app->variable_item_list));
-
-   FURI_LOG_D(TAG, "nfc_playlist_view_dispatcher_init adding view popup");
-   view_dispatcher_add_view(app->view_dispatcher, NfcPlaylistView_Popup, popup_get_view(app->popup));
-}
-
-// Initialise app data, scene manager, and view dispatcher
-NfcPlaylist* nfc_playlist_init() {
-   FURI_LOG_T(TAG, "nfc_playlist_init");
-   NfcPlaylist* app = malloc(sizeof(NfcPlaylist));
-   nfc_playlist_scene_manager_init(app);
-   nfc_playlist_view_dispatcher_init(app);
-   return app;
-}
-
-// Free all app data, scene manager, and view dispatcher
-void nfc_playlist_free(NfcPlaylist* app) {
-   FURI_LOG_T(TAG, "nfc_playlist_free");
-   scene_manager_free(app->scene_manager);
-   view_dispatcher_remove_view(app->view_dispatcher, NfcPlaylistView_Menu);
-   view_dispatcher_remove_view(app->view_dispatcher, NfcPlaylistView_Popup);
-   view_dispatcher_free(app->view_dispatcher);
-   variable_item_list_free(app->variable_item_list);
-   popup_free(app->popup);
-   free(app);
-}
-
-// Go to trace log level in the dev environment
-void nfc_playlist_set_log_level() {
-#ifdef FURI_DEBUG
-   furi_log_set_level(FuriLogLevelTrace);
-#else
-   furi_log_set_level(FuriLogLevelInfo);
-#endif
-}
-
-// Application entry point
 int32_t nfc_playlist_main(void* p) {
    // Mark argument as unused
    UNUSED(p);
 
-   nfc_playlist_set_log_level();
+   NfcPlaylist* app = nfc_playlist_alloc();
 
-   // create the app context struct, scene manager, and view dispatcher
-   FURI_LOG_I(TAG, "NFC PLaylist starting...");
-   NfcPlaylist* app = nfc_playlist_init();
-
-   // set the scene and launch the main loop
    Gui* gui = furi_record_open(RECORD_GUI);
    view_dispatcher_attach_to_gui(app->view_dispatcher, gui, ViewDispatcherTypeFullscreen);
    scene_manager_next_scene(app->scene_manager, NfcPlaylistScene_MainMenu);
-   FURI_LOG_D(TAG, "Starting dispatcher...");
    view_dispatcher_run(app->view_dispatcher);
 
    // free all memory
-   FURI_LOG_I(TAG, "NFC PLaylist finishing...");
    furi_record_close(RECORD_GUI);
    nfc_playlist_free(app);
 
    return 0;
-}
+}

+ 35 - 0
nfc_playlist.h

@@ -0,0 +1,35 @@
+#pragma once
+
+#include <furi.h>
+#include <string.h>
+
+#include <gui/gui.h>
+#include <gui/view_dispatcher.h>
+#include <gui/scene_manager.h>
+#include <gui/modules/popup.h>
+#include <gui/modules/variable_item_list.h>
+
+typedef enum {
+   NfcPlaylistView_Menu,
+   NfcPlaylistView_Popup
+} NfcPlayScenesView;
+
+typedef enum {
+   NfcPlaylistScene_MainMenu,
+   NfcPlaylistScene_EmulatingPopup,
+   NfcPlaylistScene_count
+} NfcPlaylistScene;
+
+typedef struct {
+   SceneManager* scene_manager;
+   ViewDispatcher* view_dispatcher;
+   VariableItemList* variable_item_list;
+   Popup* popup;
+   uint8_t emulate_timeout;
+   uint8_t emulate_delay;
+} NfcPlaylist;
+
+static const int options_emulate_timeout[] = { 1000, 2000, 3000, 4000, 5000, 6000, 7000, 8000, 9000, 10000 };
+const int default_emulate_timeout = 4;
+static const int options_emulate_delay[] = { 0, 1000, 2000, 3000, 4000, 5000 };
+const int default_emulate_delay = 0;

+ 4 - 0
nfc_playlist_i.h

@@ -0,0 +1,4 @@
+#pragma once
+
+#include "scences/main_menu.h"
+#include "scences/emulation.h"

+ 98 - 0
scences/emulation.c

@@ -0,0 +1,98 @@
+#include "nfc_playlist.h"
+#include "scences/emulation.h"
+
+void nfc_playlist_emulation_scene_on_enter(void* context) {
+    NfcPlaylist* app = context;
+
+    // open/alloc resources
+    Storage* storage = furi_record_open(RECORD_STORAGE);
+    Stream* stream = file_stream_alloc(storage);
+    FuriString* line = furi_string_alloc();
+    NfcPlaylistWorker* nfc_worker = nfc_playlist_worker_alloc();
+    // Read file
+    if(file_stream_open(stream, APP_DATA_PATH("playlist.txt"), FSAM_READ, FSOM_OPEN_EXISTING)) {
+        popup_reset(app->popup);
+        popup_set_context(app->popup, app);
+        popup_set_header(app->popup, "Emulating:", 64, 10, AlignCenter, AlignTop);
+        view_dispatcher_switch_to_view(app->view_dispatcher, NfcPlaylistView_Popup);
+
+        int file_position = 0;
+        // read the file line by line and print the text
+        while(stream_read_line(stream, line)) {
+            if (options_emulate_delay[app->emulate_delay] > 0) {
+                if (file_position > 0) {
+                    int time_counter_delay_ms = options_emulate_delay[app->emulate_delay];
+                    do {
+                        char display_text[30];
+                        snprintf(display_text, 30, "%s\n\n%ds", "Delaying...", (time_counter_delay_ms/1000));
+                        popup_set_text(app->popup, display_text, 64, 25, AlignCenter, AlignTop);
+                        furi_delay_ms(500);
+                        time_counter_delay_ms -= 500;
+                    } while(time_counter_delay_ms > 0);
+                } else {
+                    file_position++;
+                }
+            }
+
+            char* file_path = (char*)furi_string_get_cstr(line);
+            char* file_name = &strrchr(file_path, '/')[1];
+            int time_counter_ms = options_emulate_timeout[app->emulate_timeout];
+
+            if (storage_file_exists(storage, file_path) == false) {
+                char const* popup_text_unformatted = strcat(file_name, "\nnot found");
+                int popup_text_size = (strlen(popup_text_unformatted) + 4);
+                char popup_text[popup_text_size];
+
+                do {
+                    snprintf(popup_text, popup_text_size, "%s\n%ds", file_name, (time_counter_ms/1000));
+                    popup_set_text(app->popup, popup_text, 64, 25, AlignCenter, AlignTop);
+                    furi_delay_ms(500);
+                    time_counter_ms -= 500;
+                } while(time_counter_ms > 0);
+            } else {
+                nfc_playlist_worker_set_nfc_data(nfc_worker, file_path);
+                nfc_playlist_worker_start(nfc_worker);
+
+                int popup_text_size = (strlen(file_name) + 4);
+                char popup_text[popup_text_size];
+
+                do {
+                    snprintf(popup_text, popup_text_size, "%s\n%ds", file_name, (time_counter_ms/1000));
+                    popup_set_text(app->popup, popup_text, 64, 25, AlignCenter, AlignTop);
+                    furi_delay_ms(500);
+                    time_counter_ms -= 500;
+                } while(nfc_playlist_worker_is_emulating(nfc_worker) && time_counter_ms > 0);
+
+                if (nfc_playlist_worker_is_emulating(nfc_worker)) {
+                    nfc_playlist_worker_stop(nfc_worker);
+                }
+            }
+        }
+        popup_reset(app->popup);
+        scene_manager_previous_scene(app->scene_manager);
+    } else {
+        popup_reset(app->popup);
+        popup_set_context(app->popup, app);
+        popup_set_header(app->popup, "Error:", 64, 10, AlignCenter, AlignTop);
+        popup_set_text(app->popup, "Failed to open file\n/ext/apps_data/nfc_playlist/playlist.txt", 64, 25, AlignCenter, AlignTop);
+        view_dispatcher_switch_to_view(app->view_dispatcher, NfcPlaylistView_Popup);
+    }
+    // Free/close resources
+    furi_string_free(line);
+    file_stream_close(stream);
+    stream_free(stream);
+    nfc_playlist_worker_free(nfc_worker);
+    // Close storage
+    furi_record_close(RECORD_STORAGE);
+}
+
+bool nfc_playlist_emulation_scene_on_event(void* context, SceneManagerEvent event) {
+    UNUSED(context);
+    UNUSED(event);
+    return false;
+}
+
+void nfc_playlist_emulation_scene_on_exit(void* context) {
+   NfcPlaylist* app = context;
+   popup_reset(app->popup);
+}

+ 18 - 0
scences/emulation.h

@@ -0,0 +1,18 @@
+#pragma once
+
+#include <furi.h>
+#include <string.h>
+#include <storage/storage.h>
+#include <toolbox/stream/stream.h>
+#include <toolbox/stream/file_stream.h>
+#include <nfc_playlist_worker.h>
+#include <gui/gui.h>
+#include <gui/view_dispatcher.h>
+#include <gui/scene_manager.h>
+#include <gui/modules/popup.h>
+#include <gui/modules/variable_item_list.h>
+#include <nfc_playlist.h>
+
+void nfc_playlist_emulation_scene_on_enter(void* context);
+bool nfc_playlist_emulation_scene_on_event(void* context, SceneManagerEvent event);
+void nfc_playlist_emulation_scene_on_exit(void* context);

+ 101 - 0
scences/main_menu.c

@@ -0,0 +1,101 @@
+#include "nfc_playlist.h"
+#include "scences/main_menu.h"
+
+typedef enum {
+    NfcPlaylistEvent_ShowEmulatingPopup
+} NfcPlaylistMainMenuEvent;
+
+typedef enum {
+    NfcPlaylistSettings_Timeout,
+    NfcPlaylistSettings_Delay,
+    NfcPlaylistMenuSelection_Start
+} NfcPlaylistMenuSelection;
+
+static void nfc_playlist_menu_callback(void* context, uint32_t index) {
+    NfcPlaylist* app = context;
+    switch(index) {
+        case NfcPlaylistMenuSelection_Start:
+            scene_manager_handle_custom_event(app->scene_manager, NfcPlaylistEvent_ShowEmulatingPopup);
+            break;
+        default:
+            break;
+   }
+}
+
+static void nfc_playlist_settings_change_callback(VariableItem* item) {
+    NfcPlaylist* app = variable_item_get_context(item);
+
+    uint8_t current_option = variable_item_list_get_selected_item_index(app->variable_item_list);
+    uint8_t option_value_index = variable_item_get_current_value_index(item);
+
+    switch(current_option) {
+        case NfcPlaylistSettings_Timeout: ;
+            app->emulate_timeout = option_value_index;
+            char emulate_timeout_text[10];
+            snprintf(emulate_timeout_text, 10, "%ds", (options_emulate_timeout[option_value_index]/1000));
+            variable_item_set_current_value_text(item, (char*)emulate_timeout_text);
+            break;
+        case NfcPlaylistSettings_Delay: ;
+            app->emulate_delay = option_value_index;
+            char emulate_delay_text[10];
+            snprintf(emulate_delay_text, 10, "%ds", (options_emulate_delay[option_value_index]/1000));
+            variable_item_set_current_value_text(item, (char*)emulate_delay_text);
+            break;
+        default:
+            break;
+   }
+}
+
+void nfc_playlist_main_menu_scene_on_enter(void* context) {
+    NfcPlaylist* app = context;
+    variable_item_list_set_header(app->variable_item_list, "NFC Playlist");
+    VariableItem* emulation_timeout_settings = variable_item_list_add(
+        app->variable_item_list,
+        "Timeout",
+        10,
+        nfc_playlist_settings_change_callback,
+        app);
+    variable_item_set_current_value_index(emulation_timeout_settings, app->emulate_timeout);
+    char emulation_timeout_settings_text[10];
+    snprintf(emulation_timeout_settings_text, 10, "%ds", (options_emulate_timeout[app->emulate_timeout]/1000));
+    variable_item_set_current_value_text(emulation_timeout_settings, (char*)emulation_timeout_settings_text);
+    VariableItem* emulation_delay_settings = variable_item_list_add(
+        app->variable_item_list,
+        "Delay",
+        6,
+        nfc_playlist_settings_change_callback,
+        app);
+    variable_item_set_current_value_index(emulation_delay_settings, app->emulate_delay);
+    char emulation_delay_settings_text[10];
+    snprintf(emulation_delay_settings_text, 10, "%ds", (options_emulate_delay[app->emulate_delay]/1000));
+    variable_item_set_current_value_text(emulation_delay_settings, (char*)emulation_delay_settings_text);
+    variable_item_list_add(app->variable_item_list, "Start", 0, NULL, NULL);
+    variable_item_list_set_enter_callback(app->variable_item_list, nfc_playlist_menu_callback, app);
+    view_dispatcher_switch_to_view(app->view_dispatcher, NfcPlaylistView_Menu);
+}
+
+bool nfc_playlist_main_menu_scene_on_event(void* context, SceneManagerEvent event) {
+    NfcPlaylist* app = context;
+    bool consumed = false;
+    switch(event.type) {
+        case SceneManagerEventTypeCustom:
+            switch(event.event) {
+                case NfcPlaylistEvent_ShowEmulatingPopup:
+                    scene_manager_next_scene(app->scene_manager, NfcPlaylistScene_EmulatingPopup);
+                    consumed = true;
+                    break;
+                default:
+                    break;
+            }
+            break;
+        default:
+            consumed = false;
+            break;
+   }
+   return consumed;
+}
+
+void nfc_playlist_main_menu_scene_on_exit(void* context) {
+   NfcPlaylist* app = context;
+   variable_item_list_reset(app->variable_item_list);
+}

+ 14 - 0
scences/main_menu.h

@@ -0,0 +1,14 @@
+#pragma once
+
+#include <furi.h>
+#include <string.h>
+
+#include <gui/gui.h>
+#include <gui/view_dispatcher.h>
+#include <gui/scene_manager.h>
+#include <gui/modules/popup.h>
+#include <gui/modules/variable_item_list.h>
+
+void nfc_playlist_main_menu_scene_on_enter(void* context);
+bool nfc_playlist_main_menu_scene_on_event(void* context, SceneManagerEvent event);
+void nfc_playlist_main_menu_scene_on_exit(void* context);