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

Merge flip_store from https://github.com/jblanked/FlipStore

Willy-JL 1 год назад
Родитель
Сommit
b5ff1251f5

+ 2 - 2
flip_store/README.md

@@ -35,10 +35,10 @@ Download Flipper Zero apps directly to your Flipper Zero using WiFi. You no long
 - Download flash firmware (Marauder, Black Magic, FlipperHTTP)
 
 **v0.7**
-- Download custom apps from a GitHub URL
+- UX Improvements
 
 **v0.8**
-- App Icons
+- Download custom apps/assets from a GitHub URL
 
 **1.0**
 - Download Official Firmware/Firmware Updates

+ 118 - 346
flip_store/alloc/flip_store_alloc.c

@@ -1,11 +1,13 @@
 #include <alloc/flip_store_alloc.h>
 // Function to allocate resources for the FlipStoreApp
-FlipStoreApp* flip_store_app_alloc() {
-    FlipStoreApp* app = (FlipStoreApp*)malloc(sizeof(FlipStoreApp));
+FlipStoreApp *flip_store_app_alloc()
+{
+    FlipStoreApp *app = (FlipStoreApp *)malloc(sizeof(FlipStoreApp));
 
-    Gui* gui = furi_record_open(RECORD_GUI);
+    Gui *gui = furi_record_open(RECORD_GUI);
 
-    if(!flipper_http_init(flipper_http_rx_callback, app)) {
+    if (!flipper_http_init(flipper_http_rx_callback, app))
+    {
         FURI_LOG_E(TAG, "Failed to initialize flipper http");
         return NULL;
     }
@@ -13,411 +15,181 @@ FlipStoreApp* flip_store_app_alloc() {
     // Allocate the text input buffer
     app->uart_text_input_buffer_size_ssid = 64;
     app->uart_text_input_buffer_size_pass = 64;
-    if(!easy_flipper_set_buffer(
-           &app->uart_text_input_buffer_ssid, app->uart_text_input_buffer_size_ssid)) {
+    if (!easy_flipper_set_buffer(&app->uart_text_input_buffer_ssid, app->uart_text_input_buffer_size_ssid))
+    {
         return NULL;
     }
-    if(!easy_flipper_set_buffer(
-           &app->uart_text_input_temp_buffer_ssid, app->uart_text_input_buffer_size_ssid)) {
+    if (!easy_flipper_set_buffer(&app->uart_text_input_temp_buffer_ssid, app->uart_text_input_buffer_size_ssid))
+    {
         return NULL;
     }
-    if(!easy_flipper_set_buffer(
-           &app->uart_text_input_buffer_pass, app->uart_text_input_buffer_size_pass)) {
+    if (!easy_flipper_set_buffer(&app->uart_text_input_buffer_pass, app->uart_text_input_buffer_size_pass))
+    {
         return NULL;
     }
-    if(!easy_flipper_set_buffer(
-           &app->uart_text_input_temp_buffer_pass, app->uart_text_input_buffer_size_pass)) {
+    if (!easy_flipper_set_buffer(&app->uart_text_input_temp_buffer_pass, app->uart_text_input_buffer_size_pass))
+    {
         return NULL;
     }
 
     // Allocate ViewDispatcher
-    if(!easy_flipper_set_view_dispatcher(&app->view_dispatcher, gui, app)) {
+    if (!easy_flipper_set_view_dispatcher(&app->view_dispatcher, gui, app))
+    {
         return NULL;
     }
-
+    view_dispatcher_set_custom_event_callback(app->view_dispatcher, flip_store_custom_event_callback);
     // Main view
-    if(!easy_flipper_set_view(
-           &app->view_main,
-           FlipStoreViewMain,
-           flip_store_view_draw_callback_main,
-           NULL,
-           callback_to_app_list,
-           &app->view_dispatcher,
-           app)) {
+    if (!easy_flipper_set_view(&app->view_loader, FlipStoreViewLoader, flip_store_loader_draw_callback, NULL, callback_to_submenu_options, &app->view_dispatcher, app))
+    {
         return NULL;
     }
-    if(!easy_flipper_set_view(
-           &app->view_app_info,
-           FlipStoreViewAppInfo,
-           flip_store_view_draw_callback_app_list,
-           flip_store_input_callback,
-           callback_to_app_list,
-           &app->view_dispatcher,
-           app)) {
+    flip_store_loader_init(app->view_loader);
+    if (!easy_flipper_set_widget(&app->widget_result, FlipStoreViewWidgetResult, "Error, try again.", callback_to_submenu_options, &app->view_dispatcher))
+    {
         return NULL;
     }
-    if(!easy_flipper_set_view(
-           &app->view_firmware_download,
-           FlipStoreViewFirmwareDownload,
-           flip_store_view_draw_callback_firmware,
-           NULL,
-           callback_to_firmware_list,
-           &app->view_dispatcher,
-           app)) {
+
+    // Main view
+    if (!easy_flipper_set_view(&app->view_app_info, FlipStoreViewAppInfo, flip_store_view_draw_callback_app_list, flip_store_input_callback, callback_to_app_list, &app->view_dispatcher, app))
+    {
         return NULL;
     }
 
     // Widget
-    if(!easy_flipper_set_widget(
-           &app->widget,
-           FlipStoreViewAbout,
-           "Welcome to the FlipStore!\n------\nDownload apps via WiFi and\nrun them on your Flipper!\n------\nwww.github.com/jblanked",
-           callback_to_submenu,
-           &app->view_dispatcher)) {
+    if (!easy_flipper_set_widget(
+            &app->widget,
+            FlipStoreViewAbout,
+            "Welcome to the FlipStore!\n------\nDownload apps via WiFi and\nrun them on your Flipper!\n------\nwww.github.com/jblanked",
+            callback_to_submenu,
+            &app->view_dispatcher))
+    {
         return NULL;
     }
 
     // Popup
-    if(!easy_flipper_set_popup(
-           &app->popup,
-           FlipStoreViewPopup,
-           "Failed",
-           0,
-           0,
-           "You are not connected to Wifi.\n\nIf you have the FlipperHTTP\nflash installed, then update\nyour WiFi credentials.",
-           0,
-           10,
-           popup_callback,
-           callback_to_submenu,
-           &app->view_dispatcher,
-           app)) {
+    if (!easy_flipper_set_popup(&app->popup, FlipStoreViewPopup, "Failed", 0, 0, "You are not connected to Wifi.\n\nIf you have the FlipperHTTP\nflash installed, then update\nyour WiFi credentials.", 0, 10, popup_callback, callback_to_submenu, &app->view_dispatcher, app))
+    {
         FURI_LOG_E(TAG, "Failed to create popup");
     }
 
     // Dialog
-    if(!easy_flipper_set_dialog_ex(
-           &app->dialog_delete,
-           FlipStoreViewAppDelete,
-           "Delete App",
-           0,
-           0,
-           "Are you sure you want to delete this app?",
-           0,
-           10,
-           "No",
-           "Yes",
-           NULL,
-           dialog_delete_callback,
-           callback_to_app_list,
-           &app->view_dispatcher,
-           app)) {
-        return NULL;
-    }
-
-    if(!easy_flipper_set_dialog_ex(
-           &app->dialog_firmware,
-           FlipStoreViewFirmwareDialog,
-           "Download Firmware",
-           0,
-           0,
-           "Are you sure you want to\ndownload this firmware?",
-           0,
-           10,
-           "No",
-           "Yes",
-           NULL,
-           dialog_firmware_callback,
-           callback_to_firmware_list,
-           &app->view_dispatcher,
-           app)) {
+    if (!easy_flipper_set_dialog_ex(
+            &app->dialog_delete,
+            FlipStoreViewAppDelete,
+            "Delete App",
+            0,
+            0,
+            "Are you sure you want to delete this app?",
+            0,
+            10,
+            "No",
+            "Yes",
+            NULL,
+            dialog_delete_callback,
+            callback_to_app_list,
+            &app->view_dispatcher,
+            app))
+    {
+        return NULL;
+    }
+
+    if (!easy_flipper_set_dialog_ex(
+            &app->dialog_firmware,
+            FlipStoreViewFirmwareDialog,
+            "Download Firmware",
+            0,
+            0,
+            "Are you sure you want to\ndownload this firmware?",
+            0,
+            10,
+            "No",
+            "Yes",
+            NULL,
+            dialog_firmware_callback,
+            callback_to_firmware_list,
+            &app->view_dispatcher,
+            app))
+    {
         return NULL;
     }
 
     // Text Input
-    if(!easy_flipper_set_uart_text_input(
-           &app->uart_text_input_ssid,
-           FlipStoreViewTextInputSSID,
-           "Enter SSID",
-           app->uart_text_input_temp_buffer_ssid,
-           app->uart_text_input_buffer_size_ssid,
-           flip_store_text_updated_ssid,
-           callback_to_submenu,
-           &app->view_dispatcher,
-           app)) {
+    if (!easy_flipper_set_uart_text_input(&app->uart_text_input_ssid, FlipStoreViewTextInputSSID, "Enter SSID", app->uart_text_input_temp_buffer_ssid, app->uart_text_input_buffer_size_ssid, flip_store_text_updated_ssid, callback_to_submenu, &app->view_dispatcher, app))
+    {
         return NULL;
     }
-    if(!easy_flipper_set_uart_text_input(
-           &app->uart_text_input_pass,
-           FlipStoreViewTextInputPass,
-           "Enter Password",
-           app->uart_text_input_temp_buffer_pass,
-           app->uart_text_input_buffer_size_pass,
-           flip_store_text_updated_pass,
-           callback_to_submenu,
-           &app->view_dispatcher,
-           app)) {
+    if (!easy_flipper_set_uart_text_input(&app->uart_text_input_pass, FlipStoreViewTextInputPass, "Enter Password", app->uart_text_input_temp_buffer_pass, app->uart_text_input_buffer_size_pass, flip_store_text_updated_pass, callback_to_submenu, &app->view_dispatcher, app))
+    {
         return NULL;
     }
 
     // Variable Item List
-    if(!easy_flipper_set_variable_item_list(
-           &app->variable_item_list,
-           FlipStoreViewSettings,
-           settings_item_selected,
-           callback_to_submenu,
-           &app->view_dispatcher,
-           app)) {
+    if (!easy_flipper_set_variable_item_list(&app->variable_item_list, FlipStoreViewSettings, settings_item_selected, callback_to_submenu, &app->view_dispatcher, app))
+    {
         return NULL;
     }
-    app->variable_item_ssid =
-        variable_item_list_add(app->variable_item_list, "SSID", 0, NULL, NULL);
-    app->variable_item_pass =
-        variable_item_list_add(app->variable_item_list, "Password", 0, NULL, NULL);
+    app->variable_item_ssid = variable_item_list_add(app->variable_item_list, "SSID", 0, NULL, NULL);
+    app->variable_item_pass = variable_item_list_add(app->variable_item_list, "Password", 0, NULL, NULL);
 
     // Submenu
-    if(!easy_flipper_set_submenu(
-           &app->submenu_main,
-           FlipStoreViewSubmenu,
-           "FlipStore v0.6",
-           callback_exit_app,
-           &app->view_dispatcher)) {
-        return NULL;
-    }
-    if(!easy_flipper_set_submenu(
-           &app->submenu_options,
-           FlipStoreViewSubmenuOptions,
-           "Browse",
-           callback_to_submenu,
-           &app->view_dispatcher)) {
+    if (!easy_flipper_set_submenu(&app->submenu_main, FlipStoreViewSubmenu, "FlipStore v0.7", callback_exit_app, &app->view_dispatcher))
+    {
         return NULL;
     }
-    if(!easy_flipper_set_submenu(
-           &app->submenu_app_list,
-           FlipStoreViewAppList,
-           "App Catalog",
-           callback_to_submenu_options,
-           &app->view_dispatcher)) {
+    if (!easy_flipper_set_submenu(&app->submenu_options, FlipStoreViewSubmenuOptions, "Browse", callback_to_submenu, &app->view_dispatcher))
+    {
         return NULL;
     }
-    if(!easy_flipper_set_submenu(
-           &app->submenu_firmwares,
-           FlipStoreViewFirmwares,
-           "ESP32 Firmwares",
-           callback_to_submenu_options,
-           &app->view_dispatcher)) {
+    if (!easy_flipper_set_submenu(&app->submenu_app_list, FlipStoreViewAppList, "App Catalog", callback_to_submenu_options, &app->view_dispatcher))
+    {
         return NULL;
     }
-    if(!easy_flipper_set_submenu(
-           &app->submenu_app_list_bluetooth,
-           FlipStoreViewAppListBluetooth,
-           "Bluetooth",
-           callback_to_app_list,
-           &app->view_dispatcher)) {
-        return NULL;
-    }
-    if(!easy_flipper_set_submenu(
-           &app->submenu_app_list_games,
-           FlipStoreViewAppListGames,
-           "Games",
-           callback_to_app_list,
-           &app->view_dispatcher)) {
-        return NULL;
-    }
-    if(!easy_flipper_set_submenu(
-           &app->submenu_app_list_gpio,
-           FlipStoreViewAppListGPIO,
-           "GPIO",
-           callback_to_app_list,
-           &app->view_dispatcher)) {
-        return NULL;
-    }
-    if(!easy_flipper_set_submenu(
-           &app->submenu_app_list_infrared,
-           FlipStoreViewAppListInfrared,
-           "Infrared",
-           callback_to_app_list,
-           &app->view_dispatcher)) {
-        return NULL;
-    }
-    if(!easy_flipper_set_submenu(
-           &app->submenu_app_list_ibutton,
-           FlipStoreViewAppListiButton,
-           "iButton",
-           callback_to_app_list,
-           &app->view_dispatcher)) {
-        return NULL;
-    }
-    if(!easy_flipper_set_submenu(
-           &app->submenu_app_list_media,
-           FlipStoreViewAppListMedia,
-           "Media",
-           callback_to_app_list,
-           &app->view_dispatcher)) {
-        return NULL;
-    }
-    if(!easy_flipper_set_submenu(
-           &app->submenu_app_list_nfc,
-           FlipStoreViewAppListNFC,
-           "NFC",
-           callback_to_app_list,
-           &app->view_dispatcher)) {
-        return NULL;
-    }
-    if(!easy_flipper_set_submenu(
-           &app->submenu_app_list_rfid,
-           FlipStoreViewAppListRFID,
-           "RFID",
-           callback_to_app_list,
-           &app->view_dispatcher)) {
-        return NULL;
-    }
-    if(!easy_flipper_set_submenu(
-           &app->submenu_app_list_subghz,
-           FlipStoreViewAppListSubGHz,
-           "Sub-GHz",
-           callback_to_app_list,
-           &app->view_dispatcher)) {
-        return NULL;
-    }
-    if(!easy_flipper_set_submenu(
-           &app->submenu_app_list_tools,
-           FlipStoreViewAppListTools,
-           "Tools",
-           callback_to_app_list,
-           &app->view_dispatcher)) {
-        return NULL;
-    }
-    if(!easy_flipper_set_submenu(
-           &app->submenu_app_list_usb,
-           FlipStoreViewAppListUSB,
-           "USB",
-           callback_to_app_list,
-           &app->view_dispatcher)) {
+    if (!easy_flipper_set_submenu(&app->submenu_firmwares, FlipStoreViewFirmwares, "ESP32 Firmwares", callback_to_submenu_options, &app->view_dispatcher))
+    {
         return NULL;
     }
+
     //
-    submenu_add_item(
-        app->submenu_main, "Browse", FlipStoreSubmenuIndexOptions, callback_submenu_choices, app);
-    submenu_add_item(
-        app->submenu_main, "About", FlipStoreSubmenuIndexAbout, callback_submenu_choices, app);
-    submenu_add_item(
-        app->submenu_main,
-        "Settings",
-        FlipStoreSubmenuIndexSettings,
-        callback_submenu_choices,
-        app);
+    submenu_add_item(app->submenu_main, "Browse", FlipStoreSubmenuIndexOptions, callback_submenu_choices, app);
+    submenu_add_item(app->submenu_main, "About", FlipStoreSubmenuIndexAbout, callback_submenu_choices, app);
+    submenu_add_item(app->submenu_main, "Settings", FlipStoreSubmenuIndexSettings, callback_submenu_choices, app);
     //
-    submenu_add_item(
-        app->submenu_options,
-        "App Catalog",
-        FlipStoreSubmenuIndexAppList,
-        callback_submenu_choices,
-        app);
-    submenu_add_item(
-        app->submenu_options,
-        "ESP32 Firmwares",
-        FlipStoreSubmenuIndexFirmwares,
-        callback_submenu_choices,
-        app);
+    submenu_add_item(app->submenu_options, "App Catalog", FlipStoreSubmenuIndexAppList, callback_submenu_choices, app);
+    submenu_add_item(app->submenu_options, "ESP32 Firmwares", FlipStoreSubmenuIndexFirmwares, callback_submenu_choices, app);
 
     //
-    submenu_add_item(
-        app->submenu_app_list,
-        "Bluetooth",
-        FlipStoreSubmenuIndexAppListBluetooth,
-        callback_submenu_choices,
-        app);
-    submenu_add_item(
-        app->submenu_app_list,
-        "Games",
-        FlipStoreSubmenuIndexAppListGames,
-        callback_submenu_choices,
-        app);
-    submenu_add_item(
-        app->submenu_app_list,
-        "GPIO",
-        FlipStoreSubmenuIndexAppListGPIO,
-        callback_submenu_choices,
-        app);
-    submenu_add_item(
-        app->submenu_app_list,
-        "Infrared",
-        FlipStoreSubmenuIndexAppListInfrared,
-        callback_submenu_choices,
-        app);
-    submenu_add_item(
-        app->submenu_app_list,
-        "iButton",
-        FlipStoreSubmenuIndexAppListiButton,
-        callback_submenu_choices,
-        app);
-    submenu_add_item(
-        app->submenu_app_list,
-        "Media",
-        FlipStoreSubmenuIndexAppListMedia,
-        callback_submenu_choices,
-        app);
-    submenu_add_item(
-        app->submenu_app_list,
-        "NFC",
-        FlipStoreSubmenuIndexAppListNFC,
-        callback_submenu_choices,
-        app);
-    submenu_add_item(
-        app->submenu_app_list,
-        "RFID",
-        FlipStoreSubmenuIndexAppListRFID,
-        callback_submenu_choices,
-        app);
-    submenu_add_item(
-        app->submenu_app_list,
-        "Sub-GHz",
-        FlipStoreSubmenuIndexAppListSubGHz,
-        callback_submenu_choices,
-        app);
-    submenu_add_item(
-        app->submenu_app_list,
-        "Tools",
-        FlipStoreSubmenuIndexAppListTools,
-        callback_submenu_choices,
-        app);
-    submenu_add_item(
-        app->submenu_app_list,
-        "USB",
-        FlipStoreSubmenuIndexAppListUSB,
-        callback_submenu_choices,
-        app);
+    submenu_add_item(app->submenu_app_list, "Bluetooth", FlipStoreSubmenuIndexAppListBluetooth, callback_submenu_choices, app);
+    submenu_add_item(app->submenu_app_list, "Games", FlipStoreSubmenuIndexAppListGames, callback_submenu_choices, app);
+    submenu_add_item(app->submenu_app_list, "GPIO", FlipStoreSubmenuIndexAppListGPIO, callback_submenu_choices, app);
+    submenu_add_item(app->submenu_app_list, "Infrared", FlipStoreSubmenuIndexAppListInfrared, callback_submenu_choices, app);
+    submenu_add_item(app->submenu_app_list, "iButton", FlipStoreSubmenuIndexAppListiButton, callback_submenu_choices, app);
+    submenu_add_item(app->submenu_app_list, "Media", FlipStoreSubmenuIndexAppListMedia, callback_submenu_choices, app);
+    submenu_add_item(app->submenu_app_list, "NFC", FlipStoreSubmenuIndexAppListNFC, callback_submenu_choices, app);
+    submenu_add_item(app->submenu_app_list, "RFID", FlipStoreSubmenuIndexAppListRFID, callback_submenu_choices, app);
+    submenu_add_item(app->submenu_app_list, "Sub-GHz", FlipStoreSubmenuIndexAppListSubGHz, callback_submenu_choices, app);
+    submenu_add_item(app->submenu_app_list, "Tools", FlipStoreSubmenuIndexAppListTools, callback_submenu_choices, app);
+    submenu_add_item(app->submenu_app_list, "USB", FlipStoreSubmenuIndexAppListUSB, callback_submenu_choices, app);
     //
     // dont add any items to the app list submenu of each category yet
 
     // load settings
-    if(load_settings(
-           app->uart_text_input_buffer_ssid,
-           app->uart_text_input_buffer_size_ssid,
-           app->uart_text_input_buffer_pass,
-           app->uart_text_input_buffer_size_pass)) {
+    if (load_settings(app->uart_text_input_buffer_ssid, app->uart_text_input_buffer_size_ssid, app->uart_text_input_buffer_pass, app->uart_text_input_buffer_size_pass))
+    {
         // Update variable items
-        if(app->variable_item_ssid)
-            variable_item_set_current_value_text(
-                app->variable_item_ssid, app->uart_text_input_buffer_ssid);
+        if (app->variable_item_ssid)
+            variable_item_set_current_value_text(app->variable_item_ssid, app->uart_text_input_buffer_ssid);
         // do not display the password
 
         // Copy items into their temp buffers with safety checks
-        if(app->uart_text_input_buffer_ssid && app->uart_text_input_temp_buffer_ssid) {
-            strncpy(
-                app->uart_text_input_temp_buffer_ssid,
-                app->uart_text_input_buffer_ssid,
-                app->uart_text_input_buffer_size_ssid - 1);
-            app->uart_text_input_temp_buffer_ssid[app->uart_text_input_buffer_size_ssid - 1] =
-                '\0';
+        if (app->uart_text_input_buffer_ssid && app->uart_text_input_temp_buffer_ssid)
+        {
+            strncpy(app->uart_text_input_temp_buffer_ssid, app->uart_text_input_buffer_ssid, app->uart_text_input_buffer_size_ssid - 1);
+            app->uart_text_input_temp_buffer_ssid[app->uart_text_input_buffer_size_ssid - 1] = '\0';
         }
-        if(app->uart_text_input_buffer_pass && app->uart_text_input_temp_buffer_pass) {
-            strncpy(
-                app->uart_text_input_temp_buffer_pass,
-                app->uart_text_input_buffer_pass,
-                app->uart_text_input_buffer_size_pass - 1);
-            app->uart_text_input_temp_buffer_pass[app->uart_text_input_buffer_size_pass - 1] =
-                '\0';
+        if (app->uart_text_input_buffer_pass && app->uart_text_input_temp_buffer_pass)
+        {
+            strncpy(app->uart_text_input_temp_buffer_pass, app->uart_text_input_buffer_pass, app->uart_text_input_buffer_size_pass - 1);
+            app->uart_text_input_temp_buffer_pass[app->uart_text_input_buffer_size_pass - 1] = '\0';
         }
     }
 
@@ -425,4 +197,4 @@ FlipStoreApp* flip_store_app_alloc() {
     view_dispatcher_switch_to_view(app->view_dispatcher, FlipStoreViewSubmenu);
 
     return app;
-}
+}

+ 2 - 2
flip_store/alloc/flip_store_alloc.h

@@ -5,6 +5,6 @@
 #include <callback/flip_store_callback.h>
 
 // Function to allocate resources for the FlipStoreApp
-FlipStoreApp* flip_store_app_alloc();
+FlipStoreApp *flip_store_app_alloc();
 
-#endif // FLIP_STORE_I_H
+#endif // FLIP_STORE_I_H

+ 9 - 6
flip_store/app.c

@@ -3,27 +3,30 @@
 #include <apps/flip_store_apps.h>
 
 // Entry point for the Hello World application
-int32_t main_flip_store(void* p) {
+int32_t main_flip_store(void *p)
+{
     // Suppress unused parameter warning
     UNUSED(p);
 
     // Initialize the Hello World application
-    FlipStoreApp* app = flip_store_app_alloc();
-    if(!app) {
+    app_instance = flip_store_app_alloc();
+    if (!app_instance)
+    {
         FURI_LOG_E(TAG, "Failed to allocate FlipStoreApp");
         return -1;
     }
 
-    if(!flipper_http_ping()) {
+    if (!flipper_http_ping())
+    {
         FURI_LOG_E(TAG, "Failed to ping the device");
         return -1;
     }
 
     // Run the view dispatcher
-    view_dispatcher_run(app->view_dispatcher);
+    view_dispatcher_run(app_instance->view_dispatcher);
 
     // Free the resources used by the Hello World application
-    flip_store_app_free(app);
+    flip_store_app_free(app_instance);
     flip_catalog_free();
 
     // Return 0 to indicate success

+ 1 - 1
flip_store/application.fam

@@ -10,5 +10,5 @@ App(
     fap_description="Download apps via WiFi directly to your Flipper Zero",
     fap_author="JBlanked",
     fap_weburl="https://github.com/jblanked/FlipStore",
-    fap_version="0.6.1",
+    fap_version="0.7",
 )

+ 144 - 262
flip_store/apps/flip_store_apps.c

@@ -1,6 +1,6 @@
 #include <apps/flip_store_apps.h>
 
-FlipStoreAppInfo* flip_catalog = NULL;
+FlipStoreAppInfo *flip_catalog = NULL;
 
 uint32_t app_selected_index = 0;
 bool flip_store_sent_request = false;
@@ -10,7 +10,7 @@ bool flip_store_saved_success = false;
 uint32_t flip_store_category_index = 0;
 
 // define the list of categories
-char* categories[] = {
+char *categories[] = {
     "Bluetooth",
     "Games",
     "GPIO",
@@ -24,81 +24,77 @@ char* categories[] = {
     "USB",
 };
 
-FlipStoreAppInfo* flip_catalog_alloc() {
-    FlipStoreAppInfo* app_catalog =
-        (FlipStoreAppInfo*)malloc(MAX_APP_COUNT * sizeof(FlipStoreAppInfo));
-    if(!app_catalog) {
+FlipStoreAppInfo *flip_catalog_alloc()
+{
+    FlipStoreAppInfo *app_catalog = (FlipStoreAppInfo *)malloc(MAX_APP_COUNT * sizeof(FlipStoreAppInfo));
+    if (!app_catalog)
+    {
         FURI_LOG_E(TAG, "Failed to allocate memory for flip_catalog.");
         return NULL;
     }
-    for(int i = 0; i < MAX_APP_COUNT; i++) {
-        app_catalog[i].app_name = (char*)malloc(MAX_APP_NAME_LENGTH * sizeof(char));
-        if(!app_catalog[i].app_name) {
+    for (int i = 0; i < MAX_APP_COUNT; i++)
+    {
+        app_catalog[i].app_name = (char *)malloc(MAX_APP_NAME_LENGTH * sizeof(char));
+        if (!app_catalog[i].app_name)
+        {
             FURI_LOG_E(TAG, "Failed to allocate memory for app_name.");
             return NULL;
         }
-        app_catalog[i].app_id = (char*)malloc(MAX_APP_NAME_LENGTH * sizeof(char));
-        if(!app_catalog[i].app_id) {
+        app_catalog[i].app_id = (char *)malloc(MAX_APP_NAME_LENGTH * sizeof(char));
+        if (!app_catalog[i].app_id)
+        {
             FURI_LOG_E(TAG, "Failed to allocate memory for app_id.");
             return NULL;
         }
-        app_catalog[i].app_build_id = (char*)malloc(MAX_ID_LENGTH * sizeof(char));
-        if(!app_catalog[i].app_build_id) {
+        app_catalog[i].app_build_id = (char *)malloc(MAX_ID_LENGTH * sizeof(char));
+        if (!app_catalog[i].app_build_id)
+        {
             FURI_LOG_E(TAG, "Failed to allocate memory for app_build_id.");
             return NULL;
         }
-        app_catalog[i].app_version = (char*)malloc(MAX_APP_VERSION_LENGTH * sizeof(char));
-        if(!app_catalog[i].app_version) {
+        app_catalog[i].app_version = (char *)malloc(MAX_APP_VERSION_LENGTH * sizeof(char));
+        if (!app_catalog[i].app_version)
+        {
             FURI_LOG_E(TAG, "Failed to allocate memory for app_version.");
             return NULL;
         }
-        app_catalog[i].app_description = (char*)malloc(MAX_APP_DESCRIPTION_LENGTH * sizeof(char));
-        if(!app_catalog[i].app_description) {
+        app_catalog[i].app_description = (char *)malloc(MAX_APP_DESCRIPTION_LENGTH * sizeof(char));
+        if (!app_catalog[i].app_description)
+        {
             FURI_LOG_E(TAG, "Failed to allocate memory for app_description.");
             return NULL;
         }
     }
     return app_catalog;
 }
-void flip_catalog_free() {
-    if(!flip_catalog) {
-        return;
-    }
-    for(int i = 0; i < MAX_APP_COUNT; i++) {
-        if(flip_catalog[i].app_name) {
-            free(flip_catalog[i].app_name);
-        }
-        if(flip_catalog[i].app_id) {
-            free(flip_catalog[i].app_id);
-        }
-        if(flip_catalog[i].app_build_id) {
-            free(flip_catalog[i].app_build_id);
-        }
-        if(flip_catalog[i].app_version) {
-            free(flip_catalog[i].app_version);
-        }
-        if(flip_catalog[i].app_description) {
-            free(flip_catalog[i].app_description);
-        }
+void flip_catalog_free()
+{
+    if (flip_catalog)
+    {
+        free(flip_catalog);
     }
 }
 
-bool flip_store_process_app_list() {
+bool flip_store_process_app_list()
+{
     // Initialize the flip_catalog
     flip_catalog = flip_catalog_alloc();
-    if(!flip_catalog) {
+    if (!flip_catalog)
+    {
         FURI_LOG_E(TAG, "Failed to allocate memory for flip_catalog.");
         return false;
     }
 
-    FuriString* feed_data = flipper_http_load_from_file(fhttp.file_path);
-    if(feed_data == NULL) {
+    FuriString *feed_data = flipper_http_load_from_file(fhttp.file_path);
+    if (feed_data == NULL)
+    {
         FURI_LOG_E(TAG, "Failed to load received data from file.");
         return false;
     }
 
-    char* data_cstr = (char*)furi_string_get_cstr(feed_data);
-    if(data_cstr == NULL) {
+    char *data_cstr = (char *)furi_string_get_cstr(feed_data);
+    if (data_cstr == NULL)
+    {
         FURI_LOG_E(TAG, "Failed to get C-string from FuriString.");
         furi_string_free(feed_data);
         return false;
@@ -110,15 +106,15 @@ bool flip_store_process_app_list() {
     bool reading_key = false;
     bool reading_value = false;
     bool inside_app_object = false;
-    bool found_name = false, found_id = false, found_build_id = false, found_version = false,
-         found_description = false;
+    bool found_name = false, found_id = false, found_build_id = false, found_version = false, found_description = false;
     char current_key[MAX_KEY_LENGTH] = {0};
     size_t key_index = 0;
     char current_value[MAX_VALUE_LENGTH] = {0};
     size_t value_index = 0;
     int app_count = 0;
     enum ObjectState object_state = OBJECT_EXPECT_KEY;
-    enum {
+    enum
+    {
         STATE_SEARCH_APPS_KEY,
         STATE_SEARCH_ARRAY_START,
         STATE_READ_ARRAY_ELEMENTS,
@@ -126,100 +122,110 @@ bool flip_store_process_app_list() {
     } state = STATE_SEARCH_APPS_KEY;
 
     // Iterate through the data
-    for(size_t i = 0; data_cstr[i] != '\0' && state != STATE_DONE; ++i) {
+    for (size_t i = 0; data_cstr[i] != '\0' && state != STATE_DONE; ++i)
+    {
         char c = data_cstr[i];
 
-        if(is_escaped) {
+        if (is_escaped)
+        {
             is_escaped = false;
-            if(reading_key && key_index < MAX_KEY_LENGTH - 1) {
+            if (reading_key && key_index < MAX_KEY_LENGTH - 1)
+            {
                 current_key[key_index++] = c;
-            } else if(reading_value && value_index < MAX_VALUE_LENGTH - 1) {
+            }
+            else if (reading_value && value_index < MAX_VALUE_LENGTH - 1)
+            {
                 current_value[value_index++] = c;
             }
             continue;
         }
 
-        if(c == '\\') {
+        if (c == '\\')
+        {
             is_escaped = true;
             continue;
         }
 
-        if(c == '\"') {
+        if (c == '\"')
+        {
             in_string = !in_string;
-            if(in_string) {
-                if(!reading_key && !reading_value) {
-                    if(state == STATE_SEARCH_APPS_KEY || object_state == OBJECT_EXPECT_KEY) {
+            if (in_string)
+            {
+                if (!reading_key && !reading_value)
+                {
+                    if (state == STATE_SEARCH_APPS_KEY || object_state == OBJECT_EXPECT_KEY)
+                    {
                         reading_key = true;
                         key_index = 0;
                         current_key[0] = '\0';
-                    } else if(object_state == OBJECT_EXPECT_VALUE) {
+                    }
+                    else if (object_state == OBJECT_EXPECT_VALUE)
+                    {
                         reading_value = true;
                         value_index = 0;
                         current_value[0] = '\0';
                     }
                 }
-            } else {
-                if(reading_key) {
+            }
+            else
+            {
+                if (reading_key)
+                {
                     reading_key = false;
                     current_key[key_index] = '\0';
-                    if(state == STATE_SEARCH_APPS_KEY && strcmp(current_key, "apps") == 0) {
+                    if (state == STATE_SEARCH_APPS_KEY && strcmp(current_key, "apps") == 0)
+                    {
                         state = STATE_SEARCH_ARRAY_START;
-                    } else if(inside_app_object) {
+                    }
+                    else if (inside_app_object)
+                    {
                         object_state = OBJECT_EXPECT_COLON;
                     }
-                } else if(reading_value) {
+                }
+                else if (reading_value)
+                {
                     reading_value = false;
                     current_value[value_index] = '\0';
 
-                    if(inside_app_object) {
-                        if(strcmp(current_key, "name") == 0) {
-                            snprintf(
-                                flip_catalog[app_count].app_name,
-                                MAX_APP_NAME_LENGTH,
-                                "%.31s",
-                                current_value);
+                    if (inside_app_object)
+                    {
+                        if (strcmp(current_key, "name") == 0)
+                        {
+                            snprintf(flip_catalog[app_count].app_name, MAX_APP_NAME_LENGTH, "%.31s", current_value);
                             found_name = true;
-                        } else if(strcmp(current_key, "id") == 0) {
-                            snprintf(
-                                flip_catalog[app_count].app_id,
-                                MAX_ID_LENGTH,
-                                "%.31s",
-                                current_value);
+                        }
+                        else if (strcmp(current_key, "id") == 0)
+                        {
+                            snprintf(flip_catalog[app_count].app_id, MAX_ID_LENGTH, "%.31s", current_value);
                             found_id = true;
-                        } else if(strcmp(current_key, "build_id") == 0) {
-                            snprintf(
-                                flip_catalog[app_count].app_build_id,
-                                MAX_ID_LENGTH,
-                                "%.31s",
-                                current_value);
+                        }
+                        else if (strcmp(current_key, "build_id") == 0)
+                        {
+                            snprintf(flip_catalog[app_count].app_build_id, MAX_ID_LENGTH, "%.31s", current_value);
                             found_build_id = true;
-                        } else if(strcmp(current_key, "version") == 0) {
-                            snprintf(
-                                flip_catalog[app_count].app_version,
-                                MAX_APP_VERSION_LENGTH,
-                                "%.3s",
-                                current_value);
+                        }
+                        else if (strcmp(current_key, "version") == 0)
+                        {
+                            snprintf(flip_catalog[app_count].app_version, MAX_APP_VERSION_LENGTH, "%.3s", current_value);
                             found_version = true;
-                        } else if(strcmp(current_key, "description") == 0) {
-                            snprintf(
-                                flip_catalog[app_count].app_description,
-                                MAX_APP_DESCRIPTION_LENGTH,
-                                "%.99s",
-                                current_value);
+                        }
+                        else if (strcmp(current_key, "description") == 0)
+                        {
+                            snprintf(flip_catalog[app_count].app_description, MAX_APP_DESCRIPTION_LENGTH, "%.99s", current_value);
                             found_description = true;
                         }
 
-                        if(found_name && found_id && found_build_id && found_version &&
-                           found_description) {
+                        if (found_name && found_id && found_build_id && found_version && found_description)
+                        {
                             app_count++;
-                            if(app_count >= MAX_APP_COUNT) {
+                            if (app_count >= MAX_APP_COUNT)
+                            {
                                 FURI_LOG_I(TAG, "Reached maximum app count.");
                                 state = STATE_DONE;
                                 break;
                             }
 
-                            found_name = found_id = found_build_id = found_version =
-                                found_description = false;
+                            found_name = found_id = found_build_id = found_version = found_description = false;
                         }
 
                         object_state = OBJECT_EXPECT_COMMA_OR_END;
@@ -229,31 +235,46 @@ bool flip_store_process_app_list() {
             continue;
         }
 
-        if(in_string) {
-            if(reading_key && key_index < MAX_KEY_LENGTH - 1) {
+        if (in_string)
+        {
+            if (reading_key && key_index < MAX_KEY_LENGTH - 1)
+            {
                 current_key[key_index++] = c;
-            } else if(reading_value && value_index < MAX_VALUE_LENGTH - 1) {
+            }
+            else if (reading_value && value_index < MAX_VALUE_LENGTH - 1)
+            {
                 current_value[value_index++] = c;
             }
             continue;
         }
 
-        if(state == STATE_SEARCH_ARRAY_START && c == '[') {
+        if (state == STATE_SEARCH_ARRAY_START && c == '[')
+        {
             state = STATE_READ_ARRAY_ELEMENTS;
             continue;
         }
 
-        if(state == STATE_READ_ARRAY_ELEMENTS) {
-            if(c == '{') {
+        if (state == STATE_READ_ARRAY_ELEMENTS)
+        {
+            if (c == '{')
+            {
                 inside_app_object = true;
                 object_state = OBJECT_EXPECT_KEY;
-            } else if(c == '}') {
+            }
+            else if (c == '}')
+            {
                 inside_app_object = false;
-            } else if(c == ':') {
+            }
+            else if (c == ':')
+            {
                 object_state = OBJECT_EXPECT_VALUE;
-            } else if(c == ',') {
+            }
+            else if (c == ',')
+            {
                 object_state = OBJECT_EXPECT_KEY;
-            } else if(c == ']') {
+            }
+            else if (c == ']')
+            {
                 state = STATE_DONE;
                 break;
             }
@@ -266,181 +287,42 @@ bool flip_store_process_app_list() {
     return app_count > 0;
 }
 
-bool flip_store_get_fap_file(
-    char* build_id,
-    uint8_t target,
-    uint16_t api_major,
-    uint16_t api_minor) {
+bool flip_store_get_fap_file(char *build_id, uint8_t target, uint16_t api_major, uint16_t api_minor)
+{
     char url[128];
     fhttp.save_received_data = false;
     fhttp.is_bytes_request = true;
-    snprintf(
-        url,
-        sizeof(url),
-        "https://catalog.flipperzero.one/api/v0/application/version/%s/build/compatible?target=f%d&api=%d.%d",
-        build_id,
-        target,
-        api_major,
-        api_minor);
-    char* headers = jsmn("Content-Type", "application/octet-stream");
+    snprintf(url, sizeof(url), "https://catalog.flipperzero.one/api/v0/application/version/%s/build/compatible?target=f%d&api=%d.%d", build_id, target, api_major, api_minor);
+    char *headers = jsmn("Content-Type", "application/octet-stream");
     bool sent_request = flipper_http_get_request_bytes(url, headers);
     free(headers);
     return sent_request;
 }
 
-// function to handle the entire installation process "asynchronously"
-bool flip_store_install_app(Canvas* canvas, char* category) {
+bool flip_store_install_app(char *category)
+{
     // create /apps/FlipStore directory if it doesn't exist
     char directory_path[128];
     snprintf(directory_path, sizeof(directory_path), STORAGE_EXT_PATH_PREFIX "/apps/%s", category);
 
     // Create the directory
-    Storage* storage = furi_record_open(RECORD_STORAGE);
+    Storage *storage = furi_record_open(RECORD_STORAGE);
     storage_common_mkdir(storage, directory_path);
 
-    // Adjusted to access flip_catalog as an array of structures
-    char installation_text[64];
-    snprintf(
-        installation_text,
-        sizeof(installation_text),
-        "Installing %s",
-        flip_catalog[app_selected_index].app_name);
-    snprintf(
-        fhttp.file_path,
-        sizeof(fhttp.file_path),
-        STORAGE_EXT_PATH_PREFIX "/apps/%s/%s.fap",
-        category,
-        flip_catalog[app_selected_index].app_id);
-    canvas_draw_str(canvas, 0, 10, installation_text);
-    canvas_draw_str(canvas, 0, 20, "Sending request..");
+    snprintf(fhttp.file_path, sizeof(fhttp.file_path), STORAGE_EXT_PATH_PREFIX "/apps/%s/%s.fap", category, flip_catalog[app_selected_index].app_id);
+
     uint8_t target = furi_hal_version_get_hw_target();
     uint16_t api_major, api_minor;
     furi_hal_info_get_api_version(&api_major, &api_minor);
-    if(fhttp.state != INACTIVE &&
-       flip_store_get_fap_file(
-           flip_catalog[app_selected_index].app_build_id, target, api_major, api_minor)) {
-        canvas_draw_str(canvas, 0, 30, "Request sent.");
+    if (fhttp.state != INACTIVE && flip_store_get_fap_file(flip_catalog[app_selected_index].app_build_id, target, api_major, api_minor))
+    {
         fhttp.state = RECEIVING;
-        canvas_draw_str(canvas, 0, 40, "Receiving...");
-    } else {
-        FURI_LOG_E(TAG, "Failed to send the request");
-        flip_store_success = false;
-        return false;
-    }
-    while(fhttp.state == RECEIVING && furi_timer_is_running(fhttp.get_timeout_timer) > 0) {
-        // Wait for the feed to be received
-        furi_delay_ms(10);
+        return true;
     }
-    // furi_timer_stop(fhttp.get_timeout_timer);
-    if(fhttp.state == ISSUE) {
-        flip_store_request_error(canvas);
+    else
+    {
+        FURI_LOG_E(TAG, "Failed to send the request");
         flip_store_success = false;
         return false;
     }
-    flip_store_success = true;
-    return true;
-}
-
-// process the app list and return view
-int32_t flip_store_handle_app_list(
-    FlipStoreApp* app,
-    int32_t success_view,
-    char* category,
-    Submenu** submenu) {
-    // reset the flip_catalog
-    flip_catalog_free();
-
-    if(!app) {
-        FURI_LOG_E(TAG, "FlipStoreApp is NULL");
-        return FlipStoreViewPopup;
-    }
-    snprintf(
-        fhttp.file_path,
-        sizeof(fhttp.file_path),
-        STORAGE_EXT_PATH_PREFIX "/apps_data/flip_store/%s.json",
-        category);
-
-    fhttp.save_received_data = true;
-    fhttp.is_bytes_request = false;
-    char url[128];
-    snprintf(url, sizeof(url), "https://www.flipsocial.net/api/flipper/apps/%s/max/", category);
-    // async call to the app list with timer
-    if(fhttp.state != INACTIVE &&
-       flipper_http_get_request_with_headers(url, "{\"Content-Type\":\"application/json\"}")) {
-        furi_timer_start(fhttp.get_timeout_timer, TIMEOUT_DURATION_TICKS);
-        fhttp.state = RECEIVING;
-    } else {
-        FURI_LOG_E(TAG, "Failed to send the request");
-        fhttp.state = ISSUE;
-        return FlipStoreViewPopup;
-    }
-    while(fhttp.state == RECEIVING && furi_timer_is_running(fhttp.get_timeout_timer) > 0) {
-        // Wait for the feed to be received
-        furi_delay_ms(10);
-    }
-    furi_timer_stop(fhttp.get_timeout_timer);
-    if(fhttp.state == ISSUE) {
-        FURI_LOG_E(TAG, "Failed to receive data");
-        if(fhttp.last_response == NULL) {
-            if(fhttp.last_response != NULL) {
-                if(strstr(
-                       fhttp.last_response,
-                       "[ERROR] Not connected to Wifi. Failed to reconnect.") != NULL) {
-                    popup_set_text(
-                        app->popup,
-                        "[ERROR] WiFi Disconnected.\n\n\nUpdate your WiFi settings.\nPress BACK to return.",
-                        0,
-                        10,
-                        AlignLeft,
-                        AlignTop);
-                } else if(strstr(fhttp.last_response, "[ERROR] Failed to connect to Wifi.") != NULL) {
-                    popup_set_text(
-                        app->popup,
-                        "[ERROR] WiFi Disconnected.\n\n\nUpdate your WiFi settings.\nPress BACK to return.",
-                        0,
-                        10,
-                        AlignLeft,
-                        AlignTop);
-                } else {
-                    popup_set_text(app->popup, fhttp.last_response, 0, 50, AlignLeft, AlignTop);
-                }
-            } else {
-                popup_set_text(
-                    app->popup,
-                    "[ERROR] Unknown Error.\n\n\nUpdate your WiFi settings.\nPress BACK to return.",
-                    0,
-                    10,
-                    AlignLeft,
-                    AlignTop);
-            }
-            return FlipStoreViewPopup;
-        } else {
-            popup_set_text(app->popup, "Failed to received data.", 0, 50, AlignLeft, AlignTop);
-            return FlipStoreViewPopup;
-        }
-    } else {
-        // process the app list
-        if(flip_store_process_app_list() && submenu && flip_catalog) {
-            submenu_reset(*submenu);
-            // add each app name to submenu
-            for(int i = 0; i < MAX_APP_COUNT; i++) {
-                if(strlen(flip_catalog[i].app_name) > 0) {
-                    submenu_add_item(
-                        *submenu,
-                        flip_catalog[i].app_name,
-                        FlipStoreSubmenuIndexStartAppList + i,
-                        callback_submenu_choices,
-                        app);
-                } else {
-                    break;
-                }
-            }
-            return success_view;
-        } else {
-            FURI_LOG_E(TAG, "Failed to process the app list");
-            popup_set_text(
-                app->popup, "Failed to process the app list", 0, 10, AlignLeft, AlignTop);
-            return FlipStoreViewPopup;
-        }
-    }
-}
+}

+ 20 - 30
flip_store/apps/flip_store_apps.h

@@ -6,24 +6,25 @@
 #include <callback/flip_store_callback.h>
 
 // Define maximum limits
-#define MAX_APP_NAME_LENGTH        32
-#define MAX_ID_LENGTH              32
-#define MAX_APP_COUNT              100
+#define MAX_APP_NAME_LENGTH 32
+#define MAX_ID_LENGTH 32
+#define MAX_APP_COUNT 100
 #define MAX_APP_DESCRIPTION_LENGTH 100
-#define MAX_APP_VERSION_LENGTH     5
+#define MAX_APP_VERSION_LENGTH 5
 
 // define the list of categories
-extern char* categories[];
-
-typedef struct {
-    char* app_name;
-    char* app_id;
-    char* app_build_id;
-    char* app_version;
-    char* app_description;
+extern char *categories[];
+
+typedef struct
+{
+    char *app_name;
+    char *app_id;
+    char *app_build_id;
+    char *app_version;
+    char *app_description;
 } FlipStoreAppInfo;
 
-extern FlipStoreAppInfo* flip_catalog;
+extern FlipStoreAppInfo *flip_catalog;
 
 extern uint32_t app_selected_index;
 extern bool flip_store_sent_request;
@@ -32,34 +33,23 @@ extern bool flip_store_saved_data;
 extern bool flip_store_saved_success;
 extern uint32_t flip_store_category_index;
 
-enum ObjectState {
+enum ObjectState
+{
     OBJECT_EXPECT_KEY,
     OBJECT_EXPECT_COLON,
     OBJECT_EXPECT_VALUE,
     OBJECT_EXPECT_COMMA_OR_END
 };
 
-FlipStoreAppInfo* flip_catalog_alloc();
+FlipStoreAppInfo *flip_catalog_alloc();
 
 void flip_catalog_free();
 
 // Utility function to parse JSON incrementally from a file
 bool flip_store_process_app_list();
 
-bool flip_store_get_fap_file(
-    char* build_id,
-    uint8_t target,
-    uint16_t api_major,
-    uint16_t api_minor);
+bool flip_store_get_fap_file(char *build_id, uint8_t target, uint16_t api_major, uint16_t api_minor);
 
 // function to handle the entire installation process "asynchronously"
-bool flip_store_install_app(Canvas* canvas, char* category);
-
-// process the app list and return view
-int32_t flip_store_handle_app_list(
-    FlipStoreApp* app,
-    int32_t success_view,
-    char* category,
-    Submenu** submenu);
-
-#endif // FLIP_STORE_APPS_H
+bool flip_store_install_app(char *category);
+#endif // FLIP_STORE_APPS_H

BIN
flip_store/assets/01-main-menu.png


+ 5 - 0
flip_store/assets/CHANGELOG.md

@@ -1,3 +1,8 @@
+## v0.7
+- Improved memory allocation
+- Added updates from Derek Jamison
+- Updated Marauder to the latest version
+
 ## v0.6
 - Updated app layout
 - Added an option to download Developer Board firmware (Black Magic, FlipperHTTP, and Marauder)

+ 3 - 3
flip_store/assets/README.md

@@ -1,7 +1,7 @@
 # FlipStore
 Download Flipper Zero apps directly to your Flipper Zero using WiFi. You no longer need another device to install apps. FlipStore uses the FlipperHTTP flash for the WiFi Devboard, first introduced in the WebCrawler app: https://github.com/jblanked/WebCrawler-FlipperZero/tree/main/assets/FlipperHTTP
 
-## Features
+## Features 
 - App Catalog
 - Install Apps
 - Delete Apps 
@@ -35,10 +35,10 @@ Download Flipper Zero apps directly to your Flipper Zero using WiFi. You no long
 - Download flash firmware (Marauder, Black Magic, FlipperHTTP)
 
 **v0.7**
-- Download custom apps from a GitHub URL
+- UX Improvements
 
 **v0.8**
-- App Icons
+- Download custom apps from a GitHub URL
 
 **1.0**
 - Download Official Firmware/Firmware Updates

+ 804 - 314
flip_store/callback/flip_store_callback.c

@@ -1,190 +1,221 @@
 #include <callback/flip_store_callback.h>
 
+// Below added by Derek Jamison
+// FURI_LOG_DEV will log only during app development. Be sure that Settings/System/Log Device is "LPUART"; so we dont use serial port.
+#ifdef DEVELOPMENT
+#define FURI_LOG_DEV(tag, format, ...) furi_log_print_format(FuriLogLevelInfo, tag, format, ##__VA_ARGS__)
+#define DEV_CRASH() furi_crash()
+#else
+#define FURI_LOG_DEV(tag, format, ...)
+#define DEV_CRASH()
+#endif
+
 bool flip_store_app_does_exist = false;
 uint32_t selected_firmware_index = 0;
 
-// Callback for drawing the main screen
-void flip_store_view_draw_callback_main(Canvas* canvas, void* model) {
+static bool flip_store_dl_app_fetch(DataLoaderModel *model)
+{
     UNUSED(model);
-    canvas_set_font(canvas, FontSecondary);
-
-    if(fhttp.state == INACTIVE) {
-        canvas_draw_str(canvas, 0, 7, "Wifi Dev Board disconnected.");
-        canvas_draw_str(canvas, 0, 17, "Please connect to the board.");
-        canvas_draw_str(canvas, 0, 32, "If your board is connected,");
-        canvas_draw_str(canvas, 0, 42, "make sure you have flashed");
-        canvas_draw_str(canvas, 0, 52, "your WiFi Devboard with the");
-        canvas_draw_str(canvas, 0, 62, "latest FlipperHTTP flash.");
-        return;
+    return flip_store_install_app(categories[flip_store_category_index]);
+}
+static char *flip_store_dl_app_parse(DataLoaderModel *model)
+{
+    UNUSED(model);
+    if (fhttp.state != IDLE)
+    {
+        return NULL;
+    }
+    return "App installed successfully.";
+}
+static void flip_store_dl_app_switch_to_view(FlipStoreApp *app)
+{
+    flip_store_generic_switch_to_view(app, flip_catalog[app_selected_index].app_name, flip_store_dl_app_fetch, flip_store_dl_app_parse, 1, callback_to_app_list, FlipStoreViewLoader);
+}
+//
+static bool flip_store_fetch_app_list(DataLoaderModel *model)
+{
+    UNUSED(model);
+    flip_catalog_free();
+    snprintf(
+        fhttp.file_path,
+        sizeof(fhttp.file_path),
+        STORAGE_EXT_PATH_PREFIX "/apps_data/flip_store/%s.json", categories[flip_store_category_index]);
+    fhttp.save_received_data = true;
+    fhttp.is_bytes_request = false;
+    char url[128];
+    snprintf(url, sizeof(url), "https://www.flipsocial.net/api/flipper/apps/%s/max/", categories[flip_store_category_index]);
+    return fhttp.state != INACTIVE && flipper_http_get_request_with_headers(url, "{\"Content-Type\":\"application/json\"}");
+}
+static char *flip_store_parse_app_list(DataLoaderModel *model)
+{
+    UNUSED(model);
+    if (!app_instance)
+    {
+        FURI_LOG_E(TAG, "FlipStoreApp is NULL");
+        return "Failed to fetch app list.";
+    }
+    Submenu **submenu = NULL;
+    uint32_t view_id = 0;
+    switch (flip_store_category_index)
+    {
+    case 0:
+        submenu = &app_instance->submenu_app_list_bluetooth;
+        view_id = FlipStoreViewAppListBluetooth;
+        break;
+    case 1:
+        submenu = &app_instance->submenu_app_list_games;
+        view_id = FlipStoreViewAppListGames;
+        break;
+    case 2:
+        submenu = &app_instance->submenu_app_list_gpio;
+        view_id = FlipStoreViewAppListGPIO;
+        break;
+    case 3:
+        submenu = &app_instance->submenu_app_list_infrared;
+        view_id = FlipStoreViewAppListInfrared;
+        break;
+    case 4:
+        submenu = &app_instance->submenu_app_list_ibutton;
+        view_id = FlipStoreViewAppListiButton;
+        break;
+    case 5:
+        submenu = &app_instance->submenu_app_list_media;
+        view_id = FlipStoreViewAppListMedia;
+        break;
+    case 6:
+        submenu = &app_instance->submenu_app_list_nfc;
+        view_id = FlipStoreViewAppListNFC;
+        break;
+    case 7:
+        submenu = &app_instance->submenu_app_list_rfid;
+        view_id = FlipStoreViewAppListRFID;
+        break;
+    case 8:
+        submenu = &app_instance->submenu_app_list_subghz;
+        view_id = FlipStoreViewAppListSubGHz;
+        break;
+    case 9:
+        submenu = &app_instance->submenu_app_list_tools;
+        view_id = FlipStoreViewAppListTools;
+        break;
+    case 10:
+        submenu = &app_instance->submenu_app_list_usb;
+        view_id = FlipStoreViewAppListUSB;
+        break;
+    }
+    if (!submenu)
+    {
+        FURI_LOG_E(TAG, "Submenu is NULL");
+        return "Failed to fetch app list.";
+    }
+    if (!easy_flipper_set_submenu(submenu, view_id, categories[flip_store_category_index], callback_to_app_list, &app_instance->view_dispatcher))
+    {
+        return NULL;
     }
 
-    if(!flip_store_sent_request) {
-        flip_store_sent_request = true;
-
-        if(!flip_store_install_app(canvas, categories[flip_store_category_index])) {
-            canvas_clear(canvas);
-            canvas_draw_str(canvas, 0, 10, "Failed to install app.");
-            canvas_draw_str(canvas, 0, 60, "Press BACK to return.");
-        }
-    } else {
-        if(flip_store_success) {
-            if(fhttp.state == RECEIVING) {
-                canvas_clear(canvas);
-                canvas_draw_str(canvas, 0, 10, "Downloading app...");
-                canvas_draw_str(canvas, 0, 60, "Please wait...");
-                return;
-            } else if(fhttp.state == IDLE) {
-                canvas_clear(canvas);
-                canvas_draw_str(canvas, 0, 10, "App installed successfully.");
-                canvas_draw_str(canvas, 0, 60, "Press BACK to return.");
+    if (flip_store_process_app_list() && submenu && flip_catalog)
+    {
+        submenu_reset(*submenu);
+        // add each app name to submenu
+        for (int i = 0; i < MAX_APP_COUNT; i++)
+        {
+            if (strlen(flip_catalog[i].app_name) > 0)
+            {
+                submenu_add_item(*submenu, flip_catalog[i].app_name, FlipStoreSubmenuIndexStartAppList + i, callback_submenu_choices, app_instance);
+            }
+            else
+            {
+                break;
             }
-        } else {
-            canvas_clear(canvas);
-            canvas_draw_str(canvas, 0, 10, "Failed to install app.");
-            canvas_draw_str(canvas, 0, 60, "Press BACK to return.");
         }
+        view_dispatcher_switch_to_view(app_instance->view_dispatcher, view_id);
+        return "Fetched app list successfully.";
     }
-}
-
-// Function to draw the firmware download screen
-void flip_store_view_draw_callback_firmware(Canvas* canvas, void* model) {
-    UNUSED(model);
-
-    // Check if the HTTP state is inactive
-    if(fhttp.state == INACTIVE) {
-        canvas_set_font(canvas, FontSecondary);
-        canvas_draw_str(canvas, 0, 7, "Wifi Dev Board disconnected.");
-        canvas_draw_str(canvas, 0, 17, "Please connect to the board.");
-        canvas_draw_str(canvas, 0, 32, "If your board is connected,");
-        canvas_draw_str(canvas, 0, 42, "make sure you have flashed");
-        canvas_draw_str(canvas, 0, 52, "your WiFi Devboard with the");
-        canvas_draw_str(canvas, 0, 62, "latest FlipperHTTP flash.");
-        return;
+    else
+    {
+        FURI_LOG_E(TAG, "Failed to process the app list");
+        return "Failed to fetch app list.";
     }
-
-    // Set font and clear the canvas for the loading state
-    canvas_set_font(canvas, FontSecondary);
-    canvas_clear(canvas);
-    canvas_draw_str(canvas, 0, 10, "Loading...");
-
-    // Handle first firmware file
-    if(!sent_firmware_request) {
-        sent_firmware_request = true;
+}
+static void flip_store_switch_to_app_list(FlipStoreApp *app)
+{
+    flip_store_generic_switch_to_view(app, categories[flip_store_category_index], flip_store_fetch_app_list, flip_store_parse_app_list, 1, callback_to_submenu, FlipStoreViewLoader);
+}
+//
+static bool flip_store_fetch_firmware(DataLoaderModel *model)
+{
+    if (model->request_index == 0)
+    {
+        firmware_free();
+        firmwares = firmware_alloc();
+        if (!firmwares)
+        {
+            return false;
+        }
         firmware_request_success = flip_store_get_firmware_file(
             firmwares[selected_firmware_index].links[0],
             firmwares[selected_firmware_index].name,
             strrchr(firmwares[selected_firmware_index].links[0], '/') + 1);
-
-        if(!firmware_request_success) {
-            canvas_set_font(canvas, FontSecondary);
-            canvas_clear(canvas);
-            flip_store_request_error(canvas);
-        }
-        return;
-    } else if(sent_firmware_request && !firmware_download_success) {
-        if(!firmware_request_success || fhttp.state == ISSUE) {
-            canvas_set_font(canvas, FontSecondary);
-            canvas_clear(canvas);
-            flip_store_request_error(canvas);
-        } else if(fhttp.state == RECEIVING) {
-            canvas_set_font(canvas, FontSecondary);
-            canvas_clear(canvas);
-            canvas_draw_str(canvas, 0, 10, "Downloading file 1...");
-            canvas_draw_str(canvas, 0, 60, "Please wait...");
-        } else if(fhttp.state == IDLE) {
-            canvas_set_font(canvas, FontSecondary);
-            canvas_clear(canvas);
-            canvas_draw_str(canvas, 0, 10, "Success");
-            canvas_draw_str(canvas, 0, 60, "Downloading the next file now.");
-            firmware_download_success = true;
-        }
-        return;
+        return firmware_request_success;
     }
-
-    // Handle second firmware file
-    if(firmware_download_success && !sent_firmware_request_2) {
-        sent_firmware_request_2 = true;
+    else if (model->request_index == 1)
+    {
         firmware_request_success_2 = flip_store_get_firmware_file(
             firmwares[selected_firmware_index].links[1],
             firmwares[selected_firmware_index].name,
             strrchr(firmwares[selected_firmware_index].links[1], '/') + 1);
-
-        if(!firmware_request_success_2) {
-            canvas_set_font(canvas, FontSecondary);
-            canvas_clear(canvas);
-            flip_store_request_error(canvas);
-        }
-        return;
-    } else if(sent_firmware_request_2 && !firmware_download_success_2) {
-        if(!firmware_request_success_2 || fhttp.state == ISSUE) {
-            canvas_set_font(canvas, FontSecondary);
-            canvas_clear(canvas);
-            flip_store_request_error(canvas);
-        } else if(fhttp.state == RECEIVING) {
-            canvas_set_font(canvas, FontSecondary);
-            canvas_clear(canvas);
-            canvas_draw_str(canvas, 0, 10, "Downloading file 2...");
-            canvas_draw_str(canvas, 0, 60, "Please wait...");
-        } else if(fhttp.state == IDLE) {
-            canvas_set_font(canvas, FontSecondary);
-            canvas_clear(canvas);
-            canvas_draw_str(canvas, 0, 10, "Success");
-            canvas_draw_str(canvas, 0, 60, "Downloading the next file now.");
-            firmware_download_success_2 = true;
-        }
-        return;
+        return firmware_request_success_2;
     }
-
-    // Handle third firmware file
-    if(firmware_download_success && firmware_download_success_2 && !sent_firmware_request_3) {
-        sent_firmware_request_3 = true;
+    else if (model->request_index == 2)
+    {
         firmware_request_success_3 = flip_store_get_firmware_file(
             firmwares[selected_firmware_index].links[2],
             firmwares[selected_firmware_index].name,
             strrchr(firmwares[selected_firmware_index].links[2], '/') + 1);
-
-        if(!firmware_request_success_3) {
-            canvas_set_font(canvas, FontSecondary);
-            canvas_clear(canvas);
-            flip_store_request_error(canvas);
+        return firmware_request_success_3;
+    }
+    return false;
+}
+static char *flip_store_parse_firmware(DataLoaderModel *model)
+{
+    if (model->request_index == 0)
+    {
+        if (firmware_request_success)
+        {
+            return "File 1 installed.";
         }
-        return;
-    } else if(sent_firmware_request_3 && !firmware_download_success_3) {
-        if(!firmware_request_success_3 || fhttp.state == ISSUE) {
-            canvas_set_font(canvas, FontSecondary);
-            canvas_clear(canvas);
-            flip_store_request_error(canvas);
-        } else if(fhttp.state == RECEIVING) {
-            canvas_set_font(canvas, FontSecondary);
-            canvas_clear(canvas);
-            canvas_draw_str(canvas, 0, 10, "Downloading file 3...");
-            canvas_draw_str(canvas, 0, 60, "Please wait...");
-        } else if(fhttp.state == IDLE) {
-            canvas_set_font(canvas, FontSecondary);
-            canvas_clear(canvas);
-            canvas_draw_str(canvas, 0, 10, "Success");
-            canvas_draw_str(canvas, 0, 60, "Press BACK to return.");
-            firmware_download_success_3 = true;
+    }
+    else if (model->request_index == 1)
+    {
+        if (firmware_request_success_2)
+        {
+            return "File 2 installed.";
         }
-        return;
     }
-
-    // All files downloaded successfully
-    if(firmware_download_success && firmware_download_success_2 && firmware_download_success_3) {
-        canvas_set_font(canvas, FontSecondary);
-        canvas_clear(canvas);
-        canvas_draw_str(canvas, 0, 10, "Files downloaded successfully.");
-        canvas_draw_str(canvas, 0, 60, "Press BACK to return.");
+    else if (model->request_index == 2)
+    {
+        if (firmware_request_success_3)
+        {
+            return "Firmware downloaded successfully";
+        }
     }
+    return NULL;
+}
+static void flip_store_switch_to_firmware_list(FlipStoreApp *app)
+{
+    flip_store_generic_switch_to_view(app, firmwares[selected_firmware_index].name, flip_store_fetch_firmware, flip_store_parse_firmware, FIRMWARE_LINKS, callback_to_submenu, FlipStoreViewLoader);
 }
 
 // Function to draw the message on the canvas with word wrapping
-void draw_description(Canvas* canvas, const char* description, int x, int y) {
-    if(description == NULL || strlen(description) == 0) {
+void draw_description(Canvas *canvas, const char *description, int x, int y)
+{
+    if (description == NULL || strlen(description) == 0)
+    {
         FURI_LOG_E(TAG, "User message is NULL.");
         return;
     }
-    if(!canvas) {
+    if (!canvas)
+    {
         FURI_LOG_E(TAG, "Canvas is NULL.");
         return;
     }
@@ -194,18 +225,22 @@ void draw_description(Canvas* canvas, const char* description, int x, int y) {
     int line_num = 0;
     char line[MAX_LINE_LENGTH + 1]; // Buffer for the current line (+1 for null terminator)
 
-    while(start < msg_length && line_num < 4) {
+    while (start < msg_length && line_num < 4)
+    {
         size_t remaining = msg_length - start;
         size_t len = (remaining > MAX_LINE_LENGTH) ? MAX_LINE_LENGTH : remaining;
 
-        if(remaining > MAX_LINE_LENGTH) {
+        if (remaining > MAX_LINE_LENGTH)
+        {
             // Find the last space within the first 'len' characters
             size_t last_space = len;
-            while(last_space > 0 && description[start + last_space - 1] != ' ') {
+            while (last_space > 0 && description[start + last_space - 1] != ' ')
+            {
                 last_space--;
             }
 
-            if(last_space > 0) {
+            if (last_space > 0)
+            {
                 len = last_space; // Adjust len to the position of the last space
             }
         }
@@ -222,7 +257,8 @@ void draw_description(Canvas* canvas, const char* description, int x, int y) {
         start += len;
 
         // Skip any spaces to avoid leading spaces on the next line
-        while(start < msg_length && description[start] == ' ') {
+        while (start < msg_length && description[start] == ' ')
+        {
             start++;
         }
 
@@ -231,26 +267,25 @@ void draw_description(Canvas* canvas, const char* description, int x, int y) {
     }
 }
 
-void flip_store_view_draw_callback_app_list(Canvas* canvas, void* model) {
+void flip_store_view_draw_callback_app_list(Canvas *canvas, void *model)
+{
     UNUSED(model);
     canvas_clear(canvas);
     canvas_set_font(canvas, FontPrimary);
     char title[30];
-    snprintf(
-        title,
-        30,
-        "%s (v.%s)",
-        flip_catalog[app_selected_index].app_name,
-        flip_catalog[app_selected_index].app_version);
+    snprintf(title, 30, "%s (v.%s)", flip_catalog[app_selected_index].app_name, flip_catalog[app_selected_index].app_version);
     canvas_draw_str(canvas, 0, 10, title);
     canvas_set_font(canvas, FontSecondary);
     draw_description(canvas, flip_catalog[app_selected_index].app_description, 0, 13);
-    if(flip_store_app_does_exist) {
+    if (flip_store_app_does_exist)
+    {
         canvas_draw_icon(canvas, 0, 53, &I_ButtonLeft_4x7);
         canvas_draw_str_aligned(canvas, 7, 54, AlignLeft, AlignTop, "Delete");
         canvas_draw_icon(canvas, 45, 53, &I_ButtonBACK_10x8);
         canvas_draw_str_aligned(canvas, 57, 54, AlignLeft, AlignTop, "Back");
-    } else {
+    }
+    else
+    {
         canvas_draw_icon(canvas, 0, 53, &I_ButtonBACK_10x8);
         canvas_draw_str_aligned(canvas, 12, 54, AlignLeft, AlignTop, "Back");
     }
@@ -258,25 +293,33 @@ void flip_store_view_draw_callback_app_list(Canvas* canvas, void* model) {
     canvas_draw_str_aligned(canvas, 97, 54, AlignLeft, AlignTop, "Install");
 }
 
-bool flip_store_input_callback(InputEvent* event, void* context) {
-    FlipStoreApp* app = (FlipStoreApp*)context;
-    if(!app) {
+bool flip_store_input_callback(InputEvent *event, void *context)
+{
+    FlipStoreApp *app = (FlipStoreApp *)context;
+    if (!app)
+    {
         FURI_LOG_E(TAG, "FlipStoreApp is NULL");
         return false;
     }
-    if(event->type == InputTypeShort) {
-        if(event->key == InputKeyLeft && flip_store_app_does_exist) {
+    if (event->type == InputTypeShort)
+    {
+        if (event->key == InputKeyLeft && flip_store_app_does_exist)
+        {
             // Left button clicked, delete the app
             view_dispatcher_switch_to_view(app->view_dispatcher, FlipStoreViewAppDelete);
             return true;
         }
-        if(event->key == InputKeyRight) {
+        if (event->key == InputKeyRight)
+        {
             // Right button clicked, download the app
-            view_dispatcher_switch_to_view(app->view_dispatcher, FlipStoreViewMain);
+            flip_store_dl_app_switch_to_view(app);
             return true;
         }
-    } else if(event->type == InputTypePress) {
-        if(event->key == InputKeyBack) {
+    }
+    else if (event->type == InputTypePress)
+    {
+        if (event->key == InputKeyBack)
+        {
             // Back button clicked, switch to the previous view.
             view_dispatcher_switch_to_view(app->view_dispatcher, FlipStoreViewAppList);
             return true;
@@ -286,37 +329,36 @@ bool flip_store_input_callback(InputEvent* event, void* context) {
     return false;
 }
 
-void flip_store_text_updated_ssid(void* context) {
-    FlipStoreApp* app = (FlipStoreApp*)context;
-    if(!app) {
+void flip_store_text_updated_ssid(void *context)
+{
+    FlipStoreApp *app = (FlipStoreApp *)context;
+    if (!app)
+    {
         FURI_LOG_E(TAG, "FlipStoreApp is NULL");
         return;
     }
 
     // store the entered text
-    strncpy(
-        app->uart_text_input_buffer_ssid,
-        app->uart_text_input_temp_buffer_ssid,
-        app->uart_text_input_buffer_size_ssid);
+    strncpy(app->uart_text_input_buffer_ssid, app->uart_text_input_temp_buffer_ssid, app->uart_text_input_buffer_size_ssid);
 
     // Ensure null-termination
     app->uart_text_input_buffer_ssid[app->uart_text_input_buffer_size_ssid - 1] = '\0';
 
     // update the variable item text
-    if(app->variable_item_ssid) {
-        variable_item_set_current_value_text(
-            app->variable_item_ssid, app->uart_text_input_buffer_ssid);
+    if (app->variable_item_ssid)
+    {
+        variable_item_set_current_value_text(app->variable_item_ssid, app->uart_text_input_buffer_ssid);
     }
 
     // save the settings
     save_settings(app->uart_text_input_buffer_ssid, app->uart_text_input_buffer_pass);
 
     // if SSID and PASS are not empty, connect to the WiFi
-    if(strlen(app->uart_text_input_buffer_ssid) > 0 &&
-       strlen(app->uart_text_input_buffer_pass) > 0) {
+    if (strlen(app->uart_text_input_buffer_ssid) > 0 && strlen(app->uart_text_input_buffer_pass) > 0)
+    {
         // save wifi settings
-        if(!flipper_http_save_wifi(
-               app->uart_text_input_buffer_ssid, app->uart_text_input_buffer_pass)) {
+        if (!flipper_http_save_wifi(app->uart_text_input_buffer_ssid, app->uart_text_input_buffer_pass))
+        {
             FURI_LOG_E(TAG, "Failed to save WiFi settings");
         }
     }
@@ -324,37 +366,36 @@ void flip_store_text_updated_ssid(void* context) {
     // switch to the settings view
     view_dispatcher_switch_to_view(app->view_dispatcher, FlipStoreViewSettings);
 }
-void flip_store_text_updated_pass(void* context) {
-    FlipStoreApp* app = (FlipStoreApp*)context;
-    if(!app) {
+void flip_store_text_updated_pass(void *context)
+{
+    FlipStoreApp *app = (FlipStoreApp *)context;
+    if (!app)
+    {
         FURI_LOG_E(TAG, "FlipStoreApp is NULL");
         return;
     }
 
     // store the entered text
-    strncpy(
-        app->uart_text_input_buffer_pass,
-        app->uart_text_input_temp_buffer_pass,
-        app->uart_text_input_buffer_size_pass);
+    strncpy(app->uart_text_input_buffer_pass, app->uart_text_input_temp_buffer_pass, app->uart_text_input_buffer_size_pass);
 
     // Ensure null-termination
     app->uart_text_input_buffer_pass[app->uart_text_input_buffer_size_pass - 1] = '\0';
 
     // update the variable item text
-    if(app->variable_item_pass) {
-        variable_item_set_current_value_text(
-            app->variable_item_pass, app->uart_text_input_buffer_pass);
+    if (app->variable_item_pass)
+    {
+        variable_item_set_current_value_text(app->variable_item_pass, app->uart_text_input_buffer_pass);
     }
 
     // save the settings
     save_settings(app->uart_text_input_buffer_ssid, app->uart_text_input_buffer_pass);
 
     // if SSID and PASS are not empty, connect to the WiFi
-    if(strlen(app->uart_text_input_buffer_ssid) > 0 &&
-       strlen(app->uart_text_input_buffer_pass) > 0) {
+    if (strlen(app->uart_text_input_buffer_ssid) > 0 && strlen(app->uart_text_input_buffer_pass) > 0)
+    {
         // save wifi settings
-        if(!flipper_http_save_wifi(
-               app->uart_text_input_buffer_ssid, app->uart_text_input_buffer_pass)) {
+        if (!flipper_http_save_wifi(app->uart_text_input_buffer_ssid, app->uart_text_input_buffer_pass))
+        {
             FURI_LOG_E(TAG, "Failed to save WiFi settings");
         }
     }
@@ -363,8 +404,10 @@ void flip_store_text_updated_pass(void* context) {
     view_dispatcher_switch_to_view(app->view_dispatcher, FlipStoreViewSettings);
 }
 
-uint32_t callback_to_submenu(void* context) {
-    if(!context) {
+uint32_t callback_to_submenu(void *context)
+{
+    if (!context)
+    {
         FURI_LOG_E(TAG, "Context is NULL");
         return VIEW_NONE;
     }
@@ -373,8 +416,10 @@ uint32_t callback_to_submenu(void* context) {
     return FlipStoreViewSubmenu;
 }
 
-uint32_t callback_to_submenu_options(void* context) {
-    if(!context) {
+uint32_t callback_to_submenu_options(void *context)
+{
+    if (!context)
+    {
         FURI_LOG_E(TAG, "Context is NULL");
         return VIEW_NONE;
     }
@@ -383,8 +428,10 @@ uint32_t callback_to_submenu_options(void* context) {
     return FlipStoreViewSubmenuOptions;
 }
 
-uint32_t callback_to_firmware_list(void* context) {
-    if(!context) {
+uint32_t callback_to_firmware_list(void *context)
+{
+    if (!context)
+    {
         FURI_LOG_E(TAG, "Context is NULL");
         return VIEW_NONE;
     }
@@ -403,8 +450,10 @@ uint32_t callback_to_firmware_list(void* context) {
     return FlipStoreViewFirmwares;
 }
 
-uint32_t callback_to_app_list(void* context) {
-    if(!context) {
+uint32_t callback_to_app_list(void *context)
+{
+    if (!context)
+    {
         FURI_LOG_E(TAG, "Context is NULL");
         return VIEW_NONE;
     }
@@ -418,13 +467,16 @@ uint32_t callback_to_app_list(void* context) {
     return FlipStoreViewAppList;
 }
 
-void settings_item_selected(void* context, uint32_t index) {
-    FlipStoreApp* app = (FlipStoreApp*)context;
-    if(!app) {
+void settings_item_selected(void *context, uint32_t index)
+{
+    FlipStoreApp *app = (FlipStoreApp *)context;
+    if (!app)
+    {
         FURI_LOG_E(TAG, "FlipStoreApp is NULL");
         return;
     }
-    switch(index) {
+    switch (index)
+    {
     case 0: // Input SSID
         view_dispatcher_switch_to_view(app->view_dispatcher, FlipStoreViewTextInputSSID);
         break;
@@ -437,23 +489,28 @@ void settings_item_selected(void* context, uint32_t index) {
     }
 }
 
-void dialog_delete_callback(DialogExResult result, void* context) {
+void dialog_delete_callback(DialogExResult result, void *context)
+{
     furi_assert(context);
-    FlipStoreApp* app = (FlipStoreApp*)context;
-    if(result == DialogExResultLeft) // No
+    FlipStoreApp *app = (FlipStoreApp *)context;
+    if (result == DialogExResultLeft) // No
     {
         view_dispatcher_switch_to_view(app->view_dispatcher, FlipStoreViewAppList);
-    } else if(result == DialogExResultRight) {
+    }
+    else if (result == DialogExResultRight)
+    {
         // delete the app then return to the app list
-        if(!delete_app(
-               flip_catalog[app_selected_index].app_id, categories[flip_store_category_index])) {
+        if (!delete_app(flip_catalog[app_selected_index].app_id, categories[flip_store_category_index]))
+        {
             // pop up a message
             popup_set_header(app->popup, "[ERROR]", 0, 0, AlignLeft, AlignTop);
             popup_set_text(app->popup, "Issue deleting app.", 0, 50, AlignLeft, AlignTop);
             view_dispatcher_switch_to_view(app->view_dispatcher, FlipStoreViewPopup);
             furi_delay_ms(2000); // delay for 2 seconds
             view_dispatcher_switch_to_view(app->view_dispatcher, FlipStoreViewAppList);
-        } else {
+        }
+        else
+        {
             // pop up a message
             popup_set_header(app->popup, "[SUCCESS]", 0, 0, AlignLeft, AlignTop);
             popup_set_text(app->popup, "App deleted successfully.", 0, 50, AlignLeft, AlignTop);
@@ -464,31 +521,38 @@ void dialog_delete_callback(DialogExResult result, void* context) {
     }
 }
 
-void dialog_firmware_callback(DialogExResult result, void* context) {
+void dialog_firmware_callback(DialogExResult result, void *context)
+{
     furi_assert(context);
-    FlipStoreApp* app = (FlipStoreApp*)context;
-    if(result == DialogExResultLeft) // No
+    FlipStoreApp *app = (FlipStoreApp *)context;
+    if (result == DialogExResultLeft) // No
     {
         // switch to the firmware list
         view_dispatcher_switch_to_view(app->view_dispatcher, FlipStoreViewFirmwares);
-    } else if(result == DialogExResultRight) {
+    }
+    else if (result == DialogExResultRight)
+    {
         // download the firmware then return to the firmware list
-        view_dispatcher_switch_to_view(app->view_dispatcher, FlipStoreViewFirmwareDownload);
+        flip_store_switch_to_firmware_list(app);
     }
 }
 
-void popup_callback(void* context) {
-    FlipStoreApp* app = (FlipStoreApp*)context;
-    if(!app) {
+void popup_callback(void *context)
+{
+    FlipStoreApp *app = (FlipStoreApp *)context;
+    if (!app)
+    {
         FURI_LOG_E(TAG, "FlipStoreApp is NULL");
         return;
     }
     view_dispatcher_switch_to_view(app->view_dispatcher, FlipStoreViewSubmenu);
 }
 
-uint32_t callback_exit_app(void* context) {
+uint32_t callback_exit_app(void *context)
+{
     // Exit the application
-    if(!context) {
+    if (!context)
+    {
         FURI_LOG_E(TAG, "Context is NULL");
         return VIEW_NONE;
     }
@@ -496,16 +560,16 @@ uint32_t callback_exit_app(void* context) {
     return VIEW_NONE; // Return VIEW_NONE to exit the app
 }
 
-void callback_submenu_choices(void* context, uint32_t index) {
-    FlipStoreApp* app = (FlipStoreApp*)context;
-    if(!app) {
+void callback_submenu_choices(void *context, uint32_t index)
+{
+    FlipStoreApp *app = (FlipStoreApp *)context;
+    if (!app)
+    {
         FURI_LOG_E(TAG, "FlipStoreApp is NULL");
         return;
     }
-    switch(index) {
-    case FlipStoreSubmenuIndexMain:
-        view_dispatcher_switch_to_view(app->view_dispatcher, FlipStoreViewMain);
-        break;
+    switch (index)
+    {
     case FlipStoreSubmenuIndexAbout:
         view_dispatcher_switch_to_view(app->view_dispatcher, FlipStoreViewAbout);
         break;
@@ -521,137 +585,99 @@ void callback_submenu_choices(void* context, uint32_t index) {
         view_dispatcher_switch_to_view(app->view_dispatcher, FlipStoreViewAppList);
         break;
     case FlipStoreSubmenuIndexFirmwares:
-        if(!app->submenu_firmwares) {
+        if (!app->submenu_firmwares)
+        {
             FURI_LOG_E(TAG, "Submenu firmwares is NULL");
             return;
         }
         firmwares = firmware_alloc();
-        if(firmwares == NULL) {
+        if (firmwares == NULL)
+        {
             FURI_LOG_E(TAG, "Failed to allocate memory for firmwares");
             return;
         }
         submenu_reset(app->submenu_firmwares);
         submenu_set_header(app->submenu_firmwares, "ESP32 Firmwares");
-        for(int i = 0; i < FIRMWARE_COUNT; i++) {
-            submenu_add_item(
-                app->submenu_firmwares,
-                firmwares[i].name,
-                FlipStoreSubmenuIndexStartFirmwares + i,
-                callback_submenu_choices,
-                app);
+        for (int i = 0; i < FIRMWARE_COUNT; i++)
+        {
+            submenu_add_item(app->submenu_firmwares, firmwares[i].name, FlipStoreSubmenuIndexStartFirmwares + i, callback_submenu_choices, app);
         }
         view_dispatcher_switch_to_view(app->view_dispatcher, FlipStoreViewFirmwares);
         break;
     case FlipStoreSubmenuIndexAppListBluetooth:
         flip_store_category_index = 0;
         flip_store_app_does_exist = false;
-        view_dispatcher_switch_to_view(
-            app->view_dispatcher,
-            flip_store_handle_app_list(
-                app, FlipStoreViewAppListBluetooth, "Bluetooth", &app->submenu_app_list_bluetooth));
+        flip_store_switch_to_app_list(app);
         break;
     case FlipStoreSubmenuIndexAppListGames:
         flip_store_category_index = 1;
         flip_store_app_does_exist = false;
-        view_dispatcher_switch_to_view(
-            app->view_dispatcher,
-            flip_store_handle_app_list(
-                app, FlipStoreViewAppListGames, "Games", &app->submenu_app_list_games));
+        flip_store_switch_to_app_list(app);
         break;
     case FlipStoreSubmenuIndexAppListGPIO:
         flip_store_category_index = 2;
         flip_store_app_does_exist = false;
-        view_dispatcher_switch_to_view(
-            app->view_dispatcher,
-            flip_store_handle_app_list(
-                app, FlipStoreViewAppListGPIO, "GPIO", &app->submenu_app_list_gpio));
+        flip_store_switch_to_app_list(app);
         break;
     case FlipStoreSubmenuIndexAppListInfrared:
         flip_store_category_index = 3;
         flip_store_app_does_exist = false;
-        view_dispatcher_switch_to_view(
-            app->view_dispatcher,
-            flip_store_handle_app_list(
-                app, FlipStoreViewAppListInfrared, "Infrared", &app->submenu_app_list_infrared));
+        flip_store_switch_to_app_list(app);
         break;
     case FlipStoreSubmenuIndexAppListiButton:
         flip_store_category_index = 4;
         flip_store_app_does_exist = false;
-        view_dispatcher_switch_to_view(
-            app->view_dispatcher,
-            flip_store_handle_app_list(
-                app, FlipStoreViewAppListiButton, "iButton", &app->submenu_app_list_ibutton));
+        flip_store_switch_to_app_list(app);
         break;
     case FlipStoreSubmenuIndexAppListMedia:
         flip_store_category_index = 5;
         flip_store_app_does_exist = false;
-        view_dispatcher_switch_to_view(
-            app->view_dispatcher,
-            flip_store_handle_app_list(
-                app, FlipStoreViewAppListMedia, "Media", &app->submenu_app_list_media));
+        flip_store_switch_to_app_list(app);
         break;
     case FlipStoreSubmenuIndexAppListNFC:
         flip_store_category_index = 6;
         flip_store_app_does_exist = false;
-        view_dispatcher_switch_to_view(
-            app->view_dispatcher,
-            flip_store_handle_app_list(
-                app, FlipStoreViewAppListNFC, "NFC", &app->submenu_app_list_nfc));
+        flip_store_switch_to_app_list(app);
         break;
     case FlipStoreSubmenuIndexAppListRFID:
         flip_store_category_index = 7;
         flip_store_app_does_exist = false;
-        view_dispatcher_switch_to_view(
-            app->view_dispatcher,
-            flip_store_handle_app_list(
-                app, FlipStoreViewAppListRFID, "RFID", &app->submenu_app_list_rfid));
+        flip_store_switch_to_app_list(app);
         break;
     case FlipStoreSubmenuIndexAppListSubGHz:
         flip_store_category_index = 8;
         flip_store_app_does_exist = false;
-        view_dispatcher_switch_to_view(
-            app->view_dispatcher,
-            flip_store_handle_app_list(
-                app, FlipStoreViewAppListSubGHz, "Sub-GHz", &app->submenu_app_list_subghz));
+        flip_store_switch_to_app_list(app);
         break;
     case FlipStoreSubmenuIndexAppListTools:
         flip_store_category_index = 9;
         flip_store_app_does_exist = false;
-        view_dispatcher_switch_to_view(
-            app->view_dispatcher,
-            flip_store_handle_app_list(
-                app, FlipStoreViewAppListTools, "Tools", &app->submenu_app_list_tools));
+        flip_store_switch_to_app_list(app);
         break;
     case FlipStoreSubmenuIndexAppListUSB:
         flip_store_category_index = 10;
         flip_store_app_does_exist = false;
-        view_dispatcher_switch_to_view(
-            app->view_dispatcher,
-            flip_store_handle_app_list(
-                app, FlipStoreViewAppListUSB, "USB", &app->submenu_app_list_usb));
+        flip_store_switch_to_app_list(app);
         break;
     default:
         // Check if the index is within the firmwares list range
-        if(index >= FlipStoreSubmenuIndexStartFirmwares &&
-           index < FlipStoreSubmenuIndexStartFirmwares + 3) {
+        if (index >= FlipStoreSubmenuIndexStartFirmwares && index < FlipStoreSubmenuIndexStartFirmwares + 3)
+        {
             // Get the firmware index
             uint32_t firmware_index = index - FlipStoreSubmenuIndexStartFirmwares;
 
             // Check if the firmware index is valid
-            if((int)firmware_index >= 0 && firmware_index < FIRMWARE_COUNT) {
+            if ((int)firmware_index >= 0 && firmware_index < FIRMWARE_COUNT)
+            {
                 // Get the firmware name
                 selected_firmware_index = firmware_index;
 
                 // Switch to the firmware download view
-                dialog_ex_set_header(
-                    app->dialog_firmware,
-                    firmwares[firmware_index].name,
-                    0,
-                    0,
-                    AlignLeft,
-                    AlignTop);
+                dialog_ex_set_header(app->dialog_firmware, firmwares[firmware_index].name, 0, 0, AlignLeft, AlignTop);
                 view_dispatcher_switch_to_view(app->view_dispatcher, FlipStoreViewFirmwareDialog);
-            } else {
+            }
+            else
+            {
                 FURI_LOG_E(TAG, "Invalid firmware index");
                 popup_set_header(app->popup, "[ERROR]", 0, 0, AlignLeft, AlignTop);
                 popup_set_text(app->popup, "Issue parsing firmwarex", 0, 50, AlignLeft, AlignTop);
@@ -659,33 +685,497 @@ void callback_submenu_choices(void* context, uint32_t index) {
             }
         }
         // Check if the index is within the app list range
-        else if(
-            index >= FlipStoreSubmenuIndexStartAppList &&
-            index < FlipStoreSubmenuIndexStartAppList + MAX_APP_COUNT) {
+        else if (index >= FlipStoreSubmenuIndexStartAppList && index < FlipStoreSubmenuIndexStartAppList + MAX_APP_COUNT)
+        {
             // Get the app index
             uint32_t app_index = index - FlipStoreSubmenuIndexStartAppList;
 
             // Check if the app index is valid
-            if((int)app_index >= 0 && app_index < MAX_APP_COUNT) {
+            if ((int)app_index >= 0 && app_index < MAX_APP_COUNT)
+            {
                 // Get the app name
-                char* app_name = flip_catalog[app_index].app_name;
+                char *app_name = flip_catalog[app_index].app_name;
 
                 // Check if the app name is valid
-                if(app_name != NULL && strlen(app_name) > 0) {
+                if (app_name != NULL && strlen(app_name) > 0)
+                {
                     app_selected_index = app_index;
-                    flip_store_app_does_exist = app_exists(
-                        flip_catalog[app_selected_index].app_id,
-                        categories[flip_store_category_index]);
+                    flip_store_app_does_exist = app_exists(flip_catalog[app_selected_index].app_id, categories[flip_store_category_index]);
                     view_dispatcher_switch_to_view(app->view_dispatcher, FlipStoreViewAppInfo);
-                } else {
+                }
+                else
+                {
                     FURI_LOG_E(TAG, "Invalid app name");
                 }
-            } else {
+            }
+            else
+            {
                 FURI_LOG_E(TAG, "Invalid app index");
             }
-        } else {
+        }
+        else
+        {
             FURI_LOG_E(TAG, "Unknown submenu index");
         }
         break;
     }
 }
+
+static void flip_store_widget_set_text(char *message, Widget **widget)
+{
+    if (widget == NULL)
+    {
+        FURI_LOG_E(TAG, "flip_store_set_widget_text - widget is NULL");
+        DEV_CRASH();
+        return;
+    }
+    if (message == NULL)
+    {
+        FURI_LOG_E(TAG, "flip_store_set_widget_text - message is NULL");
+        DEV_CRASH();
+        return;
+    }
+    widget_reset(*widget);
+
+    uint32_t message_length = strlen(message); // Length of the message
+    uint32_t i = 0;                            // Index tracker
+    uint32_t formatted_index = 0;              // Tracker for where we are in the formatted message
+    char *formatted_message;                   // Buffer to hold the final formatted message
+
+    // Allocate buffer with double the message length plus one for safety
+    if (!easy_flipper_set_buffer(&formatted_message, message_length * 2 + 1))
+    {
+        return;
+    }
+
+    while (i < message_length)
+    {
+        uint32_t max_line_length = 31;                  // Maximum characters per line
+        uint32_t remaining_length = message_length - i; // Remaining characters
+        uint32_t line_length = (remaining_length < max_line_length) ? remaining_length : max_line_length;
+
+        // Check for newline character within the current segment
+        uint32_t newline_pos = i;
+        bool found_newline = false;
+        for (; newline_pos < i + line_length && newline_pos < message_length; newline_pos++)
+        {
+            if (message[newline_pos] == '\n')
+            {
+                found_newline = true;
+                break;
+            }
+        }
+
+        if (found_newline)
+        {
+            // If newline found, set line_length up to the newline
+            line_length = newline_pos - i;
+        }
+
+        // Temporary buffer to hold the current line
+        char line[32];
+        strncpy(line, message + i, line_length);
+        line[line_length] = '\0';
+
+        // If newline was found, skip it for the next iteration
+        if (found_newline)
+        {
+            i += line_length + 1; // +1 to skip the '\n' character
+        }
+        else
+        {
+            // Check if the line ends in the middle of a word and adjust accordingly
+            if (line_length == max_line_length && message[i + line_length] != '\0' && message[i + line_length] != ' ')
+            {
+                // Find the last space within the current line to avoid breaking a word
+                char *last_space = strrchr(line, ' ');
+                if (last_space != NULL)
+                {
+                    // Adjust the line_length to avoid cutting the word
+                    line_length = last_space - line;
+                    line[line_length] = '\0'; // Null-terminate at the space
+                }
+            }
+
+            // Move the index forward by the determined line_length
+            i += line_length;
+
+            // Skip any spaces at the beginning of the next line
+            while (i < message_length && message[i] == ' ')
+            {
+                i++;
+            }
+        }
+
+        // Manually copy the fixed line into the formatted_message buffer
+        for (uint32_t j = 0; j < line_length; j++)
+        {
+            formatted_message[formatted_index++] = line[j];
+        }
+
+        // Add a newline character for line spacing
+        formatted_message[formatted_index++] = '\n';
+    }
+
+    // Null-terminate the formatted_message
+    formatted_message[formatted_index] = '\0';
+
+    // Add the formatted message to the widget
+    widget_add_text_scroll_element(*widget, 0, 0, 128, 64, formatted_message);
+}
+
+void flip_store_loader_draw_callback(Canvas *canvas, void *model)
+{
+    if (!canvas || !model)
+    {
+        FURI_LOG_E(TAG, "flip_store_loader_draw_callback - canvas or model is NULL");
+        return;
+    }
+
+    SerialState http_state = fhttp.state;
+    DataLoaderModel *data_loader_model = (DataLoaderModel *)model;
+    DataState data_state = data_loader_model->data_state;
+    char *title = data_loader_model->title;
+
+    canvas_set_font(canvas, FontSecondary);
+
+    if (http_state == INACTIVE)
+    {
+        canvas_draw_str(canvas, 0, 7, "WiFi Dev Board disconnected.");
+        canvas_draw_str(canvas, 0, 17, "Please connect to the board.");
+        canvas_draw_str(canvas, 0, 32, "If your board is connected,");
+        canvas_draw_str(canvas, 0, 42, "make sure you have flashed");
+        canvas_draw_str(canvas, 0, 52, "your WiFi Devboard with the");
+        canvas_draw_str(canvas, 0, 62, "latest FlipperHTTP flash.");
+        return;
+    }
+
+    if (data_state == DataStateError || data_state == DataStateParseError)
+    {
+        flip_store_request_error(canvas);
+        return;
+    }
+
+    canvas_draw_str(canvas, 0, 7, title);
+    canvas_draw_str(canvas, 0, 17, "Loading...");
+
+    if (data_state == DataStateInitial)
+    {
+        return;
+    }
+
+    if (http_state == SENDING)
+    {
+        canvas_draw_str(canvas, 0, 27, "Fetching...");
+        return;
+    }
+
+    if (http_state == RECEIVING || data_state == DataStateRequested)
+    {
+        canvas_draw_str(canvas, 0, 27, "Receiving...");
+        return;
+    }
+
+    if (http_state == IDLE && data_state == DataStateReceived)
+    {
+        canvas_draw_str(canvas, 0, 27, "Processing...");
+        return;
+    }
+
+    if (http_state == IDLE && data_state == DataStateParsed)
+    {
+        canvas_draw_str(canvas, 0, 27, "Processed...");
+        return;
+    }
+}
+
+static void flip_store_loader_process_callback(void *context)
+{
+    if (context == NULL)
+    {
+        FURI_LOG_E(TAG, "flip_store_loader_process_callback - context is NULL");
+        DEV_CRASH();
+        return;
+    }
+
+    FlipStoreApp *app = (FlipStoreApp *)context;
+    View *view = app->view_loader;
+
+    DataState current_data_state;
+    with_view_model(view, DataLoaderModel * model, { current_data_state = model->data_state; }, false);
+
+    if (current_data_state == DataStateInitial)
+    {
+        with_view_model(
+            view,
+            DataLoaderModel * model,
+            {
+                model->data_state = DataStateRequested;
+                DataLoaderFetch fetch = model->fetcher;
+                if (fetch == NULL)
+                {
+                    FURI_LOG_E(TAG, "Model doesn't have Fetch function assigned.");
+                    model->data_state = DataStateError;
+                    return;
+                }
+
+                // Clear any previous responses
+                strncpy(fhttp.last_response, "", 1);
+                bool request_status = fetch(model);
+                if (!request_status)
+                {
+                    model->data_state = DataStateError;
+                }
+            },
+            true);
+    }
+    else if (current_data_state == DataStateRequested || current_data_state == DataStateError)
+    {
+        if (fhttp.state == IDLE && fhttp.last_response != NULL)
+        {
+            if (strstr(fhttp.last_response, "[PONG]") != NULL)
+            {
+                FURI_LOG_DEV(TAG, "PONG received.");
+            }
+            else if (strncmp(fhttp.last_response, "[SUCCESS]", 9) == 0)
+            {
+                FURI_LOG_DEV(TAG, "SUCCESS received. %s", fhttp.last_response ? fhttp.last_response : "NULL");
+            }
+            else if (strncmp(fhttp.last_response, "[ERROR]", 9) == 0)
+            {
+                FURI_LOG_DEV(TAG, "ERROR received. %s", fhttp.last_response ? fhttp.last_response : "NULL");
+            }
+            else if (strlen(fhttp.last_response) == 0)
+            {
+                // Still waiting on response
+            }
+            else
+            {
+                with_view_model(view, DataLoaderModel * model, { model->data_state = DataStateReceived; }, true);
+            }
+        }
+        else if (fhttp.state == SENDING || fhttp.state == RECEIVING)
+        {
+            // continue waiting
+        }
+        else if (fhttp.state == INACTIVE)
+        {
+            // inactive. try again
+        }
+        else if (fhttp.state == ISSUE)
+        {
+            with_view_model(view, DataLoaderModel * model, { model->data_state = DataStateError; }, true);
+        }
+        else
+        {
+            FURI_LOG_DEV(TAG, "Unexpected state: %d lastresp: %s", fhttp.state, fhttp.last_response ? fhttp.last_response : "NULL");
+            DEV_CRASH();
+        }
+    }
+    else if (current_data_state == DataStateReceived)
+    {
+        with_view_model(
+            view,
+            DataLoaderModel * model,
+            {
+                char *data_text;
+                if (model->parser == NULL)
+                {
+                    data_text = NULL;
+                    FURI_LOG_DEV(TAG, "Parser is NULL");
+                    DEV_CRASH();
+                }
+                else
+                {
+                    data_text = model->parser(model);
+                }
+                FURI_LOG_DEV(TAG, "Parsed data: %s\r\ntext: %s", fhttp.last_response ? fhttp.last_response : "NULL", data_text ? data_text : "NULL");
+                model->data_text = data_text;
+                if (data_text == NULL)
+                {
+                    model->data_state = DataStateParseError;
+                }
+                else
+                {
+                    model->data_state = DataStateParsed;
+                }
+            },
+            true);
+    }
+    else if (current_data_state == DataStateParsed)
+    {
+        with_view_model(
+            view,
+            DataLoaderModel * model,
+            {
+                if (++model->request_index < model->request_count)
+                {
+                    model->data_state = DataStateInitial;
+                }
+                else
+                {
+                    flip_store_widget_set_text(model->data_text != NULL ? model->data_text : "Empty result", &app->widget_result);
+                    if (model->data_text != NULL)
+                    {
+                        free(model->data_text);
+                        model->data_text = NULL;
+                    }
+                    view_set_previous_callback(widget_get_view(app->widget_result), model->back_callback);
+                    view_dispatcher_switch_to_view(app->view_dispatcher, FlipStoreViewWidgetResult);
+                }
+            },
+            true);
+    }
+}
+
+static void flip_store_loader_timer_callback(void *context)
+{
+    if (context == NULL)
+    {
+        FURI_LOG_E(TAG, "flip_store_loader_timer_callback - context is NULL");
+        DEV_CRASH();
+        return;
+    }
+    FlipStoreApp *app = (FlipStoreApp *)context;
+    view_dispatcher_send_custom_event(app->view_dispatcher, FlipStoreCustomEventProcess);
+}
+
+static void flip_store_loader_on_enter(void *context)
+{
+    if (context == NULL)
+    {
+        FURI_LOG_E(TAG, "flip_store_loader_on_enter - context is NULL");
+        DEV_CRASH();
+        return;
+    }
+    FlipStoreApp *app = (FlipStoreApp *)context;
+    View *view = app->view_loader;
+    with_view_model(
+        view,
+        DataLoaderModel * model,
+        {
+            view_set_previous_callback(view, model->back_callback);
+            if (model->timer == NULL)
+            {
+                model->timer = furi_timer_alloc(flip_store_loader_timer_callback, FuriTimerTypePeriodic, app);
+            }
+            furi_timer_start(model->timer, 250);
+        },
+        true);
+}
+
+static void flip_store_loader_on_exit(void *context)
+{
+    if (context == NULL)
+    {
+        FURI_LOG_E(TAG, "flip_store_loader_on_exit - context is NULL");
+        DEV_CRASH();
+        return;
+    }
+    FlipStoreApp *app = (FlipStoreApp *)context;
+    View *view = app->view_loader;
+    with_view_model(
+        view,
+        DataLoaderModel * model,
+        {
+            if (model->timer)
+            {
+                furi_timer_stop(model->timer);
+            }
+        },
+        false);
+}
+
+void flip_store_loader_init(View *view)
+{
+    if (view == NULL)
+    {
+        FURI_LOG_E(TAG, "flip_store_loader_init - view is NULL");
+        DEV_CRASH();
+        return;
+    }
+    view_allocate_model(view, ViewModelTypeLocking, sizeof(DataLoaderModel));
+    view_set_enter_callback(view, flip_store_loader_on_enter);
+    view_set_exit_callback(view, flip_store_loader_on_exit);
+}
+
+void flip_store_loader_free_model(View *view)
+{
+    if (view == NULL)
+    {
+        FURI_LOG_E(TAG, "flip_store_loader_free_model - view is NULL");
+        DEV_CRASH();
+        return;
+    }
+    with_view_model(
+        view,
+        DataLoaderModel * model,
+        {
+            if (model->timer)
+            {
+                furi_timer_free(model->timer);
+                model->timer = NULL;
+            }
+            if (model->parser_context)
+            {
+                free(model->parser_context);
+                model->parser_context = NULL;
+            }
+        },
+        false);
+}
+
+bool flip_store_custom_event_callback(void *context, uint32_t index)
+{
+    if (context == NULL)
+    {
+        FURI_LOG_E(TAG, "flip_store_custom_event_callback - context is NULL");
+        DEV_CRASH();
+        return false;
+    }
+
+    switch (index)
+    {
+    case FlipStoreCustomEventProcess:
+        flip_store_loader_process_callback(context);
+        return true;
+    default:
+        FURI_LOG_DEV(TAG, "flip_store_custom_event_callback. Unknown index: %ld", index);
+        return false;
+    }
+}
+
+void flip_store_generic_switch_to_view(FlipStoreApp *app, char *title, DataLoaderFetch fetcher, DataLoaderParser parser, size_t request_count, ViewNavigationCallback back, uint32_t view_id)
+{
+    if (app == NULL)
+    {
+        FURI_LOG_E(TAG, "flip_store_generic_switch_to_view - app is NULL");
+        DEV_CRASH();
+        return;
+    }
+
+    View *view = app->view_loader;
+    if (view == NULL)
+    {
+        FURI_LOG_E(TAG, "flip_store_generic_switch_to_view - view is NULL");
+        DEV_CRASH();
+        return;
+    }
+
+    with_view_model(
+        view,
+        DataLoaderModel * model,
+        {
+            model->title = title;
+            model->fetcher = fetcher;
+            model->parser = parser;
+            model->request_index = 0;
+            model->request_count = request_count;
+            model->back_callback = back;
+            model->data_state = DataStateInitial;
+            model->data_text = NULL;
+        },
+        true);
+
+    view_dispatcher_switch_to_view(app->view_dispatcher, view_id);
+}

+ 58 - 19
flip_store/callback/flip_store_callback.h

@@ -15,38 +15,77 @@
 extern bool flip_store_app_does_exist;
 extern uint32_t selected_firmware_index;
 
-// Callback for drawing the main screen
-void flip_store_view_draw_callback_main(Canvas* canvas, void* model);
+// Function to draw the description on the canvas with word wrapping
+void draw_description(Canvas *canvas, const char *user_message, int x, int y);
 
-void flip_store_view_draw_callback_firmware(Canvas* canvas, void* model);
+void flip_store_view_draw_callback_app_list(Canvas *canvas, void *model);
 
-// Function to draw the description on the canvas with word wrapping
-void draw_description(Canvas* canvas, const char* user_message, int x, int y);
+bool flip_store_input_callback(InputEvent *event, void *context);
+
+void flip_store_text_updated_ssid(void *context);
+
+void flip_store_text_updated_pass(void *context);
+
+uint32_t callback_to_submenu(void *context);
+
+uint32_t callback_to_submenu_options(void *context);
+
+uint32_t callback_to_firmware_list(void *context);
 
-void flip_store_view_draw_callback_app_list(Canvas* canvas, void* model);
+uint32_t callback_to_app_list(void *context);
 
-bool flip_store_input_callback(InputEvent* event, void* context);
+void settings_item_selected(void *context, uint32_t index);
 
-void flip_store_text_updated_ssid(void* context);
+void dialog_delete_callback(DialogExResult result, void *context);
+void dialog_firmware_callback(DialogExResult result, void *context);
 
-void flip_store_text_updated_pass(void* context);
+void popup_callback(void *context);
 
-uint32_t callback_to_submenu(void* context);
+uint32_t callback_exit_app(void *context);
+void callback_submenu_choices(void *context, uint32_t index);
 
-uint32_t callback_to_submenu_options(void* context);
+// Add edits by Derek Jamison
+typedef enum DataState DataState;
+enum DataState
+{
+    DataStateInitial,
+    DataStateRequested,
+    DataStateReceived,
+    DataStateParsed,
+    DataStateParseError,
+    DataStateError,
+};
 
-uint32_t callback_to_firmware_list(void* context);
+typedef enum FlipStoreCustomEvent FlipStoreCustomEvent;
+enum FlipStoreCustomEvent
+{
+    FlipStoreCustomEventProcess,
+};
 
-uint32_t callback_to_app_list(void* context);
+typedef struct DataLoaderModel DataLoaderModel;
+typedef bool (*DataLoaderFetch)(DataLoaderModel *model);
+typedef char *(*DataLoaderParser)(DataLoaderModel *model);
+struct DataLoaderModel
+{
+    char *title;
+    char *data_text;
+    DataState data_state;
+    DataLoaderFetch fetcher;
+    DataLoaderParser parser;
+    void *parser_context;
+    size_t request_index;
+    size_t request_count;
+    ViewNavigationCallback back_callback;
+    FuriTimer *timer;
+};
+void flip_store_generic_switch_to_view(FlipStoreApp *app, char *title, DataLoaderFetch fetcher, DataLoaderParser parser, size_t request_count, ViewNavigationCallback back, uint32_t view_id);
 
-void settings_item_selected(void* context, uint32_t index);
+void flip_store_loader_draw_callback(Canvas *canvas, void *model);
 
-void dialog_delete_callback(DialogExResult result, void* context);
-void dialog_firmware_callback(DialogExResult result, void* context);
+void flip_store_loader_init(View *view);
 
-void popup_callback(void* context);
+void flip_store_loader_free_model(View *view);
 
-uint32_t callback_exit_app(void* context);
-void callback_submenu_choices(void* context, uint32_t index);
+bool flip_store_custom_event_callback(void *context, uint32_t index);
 
 #endif // FLIP_STORE_CALLBACK_H

+ 198 - 139
flip_store/easy_flipper/easy_flipper.c

@@ -5,9 +5,11 @@
  * @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 +23,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 +51,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 +97,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 +129,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 +168,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 +202,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 +243,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 +282,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 +328,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 +386,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 +466,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 +523,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 +553,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;
-}
+}

+ 61 - 61
flip_store/easy_flipper/easy_flipper.h

@@ -28,14 +28,14 @@
  * @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
@@ -47,13 +47,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
@@ -62,7 +62,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
@@ -75,11 +75,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
@@ -92,10 +92,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
@@ -107,11 +107,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
@@ -125,12 +125,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
@@ -141,15 +141,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
@@ -160,15 +160,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
@@ -190,21 +190,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
@@ -223,18 +223,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
@@ -245,10 +245,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
@@ -256,6 +256,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

+ 44 - 50
flip_store/firmwares/flip_store_firmwares.c

@@ -1,6 +1,6 @@
 #include <firmwares/flip_store_firmwares.h>
 
-Firmware* firmwares = NULL;
+Firmware *firmwares = NULL;
 bool sent_firmware_request = false;
 bool sent_firmware_request_2 = false;
 bool sent_firmware_request_3 = false;
@@ -13,24 +13,32 @@ bool firmware_download_success = false;
 bool firmware_download_success_2 = false;
 bool firmware_download_success_3 = false;
 
-Firmware* firmware_alloc() {
-    Firmware* fw = (Firmware*)malloc(FIRMWARE_COUNT * sizeof(Firmware));
-    if(!fw) {
+Firmware *firmware_alloc()
+{
+    Firmware *fw = (Firmware *)malloc(FIRMWARE_COUNT * sizeof(Firmware));
+    if (!fw)
+    {
         FURI_LOG_E(TAG, "Failed to allocate memory for Firmware");
         return NULL;
     }
-    for(int i = 0; i < FIRMWARE_COUNT; i++) {
-        if(fw[i].name == NULL) {
-            fw[i].name = (char*)malloc(16);
-            if(!fw[i].name) {
+    for (int i = 0; i < FIRMWARE_COUNT; i++)
+    {
+        if (fw[i].name == NULL)
+        {
+            fw[i].name = (char *)malloc(16);
+            if (!fw[i].name)
+            {
                 FURI_LOG_E(TAG, "Failed to allocate memory for Firmware name");
                 return NULL;
             }
         }
-        for(int j = 0; j < FIRMWARE_LINKS; j++) {
-            if(fw[i].links[j] == NULL) {
-                fw[i].links[j] = (char*)malloc(256);
-                if(!fw[i].links[j]) {
+        for (int j = 0; j < FIRMWARE_LINKS; j++)
+        {
+            if (fw[i].links[j] == NULL)
+            {
+                fw[i].links[j] = (char *)malloc(256);
+                if (!fw[i].links[j])
+                {
                     FURI_LOG_E(TAG, "Failed to allocate memory for Firmware links");
                     return NULL;
                 }
@@ -40,30 +48,21 @@ Firmware* firmware_alloc() {
 
     // Black Magic
     fw[0].name = "Black Magic";
-    fw[0].links[0] =
-        "https://raw.githubusercontent.com/FZEEFlasher/fzeeflasher.github.io/main/resources/STATIC/BM/bootloader.bin";
-    fw[0].links[1] =
-        "https://raw.githubusercontent.com/FZEEFlasher/fzeeflasher.github.io/main/resources/STATIC/BM/partition-table.bin";
-    fw[0].links[2] =
-        "https://raw.githubusercontent.com/FZEEFlasher/fzeeflasher.github.io/main/resources/STATIC/BM/blackmagic.bin";
+    fw[0].links[0] = "https://raw.githubusercontent.com/FZEEFlasher/fzeeflasher.github.io/main/resources/STATIC/BM/bootloader.bin";
+    fw[0].links[1] = "https://raw.githubusercontent.com/FZEEFlasher/fzeeflasher.github.io/main/resources/STATIC/BM/partition-table.bin";
+    fw[0].links[2] = "https://raw.githubusercontent.com/FZEEFlasher/fzeeflasher.github.io/main/resources/STATIC/BM/blackmagic.bin";
 
     // FlipperHTTP
     fw[1].name = "FlipperHTTP";
-    fw[1].links[0] =
-        "https://raw.githubusercontent.com/jblanked/FlipperHTTP/main/WiFi%20Developer%20Board%20(ESP32S2)/flipper_http_bootloader.bin";
-    fw[1].links[1] =
-        "https://raw.githubusercontent.com/jblanked/FlipperHTTP/main/WiFi%20Developer%20Board%20(ESP32S2)/flipper_http_firmware_a.bin";
-    fw[1].links[2] =
-        "https://raw.githubusercontent.com/jblanked/FlipperHTTP/main/WiFi%20Developer%20Board%20(ESP32S2)/flipper_http_partitions.bin";
+    fw[1].links[0] = "https://raw.githubusercontent.com/jblanked/FlipperHTTP/main/WiFi%20Developer%20Board%20(ESP32S2)/flipper_http_bootloader.bin";
+    fw[1].links[1] = "https://raw.githubusercontent.com/jblanked/FlipperHTTP/main/WiFi%20Developer%20Board%20(ESP32S2)/flipper_http_firmware_a.bin";
+    fw[1].links[2] = "https://raw.githubusercontent.com/jblanked/FlipperHTTP/main/WiFi%20Developer%20Board%20(ESP32S2)/flipper_http_partitions.bin";
 
     // Marauder
     fw[2].name = "Marauder";
-    fw[2].links[0] =
-        "https://raw.githubusercontent.com/FZEEFlasher/fzeeflasher.github.io/main/resources/STATIC/M/FLIPDEV/esp32_marauder.ino.bootloader.bin";
-    fw[2].links[1] =
-        "https://raw.githubusercontent.com/FZEEFlasher/fzeeflasher.github.io/main/resources/STATIC/M/FLIPDEV/esp32_marauder.ino.partitions.bin";
-    fw[2].links[2] =
-        "https://raw.githubusercontent.com/FZEEFlasher/fzeeflasher.github.io/main/resources/CURRENT/esp32_marauder_v1_0_0_20240626_flipper.bin";
+    fw[2].links[0] = "https://raw.githubusercontent.com/FZEEFlasher/fzeeflasher.github.io/main/resources/STATIC/M/FLIPDEV/esp32_marauder.ino.bootloader.bin";
+    fw[2].links[1] = "https://raw.githubusercontent.com/FZEEFlasher/fzeeflasher.github.io/main/resources/STATIC/M/FLIPDEV/esp32_marauder.ino.partitions.bin";
+    fw[2].links[2] = "https://raw.githubusercontent.com/FZEEFlasher/fzeeflasher.github.io/main/resources/CURRENT/esp32_marauder_v1_1_0_20241128_flipper.bin";
 
     // https://api.github.com/repos/FZEEFlasher/fzeeflasher.github.io/contents/resources/STATIC/BM/bootloader.bin
     // https://api.github.com/repos/FZEEFlasher/fzeeflasher.github.io/contents/resources/STATIC/BM/partition-table.bin
@@ -78,39 +77,34 @@ Firmware* firmware_alloc() {
     // https://api.github.com/repos/FZEEFlasher/fzeeflasher.github.io/contents/resources/CURRENT/esp32_marauder_v1_0_0_20240626_flipper.bin
     return fw;
 }
-void firmware_free() {
-    if(firmwares) {
+void firmware_free()
+{
+    if (firmwares)
+    {
         free(firmwares);
     }
 }
 
-bool flip_store_get_firmware_file(char* link, char* name, char* filename) {
-    if(fhttp.state == INACTIVE) {
+bool flip_store_get_firmware_file(char *link, char *name, char *filename)
+{
+    if (fhttp.state == INACTIVE)
+    {
         return false;
     }
-    Storage* storage = furi_record_open(RECORD_STORAGE);
+    Storage *storage = furi_record_open(RECORD_STORAGE);
     char directory_path[64];
-    snprintf(
-        directory_path, sizeof(directory_path), STORAGE_EXT_PATH_PREFIX "/apps_data/esp_flasher");
+    snprintf(directory_path, sizeof(directory_path), STORAGE_EXT_PATH_PREFIX "/apps_data/esp_flasher");
     storage_common_mkdir(storage, directory_path);
-    snprintf(
-        directory_path,
-        sizeof(directory_path),
-        STORAGE_EXT_PATH_PREFIX "/apps_data/esp_flasher/%s",
-        firmwares[selected_firmware_index].name);
+    snprintf(directory_path, sizeof(directory_path), STORAGE_EXT_PATH_PREFIX "/apps_data/esp_flasher/%s", firmwares[selected_firmware_index].name);
     storage_common_mkdir(storage, directory_path);
-    snprintf(
-        fhttp.file_path,
-        sizeof(fhttp.file_path),
-        STORAGE_EXT_PATH_PREFIX "/apps_data/esp_flasher/%s/%s",
-        name,
-        filename);
+    snprintf(fhttp.file_path, sizeof(fhttp.file_path), STORAGE_EXT_PATH_PREFIX "/apps_data/esp_flasher/%s/%s", name, filename);
     fhttp.save_received_data = false;
     fhttp.is_bytes_request = true;
-    char* headers = jsmn("Content-Type", "application/octet-stream");
+    char *headers = jsmn("Content-Type", "application/octet-stream");
     bool sent_request = flipper_http_get_request_bytes(link, headers);
     free(headers);
-    if(sent_request) {
+    if (sent_request)
+    {
         fhttp.state = RECEIVING;
         return true;
     }

+ 8 - 7
flip_store/firmwares/flip_store_firmwares.h

@@ -5,17 +5,18 @@
 #include <flip_storage/flip_store_storage.h>
 #include <callback/flip_store_callback.h>
 
-typedef struct {
-    char* name;
-    char* links[FIRMWARE_LINKS];
+typedef struct
+{
+    char *name;
+    char *links[FIRMWARE_LINKS];
 } Firmware;
 
-extern Firmware* firmwares;
-Firmware* firmware_alloc();
+extern Firmware *firmwares;
+Firmware *firmware_alloc();
 void firmware_free();
 
 // download and waiting process
-bool flip_store_get_firmware_file(char* link, char* name, char* filename);
+bool flip_store_get_firmware_file(char *link, char *name, char *filename);
 
 extern bool sent_firmware_request;
 extern bool sent_firmware_request_2;
@@ -27,4 +28,4 @@ extern bool firmware_download_success;
 extern bool firmware_download_success_2;
 extern bool firmware_download_success_3;
 
-#endif // FLIP_STORE_FIRMWARES_H
+#endif // FLIP_STORE_FIRMWARES_H

+ 117 - 79
flip_store/flip_storage/flip_store_storage.c

@@ -1,18 +1,21 @@
 #include "flip_storage/flip_store_storage.h"
 
-void save_settings(const char* ssid, const char* password) {
+void save_settings(
+    const char *ssid,
+    const char *password)
+{
     // Create the directory for saving settings
     char directory_path[128];
-    snprintf(
-        directory_path, sizeof(directory_path), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_store");
+    snprintf(directory_path, sizeof(directory_path), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_store");
 
     // Create the directory
-    Storage* storage = furi_record_open(RECORD_STORAGE);
+    Storage *storage = furi_record_open(RECORD_STORAGE);
     storage_common_mkdir(storage, directory_path);
 
     // Open the settings file
-    File* file = storage_file_alloc(storage);
-    if(!storage_file_open(file, SETTINGS_PATH, FSAM_WRITE, FSOM_CREATE_ALWAYS)) {
+    File *file = storage_file_alloc(storage);
+    if (!storage_file_open(file, SETTINGS_PATH, FSAM_WRITE, FSOM_CREATE_ALWAYS))
+    {
         FURI_LOG_E(TAG, "Failed to open settings file for writing: %s", SETTINGS_PATH);
         storage_file_free(file);
         furi_record_close(RECORD_STORAGE);
@@ -21,15 +24,17 @@ void save_settings(const char* ssid, const char* password) {
 
     // Save the ssid length and data
     size_t ssid_length = strlen(ssid) + 1; // Include null terminator
-    if(storage_file_write(file, &ssid_length, sizeof(size_t)) != sizeof(size_t) ||
-       storage_file_write(file, ssid, ssid_length) != ssid_length) {
+    if (storage_file_write(file, &ssid_length, sizeof(size_t)) != sizeof(size_t) ||
+        storage_file_write(file, ssid, ssid_length) != ssid_length)
+    {
         FURI_LOG_E(TAG, "Failed to write SSID");
     }
 
     // Save the password length and data
     size_t password_length = strlen(password) + 1; // Include null terminator
-    if(storage_file_write(file, &password_length, sizeof(size_t)) != sizeof(size_t) ||
-       storage_file_write(file, password, password_length) != password_length) {
+    if (storage_file_write(file, &password_length, sizeof(size_t)) != sizeof(size_t) ||
+        storage_file_write(file, password, password_length) != password_length)
+    {
         FURI_LOG_E(TAG, "Failed to write password");
     }
 
@@ -38,11 +43,17 @@ void save_settings(const char* ssid, const char* password) {
     furi_record_close(RECORD_STORAGE);
 }
 
-bool load_settings(char* ssid, size_t ssid_size, char* password, size_t password_size) {
-    Storage* storage = furi_record_open(RECORD_STORAGE);
-    File* file = storage_file_alloc(storage);
-
-    if(!storage_file_open(file, SETTINGS_PATH, FSAM_READ, FSOM_OPEN_EXISTING)) {
+bool load_settings(
+    char *ssid,
+    size_t ssid_size,
+    char *password,
+    size_t password_size)
+{
+    Storage *storage = furi_record_open(RECORD_STORAGE);
+    File *file = storage_file_alloc(storage);
+
+    if (!storage_file_open(file, SETTINGS_PATH, FSAM_READ, FSOM_OPEN_EXISTING))
+    {
         FURI_LOG_E(TAG, "Failed to open settings file for reading: %s", SETTINGS_PATH);
         storage_file_free(file);
         furi_record_close(RECORD_STORAGE);
@@ -51,8 +62,9 @@ bool load_settings(char* ssid, size_t ssid_size, char* password, size_t password
 
     // Load the ssid
     size_t ssid_length;
-    if(storage_file_read(file, &ssid_length, sizeof(size_t)) != sizeof(size_t) ||
-       ssid_length > ssid_size || storage_file_read(file, ssid, ssid_length) != ssid_length) {
+    if (storage_file_read(file, &ssid_length, sizeof(size_t)) != sizeof(size_t) || ssid_length > ssid_size ||
+        storage_file_read(file, ssid, ssid_length) != ssid_length)
+    {
         FURI_LOG_E(TAG, "Failed to read SSID");
         storage_file_close(file);
         storage_file_free(file);
@@ -63,9 +75,9 @@ bool load_settings(char* ssid, size_t ssid_size, char* password, size_t password
 
     // Load the password
     size_t password_length;
-    if(storage_file_read(file, &password_length, sizeof(size_t)) != sizeof(size_t) ||
-       password_length > password_size ||
-       storage_file_read(file, password, password_length) != password_length) {
+    if (storage_file_read(file, &password_length, sizeof(size_t)) != sizeof(size_t) || password_length > password_size ||
+        storage_file_read(file, password, password_length) != password_length)
+    {
         FURI_LOG_E(TAG, "Failed to read password");
         storage_file_close(file);
         storage_file_free(file);
@@ -82,19 +94,16 @@ bool load_settings(char* ssid, size_t ssid_size, char* password, size_t password
 }
 
 // future implenetation because we need the app category
-bool delete_app(const char* app_id, const char* app_category) {
+bool delete_app(const char *app_id, const char *app_category)
+{
     // Create the directory for saving settings
     char directory_path[128];
-    snprintf(
-        directory_path,
-        sizeof(directory_path),
-        STORAGE_EXT_PATH_PREFIX "/apps/%s/%s",
-        app_category,
-        app_id);
+    snprintf(directory_path, sizeof(directory_path), STORAGE_EXT_PATH_PREFIX "/apps/%s/%s", app_category, app_id);
 
     // Create the directory
-    Storage* storage = furi_record_open(RECORD_STORAGE);
-    if(!storage_simply_remove_recursive(storage, directory_path)) {
+    Storage *storage = furi_record_open(RECORD_STORAGE);
+    if (!storage_simply_remove_recursive(storage, directory_path))
+    {
         FURI_LOG_E(TAG, "Failed to delete app: %s", app_id);
         furi_record_close(RECORD_STORAGE);
         return false;
@@ -104,17 +113,13 @@ bool delete_app(const char* app_id, const char* app_category) {
     return true;
 }
 
-bool app_exists(const char* app_id, const char* app_category) {
+bool app_exists(const char *app_id, const char *app_category)
+{
     // Check if the app exists
     char directory_path[128];
-    snprintf(
-        directory_path,
-        sizeof(directory_path),
-        STORAGE_EXT_PATH_PREFIX "/apps/%s/%s.fap",
-        app_category,
-        app_id);
-
-    Storage* storage = furi_record_open(RECORD_STORAGE);
+    snprintf(directory_path, sizeof(directory_path), STORAGE_EXT_PATH_PREFIX "/apps/%s/%s.fap", app_category, app_id);
+
+    Storage *storage = furi_record_open(RECORD_STORAGE);
     bool exists = storage_common_exists(storage, directory_path);
     furi_record_close(RECORD_STORAGE);
 
@@ -122,13 +127,10 @@ bool app_exists(const char* app_id, const char* app_category) {
 }
 
 // Function to parse JSON incrementally from a file
-bool parse_json_incrementally(
-    const char* file_path,
-    const char* target_key,
-    char* value_buffer,
-    size_t value_buffer_size) {
-    Storage* _storage = NULL;
-    File* _file = NULL;
+bool parse_json_incrementally(const char *file_path, const char *target_key, char *value_buffer, size_t value_buffer_size)
+{
+    Storage *_storage = NULL;
+    File *_file = NULL;
     char buffer[BUFFER_SIZE];
     size_t bytes_read;
     bool key_found = false;
@@ -142,71 +144,93 @@ bool parse_json_incrementally(
 
     // Open storage and file
     _storage = furi_record_open(RECORD_STORAGE);
-    if(!_storage) {
+    if (!_storage)
+    {
         FURI_LOG_E("JSON_PARSE", "Failed to open storage.");
         return false;
     }
 
     _file = storage_file_alloc(_storage);
-    if(!_file) {
+    if (!_file)
+    {
         FURI_LOG_E("JSON_PARSE", "Failed to allocate file.");
         furi_record_close(RECORD_STORAGE);
         return false;
     }
 
-    if(!storage_file_open(_file, file_path, FSAM_READ, FSOM_OPEN_EXISTING)) {
+    if (!storage_file_open(_file, file_path, FSAM_READ, FSOM_OPEN_EXISTING))
+    {
         FURI_LOG_E("JSON_PARSE", "Failed to open JSON file for reading.");
         goto cleanup;
     }
 
-    while((bytes_read = storage_file_read(_file, buffer, BUFFER_SIZE)) > 0) {
-        for(size_t i = 0; i < bytes_read; ++i) {
+    while ((bytes_read = storage_file_read(_file, buffer, BUFFER_SIZE)) > 0)
+    {
+        for (size_t i = 0; i < bytes_read; ++i)
+        {
             char c = buffer[i];
 
-            if(is_escaped) {
+            if (is_escaped)
+            {
                 is_escaped = false;
-                if(reading_key) {
-                    if(key_index < MAX_KEY_LENGTH - 1) {
+                if (reading_key)
+                {
+                    if (key_index < MAX_KEY_LENGTH - 1)
+                    {
                         current_key[key_index++] = c;
                     }
-                } else if(reading_value) {
-                    if(value_index < value_buffer_size - 1) {
+                }
+                else if (reading_value)
+                {
+                    if (value_index < value_buffer_size - 1)
+                    {
                         value_buffer[value_index++] = c;
                     }
                 }
                 continue;
             }
 
-            if(c == '\\') {
+            if (c == '\\')
+            {
                 is_escaped = true;
                 continue;
             }
 
-            if(c == '\"') {
+            if (c == '\"')
+            {
                 in_string = !in_string;
 
-                if(in_string) {
+                if (in_string)
+                {
                     // Start of a string
-                    if(!reading_key && !reading_value) {
+                    if (!reading_key && !reading_value)
+                    {
                         // Possible start of a key
                         reading_key = true;
                         key_index = 0;
                         current_key[0] = '\0';
                     }
-                } else {
+                }
+                else
+                {
                     // End of a string
-                    if(reading_key) {
+                    if (reading_key)
+                    {
                         reading_key = false;
                         current_key[key_index] = '\0';
 
-                        if(strcmp(current_key, target_key) == 0) {
+                        if (strcmp(current_key, target_key) == 0)
+                        {
                             key_found = true;
                         }
-                    } else if(reading_value) {
+                    }
+                    else if (reading_value)
+                    {
                         reading_value = false;
                         value_buffer[value_index] = '\0';
 
-                        if(key_found) {
+                        if (key_found)
+                        {
                             // Found the target value
                             goto success;
                         }
@@ -215,33 +239,43 @@ bool parse_json_incrementally(
                 continue;
             }
 
-            if(in_string) {
-                if(reading_key) {
-                    if(key_index < MAX_KEY_LENGTH - 1) {
+            if (in_string)
+            {
+                if (reading_key)
+                {
+                    if (key_index < MAX_KEY_LENGTH - 1)
+                    {
                         current_key[key_index++] = c;
                     }
-                } else if(reading_value) {
-                    if(value_index < value_buffer_size - 1) {
+                }
+                else if (reading_value)
+                {
+                    if (value_index < value_buffer_size - 1)
+                    {
                         value_buffer[value_index++] = c;
                     }
                 }
                 continue;
             }
 
-            if(c == ':' && key_found && !reading_value) {
+            if (c == ':' && key_found && !reading_value)
+            {
                 // After colon, start reading the value
                 // Skip whitespace and possible opening quote
-                while(i + 1 < bytes_read &&
-                      (buffer[i + 1] == ' ' || buffer[i + 1] == '\n' || buffer[i + 1] == '\r')) {
+                while (i + 1 < bytes_read && (buffer[i + 1] == ' ' || buffer[i + 1] == '\n' || buffer[i + 1] == '\r'))
+                {
                     i++;
                 }
 
-                if(i + 1 < bytes_read && buffer[i + 1] == '\"') {
+                if (i + 1 < bytes_read && buffer[i + 1] == '\"')
+                {
                     i++; // Move to the quote
                     in_string = true;
                     reading_value = true;
                     value_index = 0;
-                } else {
+                }
+                else
+                {
                     // Handle non-string values (e.g., numbers, booleans)
                     reading_value = true;
                     value_index = 0;
@@ -249,12 +283,14 @@ bool parse_json_incrementally(
                 continue;
             }
 
-            if(reading_value && (c == ',' || c == '}' || c == ']')) {
+            if (reading_value && (c == ',' || c == '}' || c == ']'))
+            {
                 // End of the value
                 reading_value = false;
                 value_buffer[value_index] = '\0';
 
-                if(key_found) {
+                if (key_found)
+                {
                     // Found the target value
                     goto success;
                 }
@@ -270,11 +306,13 @@ success:
     return key_found;
 
 cleanup:
-    if(_file) {
+    if (_file)
+    {
         storage_file_free(_file);
     }
-    if(_storage) {
+    if (_storage)
+    {
         furi_record_close(RECORD_STORAGE);
     }
     return false;
-}
+}

+ 15 - 13
flip_store/flip_storage/flip_store_storage.h

@@ -5,24 +5,26 @@
 #include <storage/storage.h>
 #include <flip_store.h>
 
-#define SETTINGS_PATH    STORAGE_EXT_PATH_PREFIX "/apps_data/flip_store/settings.bin"
-#define BUFFER_SIZE      64
-#define MAX_KEY_LENGTH   16
+#define SETTINGS_PATH STORAGE_EXT_PATH_PREFIX "/apps_data/flip_store/settings.bin"
+#define BUFFER_SIZE 64
+#define MAX_KEY_LENGTH 16
 #define MAX_VALUE_LENGTH 100
 
-void save_settings(const char* ssid, const char* password);
+void save_settings(
+    const char *ssid,
+    const char *password);
 
-bool load_settings(char* ssid, size_t ssid_size, char* password, size_t password_size);
+bool load_settings(
+    char *ssid,
+    size_t ssid_size,
+    char *password,
+    size_t password_size);
 
-bool delete_app(const char* app_id, const char* app_category);
+bool delete_app(const char *app_id, const char *app_category);
 
-bool app_exists(const char* app_id, const char* app_category);
+bool app_exists(const char *app_id, const char *app_category);
 
 // Function to parse JSON incrementally from a file
-bool parse_json_incrementally(
-    const char* file_path,
-    const char* target_key,
-    char* value_buffer,
-    size_t value_buffer_size);
+bool parse_json_incrementally(const char *file_path, const char *target_key, char *value_buffer, size_t value_buffer_size);
 
-#endif
+#endif

+ 82 - 40
flip_store/flip_store.c

@@ -1,122 +1,156 @@
 #include <flip_store.h>
 
+void flip_store_loader_free_model(View *view);
+FlipStoreApp *app_instance = NULL;
+
 // Function to free the resources used by FlipStoreApp
-void flip_store_app_free(FlipStoreApp* app) {
-    if(!app) {
+void flip_store_app_free(FlipStoreApp *app)
+{
+    if (!app)
+    {
         FURI_LOG_E(TAG, "FlipStoreApp is NULL");
         return;
     }
 
+    // Free Widget(s)
+    if (app->widget_result)
+    {
+        view_dispatcher_remove_view(app->view_dispatcher, FlipStoreViewWidgetResult);
+        widget_free(app->widget_result);
+    }
+
     // Free View(s)
-    if(app->view_main) {
-        view_dispatcher_remove_view(app->view_dispatcher, FlipStoreViewMain);
-        view_free(app->view_main);
+    if (app->view_loader)
+    {
+        view_dispatcher_remove_view(app->view_dispatcher, FlipStoreViewLoader);
+        flip_store_loader_free_model(app->view_loader);
+        view_free(app->view_loader);
     }
-    if(app->view_app_info) {
+
+    if (app->view_app_info)
+    {
         view_dispatcher_remove_view(app->view_dispatcher, FlipStoreViewAppInfo);
         view_free(app->view_app_info);
     }
-    if(app->view_firmware_download) {
-        view_dispatcher_remove_view(app->view_dispatcher, FlipStoreViewFirmwareDownload);
-        view_free(app->view_firmware_download);
-    }
 
     // Free Submenu(s)
-    if(app->submenu_main) {
+    if (app->submenu_main)
+    {
         view_dispatcher_remove_view(app->view_dispatcher, FlipStoreViewSubmenu);
         submenu_free(app->submenu_main);
     }
-    if(app->submenu_options) {
+    if (app->submenu_options)
+    {
         view_dispatcher_remove_view(app->view_dispatcher, FlipStoreViewSubmenuOptions);
         submenu_free(app->submenu_options);
     }
-    if(app->submenu_app_list) {
+    if (app->submenu_app_list)
+    {
         view_dispatcher_remove_view(app->view_dispatcher, FlipStoreViewAppList);
         submenu_free(app->submenu_app_list);
     }
-    if(app->submenu_firmwares) {
+    if (app->submenu_firmwares)
+    {
         view_dispatcher_remove_view(app->view_dispatcher, FlipStoreViewFirmwares);
         submenu_free(app->submenu_firmwares);
     }
-    if(app->submenu_app_list_bluetooth) {
+    if (app->submenu_app_list_bluetooth)
+    {
         view_dispatcher_remove_view(app->view_dispatcher, FlipStoreViewAppListBluetooth);
         submenu_free(app->submenu_app_list_bluetooth);
     }
-    if(app->submenu_app_list_games) {
+    if (app->submenu_app_list_games)
+    {
         view_dispatcher_remove_view(app->view_dispatcher, FlipStoreViewAppListGames);
         submenu_free(app->submenu_app_list_games);
     }
-    if(app->submenu_app_list_gpio) {
+    if (app->submenu_app_list_gpio)
+    {
         view_dispatcher_remove_view(app->view_dispatcher, FlipStoreViewAppListGPIO);
         submenu_free(app->submenu_app_list_gpio);
     }
-    if(app->submenu_app_list_infrared) {
+    if (app->submenu_app_list_infrared)
+    {
         view_dispatcher_remove_view(app->view_dispatcher, FlipStoreViewAppListInfrared);
         submenu_free(app->submenu_app_list_infrared);
     }
-    if(app->submenu_app_list_ibutton) {
+    if (app->submenu_app_list_ibutton)
+    {
         view_dispatcher_remove_view(app->view_dispatcher, FlipStoreViewAppListiButton);
         submenu_free(app->submenu_app_list_ibutton);
     }
-    if(app->submenu_app_list_media) {
+    if (app->submenu_app_list_media)
+    {
         view_dispatcher_remove_view(app->view_dispatcher, FlipStoreViewAppListMedia);
         submenu_free(app->submenu_app_list_media);
     }
-    if(app->submenu_app_list_nfc) {
+    if (app->submenu_app_list_nfc)
+    {
         view_dispatcher_remove_view(app->view_dispatcher, FlipStoreViewAppListNFC);
         submenu_free(app->submenu_app_list_nfc);
     }
-    if(app->submenu_app_list_rfid) {
+    if (app->submenu_app_list_rfid)
+    {
         view_dispatcher_remove_view(app->view_dispatcher, FlipStoreViewAppListRFID);
         submenu_free(app->submenu_app_list_rfid);
     }
-    if(app->submenu_app_list_subghz) {
+    if (app->submenu_app_list_subghz)
+    {
         view_dispatcher_remove_view(app->view_dispatcher, FlipStoreViewAppListSubGHz);
         submenu_free(app->submenu_app_list_subghz);
     }
-    if(app->submenu_app_list_tools) {
+    if (app->submenu_app_list_tools)
+    {
         view_dispatcher_remove_view(app->view_dispatcher, FlipStoreViewAppListTools);
         submenu_free(app->submenu_app_list_tools);
     }
-    if(app->submenu_app_list_usb) {
+    if (app->submenu_app_list_usb)
+    {
         view_dispatcher_remove_view(app->view_dispatcher, FlipStoreViewAppListUSB);
         submenu_free(app->submenu_app_list_usb);
     }
 
     // Free Widget(s)
-    if(app->widget) {
+    if (app->widget)
+    {
         view_dispatcher_remove_view(app->view_dispatcher, FlipStoreViewAbout);
         widget_free(app->widget);
     }
 
     // Free Variable Item List(s)
-    if(app->variable_item_list) {
+    if (app->variable_item_list)
+    {
         view_dispatcher_remove_view(app->view_dispatcher, FlipStoreViewSettings);
         variable_item_list_free(app->variable_item_list);
     }
 
     // Free Text Input(s)
-    if(app->uart_text_input_ssid) {
+    if (app->uart_text_input_ssid)
+    {
         view_dispatcher_remove_view(app->view_dispatcher, FlipStoreViewTextInputSSID);
         text_input_free(app->uart_text_input_ssid);
     }
-    if(app->uart_text_input_pass) {
+    if (app->uart_text_input_pass)
+    {
         view_dispatcher_remove_view(app->view_dispatcher, FlipStoreViewTextInputPass);
         text_input_free(app->uart_text_input_pass);
     }
 
     // Free popup
-    if(app->popup) {
+    if (app->popup)
+    {
         view_dispatcher_remove_view(app->view_dispatcher, FlipStoreViewPopup);
         popup_free(app->popup);
     }
 
     // Free dialog
-    if(app->dialog_delete) {
+    if (app->dialog_delete)
+    {
         view_dispatcher_remove_view(app->view_dispatcher, FlipStoreViewAppDelete);
         dialog_ex_free(app->dialog_delete);
     }
-    if(app->dialog_firmware) {
+    if (app->dialog_firmware)
+    {
         view_dispatcher_remove_view(app->view_dispatcher, FlipStoreViewFirmwareDialog);
         dialog_ex_free(app->dialog_firmware);
     }
@@ -134,29 +168,37 @@ void flip_store_app_free(FlipStoreApp* app) {
     free(app);
 }
 
-void flip_store_request_error(Canvas* canvas) {
-    if(fhttp.last_response != NULL) {
-        if(strstr(fhttp.last_response, "[ERROR] Not connected to Wifi. Failed to reconnect.") !=
-           NULL) {
+void flip_store_request_error(Canvas *canvas)
+{
+    if (fhttp.last_response != NULL)
+    {
+        if (strstr(fhttp.last_response, "[ERROR] Not connected to Wifi. Failed to reconnect.") != NULL)
+        {
             canvas_clear(canvas);
             canvas_draw_str(canvas, 0, 10, "[ERROR] Not connected to Wifi.");
             canvas_draw_str(canvas, 0, 50, "Update your WiFi settings.");
             canvas_draw_str(canvas, 0, 60, "Press BACK to return.");
-        } else if(strstr(fhttp.last_response, "[ERROR] Failed to connect to Wifi.") != NULL) {
+        }
+        else if (strstr(fhttp.last_response, "[ERROR] Failed to connect to Wifi.") != NULL)
+        {
             canvas_clear(canvas);
             canvas_draw_str(canvas, 0, 10, "[ERROR] Not connected to Wifi.");
             canvas_draw_str(canvas, 0, 50, "Update your WiFi settings.");
             canvas_draw_str(canvas, 0, 60, "Press BACK to return.");
-        } else {
+        }
+        else
+        {
             FURI_LOG_E(TAG, "Received an error: %s", fhttp.last_response);
             canvas_draw_str(canvas, 0, 42, "Unusual error...");
             canvas_draw_str(canvas, 0, 50, "Update your WiFi settings.");
             canvas_draw_str(canvas, 0, 60, "Press BACK to return.");
         }
-    } else {
+    }
+    else
+    {
         canvas_clear(canvas);
         canvas_draw_str(canvas, 0, 10, "[ERROR] Unknown error.");
         canvas_draw_str(canvas, 0, 50, "Update your WiFi settings.");
         canvas_draw_str(canvas, 0, 60, "Press BACK to return.");
     }
-}
+}

+ 84 - 75
flip_store/flip_store.h

@@ -1,5 +1,6 @@
 #ifndef FLIP_STORE_E_H
 #define FLIP_STORE_E_H
+#include <text_input/uart_text_input.h>
 #include <flipper_http/flipper_http.h>
 #include <easy_flipper/easy_flipper.h>
 #include <furi.h>
@@ -12,12 +13,13 @@
 #include <dialogs/dialogs.h>
 #include <jsmn/jsmn.h>
 #include <flip_store_icons.h>
-#define TAG            "FlipStore"
+#define TAG "FlipStore"
 #define FIRMWARE_COUNT 3
 #define FIRMWARE_LINKS 3
 
 // Define the submenu items for our FlipStore application
-typedef enum {
+typedef enum
+{
     FlipStoreSubmenuIndexMain, // Click to start downloading the selected app
     FlipStoreSubmenuIndexAbout,
     FlipStoreSubmenuIndexSettings,
@@ -45,88 +47,95 @@ typedef enum {
 } FlipStoreSubmenuIndex;
 
 // Define a single view for our FlipStore application
-typedef enum {
-    FlipStoreViewMain, // The main screen for downloading apps
-    //
-    FlipStoreViewSubmenu, // The submenu
-    FlipStoreViewSubmenuOptions, // The submenu options
-    //
-    FlipStoreViewAbout, // The about screen
-    FlipStoreViewSettings, // The settings screen
-    FlipStoreViewTextInputSSID, // The text input screen for SSID
-    FlipStoreViewTextInputPass, // The text input screen for password
-    //
-    FlipStoreViewPopup, // The popup screen
-    //
-    FlipStoreViewAppList, // The app list screen
-    FlipStoreViewFirmwares, // The firmwares screen (submenu)
-    FlipStoreViewFirmwareDialog, // The firmware view (DialogEx) of the selected firmware
-    FlipStoreViewFirmwareDownload, // The firmware download screen
-    //
-    FlipStoreViewAppInfo, // The app info screen (widget) of the selected app
-    FlipStoreViewAppDownload, // The app download screen (widget) of the selected app
-    FlipStoreViewAppDelete, // The app delete screen (DialogEx) of the selected app
-    //
+typedef enum
+{
+    //
+    FlipStoreViewSubmenu,          // The submenu
+    FlipStoreViewSubmenuOptions,   // The submenu options
+                                   //
+    FlipStoreViewAbout,            // The about screen
+    FlipStoreViewSettings,         // The settings screen
+    FlipStoreViewTextInputSSID,    // The text input screen for SSID
+    FlipStoreViewTextInputPass,    // The text input screen for password
+                                   //
+    FlipStoreViewPopup,            // The popup screen
+                                   //
+    FlipStoreViewAppList,          // The app list screen
+    FlipStoreViewFirmwares,        // The firmwares screen (submenu)
+    FlipStoreViewFirmwareDialog,   // The firmware view (DialogEx) of the selected firmware
+                                   //
+    FlipStoreViewAppInfo,          // The app info screen (widget) of the selected app
+    FlipStoreViewAppDownload,      // The app download screen (widget) of the selected app
+    FlipStoreViewAppDelete,        // The app delete screen (DialogEx) of the selected app
+                                   //
     FlipStoreViewAppListBluetooth, // the app list screen for Bluetooth
-    FlipStoreViewAppListGames, // the app list screen for Games
-    FlipStoreViewAppListGPIO, // the app list screen for GPIO
-    FlipStoreViewAppListInfrared, // the app list screen for Infrared
-    FlipStoreViewAppListiButton, // the app list screen for iButton
-    FlipStoreViewAppListMedia, // the app list screen for Media
-    FlipStoreViewAppListNFC, // the app list screen for NFC
-    FlipStoreViewAppListRFID, // the app list screen for RFID
-    FlipStoreViewAppListSubGHz, // the app list screen for Sub-GHz
-    FlipStoreViewAppListTools, // the app list screen for Tools
-    FlipStoreViewAppListUSB, // the app list screen for USB
+    FlipStoreViewAppListGames,     // the app list screen for Games
+    FlipStoreViewAppListGPIO,      // the app list screen for GPIO
+    FlipStoreViewAppListInfrared,  // the app list screen for Infrared
+    FlipStoreViewAppListiButton,   // the app list screen for iButton
+    FlipStoreViewAppListMedia,     // the app list screen for Media
+    FlipStoreViewAppListNFC,       // the app list screen for NFC
+    FlipStoreViewAppListRFID,      // the app list screen for RFID
+    FlipStoreViewAppListSubGHz,    // the app list screen for Sub-GHz
+    FlipStoreViewAppListTools,     // the app list screen for Tools
+    FlipStoreViewAppListUSB,       // the app list screen for USB
+                                   //
+                                   //
+    FlipStoreViewWidgetResult,     // The text box that displays the random fact
+    FlipStoreViewLoader,           // The loader screen retrieves data from the internet
 } FlipStoreView;
 
 // Each screen will have its own view
-typedef struct {
-    ViewDispatcher* view_dispatcher; // Switches between our views
-    View* view_main; // The main screen for downloading apps
-    View* view_app_info; // The app info screen (view) of the selected app
-    //
-    DialogEx* dialog_firmware; // The dialog for installing a firmware
-    View* view_firmware_download; // The firmware download screen (view) of the selected firmware
-    //
-    Submenu* submenu_main; // The submenu (main)
-    //
-    Submenu* submenu_options; // The submenu (options)
-    Submenu* submenu_app_list; // The submenu (app list) for the selected category
-    Submenu* submenu_firmwares; // The submenu (firmwares)
-    //
-    Submenu* submenu_app_list_bluetooth; // The submenu (app list) for Bluetooth
-    Submenu* submenu_app_list_games; // The submenu (app list) for Games
-    Submenu* submenu_app_list_gpio; // The submenu (app list) for GPIO
-    Submenu* submenu_app_list_infrared; // The submenu (app list) for Infrared
-    Submenu* submenu_app_list_ibutton; // The submenu (app list) for iButton
-    Submenu* submenu_app_list_media; // The submenu (app list) for Media
-    Submenu* submenu_app_list_nfc; // The submenu (app list) for NFC
-    Submenu* submenu_app_list_rfid; // The submenu (app list) for RFID
-    Submenu* submenu_app_list_subghz; // The submenu (app list) for Sub-GHz
-    Submenu* submenu_app_list_tools; // The submenu (app list) for Tools
-    Submenu* submenu_app_list_usb; // The submenu (app list) for USB
-    //
-    Widget* widget; // The widget
-    Popup* popup; // The popup
-    DialogEx* dialog_delete; // The dialog for deleting an app
-    VariableItemList* variable_item_list; // The variable item list (settngs)
-    VariableItem* variable_item_ssid; // The variable item
-    VariableItem* variable_item_pass; // The variable item
-    TextInput* uart_text_input_ssid; // The text input
-    TextInput* uart_text_input_pass; // The text input
+typedef struct
+{
+    View *view_loader;
+    Widget *widget_result;
+    //
+    ViewDispatcher *view_dispatcher; // Switches between our views
+    View *view_app_info;             // The app info screen (view) of the selected app
+    //
+    DialogEx *dialog_firmware;    // The dialog for installing a firmware
+    View *view_firmware_download; // The firmware download screen (view) of the selected firmware
+    //
+    Submenu *submenu_main; // The submenu (main)
+    //
+    Submenu *submenu_options;   // The submenu (options)
+    Submenu *submenu_app_list;  // The submenu (app list) for the selected category
+    Submenu *submenu_firmwares; // The submenu (firmwares)
+    //
+    Submenu *submenu_app_list_bluetooth; // The submenu (app list) for Bluetooth
+    Submenu *submenu_app_list_games;     // The submenu (app list) for Games
+    Submenu *submenu_app_list_gpio;      // The submenu (app list) for GPIO
+    Submenu *submenu_app_list_infrared;  // The submenu (app list) for Infrared
+    Submenu *submenu_app_list_ibutton;   // The submenu (app list) for iButton
+    Submenu *submenu_app_list_media;     // The submenu (app list) for Media
+    Submenu *submenu_app_list_nfc;       // The submenu (app list) for NFC
+    Submenu *submenu_app_list_rfid;      // The submenu (app list) for RFID
+    Submenu *submenu_app_list_subghz;    // The submenu (app list) for Sub-GHz
+    Submenu *submenu_app_list_tools;     // The submenu (app list) for Tools
+    Submenu *submenu_app_list_usb;       // The submenu (app list) for USB
+    //
+    Widget *widget;                       // The widget
+    Popup *popup;                         // The popup
+    DialogEx *dialog_delete;              // The dialog for deleting an app
+    VariableItemList *variable_item_list; // The variable item list (settngs)
+    VariableItem *variable_item_ssid;     // The variable item
+    VariableItem *variable_item_pass;     // The variable item
+    TextInput *uart_text_input_ssid; // The text input
+    TextInput *uart_text_input_pass; // The text input
 
-    char* uart_text_input_buffer_ssid; // Buffer for the text input
-    char* uart_text_input_temp_buffer_ssid; // Temporary buffer for the text input
+    char *uart_text_input_buffer_ssid;         // Buffer for the text input
+    char *uart_text_input_temp_buffer_ssid;    // Temporary buffer for the text input
     uint32_t uart_text_input_buffer_size_ssid; // Size of the text input buffer
 
-    char* uart_text_input_buffer_pass; // Buffer for the text input
-    char* uart_text_input_temp_buffer_pass; // Temporary buffer for the text input
+    char *uart_text_input_buffer_pass;         // Buffer for the text input
+    char *uart_text_input_temp_buffer_pass;    // Temporary buffer for the text input
     uint32_t uart_text_input_buffer_size_pass; // Size of the text input buffer
 } FlipStoreApp;
 
-void flip_store_app_free(FlipStoreApp* app);
+void flip_store_app_free(FlipStoreApp *app);
 
-void flip_store_request_error(Canvas* canvas);
+void flip_store_request_error(Canvas *canvas);
+extern FlipStoreApp *app_instance;
 
-#endif // FLIP_STORE_E_H
+#endif // FLIP_STORE_E_H

Разница между файлами не показана из-за своего большого размера
+ 291 - 165
flip_store/flipper_http/flipper_http.c


+ 81 - 59
flip_store/flipper_http/flipper_http.h

@@ -2,6 +2,10 @@
 #ifndef FLIPPER_HTTP_H
 #define FLIPPER_HTTP_H
 
+#include <gui/gui.h>
+#include <gui/view.h>
+#include <gui/view_dispatcher.h>
+#include <gui/modules/loading.h>
 #include <furi.h>
 #include <furi_hal.h>
 #include <furi_hal_gpio.h>
@@ -11,69 +15,72 @@
 
 // STORAGE_EXT_PATH_PREFIX is defined in the Furi SDK as /ext
 
-#define HTTP_TAG               "FlipStore" // change this to your app name
-#define http_tag               "flip_store" // change this to your app id
-#define UART_CH                (momentum_settings.uart_esp_channel) // UART channel
+#define HTTP_TAG "FlipStore"              // change this to your app name
+#define http_tag "flip_store"             // 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            2048 // UART RX buffer size
-#define RX_LINE_BUFFER_SIZE    8192 // UART RX line buffer size (increase for large responses)
-#define MAX_FILE_SHOW          8192 // Maximum data from file to show
-#define FILE_BUFFER_SIZE       1024 // File buffer size
+#define BAUDRATE (115200)                 // UART baudrate
+#define RX_BUF_SIZE 1024                  // UART RX buffer size
+#define RX_LINE_BUFFER_SIZE 8192          // UART RX line buffer size (increase for large responses)
+#define MAX_FILE_SHOW 8192                // 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;
@@ -89,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
 /**
@@ -104,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
 /**
@@ -113,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)
 /**
@@ -125,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
 /**
@@ -137,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
 /**
@@ -154,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
 /**
@@ -198,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
 /**
@@ -209,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
 /**
@@ -225,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
 /**
@@ -266,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
 /**
@@ -276,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
 /**
@@ -286,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
 /**
@@ -298,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
 /**
@@ -311,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
 /**
@@ -323,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
 /**
@@ -337,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
 /**
@@ -349,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
@@ -361,4 +368,19 @@ char* trim(const char* str);
  */
 bool flipper_http_process_response_async(bool (*http_request)(void), bool (*parse_json)(void));
 
+/**
+ * @brief Perform a task while displaying a loading screen
+ * @param http_request The function to send the request
+ * @param parse_response The function to parse the response
+ * @param success_view_id The view ID to switch to on success
+ * @param failure_view_id The view ID to switch to on failure
+ * @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);
+
 #endif // FLIPPER_HTTP_H

+ 217 - 136
flip_store/jsmn/jsmn.c

@@ -13,11 +13,13 @@
 /**
  * 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 +34,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 +46,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 +75,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 +88,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 +110,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 +146,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 +165,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 +193,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 +203,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 +252,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 +286,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 +301,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 +318,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 +337,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 +375,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 +389,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 +408,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 +424,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 *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)
+    {
         return NULL; // Handle memory allocation failure
     }
     snprintf(result, length, "{\"%s\":\"%s\"}", key, value);
@@ -392,30 +437,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, char *json_data, uint32_t max_tokens)
+{
     // Parse the JSON feed
-    if(json_data != NULL) {
+    if (json_data != NULL)
+    {
         jsmn_parser parser;
         jsmn_init(&parser);
 
         // 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 +474,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,7 +505,9 @@ 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.");
@@ -458,10 +515,12 @@ char* get_json_value(char* key, char* json_data, uint32_t max_tokens) {
 }
 
 // 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, 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) {
+    char *array_str = get_json_value(key, json_data, max_tokens);
+    if (array_str == NULL)
+    {
         FURI_LOG_E("JSMM.H", "Failed to get array for key: %s", key);
         return NULL;
     }
@@ -471,8 +530,9 @@ char* get_json_array_value(char* key, uint32_t index, char* json_data, uint32_t
     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;
@@ -480,7 +540,8 @@ char* get_json_array_value(char* key, uint32_t index, char* json_data, uint32_t
 
     // 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);
@@ -488,7 +549,8 @@ char* get_json_array_value(char* key, uint32_t index, char* json_data, uint32_t
     }
 
     // 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);
@@ -496,12 +558,9 @@ char* get_json_array_value(char* key, uint32_t index, char* json_data, uint32_t
     }
 
     // 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)
+    {
+        FURI_LOG_E("JSMM.H", "Index %lu out of bounds for array with size %d.", (unsigned long)index, tokens[0].size);
         free(tokens);
         free(array_str);
         return NULL;
@@ -509,20 +568,27 @@ char* get_json_array_value(char* key, uint32_t index, char* json_data, uint32_t
 
     // 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 (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) {
+        }
+        else if (tokens[current_token].type == JSMN_ARRAY)
+        {
             // For nested arrays, skip all elements
             current_token += 1 + tokens[current_token].size;
-        } else {
+        }
+        else
+        {
             // For primitive types, simply move to the next token
             current_token += 1;
         }
 
         // Safety check to prevent out-of-bounds
-        if(current_token >= ret) {
+        if (current_token >= ret)
+        {
             FURI_LOG_E("JSMM.H", "Unexpected end of tokens while traversing array.");
             free(tokens);
             free(array_str);
@@ -533,8 +599,9 @@ char* get_json_array_value(char* key, uint32_t index, char* json_data, uint32_t
     // Extract the array element
     jsmntok_t element = tokens[current_token];
     int length = element.end - element.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 array element.");
         free(tokens);
         free(array_str);
@@ -553,10 +620,12 @@ 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, uint32_t max_tokens, 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, max_tokens);
+    if (array_str == NULL)
+    {
         FURI_LOG_E("JSMM.H", "Failed to get array for key: %s", key);
         return NULL;
     }
@@ -566,8 +635,9 @@ char** get_json_array_values(char* key, char* json_data, uint32_t max_tokens, in
     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 +645,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 +654,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 +664,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 +677,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 +698,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 +725,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]);
         }
     }

+ 49 - 48
flip_store/jsmn/jsmn.h

@@ -19,7 +19,8 @@
 #include <stddef.h>
 
 #ifdef __cplusplus
-extern "C" {
+extern "C"
+{
 #endif
 
 #ifdef JSMN_STATIC
@@ -28,71 +29,71 @@ extern "C" {
 #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
-};
-
-/**
+    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;
+    typedef struct
+    {
+        jsmntype_t type;
+        int start;
+        int end;
+        int size;
 #ifdef JSMN_PARENT_LINKS
-    int parent;
+        int parent;
 #endif
-} jsmntok_t;
+    } 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;
-
-/**
+    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 */
@@ -116,16 +117,16 @@ JSMN_API int jsmn_parse(
 #include <furi.h>
 
 // Helper function to create a JSON object
-char* jsmn(const char* key, const char* value);
+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);
+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, char *json_data, uint32_t max_tokens);
 
 // 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, char *json_data, uint32_t max_tokens);
 
 // 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, uint32_t max_tokens, int *num_values);
 #endif /* JB_JSMN_EDIT */

Некоторые файлы не были показаны из-за большого количества измененных файлов