Browse Source

Merge flip_wifi from https://github.com/jblanked/FlipWiFi

Willy-JL 1 năm trước cách đây
mục cha
commit
865f50dd77

+ 9 - 1
flip_wifi/CHANGELOG.md

@@ -1,7 +1,15 @@
+## v1.3  
+- Updated to save credentials for the FlipWorld game.  
+- Added fast commands: CUSTOM, PING, LIST, IP/ADDRESS, and WIFI/IP.  
+- Improved memory allocation.  
+- Increased the WiFi scan list capacity from 25 to 100.  
+
+## v1.2.1
+- Fixed crash when saving networks manually.
+
 ## v1.2
 - Updated scan loading and parsing.
 - Added connectivity check on startup. (thanks to Derek Jamison)
-- Fixed crash when saving networks manually.
 
 ## v1.1
 - Fixed a freeze issue when configurations did not exist (thanks to WillyJL).  

+ 14 - 189
flip_wifi/alloc/flip_wifi_alloc.c

@@ -1,205 +1,30 @@
-#include <alloc/flip_wifi_alloc.h>
+#include <callback/flip_wifi_callback.h>
 
 // Function to allocate resources for the FlipWiFiApp
-FlipWiFiApp* flip_wifi_app_alloc() {
-    FlipWiFiApp* app = (FlipWiFiApp*)malloc(sizeof(FlipWiFiApp));
+FlipWiFiApp *flip_wifi_app_alloc()
+{
+    FlipWiFiApp *app = (FlipWiFiApp *)malloc(sizeof(FlipWiFiApp));
 
-    Gui* gui = furi_record_open(RECORD_GUI);
-
-    // initialize uart
-    if(!flipper_http_init(flipper_http_rx_callback, app)) {
-        FURI_LOG_E(TAG, "Failed to initialize flipper http");
-        return NULL;
-    }
-
-    // Allocate the text input buffer
-    app->uart_text_input_buffer_size_password_scan = 64;
-    app->uart_text_input_buffer_size_password_saved = 64;
-    app->uart_text_input_buffer_size_add_ssid = 64;
-    app->uart_text_input_buffer_size_add_password = 64;
-    if(!easy_flipper_set_buffer(
-           &app->uart_text_input_buffer_password_scan,
-           app->uart_text_input_buffer_size_password_scan)) {
-        return NULL;
-    }
-    if(!easy_flipper_set_buffer(
-           &app->uart_text_input_temp_buffer_password_scan,
-           app->uart_text_input_buffer_size_password_scan)) {
-        return NULL;
-    }
-    if(!easy_flipper_set_buffer(
-           &app->uart_text_input_buffer_password_saved,
-           app->uart_text_input_buffer_size_password_saved)) {
-        return NULL;
-    }
-    if(!easy_flipper_set_buffer(
-           &app->uart_text_input_temp_buffer_password_saved,
-           app->uart_text_input_buffer_size_password_saved)) {
-        return NULL;
-    }
-    if(!easy_flipper_set_buffer(
-           &app->uart_text_input_buffer_add_ssid, app->uart_text_input_buffer_size_add_ssid)) {
-        return NULL;
-    }
-    if(!easy_flipper_set_buffer(
-           &app->uart_text_input_temp_buffer_add_ssid,
-           app->uart_text_input_buffer_size_add_ssid)) {
-        return NULL;
-    }
-    if(!easy_flipper_set_buffer(
-           &app->uart_text_input_buffer_add_password,
-           app->uart_text_input_buffer_size_add_password)) {
-        return NULL;
-    }
-    if(!easy_flipper_set_buffer(
-           &app->uart_text_input_temp_buffer_add_password,
-           app->uart_text_input_buffer_size_add_password)) {
-        return NULL;
-    }
+    Gui *gui = furi_record_open(RECORD_GUI);
 
     // Allocate ViewDispatcher
-    if(!easy_flipper_set_view_dispatcher(&app->view_dispatcher, gui, app)) {
-        return NULL;
-    }
-
-    // View(s)
-    if(!easy_flipper_set_view(
-           &app->view_wifi_scan,
-           FlipWiFiViewWiFiScan,
-           flip_wifi_view_draw_callback_scan,
-           flip_wifi_view_input_callback_scan,
-           callback_to_submenu_scan,
-           &app->view_dispatcher,
-           app)) {
-        return NULL;
-    }
-    if(!easy_flipper_set_view(
-           &app->view_wifi_saved,
-           FlipWiFiViewWiFiSaved,
-           flip_wifi_view_draw_callback_saved,
-           flip_wifi_view_input_callback_saved,
-           callback_to_submenu_saved,
-           &app->view_dispatcher,
-           app)) {
-        return NULL;
-    }
-
-    // Widget
-    if(!easy_flipper_set_widget(
-           &app->widget_info,
-           FlipWiFiViewAbout,
-           "FlipWiFi v1.2.1\n-----\nFlipperHTTP companion app.\nScan and save WiFi networks.\n-----\nwww.github.com/jblanked",
-           callback_to_submenu_main,
-           &app->view_dispatcher)) {
-        return NULL;
-    }
-
-    // Text Input
-    if(!easy_flipper_set_uart_text_input(
-           &app->uart_text_input_password_scan,
-           FlipWiFiViewTextInputScan,
-           "Enter WiFi Password",
-           app->uart_text_input_temp_buffer_password_scan,
-           app->uart_text_input_buffer_size_password_scan,
-           flip_wifi_text_updated_password_scan,
-           callback_to_submenu_scan,
-           &app->view_dispatcher,
-           app)) {
-        return NULL;
-    }
-    if(!easy_flipper_set_uart_text_input(
-           &app->uart_text_input_password_saved,
-           FlipWiFiViewTextInputSaved,
-           "Enter WiFi Password",
-           app->uart_text_input_temp_buffer_password_saved,
-           app->uart_text_input_buffer_size_password_saved,
-           flip_wifi_text_updated_password_saved,
-           callback_to_submenu_saved,
-           &app->view_dispatcher,
-           app)) {
-        return NULL;
-    }
-    if(!easy_flipper_set_uart_text_input(
-           &app->uart_text_input_add_ssid,
-           FlipWiFiViewTextInputSavedAddSSID,
-           "Enter SSID",
-           app->uart_text_input_temp_buffer_add_ssid,
-           app->uart_text_input_buffer_size_add_ssid,
-           flip_wifi_text_updated_add_ssid,
-           callback_to_submenu_saved,
-           &app->view_dispatcher,
-           app)) {
-        return NULL;
-    }
-    if(!easy_flipper_set_uart_text_input(
-           &app->uart_text_input_add_password,
-           FlipWiFiViewTextInputSavedAddPassword,
-           "Enter Password",
-           app->uart_text_input_temp_buffer_add_password,
-           app->uart_text_input_buffer_size_add_password,
-           flip_wifi_text_updated_add_password,
-           callback_to_submenu_saved,
-           &app->view_dispatcher,
-           app)) {
+    if (!easy_flipper_set_view_dispatcher(&app->view_dispatcher, gui, app))
+    {
         return NULL;
     }
 
     // Submenu
-    if(!easy_flipper_set_submenu(
-           &app->submenu_main,
-           FlipWiFiViewSubmenuMain,
-           "FlipWiFi v1.2.1",
-           easy_flipper_callback_exit_app,
-           &app->view_dispatcher)) {
-        return NULL;
-    }
-    if(!easy_flipper_set_submenu(
-           &app->submenu_wifi_scan,
-           FlipWiFiViewSubmenuScan,
-           "WiFi Scan",
-           callback_to_submenu_main,
-           &app->view_dispatcher)) {
+    if (!easy_flipper_set_submenu(&app->submenu_main, FlipWiFiViewSubmenuMain, "FlipWiFi v1.3", callback_exit_app, &app->view_dispatcher))
+    {
         return NULL;
     }
-    if(!easy_flipper_set_submenu(
-           &app->submenu_wifi_saved,
-           FlipWiFiViewSubmenuSaved,
-           "Saved APs",
-           callback_to_submenu_main,
-           &app->view_dispatcher)) {
-        return NULL;
-    }
-    submenu_add_item(
-        app->submenu_main, "Scan", FlipWiFiSubmenuIndexWiFiScan, callback_submenu_choices, app);
-    submenu_add_item(
-        app->submenu_main,
-        "Saved APs",
-        FlipWiFiSubmenuIndexWiFiSaved,
-        callback_submenu_choices,
-        app);
-    submenu_add_item(
-        app->submenu_main, "Info", FlipWiFiSubmenuIndexAbout, callback_submenu_choices, app);
-
-    // Load the playlist from storage
-    if(!load_playlist(&app->wifi_playlist)) {
-        FURI_LOG_E(TAG, "Failed to load playlist");
-
-        // playlist is empty?
-        submenu_reset(app->submenu_wifi_saved);
-        submenu_set_header(app->submenu_wifi_saved, "Saved APs");
-        submenu_add_item(
-            app->submenu_wifi_saved,
-            "[Add Network]",
-            FlipWiFiSubmenuIndexWiFiSavedAddSSID,
-            callback_submenu_choices,
-            app);
-    } else {
-        // Update the submenu
-        flip_wifi_redraw_submenu_saved(app);
-    }
+    submenu_add_item(app->submenu_main, "Scan", FlipWiFiSubmenuIndexWiFiScan, callback_submenu_choices, app);
+    submenu_add_item(app->submenu_main, "Saved APs", FlipWiFiSubmenuIndexWiFiSaved, callback_submenu_choices, app);
+    submenu_add_item(app->submenu_main, "Commands", FlipWiFiSubmenuIndexCommands, callback_submenu_choices, app);
+    submenu_add_item(app->submenu_main, "Info", FlipWiFiSubmenuIndexAbout, callback_submenu_choices, app);
 
     // Switch to the main view
     view_dispatcher_switch_to_view(app->view_dispatcher, FlipWiFiViewSubmenuMain);
 
     return app;
-}
+}

+ 2 - 7
flip_wifi/alloc/flip_wifi_alloc.h

@@ -1,11 +1,6 @@
-#ifndef FLIP_WIFI_I_H
-#define FLIP_WIFI_I_H
+#pragma once
 
 #include <flip_wifi.h>
-#include <flip_storage/flip_wifi_storage.h>
-#include <callback/flip_wifi_callback.h>
 
 // Function to allocate resources for the FlipWiFiApp
-FlipWiFiApp* flip_wifi_app_alloc();
-
-#endif // FLIP_WIFI_I_H
+FlipWiFiApp *flip_wifi_app_alloc();

+ 26 - 30
flip_wifi/app.c

@@ -2,55 +2,51 @@
 #include <alloc/flip_wifi_alloc.h>
 
 // Entry point for the FlipWiFi application
-int32_t flip_wifi_main(void* p) {
-    // Suppress unused parameter warning
+int32_t flip_wifi_main(void *p)
+{
     UNUSED(p);
 
     // Initialize the FlipWiFi application
-    app_instance = flip_wifi_app_alloc();
-    if(!app_instance) {
+    FlipWiFiApp *app = flip_wifi_app_alloc();
+    if (!app)
+    {
         FURI_LOG_E(TAG, "Failed to allocate FlipWiFiApp");
         return -1;
     }
 
-    if(!flipper_http_ping()) {
-        FURI_LOG_E(TAG, "Failed to ping the device");
-        return -1;
-    }
+    // check if board is connected (Derek Jamison)
+    // initialize the http
+    if (flipper_http_init(flipper_http_rx_callback, app))
+    {
+        if (!flipper_http_ping())
+        {
+            FURI_LOG_E(TAG, "Failed to ping the device");
+            return -1;
+        }
 
-    // Thanks to Derek Jamison for the following code snippet:
-    if(app_instance->uart_text_input_buffer_add_ssid != NULL &&
-       app_instance->uart_text_input_buffer_add_password != NULL) {
         // Try to wait for pong response.
         uint8_t counter = 10;
-        while(fhttp.state == INACTIVE && --counter > 0) {
+        while (fhttp.state == INACTIVE && --counter > 0)
+        {
             FURI_LOG_D(TAG, "Waiting for PONG");
             furi_delay_ms(100);
         }
 
-        if(counter == 0) {
-            DialogsApp* dialogs = furi_record_open(RECORD_DIALOGS);
-            DialogMessage* message = dialog_message_alloc();
-            dialog_message_set_header(
-                message, "[FlipperHTTP Error]", 64, 0, AlignCenter, AlignTop);
-            dialog_message_set_text(
-                message,
-                "Ensure your WiFi Developer\nBoard or Pico W is connected\nand the latest FlipperHTTP\nfirmware is installed.",
-                0,
-                63,
-                AlignLeft,
-                AlignBottom);
-            dialog_message_show(dialogs, message);
-            dialog_message_free(message);
-            furi_record_close(RECORD_DIALOGS);
-        }
+        if (counter == 0)
+            easy_flipper_dialog("FlipperHTTP Error", "Ensure your WiFi Developer\nBoard or Pico W is connected\nand the latest FlipperHTTP\nfirmware is installed.");
+
+        flipper_http_deinit();
+    }
+    else
+    {
+        easy_flipper_dialog("FlipperHTTP Error", "The UART is likely busy.\nEnsure you have the correct\nflash for your board then\nrestart your Flipper Zero.");
     }
 
     // Run the view dispatcher
-    view_dispatcher_run(app_instance->view_dispatcher);
+    view_dispatcher_run(app->view_dispatcher);
 
     // Free the resources used by the FlipWiFi application
-    flip_wifi_app_free(app_instance);
+    flip_wifi_app_free(app);
 
     // Return 0 to indicate success
     return 0;

+ 1 - 1
flip_wifi/application.fam

@@ -9,6 +9,6 @@ App(
     fap_icon_assets="assets",
     fap_author="JBlanked",
     fap_weburl="https://github.com/jblanked/FlipWiFi",
-    fap_version="1.2.1",
+    fap_version="1.3",
     fap_description="FlipperHTTP companion app.",
 )

BIN
flip_wifi/assets/01-home.png


+ 797 - 263
flip_wifi/callback/flip_wifi_callback.c

@@ -1,74 +1,493 @@
 #include <callback/flip_wifi_callback.h>
 
-char* ssid_list[64];
-uint32_t ssid_index = 0;
+static char *ssid_list[64];
+static uint32_t ssid_index = 0;
+static char current_ssid[64];
+static char current_password[64];
 
-void flip_wifi_redraw_submenu_saved(FlipWiFiApp* app) {
-    // re draw the saved submenu
-    submenu_reset(app->submenu_wifi_saved);
-    submenu_set_header(app->submenu_wifi_saved, "Saved APs");
-    submenu_add_item(
-        app->submenu_wifi_saved,
-        "[Add Network]",
-        FlipWiFiSubmenuIndexWiFiSavedAddSSID,
-        callback_submenu_choices,
-        app);
-    for(uint32_t i = 0; i < app->wifi_playlist.count; i++) {
-        submenu_add_item(
-            app->submenu_wifi_saved,
-            app->wifi_playlist.ssids[i],
-            FlipWiFiSubmenuIndexWiFiSavedStart + i,
-            callback_submenu_choices,
-            app);
+static void flip_wifi_redraw_submenu_saved(void *context);
+static void flip_wifi_view_draw_callback_scan(Canvas *canvas, void *model);
+static void flip_wifi_view_draw_callback_saved(Canvas *canvas, void *model);
+static bool flip_wifi_view_input_callback_scan(InputEvent *event, void *context);
+static bool flip_wifi_view_input_callback_saved(InputEvent *event, void *context);
+static uint32_t callback_to_submenu_saved(void *context);
+static uint32_t callback_to_submenu_scan(void *context);
+static uint32_t callback_to_submenu_main(void *context);
+
+void flip_wifi_text_updated_password_scan(void *context);
+void flip_wifi_text_updated_password_saved(void *context);
+void flip_wifi_text_updated_add_ssid(void *context);
+void flip_wifi_text_updated_add_password(void *context);
+
+static bool flip_wifi_alloc_playlist(void *context)
+{
+    FlipWiFiApp *app = (FlipWiFiApp *)context;
+    if (!app)
+    {
+        FURI_LOG_E(TAG, "FlipWiFiApp is NULL");
+        return false;
+    }
+    if (!wifi_playlist)
+    {
+        wifi_playlist = (WiFiPlaylist *)malloc(sizeof(WiFiPlaylist));
+        if (!wifi_playlist)
+        {
+            FURI_LOG_E(TAG, "Failed to allocate playlist");
+            return false;
+        }
+        wifi_playlist->count = 0;
+    }
+    // Load the playlist from storage
+    if (!load_playlist(wifi_playlist))
+    {
+        FURI_LOG_E(TAG, "Failed to load playlist");
+
+        // playlist is empty?
+        submenu_reset(app->submenu_wifi);
+        submenu_set_header(app->submenu_wifi, "Saved APs");
+        submenu_add_item(app->submenu_wifi, "[Add Network]", FlipWiFiSubmenuIndexWiFiSavedAddSSID, callback_submenu_choices, app);
+    }
+    else
+    {
+        // Update the submenu
+        flip_wifi_redraw_submenu_saved(app);
+    }
+    return true;
+}
+static void flip_wifi_free_playlist(void)
+{
+    if (wifi_playlist)
+    {
+        free(wifi_playlist);
+        wifi_playlist = NULL;
     }
 }
 
-uint32_t callback_to_submenu_main(void* context) {
-    if(!context) {
-        FURI_LOG_E(TAG, "Context is NULL");
-        return VIEW_NONE;
+static bool flip_wifi_alloc_views(void *context, uint32_t view)
+{
+    FlipWiFiApp *app = (FlipWiFiApp *)context;
+    if (!app)
+    {
+        FURI_LOG_E(TAG, "FlipWiFiApp is NULL");
+        return false;
+    }
+    switch (view)
+    {
+    case FlipWiFiViewWiFiScan:
+        if (!app->view_wifi)
+        {
+            if (!easy_flipper_set_view(&app->view_wifi, FlipWiFiViewGeneric, flip_wifi_view_draw_callback_scan, flip_wifi_view_input_callback_scan, callback_to_submenu_scan, &app->view_dispatcher, app))
+            {
+                return false;
+            }
+            if (!app->view_wifi)
+            {
+                FURI_LOG_E(TAG, "Failed to allocate view for WiFi Scan");
+                return false;
+            }
+        }
+        return true;
+    case FlipWiFiViewWiFiSaved:
+        if (!app->view_wifi)
+        {
+            if (!easy_flipper_set_view(&app->view_wifi, FlipWiFiViewGeneric, flip_wifi_view_draw_callback_saved, flip_wifi_view_input_callback_saved, callback_to_submenu_saved, &app->view_dispatcher, app))
+            {
+                return false;
+            }
+            if (!app->view_wifi)
+            {
+                FURI_LOG_E(TAG, "Failed to allocate view for WiFi Scan");
+                return false;
+            }
+        }
+        return true;
+    default:
+        return false;
     }
-    UNUSED(context);
-    ssid_index = 0;
-    return FlipWiFiViewSubmenuMain;
 }
-uint32_t callback_to_submenu_scan(void* context) {
-    if(!context) {
-        FURI_LOG_E(TAG, "Context is NULL");
-        return VIEW_NONE;
+static void flip_wifi_free_views(void *context)
+{
+    FlipWiFiApp *app = (FlipWiFiApp *)context;
+    if (!app)
+    {
+        FURI_LOG_E(TAG, "FlipWiFiApp is NULL");
+        return;
+    }
+    if (app->view_wifi)
+    {
+        free(app->view_wifi);
+        app->view_wifi = NULL;
+        view_dispatcher_remove_view(app->view_dispatcher, FlipWiFiViewGeneric);
     }
-    UNUSED(context);
-    ssid_index = 0;
-    return FlipWiFiViewSubmenuScan;
 }
-uint32_t callback_to_submenu_saved(void* context) {
-    if(!context) {
-        FURI_LOG_E(TAG, "Context is NULL");
-        return VIEW_NONE;
+static bool flip_wifi_alloc_widgets(void *context, uint32_t widget)
+{
+    FlipWiFiApp *app = (FlipWiFiApp *)context;
+    if (!app)
+    {
+        FURI_LOG_E(TAG, "FlipWiFiApp is NULL");
+        return false;
+    }
+    switch (widget)
+    {
+    case FlipWiFiViewAbout:
+        if (!app->widget_info)
+        {
+            if (!easy_flipper_set_widget(&app->widget_info, FlipWiFiViewAbout, "FlipWiFi v1.3\n-----\nFlipperHTTP companion app.\nScan and save WiFi networks.\n-----\nwww.github.com/jblanked", callback_to_submenu_main, &app->view_dispatcher))
+            {
+                return false;
+            }
+            if (!app->widget_info)
+            {
+                FURI_LOG_E(TAG, "Failed to allocate widget for About");
+                return false;
+            }
+        }
+        return true;
+    default:
+        return false;
     }
-    UNUSED(context);
-    ssid_index = 0;
-    return FlipWiFiViewSubmenuSaved;
 }
-static void popup_callback_saved(void* context) {
-    FlipWiFiApp* app = (FlipWiFiApp*)context;
-    if(!app) {
-        FURI_LOG_E(TAG, "HelloWorldApp is NULL");
+static void flip_wifi_free_widgets(void *context)
+{
+    FlipWiFiApp *app = (FlipWiFiApp *)context;
+    if (!app)
+    {
+        FURI_LOG_E(TAG, "FlipWiFiApp is NULL");
         return;
     }
-    view_dispatcher_switch_to_view(app->view_dispatcher, FlipWiFiViewSubmenuSaved);
+    if (app->widget_info)
+    {
+        free(app->widget_info);
+        app->widget_info = NULL;
+        view_dispatcher_remove_view(app->view_dispatcher, FlipWiFiViewAbout);
+    }
 }
-static void popup_callback_main(void* context) {
-    FlipWiFiApp* app = (FlipWiFiApp*)context;
-    if(!app) {
-        FURI_LOG_E(TAG, "HelloWorldApp is NULL");
+static bool flip_wifi_alloc_submenus(void *context, uint32_t view)
+{
+    FlipWiFiApp *app = (FlipWiFiApp *)context;
+    if (!app)
+    {
+        FURI_LOG_E(TAG, "FlipWiFiApp is NULL");
+        return false;
+    }
+    switch (view)
+    {
+    case FlipWiFiViewSubmenuScan:
+        if (!app->submenu_wifi)
+        {
+            if (!easy_flipper_set_submenu(&app->submenu_wifi, FlipWiFiViewSubmenu, "WiFi Nearby", callback_to_submenu_main, &app->view_dispatcher))
+            {
+                return false;
+            }
+            if (!app->submenu_wifi)
+            {
+                FURI_LOG_E(TAG, "Failed to allocate submenu for WiFi Scan");
+                return false;
+            }
+        }
+        return true;
+    case FlipWiFiViewSubmenuSaved:
+        if (!app->submenu_wifi)
+        {
+            if (!easy_flipper_set_submenu(&app->submenu_wifi, FlipWiFiViewSubmenu, "Saved APs", callback_to_submenu_main, &app->view_dispatcher))
+            {
+                return false;
+            }
+            if (!app->submenu_wifi)
+            {
+                FURI_LOG_E(TAG, "Failed to allocate submenu for WiFi Saved");
+                return false;
+            }
+            if (!flip_wifi_alloc_playlist(app))
+            {
+                FURI_LOG_E(TAG, "Failed to allocate playlist");
+                return false;
+            }
+        }
+        return true;
+    case FlipWiFiViewSubmenuCommands:
+        if (!app->submenu_wifi)
+        {
+            if (!easy_flipper_set_submenu(&app->submenu_wifi, FlipWiFiViewSubmenu, "Fast Commands", callback_to_submenu_main, &app->view_dispatcher))
+            {
+                return false;
+            }
+            if (!app->submenu_wifi)
+            {
+                FURI_LOG_E(TAG, "Failed to allocate submenu for Commands");
+                return false;
+            }
+            //  PING, LIST, WIFI/LIST, IP/ADDRESS, and WIFI/IP.
+            submenu_add_item(app->submenu_wifi, "[CUSTOM]", FlipWiFiSubmenuIndexFastCommandStart + 0, callback_submenu_choices, app);
+            submenu_add_item(app->submenu_wifi, "PING", FlipWiFiSubmenuIndexFastCommandStart + 1, callback_submenu_choices, app);
+            submenu_add_item(app->submenu_wifi, "LIST", FlipWiFiSubmenuIndexFastCommandStart + 2, callback_submenu_choices, app);
+            submenu_add_item(app->submenu_wifi, "IP/ADDRESS", FlipWiFiSubmenuIndexFastCommandStart + 3, callback_submenu_choices, app);
+            submenu_add_item(app->submenu_wifi, "WIFI/IP", FlipWiFiSubmenuIndexFastCommandStart + 4, callback_submenu_choices, app);
+        }
+        return true;
+    }
+    return false;
+}
+static void flip_wifi_free_submenus(void *context)
+{
+    FlipWiFiApp *app = (FlipWiFiApp *)context;
+    if (!app)
+    {
+        FURI_LOG_E(TAG, "FlipWiFiApp is NULL");
         return;
     }
-    view_dispatcher_switch_to_view(app->view_dispatcher, FlipWiFiViewSubmenuMain);
+    if (app->submenu_wifi)
+    {
+        free(app->submenu_wifi);
+        app->submenu_wifi = NULL;
+        view_dispatcher_remove_view(app->view_dispatcher, FlipWiFiViewSubmenu);
+    }
+}
+
+static void flip_wifi_custom_command_updated(void *context)
+{
+    FlipWiFiApp *app = (FlipWiFiApp *)context;
+    if (!app)
+    {
+        FURI_LOG_E(TAG, "FlipWiFiApp is NULL");
+        return;
+    }
+    if (!app->uart_text_input_temp_buffer)
+    {
+        FURI_LOG_E(TAG, "Text input buffer is NULL");
+        return;
+    }
+    if (!app->uart_text_input_temp_buffer[0])
+    {
+        FURI_LOG_E(TAG, "Text input buffer is empty");
+        return;
+    }
+    // Send the custom command
+    flipper_http_send_data(app->uart_text_input_temp_buffer);
+    while (fhttp.last_response == NULL || strlen(fhttp.last_response) == 0)
+    {
+        furi_delay_ms(100);
+    }
+    // Switch to the view
+    char response[100];
+    snprintf(response, sizeof(response), "%s", fhttp.last_response);
+    easy_flipper_dialog("", response);
+    flipper_http_deinit();
+    view_dispatcher_switch_to_view(app->view_dispatcher, FlipWiFiViewSubmenu);
+}
+
+static bool flip_wifi_alloc_text_inputs(void *context, uint32_t view)
+{
+    FlipWiFiApp *app = (FlipWiFiApp *)context;
+    if (!app)
+    {
+        FURI_LOG_E(TAG, "FlipWiFiApp is NULL");
+        return false;
+    }
+    app->uart_text_input_buffer_size = MAX_SSID_LENGTH;
+    if (!app->uart_text_input_buffer)
+    {
+        if (!easy_flipper_set_buffer(&app->uart_text_input_buffer, app->uart_text_input_buffer_size))
+        {
+            FURI_LOG_E(TAG, "Failed to allocate text input buffer");
+            return false;
+        }
+        if (!app->uart_text_input_buffer)
+        {
+            FURI_LOG_E(TAG, "Failed to allocate text input buffer");
+            return false;
+        }
+    }
+    if (!app->uart_text_input_temp_buffer)
+    {
+        if (!easy_flipper_set_buffer(&app->uart_text_input_temp_buffer, app->uart_text_input_buffer_size))
+        {
+            FURI_LOG_E(TAG, "Failed to allocate text input temp buffer");
+            return false;
+        }
+        if (!app->uart_text_input_temp_buffer)
+        {
+            FURI_LOG_E(TAG, "Failed to allocate text input temp buffer");
+            return false;
+        }
+    }
+    switch (view)
+    {
+    case FlipWiFiViewTextInputScan:
+        if (!app->uart_text_input)
+        {
+            if (!easy_flipper_set_uart_text_input(&app->uart_text_input, FlipWiFiViewTextInput, "Enter WiFi Password", app->uart_text_input_temp_buffer, app->uart_text_input_buffer_size, flip_wifi_text_updated_password_scan, callback_to_submenu_scan, &app->view_dispatcher, app))
+            {
+                FURI_LOG_E(TAG, "Failed to allocate text input for WiFi Scan");
+                return false;
+            }
+            if (!app->uart_text_input)
+            {
+                FURI_LOG_E(TAG, "Failed to allocate text input for WiFi Scan");
+                return false;
+            }
+        }
+        return true;
+    case FlipWiFiViewTextInputSaved:
+        if (!app->uart_text_input)
+        {
+            if (!easy_flipper_set_uart_text_input(&app->uart_text_input, FlipWiFiViewTextInput, "Enter WiFi Password", app->uart_text_input_temp_buffer, app->uart_text_input_buffer_size, flip_wifi_text_updated_password_saved, callback_to_submenu_saved, &app->view_dispatcher, app))
+            {
+                FURI_LOG_E(TAG, "Failed to allocate text input for WiFi Saved");
+                return false;
+            }
+            if (!app->uart_text_input)
+            {
+                FURI_LOG_E(TAG, "Failed to allocate text input for WiFi Saved");
+                return false;
+            }
+        }
+        return true;
+    case FlipWiFiViewTextInputSavedAddSSID:
+        if (!app->uart_text_input)
+        {
+            if (!easy_flipper_set_uart_text_input(&app->uart_text_input, FlipWiFiViewTextInput, "Enter SSID", app->uart_text_input_temp_buffer, app->uart_text_input_buffer_size, flip_wifi_text_updated_add_ssid, callback_to_submenu_saved, &app->view_dispatcher, app))
+            {
+                FURI_LOG_E(TAG, "Failed to allocate text input for WiFi Saved Add SSID");
+                return false;
+            }
+            if (!app->uart_text_input)
+            {
+                FURI_LOG_E(TAG, "Failed to allocate text input for WiFi Saved Add SSID");
+                return false;
+            }
+        }
+        return true;
+    case FlipWiFiViewTextInputSavedAddPassword:
+        if (!app->uart_text_input)
+        {
+            if (!easy_flipper_set_uart_text_input(&app->uart_text_input, FlipWiFiViewTextInput, "Enter Password", app->uart_text_input_temp_buffer, app->uart_text_input_buffer_size, flip_wifi_text_updated_add_password, callback_to_submenu_saved, &app->view_dispatcher, app))
+            {
+                FURI_LOG_E(TAG, "Failed to allocate text input for WiFi Saved Add Password");
+                return false;
+            }
+            if (!app->uart_text_input)
+            {
+                FURI_LOG_E(TAG, "Failed to allocate text input for WiFi Saved Add Password");
+                return false;
+            }
+        }
+        return true;
+    case FlipWiFiSubmenuIndexFastCommandStart:
+        if (!app->uart_text_input)
+        {
+            if (!easy_flipper_set_uart_text_input(&app->uart_text_input, FlipWiFiViewTextInput, "Enter Command", app->uart_text_input_temp_buffer, app->uart_text_input_buffer_size, flip_wifi_custom_command_updated, callback_to_submenu_saved, &app->view_dispatcher, app))
+            {
+                FURI_LOG_E(TAG, "Failed to allocate text input for Fast Command");
+                return false;
+            }
+            if (!app->uart_text_input)
+            {
+                FURI_LOG_E(TAG, "Failed to allocate text input for Fast Command");
+                return false;
+            }
+        }
+        return true;
+    }
+    return false;
+}
+static void flip_wifi_free_text_inputs(void *context)
+{
+    FlipWiFiApp *app = (FlipWiFiApp *)context;
+    if (!app)
+    {
+        FURI_LOG_E(TAG, "FlipWiFiApp is NULL");
+        return;
+    }
+    if (app->uart_text_input)
+    {
+        free(app->uart_text_input);
+        app->uart_text_input = NULL;
+        view_dispatcher_remove_view(app->view_dispatcher, FlipWiFiViewTextInput);
+    }
+    if (app->uart_text_input_buffer)
+    {
+        free(app->uart_text_input_buffer);
+        app->uart_text_input_buffer = NULL;
+    }
+    if (app->uart_text_input_temp_buffer)
+    {
+        free(app->uart_text_input_temp_buffer);
+        app->uart_text_input_temp_buffer = NULL;
+    }
+}
+
+void flip_wifi_free_all(void *context)
+{
+    FlipWiFiApp *app = (FlipWiFiApp *)context;
+    if (!app)
+    {
+        FURI_LOG_E(TAG, "FlipWiFiApp is NULL");
+        return;
+    }
+    flip_wifi_free_views(app);
+    flip_wifi_free_widgets(app);
+    flip_wifi_free_submenus(app);
+    flip_wifi_free_text_inputs(app);
+    flip_wifi_free_playlist();
+}
+
+static void flip_wifi_redraw_submenu_saved(void *context)
+{
+    // re draw the saved submenu
+    FlipWiFiApp *app = (FlipWiFiApp *)context;
+    if (!app)
+    {
+        FURI_LOG_E(TAG, "FlipWiFiApp is NULL");
+        return;
+    }
+    if (!app->submenu_wifi)
+    {
+        FURI_LOG_E(TAG, "Submenu is NULL");
+        return;
+    }
+    if (!wifi_playlist)
+    {
+        FURI_LOG_E(TAG, "WiFi Playlist is NULL");
+        return;
+    }
+    submenu_reset(app->submenu_wifi);
+    submenu_set_header(app->submenu_wifi, "Saved APs");
+    submenu_add_item(app->submenu_wifi, "[Add Network]", FlipWiFiSubmenuIndexWiFiSavedAddSSID, callback_submenu_choices, app);
+    for (size_t i = 0; i < wifi_playlist->count; i++)
+    {
+        submenu_add_item(app->submenu_wifi, wifi_playlist->ssids[i], FlipWiFiSubmenuIndexWiFiSavedStart + i, callback_submenu_choices, app);
+    }
+}
+
+static uint32_t callback_to_submenu_main(void *context)
+{
+    UNUSED(context);
+    ssid_index = 0;
+    return FlipWiFiViewSubmenuMain;
+}
+static uint32_t callback_to_submenu_scan(void *context)
+{
+    UNUSED(context);
+    ssid_index = 0;
+    return FlipWiFiViewSubmenu;
+}
+static uint32_t callback_to_submenu_saved(void *context)
+{
+    UNUSED(context);
+    ssid_index = 0;
+    return FlipWiFiViewSubmenu;
+}
+uint32_t callback_exit_app(void *context)
+{
+    UNUSED(context);
+    return VIEW_NONE;
 }
 
 // Callback for drawing the main screen
-void flip_wifi_view_draw_callback_scan(Canvas* canvas, void* model) {
+static void flip_wifi_view_draw_callback_scan(Canvas *canvas, void *model)
+{
     UNUSED(model);
     canvas_clear(canvas);
     canvas_set_font(canvas, FontPrimary);
@@ -78,15 +497,15 @@ void flip_wifi_view_draw_callback_scan(Canvas* canvas, void* model) {
     canvas_draw_icon(canvas, 96, 53, &I_ButtonRight_4x7);
     canvas_draw_str_aligned(canvas, 103, 54, AlignLeft, AlignTop, "Add");
 }
-void flip_wifi_view_draw_callback_saved(Canvas* canvas, void* model) {
+static void flip_wifi_view_draw_callback_saved(Canvas *canvas, void *model)
+{
     UNUSED(model);
     canvas_clear(canvas);
     canvas_set_font(canvas, FontPrimary);
-    canvas_draw_str(canvas, 0, 10, app_instance->wifi_playlist.ssids[ssid_index]);
+    canvas_draw_str(canvas, 0, 10, current_ssid);
     canvas_set_font(canvas, FontSecondary);
-    char password[64];
-    snprintf(
-        password, sizeof(password), "Pass: %s", app_instance->wifi_playlist.passwords[ssid_index]);
+    char password[72];
+    snprintf(password, sizeof(password), "Pass: %s", current_password);
     canvas_draw_str(canvas, 0, 20, password);
     canvas_draw_icon(canvas, 0, 54, &I_ButtonLeft_4x7);
     canvas_draw_str_aligned(canvas, 7, 54, AlignLeft, AlignTop, "Delete");
@@ -99,95 +518,86 @@ void flip_wifi_view_draw_callback_saved(Canvas* canvas, void* model) {
 }
 
 // Input callback for the view (async input handling)
-bool flip_wifi_view_input_callback_scan(InputEvent* event, void* context) {
-    FlipWiFiApp* app = (FlipWiFiApp*)context;
-    if(event->type == InputTypePress && event->key == InputKeyRight) {
+static bool flip_wifi_view_input_callback_scan(InputEvent *event, void *context)
+{
+    FlipWiFiApp *app = (FlipWiFiApp *)context;
+    if (event->type == InputTypePress && event->key == InputKeyRight)
+    {
         // switch to text input to set password
-        view_dispatcher_switch_to_view(app->view_dispatcher, FlipWiFiViewTextInputScan);
+        flip_wifi_free_text_inputs(app);
+        if (!flip_wifi_alloc_text_inputs(app, FlipWiFiViewTextInputScan))
+        {
+            FURI_LOG_E(TAG, "Failed to allocate text input for WiFi Saved Add Password");
+            return false;
+        }
+        view_dispatcher_switch_to_view(app->view_dispatcher, FlipWiFiViewTextInput);
         return true;
     }
     return false;
 }
 // Input callback for the view (async input handling)
-bool flip_wifi_view_input_callback_saved(InputEvent* event, void* context) {
-    FlipWiFiApp* app = (FlipWiFiApp*)context;
-    if(!app) {
+static bool flip_wifi_view_input_callback_saved(InputEvent *event, void *context)
+{
+    FlipWiFiApp *app = (FlipWiFiApp *)context;
+    if (!app)
+    {
         FURI_LOG_E(TAG, "FlipWiFiApp is NULL");
         return false;
     }
-    if(event->type == InputTypePress && event->key == InputKeyRight) {
+    if (event->type == InputTypePress && event->key == InputKeyRight)
+    {
         // set text input buffer as the selected password
-        strncpy(
-            app->uart_text_input_temp_buffer_password_saved,
-            app->wifi_playlist.passwords[ssid_index],
-            app->uart_text_input_buffer_size_password_saved);
+        strncpy(app->uart_text_input_temp_buffer, wifi_playlist->passwords[ssid_index], app->uart_text_input_buffer_size);
         // switch to text input to set password
-        view_dispatcher_switch_to_view(app->view_dispatcher, FlipWiFiViewTextInputSaved);
-        return true;
-    } else if(event->type == InputTypePress && event->key == InputKeyOk) {
-        // save the settings
-        if(app->wifi_playlist.ssids[ssid_index] == NULL ||
-           app->wifi_playlist.passwords[ssid_index] == NULL) {
+        flip_wifi_free_text_inputs(app);
+        if (!flip_wifi_alloc_text_inputs(app, FlipWiFiViewTextInputSaved))
+        {
+            FURI_LOG_E(TAG, "Failed to allocate text input for WiFi Saved");
             return false;
         }
-        if(!app->popup) {
-            if(!easy_flipper_set_popup(
-                   &app->popup,
-                   FlipWiFiViewPopup,
-                   "[SUCCESS]",
-                   0,
-                   0,
-                   "All FlipperHTTP apps will now\nuse the selected network.",
-                   0,
-                   40,
-                   popup_callback_saved,
-                   callback_to_submenu_saved,
-                   &app->view_dispatcher,
-                   app)) {
-                return false;
-            }
-        }
-        save_settings(
-            app->wifi_playlist.ssids[ssid_index], app->wifi_playlist.passwords[ssid_index]);
+        view_dispatcher_switch_to_view(app->view_dispatcher, FlipWiFiViewTextInput);
+        return true;
+    }
+    else if (event->type == InputTypePress && event->key == InputKeyOk)
+    {
+        // save the settings
+        save_settings(wifi_playlist->ssids[ssid_index], wifi_playlist->passwords[ssid_index]);
 
-        flipper_http_save_wifi(
-            app->wifi_playlist.ssids[ssid_index], app->wifi_playlist.passwords[ssid_index]);
+        flipper_http_save_wifi(wifi_playlist->ssids[ssid_index], wifi_playlist->passwords[ssid_index]);
 
         flipper_http_connect_wifi();
 
-        popup_set_header(app->popup, "[SUCCESS]", 0, 0, AlignLeft, AlignTop);
-        popup_set_text(
-            app->popup,
-            "All FlipperHTTP apps will now\nuse the selected network.",
-            0,
-            40,
-            AlignLeft,
-            AlignTop);
-        view_set_previous_callback(popup_get_view(app->popup), callback_to_submenu_saved);
-        popup_set_callback(app->popup, popup_callback_saved);
-
-        view_dispatcher_switch_to_view(app->view_dispatcher, FlipWiFiViewPopup);
+        easy_flipper_dialog("[SUCCESS]", "All FlipperHTTP apps will now\nuse the selected network.");
         return true;
-    } else if(event->type == InputTypePress && event->key == InputKeyLeft) {
+    }
+    else if (event->type == InputTypePress && event->key == InputKeyLeft)
+    {
         // delete the selected ssid and password
-        free(app->wifi_playlist.ssids[ssid_index]);
-        free(app->wifi_playlist.passwords[ssid_index]);
+        free(wifi_playlist->ssids[ssid_index]);
+        free(wifi_playlist->passwords[ssid_index]);
         free(ssid_list[ssid_index]);
         // shift the remaining ssids and passwords
-        for(uint32_t i = ssid_index; i < app->wifi_playlist.count - 1; i++) {
-            app->wifi_playlist.ssids[i] = app->wifi_playlist.ssids[i + 1];
-            app->wifi_playlist.passwords[i] = app->wifi_playlist.passwords[i + 1];
+        for (uint32_t i = ssid_index; i < wifi_playlist->count - 1; i++)
+        {
+            // Use strncpy to prevent buffer overflows and ensure null termination
+            strncpy(wifi_playlist->ssids[i], wifi_playlist->ssids[i + 1], MAX_SSID_LENGTH - 1);
+            wifi_playlist->ssids[i][MAX_SSID_LENGTH - 1] = '\0'; // Ensure null-termination
+
+            strncpy(wifi_playlist->passwords[i], wifi_playlist->passwords[i + 1], MAX_SSID_LENGTH - 1);
+            wifi_playlist->passwords[i][MAX_SSID_LENGTH - 1] = '\0'; // Ensure null-termination
+
+            // Shift ssid_list
             ssid_list[i] = ssid_list[i + 1];
         }
-        app->wifi_playlist.count--;
+        wifi_playlist->count--;
 
         // save the playlist to storage
-        save_playlist(&app->wifi_playlist);
+        save_playlist(wifi_playlist);
 
         // re draw the saved submenu
         flip_wifi_redraw_submenu_saved(app);
         // switch back to the saved view
-        view_dispatcher_switch_to_view(app->view_dispatcher, FlipWiFiViewSubmenuSaved);
+        view_dispatcher_switch_to_view(app->view_dispatcher, FlipWiFiViewSubmenu);
         return true;
     }
     return false;
@@ -195,66 +605,80 @@ bool flip_wifi_view_input_callback_saved(InputEvent* event, void* context) {
 
 // Function to trim leading and trailing whitespace
 // Returns the trimmed start pointer and updates the length
-static char* trim_whitespace(char* start, size_t* length) {
+static char *trim_whitespace(char *start, size_t *length)
+{
     // Trim leading whitespace
-    while(*length > 0 && isspace((unsigned char)*start)) {
+    while (*length > 0 && isspace((unsigned char)*start))
+    {
         start++;
         (*length)--;
     }
 
     // Trim trailing whitespace
-    while(*length > 0 && isspace((unsigned char)start[*length - 1])) {
+    while (*length > 0 && isspace((unsigned char)start[*length - 1]))
+    {
         (*length)--;
     }
 
     return start;
 }
 
-static bool flip_wifi_handle_scan() {
-    if(!app_instance) {
+static bool flip_wifi_handle_scan(void *context)
+{
+    FlipWiFiApp *app = (FlipWiFiApp *)context;
+    if (!app)
+    {
         FURI_LOG_E(TAG, "FlipWiFiApp is NULL");
         return false;
     }
     // load the received data from the saved file
-    FuriString* scan_data = flipper_http_load_from_file(fhttp.file_path);
-    if(scan_data == NULL) {
+    FuriString *scan_data = flipper_http_load_from_file(fhttp.file_path);
+    if (scan_data == NULL)
+    {
         FURI_LOG_E(TAG, "Failed to load received data from file.");
         fhttp.state = ISSUE;
+        easy_flipper_dialog("[ERROR]", "Failed to load received data from file.");
         return false;
     }
-    char* data_cstr = (char*)furi_string_get_cstr(scan_data);
-    if(data_cstr == NULL) {
+    char *data_cstr = (char *)furi_string_get_cstr(scan_data);
+    if (data_cstr == NULL)
+    {
         FURI_LOG_E(TAG, "Failed to get C-string from FuriString.");
         furi_string_free(scan_data);
         fhttp.state = ISSUE;
         free(data_cstr);
+        easy_flipper_dialog("[ERROR]", "Failed to get C-string from FuriString.");
         return false;
     }
 
     uint32_t ssid_count = 0;
 
-    char* current_position = data_cstr;
-    char* next_comma = NULL;
+    char *current_position = data_cstr;
+    char *next_comma = NULL;
 
     // Manually split the string on commas
-    while((next_comma = strchr(current_position, ',')) != NULL) {
+    while ((next_comma = strchr(current_position, ',')) != NULL)
+    {
         // Calculate length of the SSID
         size_t ssid_length = next_comma - current_position;
 
         // Trim leading and trailing whitespace
         size_t trimmed_length = ssid_length;
-        char* trim_start = trim_whitespace(current_position, &trimmed_length);
+        char *trim_start = trim_whitespace(current_position, &trimmed_length);
 
         // Handle empty SSIDs resulting from consecutive commas
-        if(trimmed_length == 0) {
+        if (trimmed_length == 0)
+        {
             current_position = next_comma + 1; // Move past the comma
             continue;
         }
 
         // Allocate memory for the SSID and copy it
         ssid_list[ssid_count] = malloc(trimmed_length + 1);
-        if(ssid_list[ssid_count] == NULL) {
+        if (ssid_list[ssid_count] == NULL)
+        {
             FURI_LOG_E(TAG, "Memory allocation failed");
+            easy_flipper_dialog("[ERROR]", "Memory allocation failed");
             free(data_cstr);
             furi_string_free(scan_data);
             return false;
@@ -263,7 +687,8 @@ static bool flip_wifi_handle_scan() {
         ssid_list[ssid_count][trimmed_length] = '\0'; // Null-terminate the string
 
         ssid_count++;
-        if(ssid_count >= MAX_WIFI_NETWORKS) {
+        if (ssid_count >= MAX_SCAN_NETWORKS)
+        {
             FURI_LOG_E(TAG, "Maximum SSID limit reached");
             break;
         }
@@ -272,18 +697,22 @@ static bool flip_wifi_handle_scan() {
     }
 
     // Handle the last SSID after the last comma (if any)
-    if(*current_position != '\0' && ssid_count < MAX_WIFI_NETWORKS) {
+    if (*current_position != '\0' && ssid_count < MAX_SCAN_NETWORKS)
+    {
         size_t ssid_length = strlen(current_position);
 
         // Trim leading and trailing whitespace
         size_t trimmed_length = ssid_length;
-        char* trim_start = trim_whitespace(current_position, &trimmed_length);
+        char *trim_start = trim_whitespace(current_position, &trimmed_length);
 
         // Handle empty SSIDs
-        if(trimmed_length > 0) {
+        if (trimmed_length > 0)
+        {
             ssid_list[ssid_count] = malloc(trimmed_length + 1);
-            if(ssid_list[ssid_count] == NULL) {
+            if (ssid_list[ssid_count] == NULL)
+            {
                 FURI_LOG_E(TAG, "Memory allocation failed for the last SSID");
+                easy_flipper_dialog("[ERROR]", "Memory allocation failed for the last SSID");
                 return false;
             }
             strncpy(ssid_list[ssid_count], trim_start, trimmed_length);
@@ -293,214 +722,319 @@ static bool flip_wifi_handle_scan() {
     }
 
     // Add each SSID as a submenu item
-    submenu_reset(app_instance->submenu_wifi_scan);
-    submenu_set_header(app_instance->submenu_wifi_scan, "WiFi Nearby");
-    for(uint32_t i = 0; i < ssid_count; i++) {
-        char* ssid_item = ssid_list[i];
-        if(ssid_item == NULL) {
+    submenu_reset(app->submenu_wifi);
+    submenu_set_header(app->submenu_wifi, "WiFi Nearby");
+    for (uint32_t i = 0; i < ssid_count; i++)
+    {
+        char *ssid_item = ssid_list[i];
+        if (ssid_item == NULL)
+        {
             // skip any NULL entries
             continue;
         }
         char ssid[64];
         snprintf(ssid, sizeof(ssid), "%s", ssid_item);
-        submenu_add_item(
-            app_instance->submenu_wifi_scan,
-            ssid,
-            FlipWiFiSubmenuIndexWiFiScanStart + i,
-            callback_submenu_choices,
-            app_instance);
+        submenu_add_item(app->submenu_wifi, ssid, FlipWiFiSubmenuIndexWiFiScanStart + i, callback_submenu_choices, app);
     }
     free(data_cstr);
     furi_string_free(scan_data);
     return true;
 }
-void callback_submenu_choices(void* context, uint32_t index) {
-    FlipWiFiApp* app = (FlipWiFiApp*)context;
-    if(!app) {
+void callback_submenu_choices(void *context, uint32_t index)
+{
+    FlipWiFiApp *app = (FlipWiFiApp *)context;
+    if (!app)
+    {
         FURI_LOG_E(TAG, "FlipWiFiApp is NULL");
         return;
     }
-    switch(index) {
+    switch (index)
+    {
     case FlipWiFiSubmenuIndexWiFiScan:
-        // Popup
-        if(!app->popup) {
-            if(!easy_flipper_set_popup(
-                   &app->popup,
-                   FlipWiFiViewPopup,
-                   "Success",
-                   0,
-                   0,
-                   "The WiFi setting has been set.",
-                   0,
-                   10,
-                   popup_callback_saved,
-                   callback_to_submenu_saved,
-                   &app->view_dispatcher,
-                   app)) {
-                return;
-            }
+        flip_wifi_free_all(app);
+        if (!flip_wifi_alloc_submenus(app, FlipWiFiViewSubmenuScan))
+        {
+            easy_flipper_dialog("[ERROR]", "Failed to allocate submenus for WiFi Scan");
+            return;
         }
-        popup_set_header(app->popup, "[ERROR]", 0, 0, AlignLeft, AlignTop);
-        view_set_previous_callback(popup_get_view(app->popup), callback_to_submenu_main);
-        popup_set_callback(app->popup, popup_callback_main);
-
-        if(fhttp.state == INACTIVE) {
-            popup_set_text(
-                app->popup,
-                "WiFi Devboard Disconnected.\nPlease reconnect the board.",
-                0,
-                40,
-                AlignLeft,
-                AlignTop);
-            view_dispatcher_switch_to_view(app->view_dispatcher, FlipWiFiViewPopup);
+        // initialize uart
+        if (!flipper_http_init(flipper_http_rx_callback, app))
+        {
+            easy_flipper_dialog("[ERROR]", "Failed to initialize flipper http");
             return;
         }
-
-        // update the text in case the loading task fails
-        popup_set_text(
-            app->popup,
-            "Failed to scan...\nTry reconnecting the board!",
-            0,
-            40,
-            AlignLeft,
-            AlignTop);
-
+        bool _flip_wifi_handle_scan()
+        {
+            return flip_wifi_handle_scan(app);
+        }
         // scan for wifi ad parse the results
-        flipper_http_loading_task(
-            flipper_http_scan_wifi,
-            flip_wifi_handle_scan,
-            FlipWiFiViewSubmenuScan,
-            FlipWiFiViewPopup,
-            &app->view_dispatcher);
+        flipper_http_loading_task(flipper_http_scan_wifi, _flip_wifi_handle_scan, FlipWiFiViewSubmenu, FlipWiFiViewSubmenuMain, &app->view_dispatcher);
+        flipper_http_deinit();
         break;
     case FlipWiFiSubmenuIndexWiFiSaved:
-        view_dispatcher_switch_to_view(app->view_dispatcher, FlipWiFiViewSubmenuSaved);
+        flip_wifi_free_all(app);
+        if (!flip_wifi_alloc_submenus(app, FlipWiFiViewSubmenuSaved))
+        {
+            FURI_LOG_E(TAG, "Failed to allocate submenus for WiFi Saved");
+            return;
+        }
+        view_dispatcher_switch_to_view(app->view_dispatcher, FlipWiFiViewSubmenu);
         break;
     case FlipWiFiSubmenuIndexAbout:
+        flip_wifi_free_all(app);
+        if (!flip_wifi_alloc_widgets(app, FlipWiFiViewAbout))
+        {
+            FURI_LOG_E(TAG, "Failed to allocate widget for About");
+            return;
+        }
         view_dispatcher_switch_to_view(app->view_dispatcher, FlipWiFiViewAbout);
         break;
     case FlipWiFiSubmenuIndexWiFiSavedAddSSID:
-        view_dispatcher_switch_to_view(app->view_dispatcher, FlipWiFiViewTextInputSavedAddSSID);
+        flip_wifi_free_text_inputs(app);
+        if (!flip_wifi_alloc_text_inputs(app, FlipWiFiViewTextInputSavedAddSSID))
+        {
+            FURI_LOG_E(TAG, "Failed to allocate text input for WiFi Saved Add Password");
+            return;
+        }
+        view_dispatcher_switch_to_view(app->view_dispatcher, FlipWiFiViewTextInput);
+        break;
+    case FlipWiFiSubmenuIndexCommands:
+        flip_wifi_free_all(app);
+        if (!flip_wifi_alloc_submenus(app, FlipWiFiViewSubmenuCommands))
+        {
+            FURI_LOG_E(TAG, "Failed to allocate submenus for Commands");
+            return;
+        }
+        view_dispatcher_switch_to_view(app->view_dispatcher, FlipWiFiViewSubmenu);
+        break;
+    case FlipWiFiSubmenuIndexFastCommandStart ... FlipWiFiSubmenuIndexFastCommandStart + 4:
+        // initialize uart
+        if (!flipper_http_init(flipper_http_rx_callback, app))
+        {
+            easy_flipper_dialog("[ERROR]", "Failed to initialize flipper http");
+            return;
+        }
+        // Handle fast commands
+        switch (index)
+        {
+        case FlipWiFiSubmenuIndexFastCommandStart + 0:
+            // CUSTOM - send to text input and return
+            flip_wifi_free_text_inputs(app);
+            if (!flip_wifi_alloc_text_inputs(app, FlipWiFiSubmenuIndexFastCommandStart))
+            {
+                FURI_LOG_E(TAG, "Failed to allocate text input for Fast Command");
+                return;
+            }
+            view_dispatcher_switch_to_view(app->view_dispatcher, FlipWiFiViewTextInput);
+            return;
+        case FlipWiFiSubmenuIndexFastCommandStart + 1:
+            // PING
+            flipper_http_ping();
+            break;
+        case FlipWiFiSubmenuIndexFastCommandStart + 2:
+            // LIST
+            flipper_http_list_commands();
+            break;
+        case FlipWiFiSubmenuIndexFastCommandStart + 3:
+            // IP/ADDRESS
+            flipper_http_ip_address();
+            break;
+        case FlipWiFiSubmenuIndexFastCommandStart + 4:
+            // WIFI/IP
+            flipper_http_ip_wifi();
+            break;
+        default:
+            break;
+        }
+        while (fhttp.last_response == NULL || strlen(fhttp.last_response) == 0)
+        {
+            // Wait for the response
+            furi_delay_ms(100);
+        }
+        if (fhttp.last_response != NULL)
+        {
+            char response[100];
+            snprintf(response, sizeof(response), "%s", fhttp.last_response);
+            easy_flipper_dialog("", response);
+        }
+        flipper_http_deinit();
         break;
-    case 100 ... 163:
+    case 100 ... 199:
         ssid_index = index - FlipWiFiSubmenuIndexWiFiScanStart;
-        view_dispatcher_switch_to_view(app->view_dispatcher, FlipWiFiViewWiFiScan);
+        flip_wifi_free_views(app);
+        if (!flip_wifi_alloc_views(app, FlipWiFiViewWiFiScan))
+        {
+            FURI_LOG_E(TAG, "Failed to allocate views for WiFi Scan");
+            return;
+        }
+        view_dispatcher_switch_to_view(app->view_dispatcher, FlipWiFiViewGeneric);
         break;
-    case 200 ... 263:
+    case 200 ... 299:
         ssid_index = index - FlipWiFiSubmenuIndexWiFiSavedStart;
-        view_dispatcher_switch_to_view(app->view_dispatcher, FlipWiFiViewWiFiSaved);
+        flip_wifi_free_views(app);
+        snprintf(current_ssid, sizeof(current_ssid), "%s", wifi_playlist->ssids[ssid_index]);
+        snprintf(current_password, sizeof(current_password), "%s", wifi_playlist->passwords[ssid_index]);
+        if (!flip_wifi_alloc_views(app, FlipWiFiViewWiFiSaved))
+        {
+            FURI_LOG_E(TAG, "Failed to allocate views for WiFi Saved");
+            return;
+        }
+        view_dispatcher_switch_to_view(app->view_dispatcher, FlipWiFiViewGeneric);
         break;
     default:
         break;
     }
 }
 
-void flip_wifi_text_updated_password_scan(void* context) {
-    FlipWiFiApp* app = (FlipWiFiApp*)context;
-    if(!app) {
+void flip_wifi_text_updated_password_scan(void *context)
+{
+    FlipWiFiApp *app = (FlipWiFiApp *)context;
+    if (!app)
+    {
         FURI_LOG_E(TAG, "FlipWiFiApp is NULL");
         return;
     }
 
-    // store the entered text
-    strncpy(
-        app->uart_text_input_buffer_password_scan,
-        app->uart_text_input_temp_buffer_password_scan,
-        app->uart_text_input_buffer_size_password_scan);
-
+    // Store the entered text with buffer size limit
+    strncpy(app->uart_text_input_buffer, app->uart_text_input_temp_buffer, app->uart_text_input_buffer_size - 1);
     // Ensure null-termination
-    app->uart_text_input_buffer_password_scan[app->uart_text_input_buffer_size_password_scan - 1] =
-        '\0';
+    app->uart_text_input_buffer[app->uart_text_input_buffer_size - 1] = '\0';
 
-    // add the SSID and password_scan to the playlist
-    app->wifi_playlist.ssids[app->wifi_playlist.count] = strdup(ssid_list[ssid_index]);
-    app->wifi_playlist.passwords[app->wifi_playlist.count] =
-        strdup(app->uart_text_input_buffer_password_scan);
-    app->wifi_playlist.count++;
+    if (!flip_wifi_alloc_playlist(app))
+    {
+        FURI_LOG_E(TAG, "Failed to allocate playlist");
+        return;
+    }
 
-    // save the playlist to storage
-    save_playlist(&app->wifi_playlist);
+    // Ensure ssid_index is valid
+    if (ssid_index >= MAX_SCAN_NETWORKS)
+    {
+        FURI_LOG_E(TAG, "Invalid ssid_index: %ld", ssid_index);
+        return;
+    }
+
+    // Check if there's space in the playlist
+    if (wifi_playlist->count >= MAX_SAVED_NETWORKS)
+    {
+        FURI_LOG_E(TAG, "Playlist is full. Cannot add more entries.");
+        return;
+    }
+    FURI_LOG_I(TAG, "Adding SSID: %s", ssid_list[ssid_index]);
+    FURI_LOG_I(TAG, "Count: %d", wifi_playlist->count);
+    // Add the SSID and password to the playlist
+    snprintf(wifi_playlist->ssids[wifi_playlist->count], MAX_SSID_LENGTH, "%s", ssid_list[ssid_index]);
+    snprintf(wifi_playlist->passwords[wifi_playlist->count], MAX_SSID_LENGTH, "%s", app->uart_text_input_buffer);
+    wifi_playlist->count++;
 
+    // Save the updated playlist to storage
+    save_playlist(wifi_playlist);
+
+    // Redraw the submenu to reflect changes
     flip_wifi_redraw_submenu_saved(app);
 
-    // switch to back to the scan view
-    view_dispatcher_switch_to_view(app->view_dispatcher, FlipWiFiViewSubmenuScan);
+    // Switch back to the scan view
+    view_dispatcher_switch_to_view(app->view_dispatcher, FlipWiFiViewSubmenu);
 }
-void flip_wifi_text_updated_password_saved(void* context) {
-    FlipWiFiApp* app = (FlipWiFiApp*)context;
-    if(!app) {
+
+void flip_wifi_text_updated_password_saved(void *context)
+{
+    FlipWiFiApp *app = (FlipWiFiApp *)context;
+    if (!app)
+    {
         FURI_LOG_E(TAG, "FlipWiFiApp is NULL");
         return;
     }
 
     // store the entered text
-    strncpy(
-        app->uart_text_input_buffer_password_saved,
-        app->uart_text_input_temp_buffer_password_saved,
-        app->uart_text_input_buffer_size_password_saved);
+    strncpy(app->uart_text_input_buffer, app->uart_text_input_temp_buffer, app->uart_text_input_buffer_size);
 
     // Ensure null-termination
-    app->uart_text_input_buffer_password_saved[app->uart_text_input_buffer_size_password_saved - 1] =
-        '\0';
+    app->uart_text_input_buffer[app->uart_text_input_buffer_size - 1] = '\0';
 
     // update the password_saved in the playlist
-    app->wifi_playlist.passwords[ssid_index] = strdup(app->uart_text_input_buffer_password_saved);
+    snprintf(wifi_playlist->passwords[ssid_index], MAX_SSID_LENGTH, app->uart_text_input_buffer);
 
     // save the playlist to storage
-    save_playlist(&app->wifi_playlist);
+    save_playlist(wifi_playlist);
 
     // switch to back to the saved view
-    view_dispatcher_switch_to_view(app->view_dispatcher, FlipWiFiViewSubmenuSaved);
+    view_dispatcher_switch_to_view(app->view_dispatcher, FlipWiFiViewSubmenu);
 }
 
-void flip_wifi_text_updated_add_ssid(void* context) {
-    FlipWiFiApp* app = (FlipWiFiApp*)context;
-    if(!app) {
+void flip_wifi_text_updated_add_ssid(void *context)
+{
+    FlipWiFiApp *app = (FlipWiFiApp *)context;
+    if (!app)
+    {
         FURI_LOG_E(TAG, "FlipWiFiApp is NULL");
         return;
     }
 
+    // check if empty
+    if (strlen(app->uart_text_input_temp_buffer) == 0)
+    {
+        easy_flipper_dialog("[ERROR]", "SSID cannot be empty");
+        return;
+    }
+
     // store the entered text
-    strncpy(
-        app->uart_text_input_buffer_add_ssid,
-        app->uart_text_input_temp_buffer_add_ssid,
-        app->uart_text_input_buffer_size_add_ssid);
+    strncpy(app->uart_text_input_buffer, app->uart_text_input_temp_buffer, app->uart_text_input_buffer_size);
 
     // Ensure null-termination
-    app->uart_text_input_buffer_add_ssid[app->uart_text_input_buffer_size_add_ssid - 1] = '\0';
-
-    // do nothing for now, go to the next text input to set the password
-    view_dispatcher_switch_to_view(app->view_dispatcher, FlipWiFiViewTextInputSavedAddPassword);
+    app->uart_text_input_buffer[app->uart_text_input_buffer_size - 1] = '\0';
+    save_char("wifi-ssid", app->uart_text_input_buffer);
+    view_dispatcher_switch_to_view(app->view_dispatcher, FlipWiFiViewSubmenuMain);
+    uart_text_input_reset(app->uart_text_input);
+    uart_text_input_set_header_text(app->uart_text_input, "Enter Password");
+    app->uart_text_input_buffer_size = MAX_SSID_LENGTH;
+    free(app->uart_text_input_buffer);
+    free(app->uart_text_input_temp_buffer);
+    easy_flipper_set_buffer(&app->uart_text_input_buffer, app->uart_text_input_buffer_size);
+    easy_flipper_set_buffer(&app->uart_text_input_temp_buffer, app->uart_text_input_buffer_size);
+    uart_text_input_set_result_callback(app->uart_text_input, flip_wifi_text_updated_add_password, app, app->uart_text_input_temp_buffer, app->uart_text_input_buffer_size, false);
+    view_dispatcher_switch_to_view(app->view_dispatcher, FlipWiFiViewTextInput);
 }
-void flip_wifi_text_updated_add_password(void* context) {
-    FlipWiFiApp* app = (FlipWiFiApp*)context;
-    if(!app) {
+void flip_wifi_text_updated_add_password(void *context)
+{
+    FlipWiFiApp *app = (FlipWiFiApp *)context;
+    if (!app)
+    {
         FURI_LOG_E(TAG, "FlipWiFiApp is NULL");
         return;
     }
 
+    // check if empty
+    if (strlen(app->uart_text_input_temp_buffer) == 0)
+    {
+        easy_flipper_dialog("[ERROR]", "Password cannot be empty");
+        return;
+    }
+
     // store the entered text
-    strncpy(
-        app->uart_text_input_buffer_add_password,
-        app->uart_text_input_temp_buffer_add_password,
-        app->uart_text_input_buffer_size_add_password);
+    strncpy(app->uart_text_input_buffer, app->uart_text_input_temp_buffer, app->uart_text_input_buffer_size);
+
+    save_char("wifi-password", app->uart_text_input_buffer);
 
     // Ensure null-termination
-    app->uart_text_input_buffer_add_password[app->uart_text_input_buffer_size_add_password - 1] =
-        '\0';
+    app->uart_text_input_buffer[app->uart_text_input_buffer_size - 1] = '\0';
+
+    char wifi_ssid[64];
+    if (!load_char("wifi-ssid", wifi_ssid, sizeof(wifi_ssid)))
+    {
+        FURI_LOG_E(TAG, "Failed to load wifi ssid");
+        return;
+    }
 
     // add the SSID and password_scan to the playlist
-    app->wifi_playlist.ssids[app->wifi_playlist.count] =
-        strdup(app->uart_text_input_buffer_add_ssid);
-    app->wifi_playlist.passwords[app->wifi_playlist.count] =
-        strdup(app->uart_text_input_buffer_add_password);
-    app->wifi_playlist.count++;
+    snprintf(wifi_playlist->ssids[wifi_playlist->count], MAX_SSID_LENGTH, wifi_ssid);
+    snprintf(wifi_playlist->passwords[wifi_playlist->count], MAX_SSID_LENGTH, app->uart_text_input_buffer);
+    wifi_playlist->count++;
 
     // save the playlist to storage
-    save_playlist(&app->wifi_playlist);
+    save_playlist(wifi_playlist);
 
     flip_wifi_redraw_submenu_saved(app);
 
     // switch to back to the saved view
-    view_dispatcher_switch_to_view(app->view_dispatcher, FlipWiFiViewSubmenuSaved);
-}
+    view_dispatcher_switch_to_view(app->view_dispatcher, FlipWiFiViewSubmenu);
+}

+ 4 - 37
flip_wifi/callback/flip_wifi_callback.h

@@ -1,41 +1,8 @@
-#ifndef FLIP_WIFI_CALLBACK_H
-#define FLIP_WIFI_CALLBACK_H
-
+#pragma once
 #include <flip_wifi.h>
 #include <flip_storage/flip_wifi_storage.h>
 #include <flip_wifi_icons.h>
 
-// array to store each SSID
-extern char* ssid_list[64];
-extern uint32_t ssid_index;
-
-void flip_wifi_redraw_submenu_saved(FlipWiFiApp* app);
-
-uint32_t callback_to_submenu_main(void* context);
-
-uint32_t callback_to_submenu_scan(void* context);
-
-uint32_t callback_to_submenu_saved(void* context);
-
-// Callback for drawing the main screen
-void flip_wifi_view_draw_callback_scan(Canvas* canvas, void* model);
-
-void flip_wifi_view_draw_callback_saved(Canvas* canvas, void* model);
-
-// Input callback for the view (async input handling)
-bool flip_wifi_view_input_callback_scan(InputEvent* event, void* context);
-
-// Input callback for the view (async input handling)
-bool flip_wifi_view_input_callback_saved(InputEvent* event, void* context);
-
-void callback_submenu_choices(void* context, uint32_t index);
-
-void flip_wifi_text_updated_password_scan(void* context);
-
-void flip_wifi_text_updated_password_saved(void* context);
-
-void flip_wifi_text_updated_add_ssid(void* context);
-
-void flip_wifi_text_updated_add_password(void* context);
-
-#endif // FLIP_WIFI_CALLBACK_H
+void flip_wifi_free_all(void *context);
+uint32_t callback_exit_app(void *context);
+void callback_submenu_choices(void *context, uint32_t index);

+ 218 - 139
flip_wifi/easy_flipper/easy_flipper.c

@@ -1,13 +1,35 @@
 #include <easy_flipper/easy_flipper.h>
 
+void easy_flipper_dialog(
+    char *header,
+    char *text)
+{
+    DialogsApp *dialogs = furi_record_open(RECORD_DIALOGS);
+    DialogMessage *message = dialog_message_alloc();
+    dialog_message_set_header(
+        message, header, 64, 0, AlignCenter, AlignTop);
+    dialog_message_set_text(
+        message,
+        text,
+        0,
+        63,
+        AlignLeft,
+        AlignBottom);
+    dialog_message_show(dialogs, message);
+    dialog_message_free(message);
+    furi_record_close(RECORD_DIALOGS);
+}
+
 /**
  * @brief Navigation callback for exiting the application
  * @param context The context - unused
  * @return next view id (VIEW_NONE to exit the app)
  */
-uint32_t easy_flipper_callback_exit_app(void* context) {
+uint32_t easy_flipper_callback_exit_app(void *context)
+{
     // Exit the application
-    if(!context) {
+    if (!context)
+    {
         FURI_LOG_E(EASY_TAG, "Context is NULL");
         return VIEW_NONE;
     }
@@ -21,13 +43,16 @@ uint32_t easy_flipper_callback_exit_app(void* context) {
  * @param buffer_size The size of the buffer
  * @return true if successful, false otherwise
  */
-bool easy_flipper_set_buffer(char** buffer, uint32_t buffer_size) {
-    if(!buffer) {
+bool easy_flipper_set_buffer(char **buffer, uint32_t buffer_size)
+{
+    if (!buffer)
+    {
         FURI_LOG_E(EASY_TAG, "Invalid arguments provided to set_buffer");
         return false;
     }
-    *buffer = (char*)malloc(buffer_size);
-    if(!*buffer) {
+    *buffer = (char *)malloc(buffer_size);
+    if (!*buffer)
+    {
         FURI_LOG_E(EASY_TAG, "Failed to allocate buffer");
         return false;
     }
@@ -46,32 +71,39 @@ bool easy_flipper_set_buffer(char** buffer, uint32_t buffer_size) {
  * @return true if successful, false otherwise
  */
 bool easy_flipper_set_view(
-    View** view,
+    View **view,
     int32_t view_id,
-    void draw_callback(Canvas*, void*),
-    bool input_callback(InputEvent*, void*),
-    uint32_t (*previous_callback)(void*),
-    ViewDispatcher** view_dispatcher,
-    void* context) {
-    if(!view || !view_dispatcher) {
+    void draw_callback(Canvas *, void *),
+    bool input_callback(InputEvent *, void *),
+    uint32_t (*previous_callback)(void *),
+    ViewDispatcher **view_dispatcher,
+    void *context)
+{
+    if (!view || !view_dispatcher)
+    {
         FURI_LOG_E(EASY_TAG, "Invalid arguments provided to set_view");
         return false;
     }
     *view = view_alloc();
-    if(!*view) {
+    if (!*view)
+    {
         FURI_LOG_E(EASY_TAG, "Failed to allocate View");
         return false;
     }
-    if(draw_callback) {
+    if (draw_callback)
+    {
         view_set_draw_callback(*view, draw_callback);
     }
-    if(input_callback) {
+    if (input_callback)
+    {
         view_set_input_callback(*view, input_callback);
     }
-    if(context) {
+    if (context)
+    {
         view_set_context(*view, context);
     }
-    if(previous_callback) {
+    if (previous_callback)
+    {
         view_set_previous_callback(*view, previous_callback);
     }
     view_dispatcher_add_view(*view_dispatcher, view_id, *view);
@@ -85,18 +117,22 @@ bool easy_flipper_set_view(
  * @param context The context to pass to the event callback
  * @return true if successful, false otherwise
  */
-bool easy_flipper_set_view_dispatcher(ViewDispatcher** view_dispatcher, Gui* gui, void* context) {
-    if(!view_dispatcher) {
+bool easy_flipper_set_view_dispatcher(ViewDispatcher **view_dispatcher, Gui *gui, void *context)
+{
+    if (!view_dispatcher)
+    {
         FURI_LOG_E(EASY_TAG, "Invalid arguments provided to set_view_dispatcher");
         return false;
     }
     *view_dispatcher = view_dispatcher_alloc();
-    if(!*view_dispatcher) {
+    if (!*view_dispatcher)
+    {
         FURI_LOG_E(EASY_TAG, "Failed to allocate ViewDispatcher");
         return false;
     }
     view_dispatcher_attach_to_gui(*view_dispatcher, gui, ViewDispatcherTypeFullscreen);
-    if(context) {
+    if (context)
+    {
         view_dispatcher_set_event_callback_context(*view_dispatcher, context);
     }
     return true;
@@ -113,24 +149,29 @@ bool easy_flipper_set_view_dispatcher(ViewDispatcher** view_dispatcher, Gui* gui
  * @return true if successful, false otherwise
  */
 bool easy_flipper_set_submenu(
-    Submenu** submenu,
+    Submenu **submenu,
     int32_t view_id,
-    char* title,
-    uint32_t(previous_callback)(void*),
-    ViewDispatcher** view_dispatcher) {
-    if(!submenu) {
+    char *title,
+    uint32_t(previous_callback)(void *),
+    ViewDispatcher **view_dispatcher)
+{
+    if (!submenu)
+    {
         FURI_LOG_E(EASY_TAG, "Invalid arguments provided to set_submenu");
         return false;
     }
     *submenu = submenu_alloc();
-    if(!*submenu) {
+    if (!*submenu)
+    {
         FURI_LOG_E(EASY_TAG, "Failed to allocate Submenu");
         return false;
     }
-    if(title) {
+    if (title)
+    {
         submenu_set_header(*submenu, title);
     }
-    if(previous_callback) {
+    if (previous_callback)
+    {
         view_set_previous_callback(submenu_get_view(*submenu), previous_callback);
     }
     view_dispatcher_add_view(*view_dispatcher, view_id, submenu_get_view(*submenu));
@@ -147,20 +188,24 @@ bool easy_flipper_set_submenu(
  * @return true if successful, false otherwise
  */
 bool easy_flipper_set_menu(
-    Menu** menu,
+    Menu **menu,
     int32_t view_id,
-    uint32_t(previous_callback)(void*),
-    ViewDispatcher** view_dispatcher) {
-    if(!menu) {
+    uint32_t(previous_callback)(void *),
+    ViewDispatcher **view_dispatcher)
+{
+    if (!menu)
+    {
         FURI_LOG_E(EASY_TAG, "Invalid arguments provided to set_menu");
         return false;
     }
     *menu = menu_alloc();
-    if(!*menu) {
+    if (!*menu)
+    {
         FURI_LOG_E(EASY_TAG, "Failed to allocate Menu");
         return false;
     }
-    if(previous_callback) {
+    if (previous_callback)
+    {
         view_set_previous_callback(menu_get_view(*menu), previous_callback);
     }
     view_dispatcher_add_view(*view_dispatcher, view_id, menu_get_view(*menu));
@@ -177,24 +222,29 @@ bool easy_flipper_set_menu(
  * @return true if successful, false otherwise
  */
 bool easy_flipper_set_widget(
-    Widget** widget,
+    Widget **widget,
     int32_t view_id,
-    char* text,
-    uint32_t(previous_callback)(void*),
-    ViewDispatcher** view_dispatcher) {
-    if(!widget) {
+    char *text,
+    uint32_t(previous_callback)(void *),
+    ViewDispatcher **view_dispatcher)
+{
+    if (!widget)
+    {
         FURI_LOG_E(EASY_TAG, "Invalid arguments provided to set_widget");
         return false;
     }
     *widget = widget_alloc();
-    if(!*widget) {
+    if (!*widget)
+    {
         FURI_LOG_E(EASY_TAG, "Failed to allocate Widget");
         return false;
     }
-    if(text) {
+    if (text)
+    {
         widget_add_text_scroll_element(*widget, 0, 0, 128, 64, text);
     }
-    if(previous_callback) {
+    if (previous_callback)
+    {
         view_set_previous_callback(widget_get_view(*widget), previous_callback);
     }
     view_dispatcher_add_view(*view_dispatcher, view_id, widget_get_view(*widget));
@@ -213,30 +263,33 @@ bool easy_flipper_set_widget(
  * @return true if successful, false otherwise
  */
 bool easy_flipper_set_variable_item_list(
-    VariableItemList** variable_item_list,
+    VariableItemList **variable_item_list,
     int32_t view_id,
-    void (*enter_callback)(void*, uint32_t),
-    uint32_t(previous_callback)(void*),
-    ViewDispatcher** view_dispatcher,
-    void* context) {
-    if(!variable_item_list) {
+    void (*enter_callback)(void *, uint32_t),
+    uint32_t(previous_callback)(void *),
+    ViewDispatcher **view_dispatcher,
+    void *context)
+{
+    if (!variable_item_list)
+    {
         FURI_LOG_E(EASY_TAG, "Invalid arguments provided to set_variable_item_list");
         return false;
     }
     *variable_item_list = variable_item_list_alloc();
-    if(!*variable_item_list) {
+    if (!*variable_item_list)
+    {
         FURI_LOG_E(EASY_TAG, "Failed to allocate VariableItemList");
         return false;
     }
-    if(enter_callback) {
+    if (enter_callback)
+    {
         variable_item_list_set_enter_callback(*variable_item_list, enter_callback, context);
     }
-    if(previous_callback) {
-        view_set_previous_callback(
-            variable_item_list_get_view(*variable_item_list), previous_callback);
+    if (previous_callback)
+    {
+        view_set_previous_callback(variable_item_list_get_view(*variable_item_list), previous_callback);
     }
-    view_dispatcher_add_view(
-        *view_dispatcher, view_id, variable_item_list_get_view(*variable_item_list));
+    view_dispatcher_add_view(*view_dispatcher, view_id, variable_item_list_get_view(*variable_item_list));
     return true;
 }
 
@@ -249,38 +302,38 @@ bool easy_flipper_set_variable_item_list(
  * @return true if successful, false otherwise
  */
 bool easy_flipper_set_text_input(
-    TextInput** text_input,
+    TextInput **text_input,
     int32_t view_id,
-    char* header_text,
-    char* text_input_temp_buffer,
+    char *header_text,
+    char *text_input_temp_buffer,
     uint32_t text_input_buffer_size,
-    void (*result_callback)(void*),
-    uint32_t(previous_callback)(void*),
-    ViewDispatcher** view_dispatcher,
-    void* context) {
-    if(!text_input) {
+    void (*result_callback)(void *),
+    uint32_t(previous_callback)(void *),
+    ViewDispatcher **view_dispatcher,
+    void *context)
+{
+    if (!text_input)
+    {
         FURI_LOG_E(EASY_TAG, "Invalid arguments provided to set_text_input");
         return false;
     }
     *text_input = text_input_alloc();
-    if(!*text_input) {
+    if (!*text_input)
+    {
         FURI_LOG_E(EASY_TAG, "Failed to allocate TextInput");
         return false;
     }
-    if(previous_callback) {
+    if (previous_callback)
+    {
         view_set_previous_callback(text_input_get_view(*text_input), previous_callback);
     }
-    if(header_text) {
+    if (header_text)
+    {
         text_input_set_header_text(*text_input, header_text);
     }
-    if(text_input_temp_buffer && text_input_buffer_size && result_callback) {
-        text_input_set_result_callback(
-            *text_input,
-            result_callback,
-            context,
-            text_input_temp_buffer,
-            text_input_buffer_size,
-            false);
+    if (text_input_temp_buffer && text_input_buffer_size && result_callback)
+    {
+        text_input_set_result_callback(*text_input, result_callback, context, text_input_temp_buffer, text_input_buffer_size, false);
     }
     view_dispatcher_add_view(*view_dispatcher, view_id, text_input_get_view(*text_input));
     return true;
@@ -295,38 +348,38 @@ bool easy_flipper_set_text_input(
  * @return true if successful, false otherwise
  */
 bool easy_flipper_set_uart_text_input(
-    TextInput** uart_text_input,
+    TextInput **uart_text_input,
     int32_t view_id,
-    char* header_text,
-    char* uart_text_input_temp_buffer,
+    char *header_text,
+    char *uart_text_input_temp_buffer,
     uint32_t uart_text_input_buffer_size,
-    void (*result_callback)(void*),
-    uint32_t(previous_callback)(void*),
-    ViewDispatcher** view_dispatcher,
-    void* context) {
-    if(!uart_text_input) {
+    void (*result_callback)(void *),
+    uint32_t(previous_callback)(void *),
+    ViewDispatcher **view_dispatcher,
+    void *context)
+{
+    if (!uart_text_input)
+    {
         FURI_LOG_E(EASY_TAG, "Invalid arguments provided to set_uart_text_input");
         return false;
     }
     *uart_text_input = text_input_alloc();
-    if(!*uart_text_input) {
+    if (!*uart_text_input)
+    {
         FURI_LOG_E(EASY_TAG, "Failed to allocate UART_TextInput");
         return false;
     }
-    if(previous_callback) {
+    if (previous_callback)
+    {
         view_set_previous_callback(text_input_get_view(*uart_text_input), previous_callback);
     }
-    if(header_text) {
+    if (header_text)
+    {
         text_input_set_header_text(*uart_text_input, header_text);
     }
-    if(uart_text_input_temp_buffer && uart_text_input_buffer_size && result_callback) {
-        text_input_set_result_callback(
-            *uart_text_input,
-            result_callback,
-            context,
-            uart_text_input_temp_buffer,
-            uart_text_input_buffer_size,
-            false);
+    if (uart_text_input_temp_buffer && uart_text_input_buffer_size && result_callback)
+    {
+        text_input_set_result_callback(*uart_text_input, result_callback, context, uart_text_input_temp_buffer, uart_text_input_buffer_size, false);
     }
     text_input_show_illegal_symbols(*uart_text_input, true);
     view_dispatcher_add_view(*view_dispatcher, view_id, text_input_get_view(*uart_text_input));
@@ -353,52 +406,63 @@ bool easy_flipper_set_uart_text_input(
  * @return true if successful, false otherwise
  */
 bool easy_flipper_set_dialog_ex(
-    DialogEx** dialog_ex,
+    DialogEx **dialog_ex,
     int32_t view_id,
-    char* header,
+    char *header,
     uint16_t header_x,
     uint16_t header_y,
-    char* text,
+    char *text,
     uint16_t text_x,
     uint16_t text_y,
-    char* left_button_text,
-    char* right_button_text,
-    char* center_button_text,
-    void (*result_callback)(DialogExResult, void*),
-    uint32_t(previous_callback)(void*),
-    ViewDispatcher** view_dispatcher,
-    void* context) {
-    if(!dialog_ex) {
+    char *left_button_text,
+    char *right_button_text,
+    char *center_button_text,
+    void (*result_callback)(DialogExResult, void *),
+    uint32_t(previous_callback)(void *),
+    ViewDispatcher **view_dispatcher,
+    void *context)
+{
+    if (!dialog_ex)
+    {
         FURI_LOG_E(EASY_TAG, "Invalid arguments provided to set_dialog_ex");
         return false;
     }
     *dialog_ex = dialog_ex_alloc();
-    if(!*dialog_ex) {
+    if (!*dialog_ex)
+    {
         FURI_LOG_E(EASY_TAG, "Failed to allocate DialogEx");
         return false;
     }
-    if(header) {
+    if (header)
+    {
         dialog_ex_set_header(*dialog_ex, header, header_x, header_y, AlignLeft, AlignTop);
     }
-    if(text) {
+    if (text)
+    {
         dialog_ex_set_text(*dialog_ex, text, text_x, text_y, AlignLeft, AlignTop);
     }
-    if(left_button_text) {
+    if (left_button_text)
+    {
         dialog_ex_set_left_button_text(*dialog_ex, left_button_text);
     }
-    if(right_button_text) {
+    if (right_button_text)
+    {
         dialog_ex_set_right_button_text(*dialog_ex, right_button_text);
     }
-    if(center_button_text) {
+    if (center_button_text)
+    {
         dialog_ex_set_center_button_text(*dialog_ex, center_button_text);
     }
-    if(result_callback) {
+    if (result_callback)
+    {
         dialog_ex_set_result_callback(*dialog_ex, result_callback);
     }
-    if(previous_callback) {
+    if (previous_callback)
+    {
         view_set_previous_callback(dialog_ex_get_view(*dialog_ex), previous_callback);
     }
-    if(context) {
+    if (context)
+    {
         dialog_ex_set_context(*dialog_ex, context);
     }
     view_dispatcher_add_view(*view_dispatcher, view_id, dialog_ex_get_view(*dialog_ex));
@@ -422,40 +486,48 @@ bool easy_flipper_set_dialog_ex(
  * @return true if successful, false otherwise
  */
 bool easy_flipper_set_popup(
-    Popup** popup,
+    Popup **popup,
     int32_t view_id,
-    char* header,
+    char *header,
     uint16_t header_x,
     uint16_t header_y,
-    char* text,
+    char *text,
     uint16_t text_x,
     uint16_t text_y,
-    void (*result_callback)(void*),
-    uint32_t(previous_callback)(void*),
-    ViewDispatcher** view_dispatcher,
-    void* context) {
-    if(!popup) {
+    void (*result_callback)(void *),
+    uint32_t(previous_callback)(void *),
+    ViewDispatcher **view_dispatcher,
+    void *context)
+{
+    if (!popup)
+    {
         FURI_LOG_E(EASY_TAG, "Invalid arguments provided to set_popup");
         return false;
     }
     *popup = popup_alloc();
-    if(!*popup) {
+    if (!*popup)
+    {
         FURI_LOG_E(EASY_TAG, "Failed to allocate Popup");
         return false;
     }
-    if(header) {
+    if (header)
+    {
         popup_set_header(*popup, header, header_x, header_y, AlignLeft, AlignTop);
     }
-    if(text) {
+    if (text)
+    {
         popup_set_text(*popup, text, text_x, text_y, AlignLeft, AlignTop);
     }
-    if(result_callback) {
+    if (result_callback)
+    {
         popup_set_callback(*popup, result_callback);
     }
-    if(previous_callback) {
+    if (previous_callback)
+    {
         view_set_previous_callback(popup_get_view(*popup), previous_callback);
     }
-    if(context) {
+    if (context)
+    {
         popup_set_context(*popup, context);
     }
     view_dispatcher_add_view(*view_dispatcher, view_id, popup_get_view(*popup));
@@ -471,20 +543,24 @@ bool easy_flipper_set_popup(
  * @return true if successful, false otherwise
  */
 bool easy_flipper_set_loading(
-    Loading** loading,
+    Loading **loading,
     int32_t view_id,
-    uint32_t(previous_callback)(void*),
-    ViewDispatcher** view_dispatcher) {
-    if(!loading) {
+    uint32_t(previous_callback)(void *),
+    ViewDispatcher **view_dispatcher)
+{
+    if (!loading)
+    {
         FURI_LOG_E(EASY_TAG, "Invalid arguments provided to set_loading");
         return false;
     }
     *loading = loading_alloc();
-    if(!*loading) {
+    if (!*loading)
+    {
         FURI_LOG_E(EASY_TAG, "Failed to allocate Loading");
         return false;
     }
-    if(previous_callback) {
+    if (previous_callback)
+    {
         view_set_previous_callback(loading_get_view(*loading), previous_callback);
     }
     view_dispatcher_add_view(*view_dispatcher, view_id, loading_get_view(*loading));
@@ -497,16 +573,19 @@ bool easy_flipper_set_loading(
  * @param buffer The buffer to copy the string to
  * @return true if successful, false otherwise
  */
-bool easy_flipper_set_char_to_furi_string(FuriString** furi_string, char* buffer) {
-    if(!furi_string) {
+bool easy_flipper_set_char_to_furi_string(FuriString **furi_string, char *buffer)
+{
+    if (!furi_string)
+    {
         FURI_LOG_E(EASY_TAG, "Invalid arguments provided to set_buffer_to_furi_string");
         return false;
     }
     *furi_string = furi_string_alloc();
-    if(!furi_string) {
+    if (!furi_string)
+    {
         FURI_LOG_E(EASY_TAG, "Failed to allocate FuriString");
         return false;
     }
     furi_string_set_str(*furi_string, buffer);
     return true;
-}
+}

+ 66 - 61
flip_wifi/easy_flipper/easy_flipper.h

@@ -8,6 +8,7 @@
 #include <gui/view.h>
 #include <gui/modules/submenu.h>
 #include <gui/view_dispatcher.h>
+#include <gui/elements.h>
 #include <gui/modules/menu.h>
 #include <gui/modules/submenu.h>
 #include <gui/modules/widget.h>
@@ -25,19 +26,23 @@
 
 #define EASY_TAG "EasyFlipper"
 
+void easy_flipper_dialog(
+    char *header,
+    char *text);
+
 /**
  * @brief Navigation callback for exiting the application
  * @param context The context - unused
  * @return next view id (VIEW_NONE to exit the app)
  */
-uint32_t easy_flipper_callback_exit_app(void* context);
+uint32_t easy_flipper_callback_exit_app(void *context);
 /**
  * @brief Initialize a buffer
  * @param buffer The buffer to initialize
  * @param buffer_size The size of the buffer
  * @return true if successful, false otherwise
  */
-bool easy_flipper_set_buffer(char** buffer, uint32_t buffer_size);
+bool easy_flipper_set_buffer(char **buffer, uint32_t buffer_size);
 /**
  * @brief Initialize a View object
  * @param view The View object to initialize
@@ -49,13 +54,13 @@ bool easy_flipper_set_buffer(char** buffer, uint32_t buffer_size);
  * @return true if successful, false otherwise
  */
 bool easy_flipper_set_view(
-    View** view,
+    View **view,
     int32_t view_id,
-    void draw_callback(Canvas*, void*),
-    bool input_callback(InputEvent*, void*),
-    uint32_t (*previous_callback)(void*),
-    ViewDispatcher** view_dispatcher,
-    void* context);
+    void draw_callback(Canvas *, void *),
+    bool input_callback(InputEvent *, void *),
+    uint32_t (*previous_callback)(void *),
+    ViewDispatcher **view_dispatcher,
+    void *context);
 
 /**
  * @brief Initialize a ViewDispatcher object
@@ -64,7 +69,7 @@ bool easy_flipper_set_view(
  * @param context The context to pass to the event callback
  * @return true if successful, false otherwise
  */
-bool easy_flipper_set_view_dispatcher(ViewDispatcher** view_dispatcher, Gui* gui, void* context);
+bool easy_flipper_set_view_dispatcher(ViewDispatcher **view_dispatcher, Gui *gui, void *context);
 
 /**
  * @brief Initialize a Submenu object
@@ -77,11 +82,11 @@ bool easy_flipper_set_view_dispatcher(ViewDispatcher** view_dispatcher, Gui* gui
  * @return true if successful, false otherwise
  */
 bool easy_flipper_set_submenu(
-    Submenu** submenu,
+    Submenu **submenu,
     int32_t view_id,
-    char* title,
-    uint32_t(previous_callback)(void*),
-    ViewDispatcher** view_dispatcher);
+    char *title,
+    uint32_t(previous_callback)(void *),
+    ViewDispatcher **view_dispatcher);
 
 /**
  * @brief Initialize a Menu object
@@ -94,10 +99,10 @@ bool easy_flipper_set_submenu(
  * @return true if successful, false otherwise
  */
 bool easy_flipper_set_menu(
-    Menu** menu,
+    Menu **menu,
     int32_t view_id,
-    uint32_t(previous_callback)(void*),
-    ViewDispatcher** view_dispatcher);
+    uint32_t(previous_callback)(void *),
+    ViewDispatcher **view_dispatcher);
 
 /**
  * @brief Initialize a Widget object
@@ -109,11 +114,11 @@ bool easy_flipper_set_menu(
  * @return true if successful, false otherwise
  */
 bool easy_flipper_set_widget(
-    Widget** widget,
+    Widget **widget,
     int32_t view_id,
-    char* text,
-    uint32_t(previous_callback)(void*),
-    ViewDispatcher** view_dispatcher);
+    char *text,
+    uint32_t(previous_callback)(void *),
+    ViewDispatcher **view_dispatcher);
 
 /**
  * @brief Initialize a VariableItemList object
@@ -127,12 +132,12 @@ bool easy_flipper_set_widget(
  * @return true if successful, false otherwise
  */
 bool easy_flipper_set_variable_item_list(
-    VariableItemList** variable_item_list,
+    VariableItemList **variable_item_list,
     int32_t view_id,
-    void (*enter_callback)(void*, uint32_t),
-    uint32_t(previous_callback)(void*),
-    ViewDispatcher** view_dispatcher,
-    void* context);
+    void (*enter_callback)(void *, uint32_t),
+    uint32_t(previous_callback)(void *),
+    ViewDispatcher **view_dispatcher,
+    void *context);
 
 /**
  * @brief Initialize a TextInput object
@@ -143,15 +148,15 @@ bool easy_flipper_set_variable_item_list(
  * @return true if successful, false otherwise
  */
 bool easy_flipper_set_text_input(
-    TextInput** text_input,
+    TextInput **text_input,
     int32_t view_id,
-    char* header_text,
-    char* text_input_temp_buffer,
+    char *header_text,
+    char *text_input_temp_buffer,
     uint32_t text_input_buffer_size,
-    void (*result_callback)(void*),
-    uint32_t(previous_callback)(void*),
-    ViewDispatcher** view_dispatcher,
-    void* context);
+    void (*result_callback)(void *),
+    uint32_t(previous_callback)(void *),
+    ViewDispatcher **view_dispatcher,
+    void *context);
 
 /**
  * @brief Initialize a TextInput object with extra symbols
@@ -162,15 +167,15 @@ bool easy_flipper_set_text_input(
  * @return true if successful, false otherwise
  */
 bool easy_flipper_set_uart_text_input(
-    TextInput** uart_text_input,
+    TextInput **uart_text_input,
     int32_t view_id,
-    char* header_text,
-    char* uart_text_input_temp_buffer,
+    char *header_text,
+    char *uart_text_input_temp_buffer,
     uint32_t uart_text_input_buffer_size,
-    void (*result_callback)(void*),
-    uint32_t(previous_callback)(void*),
-    ViewDispatcher** view_dispatcher,
-    void* context);
+    void (*result_callback)(void *),
+    uint32_t(previous_callback)(void *),
+    ViewDispatcher **view_dispatcher,
+    void *context);
 
 /**
  * @brief Initialize a DialogEx object
@@ -192,21 +197,21 @@ bool easy_flipper_set_uart_text_input(
  * @return true if successful, false otherwise
  */
 bool easy_flipper_set_dialog_ex(
-    DialogEx** dialog_ex,
+    DialogEx **dialog_ex,
     int32_t view_id,
-    char* header,
+    char *header,
     uint16_t header_x,
     uint16_t header_y,
-    char* text,
+    char *text,
     uint16_t text_x,
     uint16_t text_y,
-    char* left_button_text,
-    char* right_button_text,
-    char* center_button_text,
-    void (*result_callback)(DialogExResult, void*),
-    uint32_t(previous_callback)(void*),
-    ViewDispatcher** view_dispatcher,
-    void* context);
+    char *left_button_text,
+    char *right_button_text,
+    char *center_button_text,
+    void (*result_callback)(DialogExResult, void *),
+    uint32_t(previous_callback)(void *),
+    ViewDispatcher **view_dispatcher,
+    void *context);
 
 /**
  * @brief Initialize a Popup object
@@ -225,18 +230,18 @@ bool easy_flipper_set_dialog_ex(
  * @return true if successful, false otherwise
  */
 bool easy_flipper_set_popup(
-    Popup** popup,
+    Popup **popup,
     int32_t view_id,
-    char* header,
+    char *header,
     uint16_t header_x,
     uint16_t header_y,
-    char* text,
+    char *text,
     uint16_t text_x,
     uint16_t text_y,
-    void (*result_callback)(void*),
-    uint32_t(previous_callback)(void*),
-    ViewDispatcher** view_dispatcher,
-    void* context);
+    void (*result_callback)(void *),
+    uint32_t(previous_callback)(void *),
+    ViewDispatcher **view_dispatcher,
+    void *context);
 
 /**
  * @brief Initialize a Loading object
@@ -247,10 +252,10 @@ bool easy_flipper_set_popup(
  * @return true if successful, false otherwise
  */
 bool easy_flipper_set_loading(
-    Loading** loading,
+    Loading **loading,
     int32_t view_id,
-    uint32_t(previous_callback)(void*),
-    ViewDispatcher** view_dispatcher);
+    uint32_t(previous_callback)(void *),
+    ViewDispatcher **view_dispatcher);
 
 /**
  * @brief Set a char butter to a FuriString
@@ -258,6 +263,6 @@ bool easy_flipper_set_loading(
  * @param buffer The buffer to copy the string to
  * @return true if successful, false otherwise
  */
-bool easy_flipper_set_char_to_furi_string(FuriString** furi_string, char* buffer);
+bool easy_flipper_set_char_to_furi_string(FuriString **furi_string, char *buffer);
 
-#endif
+#endif

+ 232 - 199
flip_wifi/flip_storage/flip_wifi_storage.c

@@ -1,6 +1,6 @@
 #include <flip_storage/flip_wifi_storage.h>
 
-char* app_ids[8] = {
+static char *app_ids[8] = {
     "flip_wifi",
     "flip_store",
     "flip_social",
@@ -8,23 +8,25 @@ char* app_ids[8] = {
     "flip_weather",
     "flip_library",
     "web_crawler",
-    "flip_rss"};
+    "flip_world"};
 
 // Function to save the playlist
-void save_playlist(WiFiPlaylist* playlist) {
-    if(!playlist) {
+void save_playlist(WiFiPlaylist *playlist)
+{
+    if (!playlist)
+    {
         FURI_LOG_E(TAG, "Playlist is NULL");
         return;
     }
 
     // Create the directory for saving settings
     char directory_path[128];
-    snprintf(
-        directory_path, sizeof(directory_path), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_wifi");
+    snprintf(directory_path, sizeof(directory_path), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_wifi/data");
 
     // Open storage
-    Storage* storage = furi_record_open(RECORD_STORAGE);
-    if(!storage) {
+    Storage *storage = furi_record_open(RECORD_STORAGE);
+    if (!storage)
+    {
         FURI_LOG_E(TAG, "Failed to open storage record");
         return;
     }
@@ -33,191 +35,111 @@ void save_playlist(WiFiPlaylist* playlist) {
     storage_common_mkdir(storage, directory_path);
 
     // Open the settings file
-    File* file = storage_file_alloc(storage);
-    if(!file) {
+    File *file = storage_file_alloc(storage);
+    if (!file)
+    {
         FURI_LOG_E(TAG, "Failed to allocate file handle");
         furi_record_close(RECORD_STORAGE);
         return;
     }
-    if(!storage_file_open(file, WIFI_SSID_LIST_PATH, FSAM_WRITE, FSOM_CREATE_ALWAYS)) {
+    if (!storage_file_open(file, WIFI_SSID_LIST_PATH, FSAM_WRITE, FSOM_CREATE_ALWAYS))
+    {
         FURI_LOG_E(TAG, "Failed to open settings file for writing: %s", WIFI_SSID_LIST_PATH);
         storage_file_free(file);
         furi_record_close(RECORD_STORAGE);
         return;
     }
 
-    for(size_t i = 0; i < playlist->count; i++) {
-        if(!playlist->ssids[i] || !playlist->passwords[i]) {
-            FURI_LOG_E(TAG, "Invalid SSID or password at index %zu", i);
-            continue;
-        }
-        size_t ssid_length = strlen(playlist->ssids[i]);
-        size_t password_length = strlen(playlist->passwords[i]);
-        if(storage_file_write(file, playlist->ssids[i], ssid_length) != ssid_length ||
-           storage_file_write(file, ",", 1) != 1 ||
-           storage_file_write(file, playlist->passwords[i], password_length) != password_length ||
-           storage_file_write(file, "\n", 1) != 1) {
-            FURI_LOG_E(TAG, "Failed to write playlist");
+    FuriString *json_result = furi_string_alloc();
+    if (!json_result)
+    {
+        FURI_LOG_E(TAG, "Failed to allocate FuriString");
+        storage_file_close(file);
+        storage_file_free(file);
+        furi_record_close(RECORD_STORAGE);
+        return;
+    }
+    furi_string_cat(json_result, "{\"ssids\":[\n");
+    for (size_t i = 0; i < playlist->count; i++)
+    {
+        furi_string_cat_printf(json_result, "{\"ssid\":\"%s\",\"password\":\"%s\"}", playlist->ssids[i], playlist->passwords[i]);
+        if (i < playlist->count - 1)
+        {
+            furi_string_cat(json_result, ",\n");
         }
     }
-
+    furi_string_cat(json_result, "\n]}");
+    size_t json_length = furi_string_size(json_result);
+    if (storage_file_write(file, furi_string_get_cstr(json_result), json_length) != json_length)
+    {
+        FURI_LOG_E(TAG, "Failed to write playlist to file");
+    }
+    furi_string_free(json_result);
     storage_file_close(file);
     storage_file_free(file);
     furi_record_close(RECORD_STORAGE);
 }
 
-// Function to load the playlist
-bool load_playlist(WiFiPlaylist* playlist) {
-    if(!playlist) {
+bool load_playlist(WiFiPlaylist *playlist)
+{
+    if (!playlist)
+    {
         FURI_LOG_E(TAG, "Playlist is NULL");
         return false;
     }
 
-    // Initialize playlist count
-    playlist->count = 0;
-
-    // Allocate memory for SSIDs and passwords if not already allocated
-    for(size_t i = 0; i < MAX_WIFI_NETWORKS; i++) {
-        if(!playlist->ssids[i]) {
-            playlist->ssids[i] = malloc(64); // Adjust size as needed
-            if(!playlist->ssids[i]) {
-                FURI_LOG_E(TAG, "Memory allocation failed for ssids[%zu]", i);
-                // Handle memory allocation failure (e.g., clean up and return)
-                return false;
-            }
-        }
-
-        if(!playlist->passwords[i]) {
-            playlist->passwords[i] = malloc(64); // Adjust size as needed
-            if(!playlist->passwords[i]) {
-                FURI_LOG_E(TAG, "Memory allocation failed for passwords[%zu]", i);
-                // Handle memory allocation failure (e.g., clean up and return)
-                return false;
-            }
-        }
-    }
-
-    // Open the settings file
-    Storage* storage = furi_record_open(RECORD_STORAGE);
-    if(!storage) {
-        FURI_LOG_E(TAG, "Failed to open storage record");
+    FuriString *json_result = flipper_http_load_from_file(WIFI_SSID_LIST_PATH);
+    if (!json_result)
+    {
+        FURI_LOG_E(TAG, "Failed to load playlist from file");
         return false;
     }
 
-    File* file = storage_file_alloc(storage);
-    if(!file) {
-        FURI_LOG_E(TAG, "Failed to allocate file handle");
-        furi_record_close(RECORD_STORAGE);
-        return false;
-    }
-
-    if(!storage_file_open(file, WIFI_SSID_LIST_PATH, FSAM_READ, FSOM_OPEN_EXISTING)) {
-        FURI_LOG_E(TAG, "Failed to open settings file for reading: %s", WIFI_SSID_LIST_PATH);
-        storage_file_free(file);
-        furi_record_close(RECORD_STORAGE);
-        return false; // Return false if the file does not exist
-    }
-
-    // Buffer to hold each line
-    char line_buffer[128];
-    size_t line_pos = 0;
-    char ch;
-
-    while(storage_file_read(file, &ch, 1) == 1) {
-        if(ch == '\n') {
-            // Null-terminate the line
-            line_buffer[line_pos] = '\0';
-
-            // Split the line into SSID and Password
-            char* comma_pos = strchr(line_buffer, ',');
-            if(comma_pos) {
-                *comma_pos = '\0'; // Replace comma with null character
-
-                // Copy SSID
-                strncpy(playlist->ssids[playlist->count], line_buffer, 63);
-                playlist->ssids[playlist->count][63] = '\0'; // Ensure null-termination
-
-                // Copy Password
-                strncpy(playlist->passwords[playlist->count], comma_pos + 1, 63);
-                playlist->passwords[playlist->count][63] = '\0'; // Ensure null-termination
-
-                playlist->count++;
-
-                if(playlist->count >= MAX_WIFI_NETWORKS) {
-                    FURI_LOG_W(
-                        TAG, "Reached maximum number of WiFi networks: %d", MAX_WIFI_NETWORKS);
-                    break;
-                }
-            } else {
-                FURI_LOG_E(TAG, "Invalid line format (no comma found): %s", line_buffer);
-            }
+    // Initialize playlist count
+    playlist->count = 0;
 
-            // Reset line buffer position for the next line
-            line_pos = 0;
-        } else {
-            if(line_pos < sizeof(line_buffer) - 1) {
-                line_buffer[line_pos++] = ch;
-            } else {
-                FURI_LOG_E(TAG, "Line buffer overflow");
-                // Optionally handle line overflow (e.g., skip the rest of the line)
-                line_pos = 0;
-            }
+    // Parse the JSON result
+    for (size_t i = 0; i < MAX_SAVED_NETWORKS; i++)
+    {
+        FuriString *json_data = get_json_array_value_furi("ssids", i, json_result);
+        if (!json_data)
+        {
+            break;
         }
-    }
-
-    // Handle the last line if it does not end with a newline
-    if(line_pos > 0) {
-        line_buffer[line_pos] = '\0';
-        char* comma_pos = strchr(line_buffer, ',');
-        if(comma_pos) {
-            *comma_pos = '\0'; // Replace comma with null character
-
-            // Copy SSID
-            strncpy(playlist->ssids[playlist->count], line_buffer, 63);
-            playlist->ssids[playlist->count][63] = '\0'; // Ensure null-termination
-
-            // Copy Password
-            strncpy(playlist->passwords[playlist->count], comma_pos + 1, 63);
-            playlist->passwords[playlist->count][63] = '\0'; // Ensure null-termination
-
-            playlist->count++;
-
-            if(playlist->count >= MAX_WIFI_NETWORKS) {
-                FURI_LOG_W(TAG, "Reached maximum number of WiFi networks: %d", MAX_WIFI_NETWORKS);
-            }
-        } else {
-            FURI_LOG_E(TAG, "Invalid line format (no comma found): %s", line_buffer);
+        FuriString *ssid = get_json_value_furi("ssid", json_data);
+        FuriString *password = get_json_value_furi("password", json_data);
+        if (!ssid || !password)
+        {
+            FURI_LOG_E(TAG, "Failed to get SSID or Password from JSON");
+            furi_string_free(json_data);
+            break;
         }
+        snprintf(playlist->ssids[i], MAX_SSID_LENGTH, "%s", furi_string_get_cstr(ssid));
+        snprintf(playlist->passwords[i], MAX_SSID_LENGTH, "%s", furi_string_get_cstr(password));
+        playlist->count++;
+        furi_string_free(json_data);
+        furi_string_free(ssid);
+        furi_string_free(password);
     }
-
-    // Close and free file resources
-    storage_file_close(file);
-    storage_file_free(file);
-    furi_record_close(RECORD_STORAGE);
-
+    furi_string_free(json_result);
     return true;
 }
 
-void save_settings(const char* ssid, const char* password) {
+void save_settings(const char *ssid, const char *password)
+{
     char edited_directory_path[128];
     char edited_file_path[128];
 
-    for(size_t i = 0; i < 8; i++) {
+    for (size_t i = 0; i < 8; i++)
+    {
         // Construct the directory and file paths for the current app
-        snprintf(
-            edited_directory_path,
-            sizeof(edited_directory_path),
-            STORAGE_EXT_PATH_PREFIX "/apps_data/%s",
-            app_ids[i]);
-        snprintf(
-            edited_file_path,
-            sizeof(edited_file_path),
-            STORAGE_EXT_PATH_PREFIX "/apps_data/%s/settings.bin",
-            app_ids[i]);
+        snprintf(edited_directory_path, sizeof(edited_directory_path), STORAGE_EXT_PATH_PREFIX "/apps_data/%s", app_ids[i]);
+        snprintf(edited_file_path, sizeof(edited_file_path), STORAGE_EXT_PATH_PREFIX "/apps_data/%s/settings.bin", app_ids[i]);
 
         // Open the storage record
-        Storage* storage = furi_record_open(RECORD_STORAGE);
-        if(!storage) {
+        Storage *storage = furi_record_open(RECORD_STORAGE);
+        if (!storage)
+        {
             FURI_LOG_E(TAG, "Failed to open storage record for app: %s", app_ids[i]);
             continue; // Skip to the next app
         }
@@ -226,24 +148,26 @@ void save_settings(const char* ssid, const char* password) {
         storage_common_mkdir(storage, edited_directory_path);
 
         // Allocate a file handle
-        File* file = storage_file_alloc(storage);
-        if(!file) {
+        File *file = storage_file_alloc(storage);
+        if (!file)
+        {
             FURI_LOG_E(TAG, "Failed to allocate storage file for app: %s", app_ids[i]);
             furi_record_close(RECORD_STORAGE);
             continue; // Skip to the next app
         }
 
         // Open the file in read mode to read existing data
-        bool file_opened =
-            storage_file_open(file, edited_file_path, FSAM_READ, FSOM_OPEN_EXISTING);
+        bool file_opened = storage_file_open(file, edited_file_path, FSAM_READ, FSOM_OPEN_EXISTING);
         size_t file_size = 0;
-        uint8_t* buffer = NULL;
+        uint8_t *buffer = NULL;
 
-        if(file_opened) {
+        if (file_opened)
+        {
             // Get the file size
             file_size = storage_file_size(file);
             buffer = malloc(file_size);
-            if(!buffer) {
+            if (!buffer)
+            {
                 FURI_LOG_E(TAG, "Failed to allocate buffer for app: %s", app_ids[i]);
                 storage_file_close(file);
                 storage_file_free(file);
@@ -252,7 +176,8 @@ void save_settings(const char* ssid, const char* password) {
             }
 
             // Read the existing data
-            if(storage_file_read(file, buffer, file_size) != file_size) {
+            if (storage_file_read(file, buffer, file_size) != file_size)
+            {
                 FURI_LOG_E(TAG, "Failed to read settings file for app: %s", app_ids[i]);
                 free(buffer);
                 storage_file_close(file);
@@ -262,7 +187,9 @@ void save_settings(const char* ssid, const char* password) {
             }
 
             storage_file_close(file);
-        } else {
+        }
+        else
+        {
             // If the file doesn't exist, initialize an empty buffer
             file_size = 0;
             buffer = NULL;
@@ -271,65 +198,70 @@ void save_settings(const char* ssid, const char* password) {
         storage_file_free(file);
 
         // Prepare new SSID and Password
-        size_t new_ssid_length = strlen(ssid) + 1; // Including null terminator
+        size_t new_ssid_length = strlen(ssid) + 1;         // Including null terminator
         size_t new_password_length = strlen(password) + 1; // Including null terminator
 
         // Calculate the new file size
-        size_t new_file_size =
-            sizeof(size_t) + new_ssid_length + sizeof(size_t) + new_password_length;
+        size_t new_file_size = sizeof(size_t) + new_ssid_length + sizeof(size_t) + new_password_length;
 
         // If there is additional data beyond SSID and Password, preserve it
         size_t additional_data_size = 0;
-        uint8_t* additional_data = NULL;
+        uint8_t *additional_data = NULL;
 
-        if(buffer) {
+        if (buffer)
+        {
             // Parse existing SSID length
-            if(file_size >= sizeof(size_t)) {
+            if (file_size >= sizeof(size_t))
+            {
                 size_t existing_ssid_length;
                 memcpy(&existing_ssid_length, buffer, sizeof(size_t));
 
                 // Parse existing Password length
-                if(file_size >= sizeof(size_t) + existing_ssid_length + sizeof(size_t)) {
+                if (file_size >= sizeof(size_t) + existing_ssid_length + sizeof(size_t))
+                {
                     size_t existing_password_length;
-                    memcpy(
-                        &existing_password_length,
-                        buffer + sizeof(size_t) + existing_ssid_length,
-                        sizeof(size_t));
+                    memcpy(&existing_password_length, buffer + sizeof(size_t) + existing_ssid_length, sizeof(size_t));
 
                     // Calculate the offset where additional data starts
-                    size_t additional_offset = sizeof(size_t) + existing_ssid_length +
-                                               sizeof(size_t) + existing_password_length;
-                    if(additional_offset < file_size) {
+                    size_t additional_offset = sizeof(size_t) + existing_ssid_length + sizeof(size_t) + existing_password_length;
+                    if (additional_offset < file_size)
+                    {
                         additional_data_size = file_size - additional_offset;
                         additional_data = malloc(additional_data_size);
-                        if(additional_data) {
-                            memcpy(
-                                additional_data, buffer + additional_offset, additional_data_size);
-                        } else {
-                            FURI_LOG_E(
-                                TAG,
-                                "Failed to allocate memory for additional data for app: %s",
-                                app_ids[i]);
+                        if (additional_data)
+                        {
+                            memcpy(additional_data, buffer + additional_offset, additional_data_size);
+                        }
+                        else
+                        {
+                            FURI_LOG_E(TAG, "Failed to allocate memory for additional data for app: %s", app_ids[i]);
                             free(buffer);
                             furi_record_close(RECORD_STORAGE);
                             continue;
                         }
                     }
-                } else {
+                }
+                else
+                {
                     FURI_LOG_E(TAG, "Settings file format invalid for app: %s", app_ids[i]);
                 }
-            } else {
+            }
+            else
+            {
                 FURI_LOG_E(TAG, "Settings file too small for app: %s", app_ids[i]);
             }
         }
 
         // Allocate a new buffer for updated data
         size_t total_new_size = new_file_size + additional_data_size;
-        uint8_t* new_buffer = malloc(total_new_size);
-        if(!new_buffer) {
+        uint8_t *new_buffer = malloc(total_new_size);
+        if (!new_buffer)
+        {
             FURI_LOG_E(TAG, "Failed to allocate new buffer for app: %s", app_ids[i]);
-            if(buffer) free(buffer);
-            if(additional_data) free(additional_data);
+            if (buffer)
+                free(buffer);
+            if (additional_data)
+                free(additional_data);
             furi_record_close(RECORD_STORAGE);
             continue;
         }
@@ -349,25 +281,30 @@ void save_settings(const char* ssid, const char* password) {
         offset += new_password_length;
 
         // Append any additional data if present
-        if(additional_data_size > 0 && additional_data) {
+        if (additional_data_size > 0 && additional_data)
+        {
             memcpy(new_buffer + offset, additional_data, additional_data_size);
             offset += additional_data_size;
         }
 
         // Free temporary buffers
-        if(buffer) free(buffer);
-        if(additional_data) free(additional_data);
+        if (buffer)
+            free(buffer);
+        if (additional_data)
+            free(additional_data);
 
         // Open the file in write mode with FSOM_CREATE_ALWAYS to overwrite it
         file = storage_file_alloc(storage);
-        if(!file) {
+        if (!file)
+        {
             FURI_LOG_E(TAG, "Failed to allocate storage file for writing: %s", app_ids[i]);
             free(new_buffer);
             furi_record_close(RECORD_STORAGE);
             continue;
         }
 
-        if(!storage_file_open(file, edited_file_path, FSAM_WRITE, FSOM_CREATE_ALWAYS)) {
+        if (!storage_file_open(file, edited_file_path, FSAM_WRITE, FSOM_CREATE_ALWAYS))
+        {
             FURI_LOG_E(TAG, "Failed to open settings file for writing: %s", edited_file_path);
             storage_file_free(file);
             free(new_buffer);
@@ -376,7 +313,8 @@ void save_settings(const char* ssid, const char* password) {
         }
 
         // Write the updated buffer back to the file
-        if(storage_file_write(file, new_buffer, total_new_size) != total_new_size) {
+        if (storage_file_write(file, new_buffer, total_new_size) != total_new_size)
+        {
             FURI_LOG_E(TAG, "Failed to write updated settings for app: %s", app_ids[i]);
         }
 
@@ -387,3 +325,98 @@ void save_settings(const char* ssid, const char* password) {
         furi_record_close(RECORD_STORAGE);
     }
 }
+bool save_char(
+    const char *path_name, const char *value)
+{
+    if (!value)
+    {
+        return false;
+    }
+    // Create the directory for saving settings
+    char directory_path[256];
+    snprintf(directory_path, sizeof(directory_path), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_wifi/data");
+
+    // Create the directory
+    Storage *storage = furi_record_open(RECORD_STORAGE);
+    if (!storage)
+    {
+        FURI_LOG_E(HTTP_TAG, "Failed to open storage record");
+        return false;
+    }
+    storage_common_mkdir(storage, directory_path);
+
+    // Open the settings file
+    File *file = storage_file_alloc(storage);
+    char file_path[256];
+    snprintf(file_path, sizeof(file_path), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_wifi/data/%s.txt", path_name);
+
+    // Open the file in write mode
+    if (!storage_file_open(file, file_path, FSAM_WRITE, FSOM_CREATE_ALWAYS))
+    {
+        FURI_LOG_E(HTTP_TAG, "Failed to open file for writing: %s", file_path);
+        storage_file_free(file);
+        furi_record_close(RECORD_STORAGE);
+        return false;
+    }
+
+    // Write the data to the file
+    size_t data_size = strlen(value) + 1; // Include null terminator
+    if (storage_file_write(file, value, data_size) != data_size)
+    {
+        FURI_LOG_E(HTTP_TAG, "Failed to append data to file");
+        storage_file_close(file);
+        storage_file_free(file);
+        furi_record_close(RECORD_STORAGE);
+        return false;
+    }
+
+    storage_file_close(file);
+    storage_file_free(file);
+    furi_record_close(RECORD_STORAGE);
+
+    return true;
+}
+
+bool load_char(
+    const char *path_name,
+    char *value,
+    size_t value_size)
+{
+    if (!value)
+    {
+        return false;
+    }
+    Storage *storage = furi_record_open(RECORD_STORAGE);
+    File *file = storage_file_alloc(storage);
+
+    char file_path[256];
+    snprintf(file_path, sizeof(file_path), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_wifi/data/%s.txt", path_name);
+
+    // Open the file for reading
+    if (!storage_file_open(file, file_path, FSAM_READ, FSOM_OPEN_EXISTING))
+    {
+        storage_file_free(file);
+        furi_record_close(RECORD_STORAGE);
+        return NULL; // Return false if the file does not exist
+    }
+
+    // Read data into the buffer
+    size_t read_count = storage_file_read(file, value, value_size);
+    if (storage_file_get_error(file) != FSE_OK)
+    {
+        FURI_LOG_E(HTTP_TAG, "Error reading from file.");
+        storage_file_close(file);
+        storage_file_free(file);
+        furi_record_close(RECORD_STORAGE);
+        return false;
+    }
+
+    // Ensure null-termination
+    value[read_count - 1] = '\0';
+
+    storage_file_close(file);
+    storage_file_free(file);
+    furi_record_close(RECORD_STORAGE);
+
+    return true;
+}

+ 13 - 9
flip_wifi/flip_storage/flip_wifi_storage.h

@@ -1,18 +1,22 @@
-#ifndef FLIP_WIFI_STORAGE_H
-#define FLIP_WIFI_STORAGE_H
+#pragma once
 
 #include <flip_wifi.h>
 
 // define the paths for all of the FlipperHTTP apps
-#define WIFI_SSID_LIST_PATH STORAGE_EXT_PATH_PREFIX "/apps_data/flip_wifi/wifi_list.txt"
-
-extern char* app_ids[8];
+#define WIFI_SSID_LIST_PATH STORAGE_EXT_PATH_PREFIX "/apps_data/flip_wifi/data/wifi_list.txt"
 
 // Function to save the playlist
-void save_playlist(WiFiPlaylist* playlist);
+void save_playlist(WiFiPlaylist *playlist);
 
 // Function to load the playlist
-bool load_playlist(WiFiPlaylist* playlist);
+bool load_playlist(WiFiPlaylist *playlist);
+
+void save_settings(const char *ssid, const char *password);
+
+bool save_char(
+    const char *path_name, const char *value);
 
-void save_settings(const char* ssid, const char* password);
-#endif
+bool load_char(
+    const char *path_name,
+    char *value,
+    size_t value_size);

+ 13 - 64
flip_wifi/flip_wifi.c

@@ -1,83 +1,32 @@
 #include "flip_wifi.h"
-
-FlipWiFiApp* app_instance = NULL;
-
+#include <callback/flip_wifi_callback.h>
+WiFiPlaylist *wifi_playlist = NULL;
 // Function to free the resources used by FlipWiFiApp
-void flip_wifi_app_free(FlipWiFiApp* app) {
-    if(!app) {
+void flip_wifi_app_free(FlipWiFiApp *app)
+{
+    if (!app)
+    {
         FURI_LOG_E(TAG, "FlipWiFiApp is NULL");
         return;
     }
 
-    // Free View(s)
-    if(app->view_wifi_scan) {
-        view_dispatcher_remove_view(app->view_dispatcher, FlipWiFiViewWiFiScan);
-        view_free(app->view_wifi_scan);
-    }
-    if(app->view_wifi_saved) {
-        view_dispatcher_remove_view(app->view_dispatcher, FlipWiFiViewWiFiSaved);
-        view_free(app->view_wifi_saved);
-    }
-
     // Free Submenu(s)
-    if(app->submenu_main) {
+    if (app->submenu_main)
+    {
         view_dispatcher_remove_view(app->view_dispatcher, FlipWiFiViewSubmenuMain);
         submenu_free(app->submenu_main);
     }
-    if(app->submenu_wifi_scan) {
-        view_dispatcher_remove_view(app->view_dispatcher, FlipWiFiViewSubmenuScan);
-        submenu_free(app->submenu_wifi_scan);
-    }
-    if(app->submenu_wifi_saved) {
-        view_dispatcher_remove_view(app->view_dispatcher, FlipWiFiViewSubmenuSaved);
-        submenu_free(app->submenu_wifi_saved);
-    }
-
-    // Free Widget(s)
-    if(app->widget_info) {
-        view_dispatcher_remove_view(app->view_dispatcher, FlipWiFiViewAbout);
-        widget_free(app->widget_info);
-    }
-
-    // Free Text Input(s)
-    if(app->uart_text_input_password_scan) {
-        view_dispatcher_remove_view(app->view_dispatcher, FlipWiFiViewTextInputScan);
-        text_input_free(app->uart_text_input_password_scan);
-    }
-    if(app->uart_text_input_password_saved) {
-        view_dispatcher_remove_view(app->view_dispatcher, FlipWiFiViewTextInputSaved);
-        text_input_free(app->uart_text_input_password_saved);
-    }
-    if(app->uart_text_input_add_ssid) {
-        view_dispatcher_remove_view(app->view_dispatcher, FlipWiFiViewTextInputSavedAddSSID);
-        text_input_free(app->uart_text_input_add_ssid);
-    }
-    if(app->uart_text_input_add_password) {
-        view_dispatcher_remove_view(app->view_dispatcher, FlipWiFiViewTextInputSavedAddPassword);
-        text_input_free(app->uart_text_input_add_password);
-    }
-
-    // free playlist
-    for(size_t i = 0; i < app->wifi_playlist.count; i++) {
-        if(app->wifi_playlist.ssids[i]) free(app->wifi_playlist.ssids[i]);
-        if(app->wifi_playlist.passwords[i]) free(app->wifi_playlist.passwords[i]);
-    }
-
-    // free popup
-    if(app->popup) {
-        view_dispatcher_remove_view(app->view_dispatcher, FlipWiFiViewPopup);
-        popup_free(app->popup);
-    }
 
-    // deinitalize flipper http
-    flipper_http_deinit();
+    flip_wifi_free_all(app);
 
     // free the view dispatcher
-    if(app->view_dispatcher) view_dispatcher_free(app->view_dispatcher);
+    if (app->view_dispatcher)
+        view_dispatcher_free(app->view_dispatcher);
 
     // close the gui
     furi_record_close(RECORD_GUI);
 
     // free the app
-    if(app) free(app);
+    if (app)
+        free(app);
 }

+ 43 - 54
flip_wifi/flip_wifi.h

@@ -5,86 +5,75 @@
 #include <easy_flipper/easy_flipper.h>
 #include <storage/storage.h>
 
-#define TAG               "FlipWiFi"
-#define MAX_WIFI_NETWORKS 25
+#define TAG "FlipWiFi"
+#define MAX_SCAN_NETWORKS 100
+#define MAX_SAVED_NETWORKS 25
+#define MAX_SSID_LENGTH 64
 
 // Define the submenu items for our FlipWiFi application
-typedef enum {
+typedef enum
+{
     FlipWiFiSubmenuIndexAbout,
     //
     FlipWiFiSubmenuIndexWiFiScan,
     FlipWiFiSubmenuIndexWiFiSaved,
+    FlipWiFiSubmenuIndexCommands,
     //
     FlipWiFiSubmenuIndexWiFiSavedAddSSID,
     //
+    FlipWiFiSubmenuIndexFastCommandStart = 50,
     FlipWiFiSubmenuIndexWiFiScanStart = 100,
     FlipWiFiSubmenuIndexWiFiSavedStart = 200,
 } FlipWiFiSubmenuIndex;
 
 // Define a single view for our FlipWiFi application
-typedef enum {
-    FlipWiFiViewWiFiScan, // The view for the wifi scan screen
+typedef enum
+{
+    FlipWiFiViewWiFiScan,  // The view for the wifi scan screen
     FlipWiFiViewWiFiSaved, // The view for the wifi scan screen
-    FlipWiFiViewSubmenuMain, // The submenu for the main screen
-    FlipWiFiViewSubmenuScan, // The submenu for the wifi scan screen
-    FlipWiFiViewSubmenuSaved, // The submenu for the wifi scan screen
-    FlipWiFiViewAbout, // The about screen
-    FlipWiFiViewTextInputScan, // The text input screen for the wifi scan screen
-    FlipWiFiViewTextInputSaved, // The text input screen for the wifi saved screen
     //
-    FlipWiFiViewTextInputSavedAddSSID, // The text input screen for the wifi saved screen
+    FlipWiFiViewSubmenuMain,     // The submenu for the main screen
+    FlipWiFiViewSubmenuScan,     // The submenu for the wifi scan screen
+    FlipWiFiViewSubmenuSaved,    // The submenu for the wifi saved screen
+    FlipWiFiViewSubmenuCommands, // The submenu for the fast commands screen
+    FlipWiFiViewAbout,           // The about screen
+    FlipWiFiViewTextInputScan,   // The text input screen for the wifi scan screen
+    FlipWiFiViewTextInputSaved,  // The text input screen for the wifi saved screen
+    //
+    FlipWiFiViewTextInputSavedAddSSID,     // The text input screen for the wifi saved screen
     FlipWiFiViewTextInputSavedAddPassword, // The text input screen for the wifi saved screen
     //
-    FlipWiFiViewPopup, // The popup screen
+    FlipWiFiViewGeneric,   // generic view
+    FlipWiFiViewSubmenu,   // generic submenu
+    FlipWiFiViewTextInput, // generic text input
 } FlipWiFiView;
 
 // Define the WiFiPlaylist structure
-typedef struct {
-    char* ssids[MAX_WIFI_NETWORKS];
-    char* passwords[MAX_WIFI_NETWORKS];
+typedef struct
+{
+    char ssids[MAX_SAVED_NETWORKS][MAX_SSID_LENGTH];
+    char passwords[MAX_SAVED_NETWORKS][MAX_SSID_LENGTH];
     size_t count;
 } WiFiPlaylist;
 
 // Each screen will have its own view
-typedef struct {
-    ViewDispatcher* view_dispatcher; // Switches between our views
-    Popup* popup; // The popup for the app
-    View* view_wifi_scan; // The view for the wifi scan screen
-    View* view_wifi_saved; // The view for the wifi saved screen
-    Submenu* submenu_main; // The submenu for the main screen
-    Submenu* submenu_wifi_scan; // The submenu for the wifi scan screen
-    Submenu* submenu_wifi_saved; // The submenu for the saved wifi screen
-    Widget* widget_info; // The widget
-    VariableItemList* variable_item_list_wifi; // The variable item list (settngs)
-    VariableItem* variable_item_ssid; // The variable item
-    TextInput* uart_text_input_password_scan; // The text input for the wifi scan screen
-    TextInput* uart_text_input_password_saved; // The text input for the wifi saved screen
-    //
-    TextInput* uart_text_input_add_ssid; // The text input for the wifi saved screen
-    TextInput* uart_text_input_add_password; // The text input for the wifi saved screen
-
-    char* uart_text_input_buffer_password_scan; // Buffer for the text input
-    char* uart_text_input_temp_buffer_password_scan; // Temporary buffer for the text input
-    uint32_t uart_text_input_buffer_size_password_scan; // Size of the text input buffer
-
-    char* uart_text_input_buffer_password_saved; // Buffer for the text input
-    char* uart_text_input_temp_buffer_password_saved; // Temporary buffer for the text input
-    uint32_t uart_text_input_buffer_size_password_saved; // Size of the text input buffer
-
-    char* uart_text_input_buffer_add_ssid; // Buffer for the text input
-    char* uart_text_input_temp_buffer_add_ssid; // Temporary buffer for the text input
-    uint32_t uart_text_input_buffer_size_add_ssid; // Size of the text input buffer
-
-    char* uart_text_input_buffer_add_password; // Buffer for the text input
-    char* uart_text_input_temp_buffer_add_password; // Temporary buffer for the text input
-    uint32_t uart_text_input_buffer_size_add_password; // Size of the text input buffer
-
-    WiFiPlaylist wifi_playlist; // The playlist of wifi networks
+typedef struct
+{
+    ViewDispatcher *view_dispatcher;           // Switches between our views
+    Widget *widget_info;                       // The widget for the about screen
+    View *view_wifi;                           // generic view for the wifi scan and saved screens
+    Submenu *submenu_main;                     // The submenu for the main screen
+    Submenu *submenu_wifi;                     // generic submenu for the wifi scan and saved screens
+    VariableItemList *variable_item_list_wifi; // The variable item list (settngs)
+    VariableItem *variable_item_ssid;          // The variable item
+    TextInput *uart_text_input;           // The text input screen
+    char *uart_text_input_buffer;              // Buffer for the text input
+    char *uart_text_input_temp_buffer;         // Temporary buffer for the text input
+    uint32_t uart_text_input_buffer_size;      // Size of the text input buffer
 } FlipWiFiApp;
 
-extern FlipWiFiApp* app_instance;
-
 // Function to free the resources used by FlipWiFiApp
-void flip_wifi_app_free(FlipWiFiApp* app);
+void flip_wifi_app_free(FlipWiFiApp *app);
+extern WiFiPlaylist *wifi_playlist; // The playlist of wifi networks
 
-#endif // FLIP_WIFI_E_H
+#endif // FLIP_WIFI_E_H

Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 294 - 160
flip_wifi/flipper_http/flipper_http.c


+ 67 - 65
flip_wifi/flipper_http/flipper_http.h

@@ -15,69 +15,72 @@
 
 // STORAGE_EXT_PATH_PREFIX is defined in the Furi SDK as /ext
 
-#define HTTP_TAG               "FlipWiFi" // change this to your app name
-#define http_tag               "flip_wifi" // change this to your app id
-#define UART_CH                (momentum_settings.uart_esp_channel) // UART channel
+#define HTTP_TAG "FlipWiFi"               // change this to your app name
+#define http_tag "flip_wifi"              // change this to your app id
+#define UART_CH (momentum_settings.uart_esp_channel)    // UART channel
 #define TIMEOUT_DURATION_TICKS (5 * 1000) // 5 seconds
-#define BAUDRATE               (115200) // UART baudrate
-#define RX_BUF_SIZE            1024 // UART RX buffer size
-#define RX_LINE_BUFFER_SIZE    4096 // UART RX line buffer size (increase for large responses)
-#define MAX_FILE_SHOW          4096 // Maximum data from file to show
-#define FILE_BUFFER_SIZE       512 // File buffer size
+#define BAUDRATE (115200)                 // UART baudrate
+#define RX_BUF_SIZE 2048                  // UART RX buffer size
+#define RX_LINE_BUFFER_SIZE 4096          // UART RX line buffer size (increase for large responses)
+#define MAX_FILE_SHOW 4096                // Maximum data from file to show
+#define FILE_BUFFER_SIZE 512              // File buffer size
 
 // Forward declaration for callback
-typedef void (*FlipperHTTP_Callback)(const char* line, void* context);
+typedef void (*FlipperHTTP_Callback)(const char *line, void *context);
 
 // State variable to track the UART state
-typedef enum {
-    INACTIVE, // Inactive state
-    IDLE, // Default state
+typedef enum
+{
+    INACTIVE,  // Inactive state
+    IDLE,      // Default state
     RECEIVING, // Receiving data
-    SENDING, // Sending data
-    ISSUE, // Issue with connection
+    SENDING,   // Sending data
+    ISSUE,     // Issue with connection
 } SerialState;
 
 // Event Flags for UART Worker Thread
-typedef enum {
+typedef enum
+{
     WorkerEvtStop = (1 << 0),
     WorkerEvtRxDone = (1 << 1),
 } WorkerEvtFlags;
 
 // FlipperHTTP Structure
-typedef struct {
-    FuriStreamBuffer* flipper_http_stream; // Stream buffer for UART communication
-    FuriHalSerialHandle* serial_handle; // Serial handle for UART communication
-    FuriThread* rx_thread; // Worker thread for UART
-    FuriThreadId rx_thread_id; // Worker thread ID
+typedef struct
+{
+    FuriStreamBuffer *flipper_http_stream;  // Stream buffer for UART communication
+    FuriHalSerialHandle *serial_handle;     // Serial handle for UART communication
+    FuriThread *rx_thread;                  // Worker thread for UART
+    FuriThreadId rx_thread_id;              // Worker thread ID
     FlipperHTTP_Callback handle_rx_line_cb; // Callback for received lines
-    void* callback_context; // Context for the callback
-    SerialState state; // State of the UART
+    void *callback_context;                 // Context for the callback
+    SerialState state;                      // State of the UART
 
     // variable to store the last received data from the UART
-    char* last_response;
+    char *last_response;
     char file_path[256]; // Path to save the received data
 
     // Timer-related members
-    FuriTimer* get_timeout_timer; // Timer for HTTP request timeout
+    FuriTimer *get_timeout_timer; // Timer for HTTP request timeout
 
     bool started_receiving_get; // Indicates if a GET request has started
-    bool just_started_get; // Indicates if GET data reception has just started
+    bool just_started_get;      // Indicates if GET data reception has just started
 
     bool started_receiving_post; // Indicates if a POST request has started
-    bool just_started_post; // Indicates if POST data reception has just started
+    bool just_started_post;      // Indicates if POST data reception has just started
 
     bool started_receiving_put; // Indicates if a PUT request has started
-    bool just_started_put; // Indicates if PUT data reception has just started
+    bool just_started_put;      // Indicates if PUT data reception has just started
 
     bool started_receiving_delete; // Indicates if a DELETE request has started
-    bool just_started_delete; // Indicates if DELETE data reception has just started
+    bool just_started_delete;      // Indicates if DELETE data reception has just started
 
     // Buffer to hold the raw bytes received from the UART
-    uint8_t* received_bytes;
+    uint8_t *received_bytes;
     size_t received_bytes_len; // Length of the received bytes
-    bool is_bytes_request; // Flag to indicate if the request is for bytes
-    bool save_bytes; // Flag to save the received data to a file
-    bool save_received_data; // Flag to save the received data to a file
+    bool is_bytes_request;     // Flag to indicate if the request is for bytes
+    bool save_bytes;           // Flag to save the received data to a file
+    bool save_received_data;   // Flag to save the received data to a file
 
     bool just_started_bytes; // Indicates if bytes data reception has just started
 } FlipperHTTP;
@@ -93,12 +96,12 @@ extern size_t file_buffer_len;
 // Function to append received data to file
 // make sure to initialize the file path before calling this function
 bool flipper_http_append_to_file(
-    const void* data,
+    const void *data,
     size_t data_size,
     bool start_new_file,
-    char* file_path);
+    char *file_path);
 
-FuriString* flipper_http_load_from_file(char* file_path);
+FuriString *flipper_http_load_from_file(char *file_path);
 
 // UART worker thread
 /**
@@ -108,7 +111,7 @@ FuriString* flipper_http_load_from_file(char* file_path);
  * @note       This function will handle received data asynchronously via the callback.
  */
 // UART worker thread
-int32_t flipper_http_worker(void* context);
+int32_t flipper_http_worker(void *context);
 
 // Timer callback function
 /**
@@ -117,7 +120,7 @@ int32_t flipper_http_worker(void* context);
  * @param      context   The context to pass to the callback.
  * @note       This function will be called when the GET request times out.
  */
-void get_timeout_timer_callback(void* context);
+void get_timeout_timer_callback(void *context);
 
 // UART RX Handler Callback (Interrupt Context)
 /**
@@ -129,9 +132,9 @@ void get_timeout_timer_callback(void* context);
  * @note       This function will handle received data asynchronously via the callback.
  */
 void _flipper_http_rx_callback(
-    FuriHalSerialHandle* handle,
+    FuriHalSerialHandle *handle,
     FuriHalSerialRxEvent event,
-    void* context);
+    void *context);
 
 // UART initialization function
 /**
@@ -141,7 +144,7 @@ void _flipper_http_rx_callback(
  * @param      context   The context to pass to the callback.
  * @note       The received data will be handled asynchronously via the callback.
  */
-bool flipper_http_init(FlipperHTTP_Callback callback, void* context);
+bool flipper_http_init(FlipperHTTP_Callback callback, void *context);
 
 // Deinitialize UART
 /**
@@ -158,7 +161,7 @@ void flipper_http_deinit();
  * @param      data  The data to send over UART.
  * @note       The data will be sent over UART with a newline character appended.
  */
-bool flipper_http_send_data(const char* data);
+bool flipper_http_send_data(const char *data);
 
 // Function to send a PING request
 /**
@@ -202,7 +205,7 @@ bool flipper_http_led_off();
  * @param      json_data The JSON data to parse.
  * @note       The received data will be handled asynchronously via the callback.
  */
-bool flipper_http_parse_json(const char* key, const char* json_data);
+bool flipper_http_parse_json(const char *key, const char *json_data);
 
 // Function to parse JSON array data
 /**
@@ -213,7 +216,7 @@ bool flipper_http_parse_json(const char* key, const char* json_data);
  * @param      json_data The JSON array data to parse.
  * @note       The received data will be handled asynchronously via the callback.
  */
-bool flipper_http_parse_json_array(const char* key, int index, const char* json_data);
+bool flipper_http_parse_json_array(const char *key, int index, const char *json_data);
 
 // Function to scan for WiFi networks
 /**
@@ -229,7 +232,7 @@ bool flipper_http_scan_wifi();
  * @return     true if the request was successful, false otherwise.
  * @note       The received data will be handled asynchronously via the callback.
  */
-bool flipper_http_save_wifi(const char* ssid, const char* password);
+bool flipper_http_save_wifi(const char *ssid, const char *password);
 
 // Function to get IP address of WiFi Devboard
 /**
@@ -270,7 +273,7 @@ bool flipper_http_connect_wifi();
  * @param      url  The URL to send the GET request to.
  * @note       The received data will be handled asynchronously via the callback.
  */
-bool flipper_http_get_request(const char* url);
+bool flipper_http_get_request(const char *url);
 
 // Function to send a GET request with headers
 /**
@@ -280,7 +283,7 @@ bool flipper_http_get_request(const char* url);
  * @param      headers  The headers to send with the GET request.
  * @note       The received data will be handled asynchronously via the callback.
  */
-bool flipper_http_get_request_with_headers(const char* url, const char* headers);
+bool flipper_http_get_request_with_headers(const char *url, const char *headers);
 
 // Function to send a GET request with headers and return bytes
 /**
@@ -290,7 +293,7 @@ bool flipper_http_get_request_with_headers(const char* url, const char* headers)
  * @param      headers  The headers to send with the GET request.
  * @note       The received data will be handled asynchronously via the callback.
  */
-bool flipper_http_get_request_bytes(const char* url, const char* headers);
+bool flipper_http_get_request_bytes(const char *url, const char *headers);
 
 // Function to send a POST request with headers
 /**
@@ -302,9 +305,9 @@ bool flipper_http_get_request_bytes(const char* url, const char* headers);
  * @note       The received data will be handled asynchronously via the callback.
  */
 bool flipper_http_post_request_with_headers(
-    const char* url,
-    const char* headers,
-    const char* payload);
+    const char *url,
+    const char *headers,
+    const char *payload);
 
 // Function to send a POST request with headers and return bytes
 /**
@@ -315,7 +318,7 @@ bool flipper_http_post_request_with_headers(
  * @param      payload  The data to send with the POST request.
  * @note       The received data will be handled asynchronously via the callback.
  */
-bool flipper_http_post_request_bytes(const char* url, const char* headers, const char* payload);
+bool flipper_http_post_request_bytes(const char *url, const char *headers, const char *payload);
 
 // Function to send a PUT request with headers
 /**
@@ -327,9 +330,9 @@ bool flipper_http_post_request_bytes(const char* url, const char* headers, const
  * @note       The received data will be handled asynchronously via the callback.
  */
 bool flipper_http_put_request_with_headers(
-    const char* url,
-    const char* headers,
-    const char* payload);
+    const char *url,
+    const char *headers,
+    const char *payload);
 
 // Function to send a DELETE request with headers
 /**
@@ -341,9 +344,9 @@ bool flipper_http_put_request_with_headers(
  * @note       The received data will be handled asynchronously via the callback.
  */
 bool flipper_http_delete_request_with_headers(
-    const char* url,
-    const char* headers,
-    const char* payload);
+    const char *url,
+    const char *headers,
+    const char *payload);
 
 // Function to handle received data asynchronously
 /**
@@ -353,10 +356,10 @@ bool flipper_http_delete_request_with_headers(
  * @param      context  The context passed to the callback.
  * @note       The received data will be handled asynchronously via the callback and handles the state of the UART.
  */
-void flipper_http_rx_callback(const char* line, void* context);
+void flipper_http_rx_callback(const char *line, void *context);
 
 // Function to trim leading and trailing spaces and newlines from a constant string
-char* trim(const char* str);
+char *trim(const char *str);
 /**
  * @brief Process requests and parse JSON data asynchronously
  * @param http_request The function to send the request
@@ -374,11 +377,10 @@ bool flipper_http_process_response_async(bool (*http_request)(void), bool (*pars
  * @param view_dispatcher The view dispatcher to use
  * @return
  */
-void flipper_http_loading_task(
-    bool (*http_request)(void),
-    bool (*parse_response)(void),
-    uint32_t success_view_id,
-    uint32_t failure_view_id,
-    ViewDispatcher** view_dispatcher);
+void flipper_http_loading_task(bool (*http_request)(void),
+                               bool (*parse_response)(void),
+                               uint32_t success_view_id,
+                               uint32_t failure_view_id,
+                               ViewDispatcher **view_dispatcher);
 
 #endif // FLIPPER_HTTP_H

+ 285 - 166
flip_wifi/jsmn/jsmn.c

@@ -7,17 +7,17 @@
  */
 
 #include <jsmn/jsmn.h>
-#include <stdlib.h>
-#include <string.h>
 
 /**
  * Allocates a fresh unused token from the token pool.
  */
-static jsmntok_t*
-    jsmn_alloc_token(jsmn_parser* parser, jsmntok_t* tokens, const size_t num_tokens) {
-    jsmntok_t* tok;
+static jsmntok_t *jsmn_alloc_token(jsmn_parser *parser, jsmntok_t *tokens,
+                                   const size_t num_tokens)
+{
+    jsmntok_t *tok;
 
-    if(parser->toknext >= num_tokens) {
+    if (parser->toknext >= num_tokens)
+    {
         return NULL;
     }
     tok = &tokens[parser->toknext++];
@@ -32,8 +32,9 @@ static jsmntok_t*
 /**
  * Fills token type and boundaries.
  */
-static void
-    jsmn_fill_token(jsmntok_t* token, const jsmntype_t type, const int start, const int end) {
+static void jsmn_fill_token(jsmntok_t *token, const jsmntype_t type,
+                            const int start, const int end)
+{
     token->type = type;
     token->start = start;
     token->end = end;
@@ -43,19 +44,19 @@ static void
 /**
  * Fills next available token with JSON primitive.
  */
-static int jsmn_parse_primitive(
-    jsmn_parser* parser,
-    const char* js,
-    const size_t len,
-    jsmntok_t* tokens,
-    const size_t num_tokens) {
-    jsmntok_t* token;
+static int jsmn_parse_primitive(jsmn_parser *parser, const char *js,
+                                const size_t len, jsmntok_t *tokens,
+                                const size_t num_tokens)
+{
+    jsmntok_t *token;
     int start;
 
     start = parser->pos;
 
-    for(; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) {
-        switch(js[parser->pos]) {
+    for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++)
+    {
+        switch (js[parser->pos])
+        {
 #ifndef JSMN_STRICT
         /* In strict mode primitive must be followed by "," or "}" or "]" */
         case ':':
@@ -72,7 +73,8 @@ static int jsmn_parse_primitive(
             /* to quiet a warning from gcc*/
             break;
         }
-        if(js[parser->pos] < 32 || js[parser->pos] >= 127) {
+        if (js[parser->pos] < 32 || js[parser->pos] >= 127)
+        {
             parser->pos = start;
             return JSMN_ERROR_INVAL;
         }
@@ -84,12 +86,14 @@ static int jsmn_parse_primitive(
 #endif
 
 found:
-    if(tokens == NULL) {
+    if (tokens == NULL)
+    {
         parser->pos--;
         return 0;
     }
     token = jsmn_alloc_token(parser, tokens, num_tokens);
-    if(token == NULL) {
+    if (token == NULL)
+    {
         parser->pos = start;
         return JSMN_ERROR_NOMEM;
     }
@@ -104,29 +108,31 @@ found:
 /**
  * Fills next token with JSON string.
  */
-static int jsmn_parse_string(
-    jsmn_parser* parser,
-    const char* js,
-    const size_t len,
-    jsmntok_t* tokens,
-    const size_t num_tokens) {
-    jsmntok_t* token;
+static int jsmn_parse_string(jsmn_parser *parser, const char *js,
+                             const size_t len, jsmntok_t *tokens,
+                             const size_t num_tokens)
+{
+    jsmntok_t *token;
 
     int start = parser->pos;
 
     /* Skip starting quote */
     parser->pos++;
 
-    for(; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) {
+    for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++)
+    {
         char c = js[parser->pos];
 
         /* Quote: end of string */
-        if(c == '\"') {
-            if(tokens == NULL) {
+        if (c == '\"')
+        {
+            if (tokens == NULL)
+            {
                 return 0;
             }
             token = jsmn_alloc_token(parser, tokens, num_tokens);
-            if(token == NULL) {
+            if (token == NULL)
+            {
                 parser->pos = start;
                 return JSMN_ERROR_NOMEM;
             }
@@ -138,10 +144,12 @@ static int jsmn_parse_string(
         }
 
         /* Backslash: Quoted symbol expected */
-        if(c == '\\' && parser->pos + 1 < len) {
+        if (c == '\\' && parser->pos + 1 < len)
+        {
             int i;
             parser->pos++;
-            switch(js[parser->pos]) {
+            switch (js[parser->pos])
+            {
             /* Allowed escaped symbols */
             case '\"':
             case '/':
@@ -155,11 +163,13 @@ static int jsmn_parse_string(
             /* Allows escaped symbol \uXXXX */
             case 'u':
                 parser->pos++;
-                for(i = 0; i < 4 && parser->pos < len && js[parser->pos] != '\0'; i++) {
+                for (i = 0; i < 4 && parser->pos < len && js[parser->pos] != '\0'; i++)
+                {
                     /* If it isn't a hex character we have an error */
-                    if(!((js[parser->pos] >= 48 && js[parser->pos] <= 57) || /* 0-9 */
-                         (js[parser->pos] >= 65 && js[parser->pos] <= 70) || /* A-F */
-                         (js[parser->pos] >= 97 && js[parser->pos] <= 102))) { /* a-f */
+                    if (!((js[parser->pos] >= 48 && js[parser->pos] <= 57) || /* 0-9 */
+                          (js[parser->pos] >= 65 && js[parser->pos] <= 70) || /* A-F */
+                          (js[parser->pos] >= 97 && js[parser->pos] <= 102)))
+                    { /* a-f */
                         parser->pos = start;
                         return JSMN_ERROR_INVAL;
                     }
@@ -181,7 +191,8 @@ static int jsmn_parse_string(
 /**
  * Create JSON parser over an array of tokens
  */
-void jsmn_init(jsmn_parser* parser) {
+void jsmn_init(jsmn_parser *parser)
+{
     parser->pos = 0;
     parser->toknext = 0;
     parser->toksuper = -1;
@@ -190,38 +201,41 @@ void jsmn_init(jsmn_parser* parser) {
 /**
  * Parse JSON string and fill tokens.
  */
-int jsmn_parse(
-    jsmn_parser* parser,
-    const char* js,
-    const size_t len,
-    jsmntok_t* tokens,
-    const unsigned int num_tokens) {
+int jsmn_parse(jsmn_parser *parser, const char *js, const size_t len,
+               jsmntok_t *tokens, const unsigned int num_tokens)
+{
     int r;
     int i;
-    jsmntok_t* token;
+    jsmntok_t *token;
     int count = parser->toknext;
 
-    for(; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) {
+    for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++)
+    {
         char c;
         jsmntype_t type;
 
         c = js[parser->pos];
-        switch(c) {
+        switch (c)
+        {
         case '{':
         case '[':
             count++;
-            if(tokens == NULL) {
+            if (tokens == NULL)
+            {
                 break;
             }
             token = jsmn_alloc_token(parser, tokens, num_tokens);
-            if(token == NULL) {
+            if (token == NULL)
+            {
                 return JSMN_ERROR_NOMEM;
             }
-            if(parser->toksuper != -1) {
-                jsmntok_t* t = &tokens[parser->toksuper];
+            if (parser->toksuper != -1)
+            {
+                jsmntok_t *t = &tokens[parser->toksuper];
 #ifdef JSMN_STRICT
                 /* In strict mode an object or array can't become a key */
-                if(t->type == JSMN_OBJECT) {
+                if (t->type == JSMN_OBJECT)
+                {
                     return JSMN_ERROR_INVAL;
                 }
 #endif
@@ -236,26 +250,33 @@ int jsmn_parse(
             break;
         case '}':
         case ']':
-            if(tokens == NULL) {
+            if (tokens == NULL)
+            {
                 break;
             }
             type = (c == '}' ? JSMN_OBJECT : JSMN_ARRAY);
 #ifdef JSMN_PARENT_LINKS
-            if(parser->toknext < 1) {
+            if (parser->toknext < 1)
+            {
                 return JSMN_ERROR_INVAL;
             }
             token = &tokens[parser->toknext - 1];
-            for(;;) {
-                if(token->start != -1 && token->end == -1) {
-                    if(token->type != type) {
+            for (;;)
+            {
+                if (token->start != -1 && token->end == -1)
+                {
+                    if (token->type != type)
+                    {
                         return JSMN_ERROR_INVAL;
                     }
                     token->end = parser->pos + 1;
                     parser->toksuper = token->parent;
                     break;
                 }
-                if(token->parent == -1) {
-                    if(token->type != type || parser->toksuper == -1) {
+                if (token->parent == -1)
+                {
+                    if (token->type != type || parser->toksuper == -1)
+                    {
                         return JSMN_ERROR_INVAL;
                     }
                     break;
@@ -263,10 +284,13 @@ int jsmn_parse(
                 token = &tokens[token->parent];
             }
 #else
-            for(i = parser->toknext - 1; i >= 0; i--) {
+            for (i = parser->toknext - 1; i >= 0; i--)
+            {
                 token = &tokens[i];
-                if(token->start != -1 && token->end == -1) {
-                    if(token->type != type) {
+                if (token->start != -1 && token->end == -1)
+                {
+                    if (token->type != type)
+                    {
                         return JSMN_ERROR_INVAL;
                     }
                     parser->toksuper = -1;
@@ -275,12 +299,15 @@ int jsmn_parse(
                 }
             }
             /* Error if unmatched closing bracket */
-            if(i == -1) {
+            if (i == -1)
+            {
                 return JSMN_ERROR_INVAL;
             }
-            for(; i >= 0; i--) {
+            for (; i >= 0; i--)
+            {
                 token = &tokens[i];
-                if(token->start != -1 && token->end == -1) {
+                if (token->start != -1 && token->end == -1)
+                {
                     parser->toksuper = i;
                     break;
                 }
@@ -289,11 +316,13 @@ int jsmn_parse(
             break;
         case '\"':
             r = jsmn_parse_string(parser, js, len, tokens, num_tokens);
-            if(r < 0) {
+            if (r < 0)
+            {
                 return r;
             }
             count++;
-            if(parser->toksuper != -1 && tokens != NULL) {
+            if (parser->toksuper != -1 && tokens != NULL)
+            {
                 tokens[parser->toksuper].size++;
             }
             break;
@@ -306,15 +335,19 @@ int jsmn_parse(
             parser->toksuper = parser->toknext - 1;
             break;
         case ',':
-            if(tokens != NULL && parser->toksuper != -1 &&
-               tokens[parser->toksuper].type != JSMN_ARRAY &&
-               tokens[parser->toksuper].type != JSMN_OBJECT) {
+            if (tokens != NULL && parser->toksuper != -1 &&
+                tokens[parser->toksuper].type != JSMN_ARRAY &&
+                tokens[parser->toksuper].type != JSMN_OBJECT)
+            {
 #ifdef JSMN_PARENT_LINKS
                 parser->toksuper = tokens[parser->toksuper].parent;
 #else
-                for(i = parser->toknext - 1; i >= 0; i--) {
-                    if(tokens[i].type == JSMN_ARRAY || tokens[i].type == JSMN_OBJECT) {
-                        if(tokens[i].start != -1 && tokens[i].end == -1) {
+                for (i = parser->toknext - 1; i >= 0; i--)
+                {
+                    if (tokens[i].type == JSMN_ARRAY || tokens[i].type == JSMN_OBJECT)
+                    {
+                        if (tokens[i].start != -1 && tokens[i].end == -1)
+                        {
                             parser->toksuper = i;
                             break;
                         }
@@ -340,9 +373,12 @@ int jsmn_parse(
         case 'f':
         case 'n':
             /* And they must not be keys of the object */
-            if(tokens != NULL && parser->toksuper != -1) {
-                const jsmntok_t* t = &tokens[parser->toksuper];
-                if(t->type == JSMN_OBJECT || (t->type == JSMN_STRING && t->size != 0)) {
+            if (tokens != NULL && parser->toksuper != -1)
+            {
+                const jsmntok_t *t = &tokens[parser->toksuper];
+                if (t->type == JSMN_OBJECT ||
+                    (t->type == JSMN_STRING && t->size != 0))
+                {
                     return JSMN_ERROR_INVAL;
                 }
             }
@@ -351,11 +387,13 @@ int jsmn_parse(
         default:
 #endif
             r = jsmn_parse_primitive(parser, js, len, tokens, num_tokens);
-            if(r < 0) {
+            if (r < 0)
+            {
                 return r;
             }
             count++;
-            if(parser->toksuper != -1 && tokens != NULL) {
+            if (parser->toksuper != -1 && tokens != NULL)
+            {
                 tokens[parser->toksuper].size++;
             }
             break;
@@ -368,10 +406,13 @@ int jsmn_parse(
         }
     }
 
-    if(tokens != NULL) {
-        for(i = parser->toknext - 1; i >= 0; i--) {
+    if (tokens != NULL)
+    {
+        for (i = parser->toknext - 1; i >= 0; i--)
+        {
             /* Unmatched opened object or array */
-            if(tokens[i].start != -1 && tokens[i].end == -1) {
+            if (tokens[i].start != -1 && tokens[i].end == -1)
+            {
                 return JSMN_ERROR_PART;
             }
         }
@@ -381,10 +422,12 @@ int jsmn_parse(
 }
 
 // Helper function to create a JSON object
-char* jsmn(const char* key, const char* value) {
-    int length = strlen(key) + strlen(value) + 8; // Calculate required length
-    char* result = (char*)malloc(length * sizeof(char)); // Allocate memory
-    if(result == NULL) {
+char *get_json(const char *key, const char *value)
+{
+    int length = strlen(key) + strlen(value) + 8;         // Calculate required length
+    char *result = (char *)malloc(length * sizeof(char)); // Allocate memory
+    if (result == NULL)
+    {
         return NULL; // Handle memory allocation failure
     }
     snprintf(result, length, "{\"%s\":\"%s\"}", key, value);
@@ -392,30 +435,36 @@ char* jsmn(const char* key, const char* value) {
 }
 
 // Helper function to compare JSON keys
-int jsoneq(const char* json, jsmntok_t* tok, const char* s) {
-    if(tok->type == JSMN_STRING && (int)strlen(s) == tok->end - tok->start &&
-       strncmp(json + tok->start, s, tok->end - tok->start) == 0) {
+int jsoneq(const char *json, jsmntok_t *tok, const char *s)
+{
+    if (tok->type == JSMN_STRING && (int)strlen(s) == tok->end - tok->start &&
+        strncmp(json + tok->start, s, tok->end - tok->start) == 0)
+    {
         return 0;
     }
     return -1;
 }
 
 // Return the value of the key in the JSON data
-char* get_json_value(char* key, char* json_data, uint32_t max_tokens) {
+char *get_json_value(char *key, const char *json_data)
+{
     // Parse the JSON feed
-    if(json_data != NULL) {
+    if (json_data != NULL)
+    {
         jsmn_parser parser;
         jsmn_init(&parser);
-
+        uint32_t max_tokens = json_token_count(json_data);
         // Allocate tokens array on the heap
-        jsmntok_t* tokens = malloc(sizeof(jsmntok_t) * max_tokens);
-        if(tokens == NULL) {
+        jsmntok_t *tokens = malloc(sizeof(jsmntok_t) * max_tokens);
+        if (tokens == NULL)
+        {
             FURI_LOG_E("JSMM.H", "Failed to allocate memory for JSON tokens.");
             return NULL;
         }
 
         int ret = jsmn_parse(&parser, json_data, strlen(json_data), tokens, max_tokens);
-        if(ret < 0) {
+        if (ret < 0)
+        {
             // Handle parsing errors
             FURI_LOG_E("JSMM.H", "Failed to parse JSON: %d", ret);
             free(tokens);
@@ -423,19 +472,23 @@ char* get_json_value(char* key, char* json_data, uint32_t max_tokens) {
         }
 
         // Ensure that the root element is an object
-        if(ret < 1 || tokens[0].type != JSMN_OBJECT) {
+        if (ret < 1 || tokens[0].type != JSMN_OBJECT)
+        {
             FURI_LOG_E("JSMM.H", "Root element is not an object.");
             free(tokens);
             return NULL;
         }
 
         // Loop through the tokens to find the key
-        for(int i = 1; i < ret; i++) {
-            if(jsoneq(json_data, &tokens[i], key) == 0) {
+        for (int i = 1; i < ret; i++)
+        {
+            if (jsoneq(json_data, &tokens[i], key) == 0)
+            {
                 // We found the key. Now, return the associated value.
                 int length = tokens[i + 1].end - tokens[i + 1].start;
-                char* value = malloc(length + 1);
-                if(value == NULL) {
+                char *value = malloc(length + 1);
+                if (value == NULL)
+                {
                     FURI_LOG_E("JSMM.H", "Failed to allocate memory for value.");
                     free(tokens);
                     return NULL;
@@ -450,102 +503,139 @@ char* get_json_value(char* key, char* json_data, uint32_t max_tokens) {
 
         // Free the token array if key was not found
         free(tokens);
-    } else {
+    }
+    else
+    {
         FURI_LOG_E("JSMM.H", "JSON data is NULL");
     }
-    FURI_LOG_E("JSMM.H", "Failed to find the key in the JSON.");
+    char warning[128];
+    snprintf(warning, sizeof(warning), "Failed to find the key \"%s\" in the JSON.", key);
+    FURI_LOG_E("JSMM.H", warning);
     return NULL; // Return NULL if something goes wrong
 }
 
-// Revised get_json_array_value function
-char* get_json_array_value(char* key, uint32_t index, char* json_data, uint32_t max_tokens) {
-    // Retrieve the array string for the given key
-    char* array_str = get_json_value(key, json_data, max_tokens);
-    if(array_str == NULL) {
+// Helper function to skip a token and all its descendants.
+// Returns the index of the next token after skipping this one.
+// On error or out of bounds, returns -1.
+static int skip_token(const jsmntok_t *tokens, int start, int total)
+{
+    if (start < 0 || start >= total)
+        return -1;
+
+    int i = start;
+    if (tokens[i].type == JSMN_OBJECT)
+    {
+        // For an object: size is number of key-value pairs
+        int pairs = tokens[i].size;
+        i++; // move to first key-value pair
+        for (int p = 0; p < pairs; p++)
+        {
+            // skip key (primitive/string)
+            i++;
+            if (i >= total)
+                return -1;
+            // skip value (which could be object/array and must be skipped recursively)
+            i = skip_token(tokens, i, total);
+            if (i == -1)
+                return -1;
+        }
+        return i; // i is now just past the object
+    }
+    else if (tokens[i].type == JSMN_ARRAY)
+    {
+        // For an array: size is number of elements
+        int elems = tokens[i].size;
+        i++; // move to first element
+        for (int e = 0; e < elems; e++)
+        {
+            i = skip_token(tokens, i, total);
+            if (i == -1)
+                return -1;
+        }
+        return i; // i is now just past the array
+    }
+    else
+    {
+        // Primitive or string token, just skip it
+        return i + 1;
+    }
+}
+
+// Revised get_json_array_value
+char *get_json_array_value(char *key, uint32_t index, const char *json_data)
+{
+    // Always extract the full array each time from the original json_data
+    char *array_str = get_json_value(key, json_data);
+    if (array_str == NULL)
+    {
         FURI_LOG_E("JSMM.H", "Failed to get array for key: %s", key);
         return NULL;
     }
+    uint32_t max_tokens = json_token_count(array_str);
 
-    // Initialize the JSON parser
     jsmn_parser parser;
     jsmn_init(&parser);
-
-    // Allocate memory for JSON tokens
-    jsmntok_t* tokens = malloc(sizeof(jsmntok_t) * max_tokens);
-    if(tokens == NULL) {
+    jsmntok_t *tokens = malloc(sizeof(jsmntok_t) * max_tokens);
+    if (tokens == NULL)
+    {
         FURI_LOG_E("JSMM.H", "Failed to allocate memory for JSON tokens.");
         free(array_str);
         return NULL;
     }
 
-    // Parse the JSON array
     int ret = jsmn_parse(&parser, array_str, strlen(array_str), tokens, max_tokens);
-    if(ret < 0) {
+    if (ret < 0)
+    {
         FURI_LOG_E("JSMM.H", "Failed to parse JSON array: %d", ret);
         free(tokens);
         free(array_str);
         return NULL;
     }
 
-    // Ensure the root element is an array
-    if(ret < 1 || tokens[0].type != JSMN_ARRAY) {
+    if (ret < 1 || tokens[0].type != JSMN_ARRAY)
+    {
         FURI_LOG_E("JSMM.H", "Value for key '%s' is not an array.", key);
         free(tokens);
         free(array_str);
         return NULL;
     }
 
-    // Check if the index is within bounds
-    if(index >= (uint32_t)tokens[0].size) {
-        FURI_LOG_E(
-            "JSMM.H",
-            "Index %lu out of bounds for array with size %d.",
-            (unsigned long)index,
-            tokens[0].size);
+    if (index >= (uint32_t)tokens[0].size)
+    {
         free(tokens);
         free(array_str);
         return NULL;
     }
 
-    // Locate the token corresponding to the desired array element
-    int current_token = 1; // Start after the array token
-    for(uint32_t i = 0; i < index; i++) {
-        if(tokens[current_token].type == JSMN_OBJECT) {
-            // For objects, skip all key-value pairs
-            current_token += 1 + 2 * tokens[current_token].size;
-        } else if(tokens[current_token].type == JSMN_ARRAY) {
-            // For nested arrays, skip all elements
-            current_token += 1 + tokens[current_token].size;
-        } else {
-            // For primitive types, simply move to the next token
-            current_token += 1;
-        }
-
-        // Safety check to prevent out-of-bounds
-        if(current_token >= ret) {
-            FURI_LOG_E("JSMM.H", "Unexpected end of tokens while traversing array.");
+    // Find the index-th element: start from token[1], which is the first element
+    int elem_token = 1;
+    for (uint32_t i = 0; i < index; i++)
+    {
+        elem_token = skip_token(tokens, elem_token, ret);
+        if (elem_token == -1 || elem_token >= ret)
+        {
+            FURI_LOG_E("JSMM.H", "Error skipping tokens to reach element %lu.", i);
             free(tokens);
             free(array_str);
             return NULL;
         }
     }
 
-    // Extract the array element
-    jsmntok_t element = tokens[current_token];
+    // Now elem_token should point to the token of the requested element
+    jsmntok_t element = tokens[elem_token];
     int length = element.end - element.start;
-    char* value = malloc(length + 1);
-    if(value == NULL) {
+    char *value = malloc(length + 1);
+    if (!value)
+    {
         FURI_LOG_E("JSMM.H", "Failed to allocate memory for array element.");
         free(tokens);
         free(array_str);
         return NULL;
     }
 
-    // Copy the element value to a new string
     strncpy(value, array_str + element.start, length);
-    value[length] = '\0'; // Null-terminate the string
+    value[length] = '\0';
 
-    // Clean up
     free(tokens);
     free(array_str);
 
@@ -553,21 +643,24 @@ char* get_json_array_value(char* key, uint32_t index, char* json_data, uint32_t
 }
 
 // Revised get_json_array_values function with correct token skipping
-char** get_json_array_values(char* key, char* json_data, uint32_t max_tokens, int* num_values) {
+char **get_json_array_values(char *key, char *json_data, int *num_values)
+{
     // Retrieve the array string for the given key
-    char* array_str = get_json_value(key, json_data, max_tokens);
-    if(array_str == NULL) {
+    char *array_str = get_json_value(key, json_data);
+    if (array_str == NULL)
+    {
         FURI_LOG_E("JSMM.H", "Failed to get array for key: %s", key);
         return NULL;
     }
-
+    uint32_t max_tokens = json_token_count(array_str);
     // Initialize the JSON parser
     jsmn_parser parser;
     jsmn_init(&parser);
 
     // Allocate memory for JSON tokens
-    jsmntok_t* tokens = malloc(sizeof(jsmntok_t) * max_tokens); // Allocate on the heap
-    if(tokens == NULL) {
+    jsmntok_t *tokens = malloc(sizeof(jsmntok_t) * max_tokens); // Allocate on the heap
+    if (tokens == NULL)
+    {
         FURI_LOG_E("JSMM.H", "Failed to allocate memory for JSON tokens.");
         free(array_str);
         return NULL;
@@ -575,7 +668,8 @@ char** get_json_array_values(char* key, char* json_data, uint32_t max_tokens, in
 
     // Parse the JSON array
     int ret = jsmn_parse(&parser, array_str, strlen(array_str), tokens, max_tokens);
-    if(ret < 0) {
+    if (ret < 0)
+    {
         FURI_LOG_E("JSMM.H", "Failed to parse JSON array: %d", ret);
         free(tokens);
         free(array_str);
@@ -583,7 +677,8 @@ char** get_json_array_values(char* key, char* json_data, uint32_t max_tokens, in
     }
 
     // Ensure the root element is an array
-    if(tokens[0].type != JSMN_ARRAY) {
+    if (tokens[0].type != JSMN_ARRAY)
+    {
         FURI_LOG_E("JSMM.H", "Value for key '%s' is not an array.", key);
         free(tokens);
         free(array_str);
@@ -592,8 +687,9 @@ char** get_json_array_values(char* key, char* json_data, uint32_t max_tokens, in
 
     // Allocate memory for the array of values (maximum possible)
     int array_size = tokens[0].size;
-    char** values = malloc(array_size * sizeof(char*));
-    if(values == NULL) {
+    char **values = malloc(array_size * sizeof(char *));
+    if (values == NULL)
+    {
         FURI_LOG_E("JSMM.H", "Failed to allocate memory for array of values.");
         free(tokens);
         free(array_str);
@@ -604,15 +700,18 @@ char** get_json_array_values(char* key, char* json_data, uint32_t max_tokens, in
 
     // Traverse the array and extract all object values
     int current_token = 1; // Start after the array token
-    for(int i = 0; i < array_size; i++) {
-        if(current_token >= ret) {
+    for (int i = 0; i < array_size; i++)
+    {
+        if (current_token >= ret)
+        {
             FURI_LOG_E("JSMM.H", "Unexpected end of tokens while traversing array.");
             break;
         }
 
         jsmntok_t element = tokens[current_token];
 
-        if(element.type != JSMN_OBJECT) {
+        if (element.type != JSMN_OBJECT)
+        {
             FURI_LOG_E("JSMM.H", "Array element %d is not an object, skipping.", i);
             // Skip this element
             current_token += 1;
@@ -622,10 +721,12 @@ char** get_json_array_values(char* key, char* json_data, uint32_t max_tokens, in
         int length = element.end - element.start;
 
         // Allocate a new string for the value and copy the data
-        char* value = malloc(length + 1);
-        if(value == NULL) {
+        char *value = malloc(length + 1);
+        if (value == NULL)
+        {
             FURI_LOG_E("JSMM.H", "Failed to allocate memory for array element.");
-            for(int j = 0; j < actual_num_values; j++) {
+            for (int j = 0; j < actual_num_values; j++)
+            {
                 free(values[j]);
             }
             free(values);
@@ -647,14 +748,17 @@ char** get_json_array_values(char* key, char* json_data, uint32_t max_tokens, in
     *num_values = actual_num_values;
 
     // Reallocate the values array to actual_num_values if necessary
-    if(actual_num_values < array_size) {
-        char** reduced_values = realloc(values, actual_num_values * sizeof(char*));
-        if(reduced_values != NULL) {
+    if (actual_num_values < array_size)
+    {
+        char **reduced_values = realloc(values, actual_num_values * sizeof(char *));
+        if (reduced_values != NULL)
+        {
             values = reduced_values;
         }
 
         // Free the remaining values
-        for(int i = actual_num_values; i < array_size; i++) {
+        for (int i = actual_num_values; i < array_size; i++)
+        {
             free(values[i]);
         }
     }
@@ -664,3 +768,18 @@ char** get_json_array_values(char* key, char* json_data, uint32_t max_tokens, in
     free(array_str);
     return values;
 }
+
+int json_token_count(const char *json)
+{
+    if (json == NULL)
+    {
+        return JSMN_ERROR_INVAL;
+    }
+
+    jsmn_parser parser;
+    jsmn_init(&parser);
+
+    // Pass NULL for tokens and 0 for num_tokens to get the token count only
+    int ret = jsmn_parse(&parser, json, strlen(json), NULL, 0);
+    return ret; // If ret >= 0, it represents the number of tokens needed.
+}

+ 15 - 72
flip_wifi/jsmn/jsmn.h

@@ -17,9 +17,11 @@
 #define JSMN_H
 
 #include <stddef.h>
+#include <jsmn/jsmn_h.h>
 
 #ifdef __cplusplus
-extern "C" {
+extern "C"
+{
 #endif
 
 #ifdef JSMN_STATIC
@@ -27,72 +29,17 @@ extern "C" {
 #else
 #define JSMN_API extern
 #endif
-
-/**
-     * JSON type identifier. Basic types are:
-     * 	o Object
-     * 	o Array
-     * 	o String
-     * 	o Other primitive: number, boolean (true/false) or null
-     */
-typedef enum {
-    JSMN_UNDEFINED = 0,
-    JSMN_OBJECT = 1 << 0,
-    JSMN_ARRAY = 1 << 1,
-    JSMN_STRING = 1 << 2,
-    JSMN_PRIMITIVE = 1 << 3
-} jsmntype_t;
-
-enum jsmnerr {
-    /* Not enough tokens were provided */
-    JSMN_ERROR_NOMEM = -1,
-    /* Invalid character inside JSON string */
-    JSMN_ERROR_INVAL = -2,
-    /* The string is not a full JSON packet, more bytes expected */
-    JSMN_ERROR_PART = -3
-};
-
-/**
-     * JSON token description.
-     * type		type (object, array, string etc.)
-     * start	start position in JSON data string
-     * end		end position in JSON data string
-     */
-typedef struct {
-    jsmntype_t type;
-    int start;
-    int end;
-    int size;
-#ifdef JSMN_PARENT_LINKS
-    int parent;
-#endif
-} jsmntok_t;
-
-/**
-     * JSON parser. Contains an array of token blocks available. Also stores
-     * the string being parsed now and current position in that string.
-     */
-typedef struct {
-    unsigned int pos; /* offset in the JSON string */
-    unsigned int toknext; /* next token to allocate */
-    int toksuper; /* superior token node, e.g. parent object or array */
-} jsmn_parser;
-
-/**
+    /**
      * Create JSON parser over an array of tokens
      */
-JSMN_API void jsmn_init(jsmn_parser* parser);
+    JSMN_API void jsmn_init(jsmn_parser *parser);
 
-/**
+    /**
      * Run JSON parser. It parses a JSON data string into and array of tokens, each
      * describing a single JSON object.
      */
-JSMN_API int jsmn_parse(
-    jsmn_parser* parser,
-    const char* js,
-    const size_t len,
-    jsmntok_t* tokens,
-    const unsigned int num_tokens);
+    JSMN_API int jsmn_parse(jsmn_parser *parser, const char *js, const size_t len,
+                            jsmntok_t *tokens, const unsigned int num_tokens);
 
 #ifndef JSMN_HEADER
 /* Implementation has been moved to jsmn.c */
@@ -109,23 +56,19 @@ JSMN_API int jsmn_parse(
 #define JB_JSMN_EDIT
 /* Added in by JBlanked on 2024-10-16 for use in Flipper Zero SDK*/
 
-#include <string.h>
-#include <stdint.h>
-#include <stdlib.h>
-#include <stdio.h>
-#include <furi.h>
-
 // Helper function to create a JSON object
-char* jsmn(const char* key, const char* value);
+char *get_json(const char *key, const char *value);
 // Helper function to compare JSON keys
-int jsoneq(const char* json, jsmntok_t* tok, const char* s);
+int jsoneq(const char *json, jsmntok_t *tok, const char *s);
 
 // Return the value of the key in the JSON data
-char* get_json_value(char* key, char* json_data, uint32_t max_tokens);
+char *get_json_value(char *key, const char *json_data);
 
 // Revised get_json_array_value function
-char* get_json_array_value(char* key, uint32_t index, char* json_data, uint32_t max_tokens);
+char *get_json_array_value(char *key, uint32_t index, const char *json_data);
 
 // Revised get_json_array_values function with correct token skipping
-char** get_json_array_values(char* key, char* json_data, uint32_t max_tokens, int* num_values);
+char **get_json_array_values(char *key, char *json_data, int *num_values);
+
+int json_token_count(const char *json);
 #endif /* JB_JSMN_EDIT */

+ 718 - 0
flip_wifi/jsmn/jsmn_furi.c

@@ -0,0 +1,718 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2010 Serge Zaitsev
+ *
+ * [License text continues...]
+ */
+
+#include <jsmn/jsmn_furi.h>
+
+// Forward declarations of helper functions
+static int jsoneq_furi(const FuriString *json, jsmntok_t *tok, const FuriString *s);
+static int skip_token(const jsmntok_t *tokens, int start, int total);
+
+/**
+ * Allocates a fresh unused token from the token pool.
+ */
+static jsmntok_t *jsmn_alloc_token(jsmn_parser *parser, jsmntok_t *tokens,
+                                   const size_t num_tokens)
+{
+    if (parser->toknext >= num_tokens)
+    {
+        return NULL;
+    }
+    jsmntok_t *tok = &tokens[parser->toknext++];
+    tok->start = tok->end = -1;
+    tok->size = 0;
+#ifdef JSMN_PARENT_LINKS
+    tok->parent = -1;
+#endif
+    return tok;
+}
+
+/**
+ * Fills token type and boundaries.
+ */
+static void jsmn_fill_token(jsmntok_t *token, const jsmntype_t type,
+                            const int start, const int end)
+{
+    token->type = type;
+    token->start = start;
+    token->end = end;
+    token->size = 0;
+}
+
+/**
+ * Fills next available token with JSON primitive.
+ * Now uses FuriString to access characters.
+ */
+static int jsmn_parse_primitive(jsmn_parser *parser, const FuriString *js,
+                                jsmntok_t *tokens, const size_t num_tokens)
+{
+    size_t len = furi_string_size(js);
+    int start = parser->pos;
+
+    for (; parser->pos < len; parser->pos++)
+    {
+        char c = furi_string_get_char(js, parser->pos);
+        switch (c)
+        {
+#ifndef JSMN_STRICT
+        case ':':
+#endif
+        case '\t':
+        case '\r':
+        case '\n':
+        case ' ':
+        case ',':
+        case ']':
+        case '}':
+            goto found;
+        default:
+            break;
+        }
+        if (c < 32 || c >= 127)
+        {
+            parser->pos = start;
+            return JSMN_ERROR_INVAL;
+        }
+    }
+
+#ifdef JSMN_STRICT
+    // In strict mode primitive must be followed by a comma/object/array
+    parser->pos = start;
+    return JSMN_ERROR_PART;
+#endif
+
+found:
+    if (tokens == NULL)
+    {
+        parser->pos--;
+        return 0;
+    }
+    jsmntok_t *token = jsmn_alloc_token(parser, tokens, num_tokens);
+    if (token == NULL)
+    {
+        parser->pos = start;
+        return JSMN_ERROR_NOMEM;
+    }
+    jsmn_fill_token(token, JSMN_PRIMITIVE, start, parser->pos);
+#ifdef JSMN_PARENT_LINKS
+    token->parent = parser->toksuper;
+#endif
+    parser->pos--;
+    return 0;
+}
+
+/**
+ * Fills next token with JSON string.
+ * Now uses FuriString to access characters.
+ */
+static int jsmn_parse_string(jsmn_parser *parser, const FuriString *js,
+                             jsmntok_t *tokens, const size_t num_tokens)
+{
+    size_t len = furi_string_size(js);
+    int start = parser->pos;
+    parser->pos++;
+
+    for (; parser->pos < len; parser->pos++)
+    {
+        char c = furi_string_get_char(js, parser->pos);
+        if (c == '\"')
+        {
+            if (tokens == NULL)
+            {
+                return 0;
+            }
+            jsmntok_t *token = jsmn_alloc_token(parser, tokens, num_tokens);
+            if (token == NULL)
+            {
+                parser->pos = start;
+                return JSMN_ERROR_NOMEM;
+            }
+            jsmn_fill_token(token, JSMN_STRING, start + 1, parser->pos);
+#ifdef JSMN_PARENT_LINKS
+            token->parent = parser->toksuper;
+#endif
+            return 0;
+        }
+
+        if (c == '\\' && (parser->pos + 1) < len)
+        {
+            parser->pos++;
+            char esc = furi_string_get_char(js, parser->pos);
+            switch (esc)
+            {
+            case '\"':
+            case '/':
+            case '\\':
+            case 'b':
+            case 'f':
+            case 'r':
+            case 'n':
+            case 't':
+                break;
+            case 'u':
+            {
+                parser->pos++;
+                for (int i = 0; i < 4 && parser->pos < len; i++)
+                {
+                    char hex = furi_string_get_char(js, parser->pos);
+                    if (!((hex >= '0' && hex <= '9') ||
+                          (hex >= 'A' && hex <= 'F') ||
+                          (hex >= 'a' && hex <= 'f')))
+                    {
+                        parser->pos = start;
+                        return JSMN_ERROR_INVAL;
+                    }
+                    parser->pos++;
+                }
+                parser->pos--;
+                break;
+            }
+            default:
+                parser->pos = start;
+                return JSMN_ERROR_INVAL;
+            }
+        }
+    }
+    parser->pos = start;
+    return JSMN_ERROR_PART;
+}
+
+/**
+ * Create JSON parser
+ */
+void jsmn_init_furi(jsmn_parser *parser)
+{
+    parser->pos = 0;
+    parser->toknext = 0;
+    parser->toksuper = -1;
+}
+
+/**
+ * Parse JSON string and fill tokens.
+ * Now uses FuriString for the input JSON.
+ */
+int jsmn_parse_furi(jsmn_parser *parser, const FuriString *js,
+                    jsmntok_t *tokens, const unsigned int num_tokens)
+{
+    size_t len = furi_string_size(js);
+    int r;
+    int i;
+    int count = parser->toknext;
+
+    for (; parser->pos < len; parser->pos++)
+    {
+        char c = furi_string_get_char(js, parser->pos);
+        jsmntype_t type;
+
+        switch (c)
+        {
+        case '{':
+        case '[':
+        {
+            count++;
+            if (tokens == NULL)
+            {
+                break;
+            }
+            jsmntok_t *token = jsmn_alloc_token(parser, tokens, num_tokens);
+            if (token == NULL)
+                return JSMN_ERROR_NOMEM;
+            if (parser->toksuper != -1)
+            {
+                jsmntok_t *t = &tokens[parser->toksuper];
+#ifdef JSMN_STRICT
+                if (t->type == JSMN_OBJECT)
+                    return JSMN_ERROR_INVAL;
+#endif
+                t->size++;
+#ifdef JSMN_PARENT_LINKS
+                token->parent = parser->toksuper;
+#endif
+            }
+            token->type = (c == '{' ? JSMN_OBJECT : JSMN_ARRAY);
+            token->start = parser->pos;
+            parser->toksuper = parser->toknext - 1;
+            break;
+        }
+        case '}':
+        case ']':
+            if (tokens == NULL)
+            {
+                break;
+            }
+            type = (c == '}' ? JSMN_OBJECT : JSMN_ARRAY);
+#ifdef JSMN_PARENT_LINKS
+            if (parser->toknext < 1)
+            {
+                return JSMN_ERROR_INVAL;
+            }
+            {
+                jsmntok_t *token = &tokens[parser->toknext - 1];
+                for (;;)
+                {
+                    if (token->start != -1 && token->end == -1)
+                    {
+                        if (token->type != type)
+                            return JSMN_ERROR_INVAL;
+                        token->end = parser->pos + 1;
+                        parser->toksuper = token->parent;
+                        break;
+                    }
+                    if (token->parent == -1)
+                    {
+                        if (token->type != type || parser->toksuper == -1)
+                        {
+                            return JSMN_ERROR_INVAL;
+                        }
+                        break;
+                    }
+                    token = &tokens[token->parent];
+                }
+            }
+#else
+            {
+                jsmntok_t *token;
+                for (i = parser->toknext - 1; i >= 0; i--)
+                {
+                    token = &tokens[i];
+                    if (token->start != -1 && token->end == -1)
+                    {
+                        if (token->type != type)
+                            return JSMN_ERROR_INVAL;
+                        parser->toksuper = -1;
+                        token->end = parser->pos + 1;
+                        break;
+                    }
+                }
+                if (i == -1)
+                    return JSMN_ERROR_INVAL;
+                for (; i >= 0; i--)
+                {
+                    token = &tokens[i];
+                    if (token->start != -1 && token->end == -1)
+                    {
+                        parser->toksuper = i;
+                        break;
+                    }
+                }
+            }
+#endif
+            break;
+        case '\"':
+            r = jsmn_parse_string(parser, js, tokens, num_tokens);
+            if (r < 0)
+                return r;
+            count++;
+            if (parser->toksuper != -1 && tokens != NULL)
+            {
+                tokens[parser->toksuper].size++;
+            }
+            break;
+        case '\t':
+        case '\r':
+        case '\n':
+        case ' ':
+            // Whitespace - ignore
+            break;
+        case ':':
+            parser->toksuper = parser->toknext - 1;
+            break;
+        case ',':
+            if (tokens != NULL && parser->toksuper != -1 &&
+                tokens[parser->toksuper].type != JSMN_ARRAY &&
+                tokens[parser->toksuper].type != JSMN_OBJECT)
+            {
+#ifdef JSMN_PARENT_LINKS
+                parser->toksuper = tokens[parser->toksuper].parent;
+#else
+                for (i = parser->toknext - 1; i >= 0; i--)
+                {
+                    if (tokens[i].type == JSMN_ARRAY || tokens[i].type == JSMN_OBJECT)
+                    {
+                        if (tokens[i].start != -1 && tokens[i].end == -1)
+                        {
+                            parser->toksuper = i;
+                            break;
+                        }
+                    }
+                }
+#endif
+            }
+            break;
+#ifdef JSMN_STRICT
+        case '-':
+        case '0':
+        case '1':
+        case '2':
+        case '3':
+        case '4':
+        case '5':
+        case '6':
+        case '7':
+        case '8':
+        case '9':
+        case 't':
+        case 'f':
+        case 'n':
+            if (tokens != NULL && parser->toksuper != -1)
+            {
+                const jsmntok_t *t = &tokens[parser->toksuper];
+                if (t->type == JSMN_OBJECT ||
+                    (t->type == JSMN_STRING && t->size != 0))
+                {
+                    return JSMN_ERROR_INVAL;
+                }
+            }
+#else
+        default:
+#endif
+            r = jsmn_parse_primitive(parser, js, tokens, num_tokens);
+            if (r < 0)
+                return r;
+            count++;
+            if (parser->toksuper != -1 && tokens != NULL)
+            {
+                tokens[parser->toksuper].size++;
+            }
+            break;
+#ifdef JSMN_STRICT
+        default:
+            return JSMN_ERROR_INVAL;
+#endif
+        }
+    }
+
+    if (tokens != NULL)
+    {
+        for (i = parser->toknext - 1; i >= 0; i--)
+        {
+            if (tokens[i].start != -1 && tokens[i].end == -1)
+            {
+                return JSMN_ERROR_PART;
+            }
+        }
+    }
+
+    return count;
+}
+
+// Helper function to create a JSON object: {"key":"value"}
+FuriString *get_json_furi(const FuriString *key, const FuriString *value)
+{
+    FuriString *result = furi_string_alloc();
+    furi_string_printf(result, "{\"%s\":\"%s\"}",
+                       furi_string_get_cstr(key),
+                       furi_string_get_cstr(value));
+    return result; // Caller responsible for furi_string_free
+}
+
+// Helper function to compare JSON keys
+static int jsoneq_furi(const FuriString *json, jsmntok_t *tok, const FuriString *s)
+{
+    size_t s_len = furi_string_size(s);
+    size_t tok_len = tok->end - tok->start;
+
+    if (tok->type != JSMN_STRING)
+        return -1;
+    if (s_len != tok_len)
+        return -1;
+
+    FuriString *sub = furi_string_alloc_set(json);
+    furi_string_mid(sub, tok->start, tok_len);
+
+    int res = furi_string_cmp(sub, s);
+    furi_string_free(sub);
+
+    return (res == 0) ? 0 : -1;
+}
+
+// Skip a token and its descendants
+static int skip_token(const jsmntok_t *tokens, int start, int total)
+{
+    if (start < 0 || start >= total)
+        return -1;
+
+    int i = start;
+    if (tokens[i].type == JSMN_OBJECT)
+    {
+        int pairs = tokens[i].size;
+        i++;
+        for (int p = 0; p < pairs; p++)
+        {
+            i++; // skip key
+            if (i >= total)
+                return -1;
+            i = skip_token(tokens, i, total); // skip value
+            if (i == -1)
+                return -1;
+        }
+        return i;
+    }
+    else if (tokens[i].type == JSMN_ARRAY)
+    {
+        int elems = tokens[i].size;
+        i++;
+        for (int e = 0; e < elems; e++)
+        {
+            i = skip_token(tokens, i, total);
+            if (i == -1)
+                return -1;
+        }
+        return i;
+    }
+    else
+    {
+        return i + 1;
+    }
+}
+
+/**
+ * Parse JSON and return the value associated with a given char* key.
+ */
+FuriString *get_json_value_furi(const char *key, const FuriString *json_data)
+{
+    if (json_data == NULL)
+    {
+        FURI_LOG_E("JSMM.H", "JSON data is NULL");
+        return NULL;
+    }
+    uint32_t max_tokens = json_token_count_furi(json_data);
+    // Create a temporary FuriString from key
+    FuriString *key_str = furi_string_alloc();
+    furi_string_cat_str(key_str, key);
+
+    jsmn_parser parser;
+    jsmn_init_furi(&parser);
+
+    jsmntok_t *tokens = (jsmntok_t *)malloc(sizeof(jsmntok_t) * max_tokens);
+    if (tokens == NULL)
+    {
+        FURI_LOG_E("JSMM.H", "Failed to allocate memory for JSON tokens.");
+        furi_string_free(key_str);
+        return NULL;
+    }
+
+    int ret = jsmn_parse_furi(&parser, json_data, tokens, max_tokens);
+    if (ret < 0)
+    {
+        FURI_LOG_E("JSMM.H", "Failed to parse JSON: %d", ret);
+        free(tokens);
+        furi_string_free(key_str);
+        return NULL;
+    }
+
+    if (ret < 1 || tokens[0].type != JSMN_OBJECT)
+    {
+        FURI_LOG_E("JSMM.H", "Root element is not an object.");
+        free(tokens);
+        furi_string_free(key_str);
+        return NULL;
+    }
+
+    for (int i = 1; i < ret; i++)
+    {
+        if (jsoneq_furi(json_data, &tokens[i], key_str) == 0)
+        {
+            int length = tokens[i + 1].end - tokens[i + 1].start;
+            FuriString *value = furi_string_alloc_set(json_data);
+            furi_string_mid(value, tokens[i + 1].start, length);
+            free(tokens);
+            furi_string_free(key_str);
+            return value;
+        }
+    }
+
+    free(tokens);
+    furi_string_free(key_str);
+    char warning[128];
+    snprintf(warning, sizeof(warning), "Failed to find the key \"%s\" in the JSON.", key);
+    FURI_LOG_E("JSMM.H", warning);
+    return NULL;
+}
+
+/**
+ * Return the value at a given index in a JSON array for a given char* key.
+ */
+FuriString *get_json_array_value_furi(const char *key, uint32_t index, const FuriString *json_data)
+{
+    FuriString *array_str = get_json_value_furi(key, json_data);
+    if (array_str == NULL)
+    {
+        FURI_LOG_E("JSMM.H", "Failed to get array for key");
+        return NULL;
+    }
+    uint32_t max_tokens = json_token_count_furi(array_str);
+    jsmn_parser parser;
+    jsmn_init_furi(&parser);
+
+    jsmntok_t *tokens = (jsmntok_t *)malloc(sizeof(jsmntok_t) * max_tokens);
+    if (tokens == NULL)
+    {
+        FURI_LOG_E("JSMM.H", "Failed to allocate memory for JSON tokens.");
+        furi_string_free(array_str);
+        return NULL;
+    }
+
+    int ret = jsmn_parse_furi(&parser, array_str, tokens, max_tokens);
+    if (ret < 0)
+    {
+        FURI_LOG_E("JSMM.H", "Failed to parse JSON array: %d", ret);
+        free(tokens);
+        furi_string_free(array_str);
+        return NULL;
+    }
+
+    if (ret < 1 || tokens[0].type != JSMN_ARRAY)
+    {
+        FURI_LOG_E("JSMM.H", "Value for key is not an array.");
+        free(tokens);
+        furi_string_free(array_str);
+        return NULL;
+    }
+
+    if (index >= (uint32_t)tokens[0].size)
+    {
+        free(tokens);
+        furi_string_free(array_str);
+        return NULL;
+    }
+
+    int elem_token = 1;
+    for (uint32_t i = 0; i < index; i++)
+    {
+        elem_token = skip_token(tokens, elem_token, ret);
+        if (elem_token == -1 || elem_token >= ret)
+        {
+            FURI_LOG_E("JSMM.H", "Error skipping tokens to reach element %lu.", i);
+            free(tokens);
+            furi_string_free(array_str);
+            return NULL;
+        }
+    }
+
+    jsmntok_t element = tokens[elem_token];
+    int length = element.end - element.start;
+
+    FuriString *value = furi_string_alloc_set(array_str);
+    furi_string_mid(value, element.start, length);
+
+    free(tokens);
+    furi_string_free(array_str);
+
+    return value;
+}
+
+/**
+ * Extract all object values from a JSON array associated with a given char* key.
+ */
+FuriString **get_json_array_values_furi(const char *key, const FuriString *json_data, int *num_values)
+{
+    *num_values = 0;
+    // Convert key to FuriString and call get_json_value_furi
+    FuriString *array_str = get_json_value_furi(key, json_data);
+    if (array_str == NULL)
+    {
+        FURI_LOG_E("JSMM.H", "Failed to get array for key");
+        return NULL;
+    }
+
+    uint32_t max_tokens = json_token_count_furi(array_str);
+    jsmn_parser parser;
+    jsmn_init_furi(&parser);
+
+    jsmntok_t *tokens = (jsmntok_t *)malloc(sizeof(jsmntok_t) * max_tokens);
+    if (tokens == NULL)
+    {
+        FURI_LOG_E("JSMM.H", "Failed to allocate memory for JSON tokens.");
+        furi_string_free(array_str);
+        return NULL;
+    }
+
+    int ret = jsmn_parse_furi(&parser, array_str, tokens, max_tokens);
+    if (ret < 0)
+    {
+        FURI_LOG_E("JSMM.H", "Failed to parse JSON array: %d", ret);
+        free(tokens);
+        furi_string_free(array_str);
+        return NULL;
+    }
+
+    if (tokens[0].type != JSMN_ARRAY)
+    {
+        FURI_LOG_E("JSMM.H", "Value for key is not an array.");
+        free(tokens);
+        furi_string_free(array_str);
+        return NULL;
+    }
+
+    int array_size = tokens[0].size;
+    FuriString **values = (FuriString **)malloc(array_size * sizeof(FuriString *));
+    if (values == NULL)
+    {
+        FURI_LOG_E("JSMM.H", "Failed to allocate memory for array of values.");
+        free(tokens);
+        furi_string_free(array_str);
+        return NULL;
+    }
+
+    int actual_num_values = 0;
+    int current_token = 1;
+    for (int i = 0; i < array_size; i++)
+    {
+        if (current_token >= ret)
+        {
+            FURI_LOG_E("JSMM.H", "Unexpected end of tokens while traversing array.");
+            break;
+        }
+
+        jsmntok_t element = tokens[current_token];
+
+        int length = element.end - element.start;
+        FuriString *value = furi_string_alloc_set(array_str);
+        furi_string_mid(value, element.start, length);
+
+        values[actual_num_values] = value;
+        actual_num_values++;
+
+        // Skip this element and its descendants
+        current_token = skip_token(tokens, current_token, ret);
+        if (current_token == -1)
+        {
+            FURI_LOG_E("JSMM.H", "Error skipping tokens after element %d.", i);
+            break;
+        }
+    }
+
+    *num_values = actual_num_values;
+    if (actual_num_values < array_size)
+    {
+        FuriString **reduced_values = (FuriString **)realloc(values, actual_num_values * sizeof(FuriString *));
+        if (reduced_values != NULL)
+        {
+            values = reduced_values;
+        }
+    }
+
+    free(tokens);
+    furi_string_free(array_str);
+    return values;
+}
+
+uint32_t json_token_count_furi(const FuriString *json)
+{
+    if (json == NULL)
+    {
+        return JSMN_ERROR_INVAL;
+    }
+
+    jsmn_parser parser;
+    jsmn_init_furi(&parser);
+
+    // Pass NULL for tokens and 0 for num_tokens to get the token count only
+    int ret = jsmn_parse_furi(&parser, json, NULL, 0);
+    return ret; // If ret >= 0, it represents the number of tokens needed.
+}

+ 74 - 0
flip_wifi/jsmn/jsmn_furi.h

@@ -0,0 +1,74 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2010 Serge Zaitsev
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * [License text continues...]
+ */
+
+#ifndef JSMN_FURI_H
+#define JSMN_FURI_H
+
+#include <jsmn/jsmn_h.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+#ifdef JSMN_STATIC
+#define JSMN_API static
+#else
+#define JSMN_API extern
+#endif
+
+    JSMN_API void jsmn_init_furi(jsmn_parser *parser);
+    JSMN_API int jsmn_parse_furi(jsmn_parser *parser, const FuriString *js,
+                                 jsmntok_t *tokens, const unsigned int num_tokens);
+
+#ifndef JSMN_HEADER
+/* Implementation in jsmn_furi.c */
+#endif /* JSMN_HEADER */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* JSMN_FURI_H */
+
+#ifndef JB_JSMN_FURI_EDIT
+#define JB_JSMN_FURI_EDIT
+
+// Helper function to create a JSON object
+FuriString *get_json_furi(const FuriString *key, const FuriString *value);
+
+// Updated signatures to accept const char* key
+FuriString *get_json_value_furi(const char *key, const FuriString *json_data);
+FuriString *get_json_array_value_furi(const char *key, uint32_t index, const FuriString *json_data);
+FuriString **get_json_array_values_furi(const char *key, const FuriString *json_data, int *num_values);
+
+uint32_t json_token_count_furi(const FuriString *json);
+/* Example usage:
+char *json = "{\"key1\":\"value1\",\"key2\":\"value2\"}";
+FuriString *json_data = char_to_furi_string(json);
+if (!json_data)
+{
+    FURI_LOG_E(TAG, "Failed to allocate FuriString");
+    return -1;
+}
+FuriString *value = get_json_value_furi("key1", json_data, json_token_count_furi(json_data));
+if (value)
+{
+    FURI_LOG_I(TAG, "Value: %s", furi_string_get_cstr(value));
+    furi_string_free(value);
+}
+furi_string_free(json_data);
+*/
+#endif /* JB_JSMN_EDIT */

+ 14 - 0
flip_wifi/jsmn/jsmn_h.c

@@ -0,0 +1,14 @@
+#include <jsmn/jsmn_h.h>
+FuriString *char_to_furi_string(const char *str)
+{
+    FuriString *furi_str = furi_string_alloc();
+    if (!furi_str)
+    {
+        return NULL;
+    }
+    for (size_t i = 0; i < strlen(str); i++)
+    {
+        furi_string_push_back(furi_str, str[i]);
+    }
+    return furi_str;
+}

+ 53 - 0
flip_wifi/jsmn/jsmn_h.h

@@ -0,0 +1,53 @@
+#pragma once
+#include <furi.h>
+#include <stddef.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+typedef enum
+{
+    JSMN_UNDEFINED = 0,
+    JSMN_OBJECT = 1 << 0,
+    JSMN_ARRAY = 1 << 1,
+    JSMN_STRING = 1 << 2,
+    JSMN_PRIMITIVE = 1 << 3
+} jsmntype_t;
+
+enum jsmnerr
+{
+    JSMN_ERROR_NOMEM = -1,
+    JSMN_ERROR_INVAL = -2,
+    JSMN_ERROR_PART = -3
+};
+
+typedef struct
+{
+    jsmntype_t type;
+    int start;
+    int end;
+    int size;
+#ifdef JSMN_PARENT_LINKS
+    int parent;
+#endif
+} jsmntok_t;
+
+typedef struct
+{
+    unsigned int pos;     /* offset in the JSON string */
+    unsigned int toknext; /* next token to allocate */
+    int toksuper;         /* superior token node, e.g. parent object or array */
+} jsmn_parser;
+
+typedef struct
+{
+    char *key;
+    char *value;
+} JSON;
+
+typedef struct
+{
+    FuriString *key;
+    FuriString *value;
+} FuriJSON;
+
+FuriString *char_to_furi_string(const char *str);

Một số tệp đã không được hiển thị bởi vì quá nhiều tập tin thay đổi trong này khác