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

add nfc playlist

https://github.com/acegoal07/FlipperZero_NFC_Playlist/tree/main
MX 1 год назад
Сommit
1dd4978102

+ 1 - 0
.gitignore

@@ -0,0 +1 @@
+notes.txt

+ 25 - 0
README.md

@@ -0,0 +1,25 @@
+# FlipperZero_NFC_Playlist:
+The idea behind this app is to allow for you to test multiple copies of NFC's at once as a bulk test
+
+## How it works:
+When starting the app you are greeted by a select file option where you choose the playlist you wanna run.
+
+All the playlists should be placed in ext/apps_data/nfc_playlist and an example of how the data in the file should look can be found below.
+```txt
+/ext/nfc/link.nfc
+/ext/nfc/link2.nfc
+```
+An example file can be found in the repository
+
+## How to build
+This app was design, built and tested using the <a href="https://github.com/Flipper-XFW/Xtreme-Firmware">Xtreme firmware</a> so keep that in mind when building the FAP for yourself
+
+## Settings:
+- Emulate time (How long the NFC card will be emulated for)
+- Delay time (How long the gap between the cards will be)
+- LED indicator (Whether or not the LED's will be on)
+- Reset settings (Puts all the settings back to the defaults)
+
+## Playlist editor:
+- Delete playlist (Deletes the selected playlist)
+- Rename playlist (Renames the selected playlist the new name provided)

+ 22 - 0
application.fam

@@ -0,0 +1,22 @@
+App(
+    appid="nfc_playlist",
+    name="NFC Playlist",
+    apptype=FlipperAppType.EXTERNAL,
+    entry_point="nfc_playlist_main",
+    requires=["gui", "nfc"],
+    stack_size=4 * 1024,
+    fap_category="NFC",
+    fap_author="@acegoal07",
+    fap_weburl="https://github.com/acegoal07/FlipperZero_NFC_Playlist/tree/main",
+    fap_version="1.4",
+    fap_icon="icon.png",
+    fap_icon_assets="assets",
+    fap_private_libs=[
+        Lib(
+            name="worker",
+        ),
+        Lib(
+            name="led",
+        ),
+    ],
+)

BIN
assets/sub1_10px.png



+ 42 - 0
lib/led/nfc_playlist_led.c

@@ -0,0 +1,42 @@
+#include "nfc_playlist_led.h"
+
+NotificationMessage blink_message_normal = {
+   .type = NotificationMessageTypeLedBlinkStart,
+   .data.led_blink.color = LightBlue | LightGreen,
+   .data.led_blink.on_time = 10,
+   .data.led_blink.period = 100
+};
+const NotificationSequence blink_sequence_normal = {
+   &blink_message_normal,
+   &message_do_not_reset,
+   NULL
+};
+
+NotificationMessage blink_message_error = {
+   .type = NotificationMessageTypeLedBlinkStart,
+   .data.led_blink.color = LightRed,
+   .data.led_blink.on_time = 10,
+   .data.led_blink.period = 100
+};
+
+const NotificationSequence blink_sequence_error = {
+   &blink_message_error,
+   &message_do_not_reset,
+   NULL
+};
+
+void start_blink(NfcPlaylist* nfc_playlist, int state) {
+   if (nfc_playlist->settings.emulate_led_indicator) {
+      if (state == NfcPlaylistLedState_Normal) {
+         notification_message_block(nfc_playlist->notification, &blink_sequence_normal);
+      } else if (state == NfcPlaylistLedState_Error) {
+         notification_message_block(nfc_playlist->notification, &blink_sequence_error);
+      }
+   }
+}
+
+void stop_blink(NfcPlaylist* nfc_playlist) {
+   if (nfc_playlist->settings.emulate_led_indicator) {
+      notification_message_block(nfc_playlist->notification, &sequence_blink_stop);
+   }
+}

+ 11 - 0
lib/led/nfc_playlist_led.h

@@ -0,0 +1,11 @@
+#pragma once
+#include <../../nfc_playlist.h>
+#include <notification/notification_messages.h>
+
+typedef enum NfcPlaylistLedState {
+   NfcPlaylistLedState_Normal,
+   NfcPlaylistLedState_Error
+} NfcPlaylistLedState;
+
+void start_blink(NfcPlaylist* nfc_playlist, int state);
+void stop_blink(NfcPlaylist* nfc_playlist);

+ 83 - 0
lib/worker/nfc_playlist_worker.c

@@ -0,0 +1,83 @@
+#include "nfc_playlist_worker.h"
+
+NfcPlaylistWorker* nfc_playlist_worker_alloc() {
+   NfcPlaylistWorker* nfc_playlist_worker = malloc(sizeof(NfcPlaylistWorker));
+
+   nfc_playlist_worker->thread = furi_thread_alloc_ex("NfcPlaylistWorker", 8192, nfc_playlist_worker_task, nfc_playlist_worker);
+   nfc_playlist_worker->state = NfcPlaylistWorkerState_Stopped;
+
+   nfc_playlist_worker->nfc = nfc_alloc();
+   nfc_playlist_worker->nfc_device = nfc_device_alloc();
+
+   return nfc_playlist_worker;
+}
+
+void nfc_playlist_worker_free(NfcPlaylistWorker* nfc_playlist_worker) {
+   furi_assert(nfc_playlist_worker);
+   furi_thread_free(nfc_playlist_worker->thread);
+
+   nfc_free(nfc_playlist_worker->nfc);
+   nfc_device_free(nfc_playlist_worker->nfc_device);
+
+   free(nfc_playlist_worker);
+}
+
+void nfc_playlist_worker_stop(NfcPlaylistWorker* nfc_playlist_worker) {
+   furi_assert(nfc_playlist_worker);
+   if (nfc_playlist_worker->state != NfcPlaylistWorkerState_Stopped) {
+      nfc_playlist_worker->state = NfcPlaylistWorkerState_Stopped;
+      furi_thread_join(nfc_playlist_worker->thread);
+   }
+}
+
+void nfc_playlist_worker_start(NfcPlaylistWorker* nfc_playlist_worker) {
+   furi_assert(nfc_playlist_worker);
+   nfc_playlist_worker->state = NfcPlaylistWorkerState_Emulating;
+   furi_thread_start(nfc_playlist_worker->thread);
+}
+
+int32_t nfc_playlist_worker_task(void* context) {
+   NfcPlaylistWorker* nfc_playlist_worker = context;
+
+   if (nfc_playlist_worker->state == NfcPlaylistWorkerState_Emulating) {
+
+      nfc_playlist_worker->nfc_listener =
+         nfc_listener_alloc(nfc_playlist_worker->nfc,
+            nfc_playlist_worker->nfc_protocol,
+            nfc_device_get_data(nfc_playlist_worker->nfc_device, nfc_playlist_worker->nfc_protocol)
+         );
+      nfc_listener_start(nfc_playlist_worker->nfc_listener, NULL, NULL);
+
+      while(nfc_playlist_worker->state == NfcPlaylistWorkerState_Emulating) {
+         furi_delay_ms(50);
+      }
+
+      nfc_listener_stop(nfc_playlist_worker->nfc_listener);
+      nfc_listener_free(nfc_playlist_worker->nfc_listener);
+   }
+
+   nfc_playlist_worker->state = NfcPlaylistWorkerState_Stopped;
+
+   return 0;
+}
+
+bool nfc_playlist_worker_is_emulating(NfcPlaylistWorker* nfc_playlist_worker) {
+   if (nfc_playlist_worker->state == NfcPlaylistWorkerState_Emulating) {
+      return true;
+   }
+   return false;
+}
+
+void nfc_playlist_worker_set_nfc_data(NfcPlaylistWorker* nfc_playlist_worker, char* file_path) {
+   nfc_device_clear(nfc_playlist_worker->nfc_device);
+   nfc_device_load(nfc_playlist_worker->nfc_device, file_path);
+   nfc_playlist_worker->nfc_protocol = nfc_device_get_protocol(nfc_playlist_worker->nfc_device);
+}
+
+void nfc_playlist_worker_clear_nfc_data(NfcPlaylistWorker* nfc_playlist_worker) {
+   nfc_device_clear(nfc_playlist_worker->nfc_device);
+}
+
+NfcDeviceData* nfc_playlist_worker_get_nfc_data(NfcPlaylistWorker* nfc_playlist_worker) {
+   return nfc_playlist_worker->nfc_data;
+}

+ 33 - 0
lib/worker/nfc_playlist_worker.h

@@ -0,0 +1,33 @@
+#pragma once
+#include <furi.h>
+#include <furi_hal.h>
+#include <nfc/nfc.h>
+#include <nfc/nfc_device.h>
+#include <nfc/nfc_listener.h>
+
+typedef enum NfcPlaylistWorkerState {
+   NfcPlaylistWorkerState_Emulating,
+   NfcPlaylistWorkerState_Stopped
+} NfcPlaylistWorkerState;
+
+typedef struct NfcPlaylistWorker {
+   FuriThread* thread;
+   NfcPlaylistWorkerState state;
+   NfcListener* nfc_listener;
+   NfcDevice* nfc_device;
+   NfcProtocol nfc_protocol;
+   NfcDeviceData* nfc_data;
+   Nfc* nfc;
+} NfcPlaylistWorker;
+
+NfcPlaylistWorker* nfc_playlist_worker_alloc();
+void nfc_playlist_worker_free(NfcPlaylistWorker* nfc_playlist_worker);
+void nfc_playlist_worker_stop(NfcPlaylistWorker* nfc_playlist_worker);
+void nfc_playlist_worker_start(NfcPlaylistWorker* nfc_playlist_worker);
+
+int32_t nfc_playlist_worker_task(void* context);
+
+bool nfc_playlist_worker_is_emulating(NfcPlaylistWorker* nfc_playlist_worker);
+void nfc_playlist_worker_set_nfc_data(NfcPlaylistWorker* nfc_playlist_worker, char* file_path);
+void nfc_playlist_worker_clear_nfc_data(NfcPlaylistWorker* nfc_playlist_worker);
+NfcDeviceData* nfc_playlist_worker_get_nfc_data(NfcPlaylistWorker* nfc_playlist_worker);

+ 120 - 0
nfc_playlist.c

@@ -0,0 +1,120 @@
+#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_settings_scene_on_enter,
+   nfc_playlist_emulation_scene_on_enter,
+   nfc_playlist_file_select_scene_on_enter,
+   nfc_playlist_file_edit_scene_on_enter,
+   nfc_playlist_text_input_scene_on_enter
+};
+
+static bool (*const nfc_playlist_scene_on_event_handlers[])(void*, SceneManagerEvent) = {
+   nfc_playlist_main_menu_scene_on_event,
+   nfc_playlist_settings_scene_on_event,
+   nfc_playlist_emulation_scene_on_event,
+   nfc_playlist_file_select_scene_on_event,
+   nfc_playlist_file_edit_scene_on_event,
+   nfc_playlist_text_input_scene_on_event
+};
+
+static void (*const nfc_playlist_scene_on_exit_handlers[])(void*) = {
+   nfc_playlist_main_menu_scene_on_exit,
+   nfc_playlist_settings_scene_on_exit,
+   nfc_playlist_emulation_scene_on_exit,
+   nfc_playlist_file_select_scene_on_exit,
+   nfc_playlist_file_edit_scene_on_exit,
+   nfc_playlist_text_input_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* nfc_playlist = context;
+   return scene_manager_handle_custom_event(nfc_playlist->scene_manager, custom_event);
+}
+
+static bool nfc_playlist_back_event_callback(void* context) {
+   furi_assert(context);
+   NfcPlaylist* nfc_playlist = context;
+   return scene_manager_handle_back_event(nfc_playlist->scene_manager);
+}
+
+static NfcPlaylist* nfc_playlist_alloc() {
+   NfcPlaylist* nfc_playlist = malloc(sizeof(NfcPlaylist));
+   furi_assert(nfc_playlist);
+   nfc_playlist->scene_manager = scene_manager_alloc(&nfc_playlist_scene_manager_handlers, nfc_playlist);
+   nfc_playlist->view_dispatcher = view_dispatcher_alloc();
+   view_dispatcher_enable_queue(nfc_playlist->view_dispatcher);
+   nfc_playlist->variable_item_list = variable_item_list_alloc();
+   nfc_playlist->submenu = submenu_alloc();
+
+   nfc_playlist->settings.base_file_path = furi_string_alloc_set_str("/ext/apps_data/nfc_playlist/");
+   nfc_playlist->settings.file_path = nfc_playlist->settings.base_file_path;
+   nfc_playlist->settings.file_selected = false;
+   nfc_playlist->settings.file_selected_check = false;
+   nfc_playlist->settings.emulate_timeout = default_emulate_timeout;
+   nfc_playlist->settings.emulate_delay = default_emulate_delay;
+   nfc_playlist->settings.emulate_led_indicator = default_emulate_led_indicator;
+
+   nfc_playlist->notification = furi_record_open(RECORD_NOTIFICATION);
+   nfc_playlist->file_browser = file_browser_alloc(nfc_playlist->settings.file_path);
+   nfc_playlist->text_input = text_input_alloc();
+   nfc_playlist->popup = popup_alloc();
+
+   view_dispatcher_set_event_callback_context(nfc_playlist->view_dispatcher, nfc_playlist);
+   view_dispatcher_set_custom_event_callback(nfc_playlist->view_dispatcher, nfc_playlist_custom_callback);
+   view_dispatcher_set_navigation_event_callback(nfc_playlist->view_dispatcher, nfc_playlist_back_event_callback);
+   view_dispatcher_add_view(nfc_playlist->view_dispatcher, NfcPlaylistView_Menu, submenu_get_view(nfc_playlist->submenu));
+   view_dispatcher_add_view(nfc_playlist->view_dispatcher, NfcPlaylistView_Settings, variable_item_list_get_view(nfc_playlist->variable_item_list));
+   view_dispatcher_add_view(nfc_playlist->view_dispatcher, NfcPlaylistView_Popup, popup_get_view(nfc_playlist->popup));
+   view_dispatcher_add_view(nfc_playlist->view_dispatcher, NfcPlaylistView_FileSelect, file_browser_get_view(nfc_playlist->file_browser));
+   view_dispatcher_add_view(nfc_playlist->view_dispatcher, NfcPlaylistView_FileEdit, submenu_get_view(nfc_playlist->submenu));
+   view_dispatcher_add_view(nfc_playlist->view_dispatcher, NfcPlaylistView_TextInput, text_input_get_view(nfc_playlist->text_input));
+   return nfc_playlist;
+}
+
+static void nfc_playlist_free(NfcPlaylist* nfc_playlist) {
+   furi_assert(nfc_playlist);
+   scene_manager_free(nfc_playlist->scene_manager);
+   view_dispatcher_remove_view(nfc_playlist->view_dispatcher, NfcPlaylistView_Menu);
+   view_dispatcher_remove_view(nfc_playlist->view_dispatcher, NfcPlaylistView_Settings);
+   view_dispatcher_remove_view(nfc_playlist->view_dispatcher, NfcPlaylistView_Popup);
+   view_dispatcher_remove_view(nfc_playlist->view_dispatcher, NfcPlaylistView_FileSelect);
+   view_dispatcher_remove_view(nfc_playlist->view_dispatcher, NfcPlaylistView_FileEdit);
+   view_dispatcher_remove_view(nfc_playlist->view_dispatcher, NfcPlaylistView_TextInput);
+   view_dispatcher_free(nfc_playlist->view_dispatcher);
+   variable_item_list_free(nfc_playlist->variable_item_list);
+   submenu_free(nfc_playlist->submenu);
+   file_browser_free(nfc_playlist->file_browser);
+   text_input_free(nfc_playlist->text_input);
+   popup_free(nfc_playlist->popup);
+   furi_record_close(RECORD_NOTIFICATION);
+   furi_string_free(nfc_playlist->settings.base_file_path);
+   furi_string_free(nfc_playlist->settings.file_path);
+   free(nfc_playlist->playlist_name);
+   free(nfc_playlist);
+}
+
+int32_t nfc_playlist_main(void* p) {
+   UNUSED(p);
+
+   NfcPlaylist* nfc_playlist = nfc_playlist_alloc();
+
+   Gui* gui = furi_record_open(RECORD_GUI);
+   view_dispatcher_attach_to_gui(nfc_playlist->view_dispatcher, gui, ViewDispatcherTypeFullscreen);
+   scene_manager_next_scene(nfc_playlist->scene_manager, NfcPlaylistScene_MainMenu);
+   view_dispatcher_run(nfc_playlist->view_dispatcher);
+
+   furi_record_close(RECORD_GUI);
+   nfc_playlist_free(nfc_playlist);
+
+   return 0;
+}

+ 64 - 0
nfc_playlist.h

@@ -0,0 +1,64 @@
+#pragma once
+#include <furi.h>
+#include <furi_hal.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>
+#include <gui/modules/submenu.h>
+#include <gui/modules/file_browser.h>
+#include <gui/modules/text_input.h>
+#include <notification/notification_messages.h>
+#include <nfc_playlist_worker.h>
+
+typedef enum {
+    NfcPlaylistView_Menu,
+    NfcPlaylistView_Settings,
+    NfcPlaylistView_Popup,
+    NfcPlaylistView_FileSelect,
+    NfcPlaylistView_FileEdit,
+    NfcPlaylistView_TextInput
+} NfcPlayScenesView;
+
+typedef enum {
+    NfcPlaylistScene_MainMenu,
+    NfcPlaylistScene_Settings,
+    NfcPlaylistScene_EmulatingPopup,
+    NfcPlaylistScene_FileSelect,
+    NfcPlaylistScene_FileEdit,
+    NfcPlaylistScene_TextInput,
+    NfcPlaylistScene_count
+} NfcPlaylistScene;
+
+typedef struct {
+    FuriString* base_file_path;
+    FuriString* file_path;
+    bool file_selected;
+    bool file_selected_check;
+    uint8_t emulate_timeout;
+    uint8_t emulate_delay;
+    bool emulate_led_indicator;
+} NfcPlaylistSettings;
+
+typedef struct {
+    SceneManager* scene_manager;
+    ViewDispatcher* view_dispatcher;
+    VariableItemList* variable_item_list;
+    FileBrowser* file_browser;
+    TextInput* text_input;
+    Submenu* submenu;
+    Popup* popup;
+    NotificationApp* notification;
+    FuriThread* thread;
+    NfcPlaylistWorker* nfc_playlist_worker;
+    NfcPlaylistSettings settings;
+    char* playlist_name;
+} NfcPlaylist;
+
+static const int options_emulate_timeout[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12};
+static const int default_emulate_timeout = 4;
+static const int options_emulate_delay[] = {0, 1, 2, 3, 4, 5, 6};
+static const int default_emulate_delay = 0;
+static const bool default_emulate_led_indicator = true;

+ 8 - 0
nfc_playlist_i.h

@@ -0,0 +1,8 @@
+#pragma once
+#include "scences/main_menu.h"
+#include "scences/settings.h"
+#include "scences/emulation.h"
+#include "scences/file_select.h"
+#include "scences/file_edit.h"
+#include "scences/text_input.h"
+#include <nfc_playlist_icons.h>

+ 3 - 0
playlist.txt

@@ -0,0 +1,3 @@
+/ext/nfc/RickRoll.nfc
+/ext/nfc/Website.nfc
+/ext/nfc/Phone_number.nfc

+ 156 - 0
scences/emulation.c

@@ -0,0 +1,156 @@
+#include "nfc_playlist.h"
+#include "scences/emulation.h"
+
+NfcPlaylistEmulationState EmulationState = NfcPlaylistEmulationState_Stopped;
+
+void nfc_playlist_emulation_scene_on_enter(void* context) {
+   NfcPlaylist* nfc_playlist = context;
+   nfc_playlist_emulation_setup(nfc_playlist);
+   nfc_playlist_emulation_start(nfc_playlist);
+}
+
+bool nfc_playlist_emulation_scene_on_event(void* context, SceneManagerEvent event) {
+   UNUSED(context);
+   if (event.event == 0 && EmulationState == NfcPlaylistEmulationState_Emulating) {
+      EmulationState = NfcPlaylistEmulationState_Canceled;
+      return true;
+   }
+   return false;
+}
+
+void nfc_playlist_emulation_scene_on_exit(void* context) {
+   NfcPlaylist* nfc_playlist = context;
+   EmulationState = NfcPlaylistEmulationState_Stopped;
+   nfc_playlist_emulation_stop(nfc_playlist);
+   nfc_playlist_emulation_free(nfc_playlist);
+   popup_reset(nfc_playlist->popup);
+}
+
+void nfc_playlist_emulation_setup(void* context) {
+   NfcPlaylist* nfc_playlist = context;
+   nfc_playlist->thread = furi_thread_alloc_ex("NfcPlaylistEmulationWorker", 8192, nfc_playlist_emulation_task, nfc_playlist);
+   nfc_playlist->nfc_playlist_worker = nfc_playlist_worker_alloc();
+}
+
+void nfc_playlist_emulation_free(NfcPlaylist* nfc_playlist) {
+   furi_assert(nfc_playlist);
+   furi_thread_free(nfc_playlist->thread);
+   nfc_playlist_worker_free(nfc_playlist->nfc_playlist_worker);
+   nfc_playlist->thread = NULL;
+   nfc_playlist->nfc_playlist_worker = NULL;
+}
+
+void nfc_playlist_emulation_start(NfcPlaylist* nfc_playlist) {
+   furi_assert(nfc_playlist);
+   furi_thread_start(nfc_playlist->thread);
+}
+
+void nfc_playlist_emulation_stop(NfcPlaylist* nfc_playlist) {
+   furi_assert(nfc_playlist);
+   furi_thread_join(nfc_playlist->thread);
+}
+
+int32_t nfc_playlist_emulation_task(void* context) {
+   NfcPlaylist* nfc_playlist = context;
+
+   Storage* storage = furi_record_open(RECORD_STORAGE);
+   Stream* stream = file_stream_alloc(storage);
+   FuriString* line = furi_string_alloc();
+
+   popup_reset(nfc_playlist->popup);
+   popup_set_context(nfc_playlist->popup, nfc_playlist);
+   view_dispatcher_switch_to_view(nfc_playlist->view_dispatcher, NfcPlaylistView_Popup);
+
+   if (file_stream_open(stream, furi_string_get_cstr(nfc_playlist->settings.file_path), FSAM_READ, FSOM_OPEN_EXISTING) && nfc_playlist->settings.file_selected_check) {
+      EmulationState = NfcPlaylistEmulationState_Emulating;
+      int file_position = 0;
+      while(stream_read_line(stream, line) && EmulationState == NfcPlaylistEmulationState_Emulating) {
+
+         char* file_path = (char*)furi_string_get_cstr(line);
+
+         if (strlen(file_path) <= 1) {continue;}
+
+         if (nfc_playlist->settings.emulate_delay > 0 && file_position != 0) {
+            popup_set_header(nfc_playlist->popup, "Delaying", 64, 10, AlignCenter, AlignTop);
+            start_blink(nfc_playlist, NfcPlaylistLedState_Error);
+            int time_counter_delay_ms = (options_emulate_delay[nfc_playlist->settings.emulate_delay]*1000);
+            do {
+               char display_text[10];
+               snprintf(display_text, 10, "%ds", (time_counter_delay_ms/1000));
+               popup_set_text(nfc_playlist->popup, display_text, 64, 50, AlignCenter, AlignTop);
+               furi_delay_ms(50);
+               time_counter_delay_ms -= 50;
+            } while(time_counter_delay_ms > 0 && EmulationState == NfcPlaylistEmulationState_Emulating);
+         } else if (nfc_playlist->settings.emulate_delay > 0) {
+            file_position++;
+         }
+
+         if (EmulationState != NfcPlaylistEmulationState_Emulating) {break;}
+
+         char* file_name = strchr(file_path, '/') != NULL ? &strrchr(file_path, '/')[1] : file_path;
+         char const* file_ext = &strrchr(file_path, '.')[1];
+         int time_counter_ms = (options_emulate_timeout[nfc_playlist->settings.emulate_timeout]*1000);
+
+         if (storage_file_exists(storage, file_path) == false) {
+            int popup_header_text_size = strlen(file_name) + 18;
+            char popup_header_text[popup_header_text_size];
+            snprintf(popup_header_text, popup_header_text_size, "%s\n%s", "ERROR not found:", file_name);
+            popup_set_header(nfc_playlist->popup, popup_header_text, 64, 10, AlignCenter, AlignTop);
+            start_blink(nfc_playlist, NfcPlaylistLedState_Error);
+            do {
+               char popup_text[10];
+               snprintf(popup_text, 10, "%ds", (time_counter_ms/1000));
+               popup_set_text(nfc_playlist->popup, popup_text, 64, 50, AlignCenter, AlignTop);
+               furi_delay_ms(50);
+               time_counter_ms -= 50;
+            } while(time_counter_ms > 0 && EmulationState == NfcPlaylistEmulationState_Emulating);
+         } else if (strcasestr(file_ext, "nfc") == NULL) {
+            int popup_header_text_size = strlen(file_name) + 21;
+            char popup_header_text[popup_header_text_size];
+            snprintf(popup_header_text, popup_header_text_size, "%s\n%s", "ERROR invalid file:", file_name);
+            popup_set_header(nfc_playlist->popup, popup_header_text, 64, 10, AlignCenter, AlignTop);
+            start_blink(nfc_playlist, NfcPlaylistLedState_Error);
+            do {
+               char popup_text[10];
+               snprintf(popup_text, 10, "%ds", (time_counter_ms/1000));
+               popup_set_text(nfc_playlist->popup, popup_text, 64, 50, AlignCenter, AlignTop);
+               furi_delay_ms(50);
+               time_counter_ms -= 50;
+            } while(time_counter_ms > 0 && EmulationState == NfcPlaylistEmulationState_Emulating);
+         } else {
+            int popup_header_text_size = strlen(file_name) + 12;
+            char popup_header_text[popup_header_text_size];
+            snprintf(popup_header_text, popup_header_text_size, "%s\n%s", "Emulating:", file_name);
+            popup_set_header(nfc_playlist->popup, popup_header_text, 64, 10, AlignCenter, AlignTop);
+            nfc_playlist_worker_set_nfc_data(nfc_playlist->nfc_playlist_worker, file_path);
+            nfc_playlist_worker_start(nfc_playlist->nfc_playlist_worker);
+            start_blink(nfc_playlist, NfcPlaylistLedState_Normal);
+            do {
+               char popup_text[10];
+               snprintf(popup_text, 10, "%ds", (time_counter_ms/1000));
+               popup_set_text(nfc_playlist->popup, popup_text, 64, 50, AlignCenter, AlignTop);
+               furi_delay_ms(50);
+               time_counter_ms -= 50;
+            } while(nfc_playlist_worker_is_emulating(nfc_playlist->nfc_playlist_worker) && time_counter_ms > 0 && EmulationState == NfcPlaylistEmulationState_Emulating);
+            nfc_playlist_worker_stop(nfc_playlist->nfc_playlist_worker);
+            nfc_playlist_worker_clear_nfc_data(nfc_playlist->nfc_playlist_worker);
+         }
+         free(file_path);
+      }
+      popup_reset(nfc_playlist->popup);
+      popup_set_header(nfc_playlist->popup, EmulationState == NfcPlaylistEmulationState_Canceled ? "Emulation stopped" : "Emulation finished", 64, 10, AlignCenter, AlignTop);
+      popup_set_text(nfc_playlist->popup, "Press back", 64, 50, AlignCenter, AlignTop);
+      stop_blink(nfc_playlist);
+      EmulationState = NfcPlaylistEmulationState_Stopped;
+   } else {
+      popup_set_header(nfc_playlist->popup, "Failed to open playlist", 64, 10, AlignCenter, AlignTop);
+      popup_set_text(nfc_playlist->popup, "Press back", 64, 50, AlignCenter, AlignTop);
+   }
+
+   furi_string_free(line);
+   file_stream_close(stream);
+   furi_record_close(RECORD_STORAGE);
+   stream_free(stream);
+
+   return 0;
+}

+ 27 - 0
scences/emulation.h

@@ -0,0 +1,27 @@
+#pragma once
+#include <furi.h>
+#include <storage/storage.h>
+#include <toolbox/stream/stream.h>
+#include <toolbox/stream/file_stream.h>
+#include <lib/worker/nfc_playlist_worker.h>
+#include <lib/led/nfc_playlist_led.h>
+#include <gui/gui.h>
+#include <gui/view_dispatcher.h>
+#include <gui/scene_manager.h>
+#include <gui/modules/popup.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);
+
+void nfc_playlist_emulation_setup(void* context);
+void nfc_playlist_emulation_free(NfcPlaylist* nfc_playlist);
+void nfc_playlist_emulation_start(NfcPlaylist* nfc_playlist);
+void nfc_playlist_emulation_stop(NfcPlaylist* nfc_playlist);
+int32_t nfc_playlist_emulation_task(void* context);
+
+typedef enum NfcPlaylistEmulationState {
+   NfcPlaylistEmulationState_Emulating,
+   NfcPlaylistEmulationState_Stopped,
+   NfcPlaylistEmulationState_Canceled
+} NfcPlaylistEmulationState;

+ 70 - 0
scences/file_edit.c

@@ -0,0 +1,70 @@
+#include "nfc_playlist.h"
+#include "scences/file_edit.h"
+
+typedef enum {
+   NfcPlaylistMenuSelection_DeletePlaylist,
+   NfcPlaylistMenuSelection_RenamePlaylist,
+   NfcPlaylistMenuSelection_EditList
+} NfcPlaylistMenuSelection;
+
+void nfc_playlist_file_edit_menu_callback(void* context, uint32_t index) {
+   NfcPlaylist* nfc_playlist = context;
+   Storage* storage = furi_record_open(RECORD_STORAGE);
+   switch(index) {
+      case NfcPlaylistMenuSelection_DeletePlaylist: {
+         storage_simply_remove(storage, furi_string_get_cstr(nfc_playlist->settings.file_path));
+         nfc_playlist->settings.file_selected = false;
+         nfc_playlist->settings.file_selected_check = false;
+         nfc_playlist->settings.file_path = nfc_playlist->settings.base_file_path;
+         scene_manager_previous_scene(nfc_playlist->scene_manager);
+         break;
+      }
+      case NfcPlaylistMenuSelection_RenamePlaylist: {
+         scene_manager_next_scene(nfc_playlist->scene_manager, NfcPlaylistScene_TextInput);
+         break;
+      }
+      case NfcPlaylistMenuSelection_EditList: {
+         break;
+      }
+      default:
+         break;
+   }
+   furi_record_close(RECORD_STORAGE);
+}
+
+void nfc_playlist_file_edit_scene_on_enter(void* context) {
+   NfcPlaylist* nfc_playlist = context;
+
+   submenu_set_header(nfc_playlist->submenu, "Edit Playlist");
+
+   submenu_add_lockable_item(
+      nfc_playlist->submenu,
+      "Delete Playlist",
+      NfcPlaylistMenuSelection_DeletePlaylist,
+      nfc_playlist_file_edit_menu_callback,
+      nfc_playlist,
+      !nfc_playlist->settings.file_selected_check,
+      "No\nplaylist\nselected");
+
+   submenu_add_lockable_item(
+      nfc_playlist->submenu,
+      "Rename Playlist",
+      NfcPlaylistMenuSelection_RenamePlaylist,
+      nfc_playlist_file_edit_menu_callback,
+      nfc_playlist,
+      !nfc_playlist->settings.file_selected_check,
+      "No\nplaylist\nselected");
+
+   view_dispatcher_switch_to_view(nfc_playlist->view_dispatcher, NfcPlaylistView_FileEdit);
+}
+
+bool nfc_playlist_file_edit_scene_on_event(void* context, SceneManagerEvent event) {
+   UNUSED(event);
+   UNUSED(context);
+   return false;
+}
+
+void nfc_playlist_file_edit_scene_on_exit(void* context) {
+   NfcPlaylist* nfc_playlist = context;
+   submenu_reset(nfc_playlist->submenu);
+}

+ 13 - 0
scences/file_edit.h

@@ -0,0 +1,13 @@
+#pragma once
+#include <furi.h>
+#include <gui/gui.h>
+#include <gui/view_dispatcher.h>
+#include <gui/scene_manager.h>
+#include <gui/modules/submenu.h>
+#include <storage/storage.h>
+#include <toolbox/stream/stream.h>
+#include <toolbox/stream/file_stream.h>
+
+void nfc_playlist_file_edit_scene_on_enter(void* context);
+bool nfc_playlist_file_edit_scene_on_event(void* context, SceneManagerEvent event);
+void nfc_playlist_file_edit_scene_on_exit(void* context);

+ 34 - 0
scences/file_select.c

@@ -0,0 +1,34 @@
+#include "nfc_playlist.h"
+#include "scences/file_select.h"
+
+void nfc_playlist_file_select_menu_callback(void* context) {
+   NfcPlaylist* nfc_playlist = context;
+   nfc_playlist->settings.file_selected_check = true;
+   scene_manager_previous_scene(nfc_playlist->scene_manager);
+}
+
+void nfc_playlist_file_select_scene_on_enter(void* context) {
+   NfcPlaylist* nfc_playlist = context;
+   file_browser_configure(
+      nfc_playlist->file_browser,
+      ".txt",
+      furi_string_get_cstr(nfc_playlist->settings.base_file_path),
+      true,
+      true,
+      &I_sub1_10px,
+      true);
+   view_dispatcher_switch_to_view(nfc_playlist->view_dispatcher, NfcPlaylistView_FileSelect);
+   file_browser_set_callback(nfc_playlist->file_browser, nfc_playlist_file_select_menu_callback, nfc_playlist);
+   file_browser_start(nfc_playlist->file_browser, nfc_playlist->settings.base_file_path);
+}
+
+bool nfc_playlist_file_select_scene_on_event(void* context, SceneManagerEvent event) {
+   UNUSED(event);
+   UNUSED(context);
+   return false;
+}
+
+void nfc_playlist_file_select_scene_on_exit(void* context) {
+   NfcPlaylist* nfc_playlist = context;
+   file_browser_stop(nfc_playlist->file_browser);
+}

+ 11 - 0
scences/file_select.h

@@ -0,0 +1,11 @@
+#pragma once
+#include <furi.h>
+#include <gui/gui.h>
+#include <gui/view_dispatcher.h>
+#include <gui/scene_manager.h>
+#include <gui/modules/file_browser.h>
+#include <nfc_playlist_icons.h>
+
+void nfc_playlist_file_select_scene_on_enter(void* context);
+bool nfc_playlist_file_select_scene_on_event(void* context, SceneManagerEvent event);
+void nfc_playlist_file_select_scene_on_exit(void* context);

+ 111 - 0
scences/main_menu.c

@@ -0,0 +1,111 @@
+#include "nfc_playlist.h"
+#include "scences/main_menu.h"
+
+typedef enum {
+   NfcPlaylistEvent_ShowEmulatingPopup,
+   NfcPlaylistEvent_ShowFileSelect,
+   NfcPlaylistEvent_ShowFileEdit,
+   NfcPlaylistEvent_ShowSettings
+} NfcPlaylistMainMenuEvent;
+
+typedef enum {
+   NfcPlaylistMenuSelection_Start,
+   NfcPlaylistMenuSelection_FileSelect,
+   NfcPlaylistMenuSelection_FileEdit,
+   NfcPlaylistMenuSelection_Settings
+} NfcPlaylistMenuSelection;
+
+void nfc_playlist_main_menu_menu_callback(void* context, uint32_t index) {
+   NfcPlaylist* nfc_playlist = context;
+   switch(index) {
+      case NfcPlaylistMenuSelection_Start:
+         scene_manager_handle_custom_event(nfc_playlist->scene_manager, NfcPlaylistEvent_ShowEmulatingPopup);
+         break;
+      case NfcPlaylistMenuSelection_FileSelect:
+         scene_manager_handle_custom_event(nfc_playlist->scene_manager, NfcPlaylistEvent_ShowFileSelect);
+         break;
+      case NfcPlaylistMenuSelection_FileEdit:
+         scene_manager_handle_custom_event(nfc_playlist->scene_manager, NfcPlaylistEvent_ShowFileEdit);
+         break;
+      case NfcPlaylistMenuSelection_Settings:
+         scene_manager_handle_custom_event(nfc_playlist->scene_manager, NfcPlaylistEvent_ShowSettings);
+         break;
+      default:
+         break;
+   }
+}
+
+void nfc_playlist_main_menu_scene_on_enter(void* context) {
+   NfcPlaylist* nfc_playlist = context;
+   if (!nfc_playlist->settings.file_selected) {
+      nfc_playlist->settings.file_selected = true;
+      scene_manager_next_scene(nfc_playlist->scene_manager, NfcPlaylistScene_FileSelect);
+      return;
+   }
+
+   submenu_set_header(nfc_playlist->submenu, "NFC Playlist");
+
+   submenu_add_lockable_item(
+      nfc_playlist->submenu,
+      "Start",
+      NfcPlaylistMenuSelection_Start,
+      nfc_playlist_main_menu_menu_callback,
+      nfc_playlist,
+      !nfc_playlist->settings.file_selected_check,
+      "No\nplaylist\nselected");
+
+   submenu_add_item(
+      nfc_playlist->submenu,
+      "Select playlist",
+      NfcPlaylistMenuSelection_FileSelect,
+      nfc_playlist_main_menu_menu_callback,
+      nfc_playlist);
+
+   submenu_add_item(
+      nfc_playlist->submenu,
+      "Edit playlist",
+      NfcPlaylistMenuSelection_FileEdit,
+      nfc_playlist_main_menu_menu_callback,
+      nfc_playlist);
+
+   submenu_add_item(
+      nfc_playlist->submenu,
+      "Settings",
+      NfcPlaylistMenuSelection_Settings,
+      nfc_playlist_main_menu_menu_callback,
+      nfc_playlist);
+   view_dispatcher_switch_to_view(nfc_playlist->view_dispatcher, NfcPlaylistView_Menu);
+}
+
+bool nfc_playlist_main_menu_scene_on_event(void* context, SceneManagerEvent event) {
+    NfcPlaylist* nfc_playlist = context;
+    bool consumed = false;
+    if (event.type == SceneManagerEventTypeCustom) {
+      switch(event.event) {
+         case NfcPlaylistEvent_ShowEmulatingPopup:
+            scene_manager_next_scene(nfc_playlist->scene_manager, NfcPlaylistScene_EmulatingPopup);
+            consumed = true;
+            break;
+         case NfcPlaylistEvent_ShowFileSelect:
+            scene_manager_next_scene(nfc_playlist->scene_manager, NfcPlaylistScene_FileSelect);
+            consumed = true;
+            break;
+         case NfcPlaylistEvent_ShowFileEdit:
+            scene_manager_next_scene(nfc_playlist->scene_manager, NfcPlaylistScene_FileEdit);
+            consumed = true;
+            break;
+         case NfcPlaylistEvent_ShowSettings:
+            scene_manager_next_scene(nfc_playlist->scene_manager, NfcPlaylistScene_Settings);
+            consumed = true;
+            break;
+         default:
+            break;
+      }
+   }
+   return consumed;
+}
+
+void nfc_playlist_main_menu_scene_on_exit(void* context) {
+   NfcPlaylist* nfc_playlist = context;
+   submenu_reset(nfc_playlist->submenu);
+}

+ 10 - 0
scences/main_menu.h

@@ -0,0 +1,10 @@
+#pragma once
+#include <furi.h>
+#include <gui/gui.h>
+#include <gui/view_dispatcher.h>
+#include <gui/scene_manager.h>
+#include <gui/modules/submenu.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);

+ 153 - 0
scences/settings.c

@@ -0,0 +1,153 @@
+#include "nfc_playlist.h"
+#include "scences/settings.h"
+
+typedef enum {
+    NfcPlaylistSettings_Timeout,
+    NfcPlaylistSettings_Delay,
+    NfcPlaylistSettings_LedIndicator,
+    NfcPlaylistSettings_Reset
+} NfcPlaylistMenuSelection;
+
+void nfc_playlist_settings_menu_callback(void* context, uint32_t index) {
+    NfcPlaylist* nfc_playlist = context;
+    if(index == NfcPlaylistSettings_Reset) {
+        nfc_playlist->settings.emulate_timeout = default_emulate_timeout;
+        VariableItem* emulation_timeout_settings =
+            variable_item_list_get(nfc_playlist->variable_item_list, NfcPlaylistSettings_Timeout);
+        variable_item_set_current_value_index(
+            emulation_timeout_settings, nfc_playlist->settings.emulate_timeout);
+        char emulation_timeout_settings_text[3];
+        snprintf(
+            emulation_timeout_settings_text,
+            3,
+            "%ds",
+            options_emulate_timeout[nfc_playlist->settings.emulate_timeout]);
+        variable_item_set_current_value_text(
+            emulation_timeout_settings, (char*)emulation_timeout_settings_text);
+
+        nfc_playlist->settings.emulate_delay = default_emulate_delay;
+        VariableItem* emulation_delay_settings =
+            variable_item_list_get(nfc_playlist->variable_item_list, NfcPlaylistSettings_Delay);
+        variable_item_set_current_value_index(
+            emulation_delay_settings, nfc_playlist->settings.emulate_delay);
+        char emulation_delay_settings_text[3];
+        snprintf(
+            emulation_delay_settings_text,
+            3,
+            "%ds",
+            options_emulate_delay[nfc_playlist->settings.emulate_delay]);
+        variable_item_set_current_value_text(
+            emulation_delay_settings, (char*)emulation_delay_settings_text);
+
+        nfc_playlist->settings.emulate_led_indicator = default_emulate_led_indicator;
+        VariableItem* emulation_led_indicator_settings = variable_item_list_get(
+            nfc_playlist->variable_item_list, NfcPlaylistSettings_LedIndicator);
+        variable_item_set_current_value_index(
+            emulation_led_indicator_settings, nfc_playlist->settings.emulate_led_indicator);
+        variable_item_set_current_value_text(
+            emulation_led_indicator_settings,
+            nfc_playlist->settings.emulate_led_indicator ? "ON" : "OFF");
+    }
+}
+
+void nfc_playlist_settings_options_change_callback(VariableItem* item) {
+    NfcPlaylist* nfc_playlist = variable_item_get_context(item);
+
+    uint8_t current_option =
+        variable_item_list_get_selected_item_index(nfc_playlist->variable_item_list);
+    uint8_t option_value_index = variable_item_get_current_value_index(item);
+
+    switch(current_option) {
+    case NfcPlaylistSettings_Timeout: {
+        nfc_playlist->settings.emulate_timeout = option_value_index;
+        char emulate_timeout_text[3];
+        snprintf(emulate_timeout_text, 3, "%ds", options_emulate_timeout[option_value_index]);
+        variable_item_set_current_value_text(item, (char*)emulate_timeout_text);
+        break;
+    }
+    case NfcPlaylistSettings_Delay: {
+        nfc_playlist->settings.emulate_delay = option_value_index;
+        char emulate_delay_text[3];
+        snprintf(emulate_delay_text, 3, "%ds", options_emulate_delay[option_value_index]);
+        variable_item_set_current_value_text(item, (char*)emulate_delay_text);
+        break;
+    }
+    case NfcPlaylistSettings_LedIndicator:
+        nfc_playlist->settings.emulate_led_indicator = option_value_index;
+        variable_item_set_current_value_text(
+            item, nfc_playlist->settings.emulate_led_indicator ? "ON" : "OFF");
+        break;
+    default:
+        break;
+    }
+}
+
+void nfc_playlist_settings_scene_on_enter(void* context) {
+    NfcPlaylist* nfc_playlist = context;
+
+    //variable_item_list_set_header(nfc_playlist->variable_item_list, "Settings");
+
+    VariableItem* emulation_timeout_settings = variable_item_list_add(
+        nfc_playlist->variable_item_list,
+        "Emulate time",
+        (sizeof(options_emulate_timeout) / sizeof(options_emulate_timeout[0])),
+        nfc_playlist_settings_options_change_callback,
+        nfc_playlist);
+    variable_item_set_current_value_index(
+        emulation_timeout_settings, nfc_playlist->settings.emulate_timeout);
+    char emulation_timeout_settings_text[3];
+    snprintf(
+        emulation_timeout_settings_text,
+        3,
+        "%ds",
+        options_emulate_timeout[nfc_playlist->settings.emulate_timeout]);
+    variable_item_set_current_value_text(
+        emulation_timeout_settings, (char*)emulation_timeout_settings_text);
+
+    VariableItem* emulation_delay_settings = variable_item_list_add(
+        nfc_playlist->variable_item_list,
+        "Delay time",
+        (sizeof(options_emulate_delay) / sizeof(options_emulate_delay[0])),
+        nfc_playlist_settings_options_change_callback,
+        nfc_playlist);
+    variable_item_set_current_value_index(
+        emulation_delay_settings, nfc_playlist->settings.emulate_delay);
+    char emulation_delay_settings_text[3];
+    snprintf(
+        emulation_delay_settings_text,
+        3,
+        "%ds",
+        options_emulate_delay[nfc_playlist->settings.emulate_delay]);
+    variable_item_set_current_value_text(
+        emulation_delay_settings, (char*)emulation_delay_settings_text);
+
+    VariableItem* emulation_led_indicator_settings = variable_item_list_add(
+        nfc_playlist->variable_item_list,
+        "LED Indicator",
+        2,
+        nfc_playlist_settings_options_change_callback,
+        nfc_playlist);
+    variable_item_set_current_value_index(
+        emulation_led_indicator_settings, nfc_playlist->settings.emulate_led_indicator);
+    variable_item_set_current_value_text(
+        emulation_led_indicator_settings,
+        nfc_playlist->settings.emulate_led_indicator ? "ON" : "OFF");
+
+    variable_item_list_add(nfc_playlist->variable_item_list, "Reset settings", 0, NULL, NULL);
+
+    variable_item_list_set_enter_callback(
+        nfc_playlist->variable_item_list, nfc_playlist_settings_menu_callback, nfc_playlist);
+
+    view_dispatcher_switch_to_view(nfc_playlist->view_dispatcher, NfcPlaylistView_Settings);
+}
+
+bool nfc_playlist_settings_scene_on_event(void* context, SceneManagerEvent event) {
+    UNUSED(context);
+    UNUSED(event);
+    return false;
+}
+
+void nfc_playlist_settings_scene_on_exit(void* context) {
+    NfcPlaylist* nfc_playlist = context;
+    variable_item_list_reset(nfc_playlist->variable_item_list);
+}

+ 10 - 0
scences/settings.h

@@ -0,0 +1,10 @@
+#pragma once
+#include <furi.h>
+#include <gui/gui.h>
+#include <gui/view_dispatcher.h>
+#include <gui/scene_manager.h>
+#include <gui/modules/variable_item_list.h>
+
+void nfc_playlist_settings_scene_on_enter(void* context);
+bool nfc_playlist_settings_scene_on_event(void* context, SceneManagerEvent event);
+void nfc_playlist_settings_scene_on_exit(void* context);

+ 67 - 0
scences/text_input.c

@@ -0,0 +1,67 @@
+#include "nfc_playlist.h"
+#include "scences/text_input.h"
+
+void nfc_playlist_text_input_menu_callback(void* context) {
+    NfcPlaylist* nfc_playlist = context;
+    Storage* storage = furi_record_open(RECORD_STORAGE);
+
+    char const* old_file_path = (char*)furi_string_get_cstr(nfc_playlist->settings.file_path);
+    char const* old_file_name =
+        strchr(old_file_path, '/') != NULL ? &strrchr(old_file_path, '/')[1] : old_file_path;
+
+    int file_path_size = (strlen(old_file_path) - strlen(old_file_name) + 1);
+
+    char* file_path = (char*)malloc(file_path_size);
+    snprintf(file_path, file_path_size, "%s", old_file_path);
+
+    int new_file_path_size =
+        (strlen(nfc_playlist->playlist_name) + strlen(".txt") + file_path_size + 1);
+
+    char* new_file_name = (char*)malloc(new_file_path_size);
+    snprintf(
+        new_file_name,
+        new_file_path_size,
+        "%s%s%s",
+        file_path,
+        nfc_playlist->playlist_name,
+        ".txt");
+
+    if(!storage_file_exists(storage, new_file_name)) {
+        storage_common_rename(
+            storage, furi_string_get_cstr(nfc_playlist->settings.file_path), new_file_name);
+        nfc_playlist->settings.file_path = furi_string_alloc_set_str(new_file_name);
+    }
+
+    free(new_file_name);
+    free(file_path);
+    free(nfc_playlist->playlist_name);
+    furi_record_close(RECORD_STORAGE);
+
+    scene_manager_previous_scene(nfc_playlist->scene_manager);
+}
+
+void nfc_playlist_text_input_scene_on_enter(void* context) {
+    NfcPlaylist* nfc_playlist = context;
+    nfc_playlist->playlist_name = (char*)malloc(50);
+    text_input_set_header_text(nfc_playlist->text_input, "Enter new file name");
+    text_input_set_minimum_length(nfc_playlist->text_input, 1);
+    text_input_set_result_callback(
+        nfc_playlist->text_input,
+        nfc_playlist_text_input_menu_callback,
+        nfc_playlist,
+        nfc_playlist->playlist_name,
+        50,
+        true);
+    view_dispatcher_switch_to_view(nfc_playlist->view_dispatcher, NfcPlaylistView_TextInput);
+}
+
+bool nfc_playlist_text_input_scene_on_event(void* context, SceneManagerEvent event) {
+    UNUSED(context);
+    UNUSED(event);
+    return false;
+}
+
+void nfc_playlist_text_input_scene_on_exit(void* context) {
+    NfcPlaylist* nfc_playlist = context;
+    text_input_reset(nfc_playlist->text_input);
+}

+ 13 - 0
scences/text_input.h

@@ -0,0 +1,13 @@
+#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/file_browser.h>
+#include <gui/modules/text_input.h>
+#include <storage/storage.h>
+
+void nfc_playlist_text_input_scene_on_enter(void* context);
+bool nfc_playlist_text_input_scene_on_event(void* context, SceneManagerEvent event);
+void nfc_playlist_text_input_scene_on_exit(void* context);