Преглед на файлове

Add flip_social from https://github.com/jblanked/FlipSocial

git-subtree-dir: flip_social
git-subtree-mainline: 5100a113a4fd43eb03e2a16b7b8a9a1d15695638
git-subtree-split: 34841bccc27f541655029d3a0bd7ed45f393878a
Willy-JL преди 1 година
родител
ревизия
8164b80b3a
променени са 42 файла, в които са добавени 8227 реда и са изтрити 0 реда
  1. BIN
      flip_social/.DS_Store
  2. 2 0
      flip_social/.gitattributes
  3. 5 0
      flip_social/.gitignore
  4. 1 0
      flip_social/.gitsubtree
  5. 88 0
      flip_social/README.md
  6. 47 0
      flip_social/app.c
  7. BIN
      flip_social/app.png
  8. BIN
      flip_social/app_new.png
  9. 14 0
      flip_social/application.fam
  10. BIN
      flip_social/assets/.DS_Store
  11. BIN
      flip_social/assets/ButtonBACK_10x8.png
  12. BIN
      flip_social/assets/ButtonLeft_4x7.png
  13. BIN
      flip_social/assets/ButtonOK_7x7.png
  14. BIN
      flip_social/assets/ButtonRight_4x7.png
  15. BIN
      flip_social/assets/ButtonUp_7x4.png
  16. 21 0
      flip_social/assets/CHANGELOG.md
  17. BIN
      flip_social/assets/KeyBackspaceSelected_16x9.png
  18. BIN
      flip_social/assets/KeyBackspace_16x9.png
  19. BIN
      flip_social/assets/KeySaveSelected_24x11.png
  20. BIN
      flip_social/assets/KeySave_24x11.png
  21. 87 0
      flip_social/assets/README.md
  22. BIN
      flip_social/assets/WarningDolphin_45x42.png
  23. BIN
      flip_social/assets/flip-social-enter-message.png
  24. BIN
      flip_social/assets/flip-social-logged-out-menu.png
  25. BIN
      flip_social/assets/flip-social-main-menu.png
  26. BIN
      flip_social/assets/flip-social-post-1.png
  27. BIN
      flip_social/assets/flip-social-post-2.png
  28. BIN
      flip_social/assets/flip-social-register.png
  29. 596 0
      flip_social/easy_flipper.h
  30. 1062 0
      flip_social/flip_social_callback.h
  31. 1008 0
      flip_social/flip_social_draw.h
  32. 310 0
      flip_social/flip_social_e.h
  33. 141 0
      flip_social/flip_social_explore.h
  34. 209 0
      flip_social/flip_social_feed.h
  35. 294 0
      flip_social/flip_social_free.h
  36. 158 0
      flip_social/flip_social_friends.h
  37. 542 0
      flip_social/flip_social_i.h
  38. 396 0
      flip_social/flip_social_messages.h
  39. 381 0
      flip_social/flip_social_storage.h
  40. 1197 0
      flip_social/flipper_http.h
  41. 865 0
      flip_social/jsmn.h
  42. 803 0
      flip_social/uart_text_input.h

BIN
flip_social/.DS_Store


+ 2 - 0
flip_social/.gitattributes

@@ -0,0 +1,2 @@
+# Auto detect text files and perform LF normalization
+* text=auto

+ 5 - 0
flip_social/.gitignore

@@ -0,0 +1,5 @@
+
+.vscode/compile_commands.json
+.DS_Store
+.DS_Store
+.DS_Store

+ 1 - 0
flip_social/.gitsubtree

@@ -0,0 +1 @@
+https://github.com/jblanked/FlipSocial main /

+ 88 - 0
flip_social/README.md

@@ -0,0 +1,88 @@
+# FlipSocial
+The first social media app for Flipper Zero. Connect with other users directly on your device through WiFi.
+
+The highlight of this app is customizable pre-saves, which, as explained below, aim to address the challenges of typing with the directional pad.
+
+FlipSocial uses the FlipperHTTP flash for the WiFi Devboard, first introduced in the WebCrawler app:https://github.com/jblanked/FlipperHTTP
+
+## Requirements
+- WiFi Dev Board or Raspberry Pi Pico W for Flipper Zero with FlipperHTTP Flash: https://github.com/jblanked/FlipperHTTP
+- WiFi Access Point
+
+
+## Features
+- Login/Logout
+- Registration
+- Feed
+- Profile
+- Customizable Pre-Saves
+- Explore (NEW)
+- Friends (NEW)
+- Direct Messaging (NEW)
+
+**Login/Logout:** Log in to your account to view and post on the Feed. You can also change your password and log out when needed.
+
+**Registration:** Create an account with just a username and password—no email or personal information required or collected.
+
+**Feed:** View up to 50 of the latest posts, create your own posts, and "Flip" a post—FlipSocial’s version of liking or favoriting a post.
+
+**Customizable Pre-Saves:** The biggest challenge with a social media app on the Flipper Zero is using only the directional pad for input. To address this, I implemented a pre-saved text system. The pre-saves are stored in a pre_saved_messages.txt file on your SD card. You can edit the pre-saves by opening qFlipper, downloading the file from the /apps_data/flip_social/ folder, adding your pre-saves (separated by new lines), and then copying it back to your SD card. You can also create pre-saves directly within the app.
+
+**Explore:** Discover other users and add them as friends.
+
+**Friends:** View and remove friends.
+
+**Direct Messaging:** Send direct messages to other Flipper users and view your conversations.
+
+## Roadmap
+**v0.2**
+- Stability Patch
+
+**v0.3**
+- Explore Page
+- Friends
+
+**v0.4**
+- Direct Messaging
+
+**v0.5**
+- Improved memory allocation
+- Improved Feed Page
+- Raspberry Pi Pico W Support
+
+**v0.6**
+- Improved User Profile (Bio, friend count, block)
+
+**v0.7**
+- Improved Explore Page
+
+**v0.8**
+- Create and Post Pixel Art
+
+**v1.0**
+- Official Release
+
+## Contribution
+This is a big project, and I welcome all contributors, especially developers interested in animations and graphics. Fork the repository, create a pull request, and I will review your edits.
+
+## Known Bugs
+1. When clicking any button other than the BACK button in the Feed view, post creation view, messages view, or the friends view, the app doesn't respond to inputs.
+- **Solution:** Restart your Flipper device.
+   
+2. When trying to log in, the app shows "Awaiting response..." and nothing happens for more than 30 seconds.
+- **Solution 1:** Update your WiFi credentials. Sometimes, you just need to hit Save again on either the SSID or password.
+- **Solution 2:** Ensure your WiFi Devboard is plugged in, then restart the app.
+- **Solution 3:** Ensure your WiFi Devboard is plugged in, then restart your Flipper device.
+   
+3. When accessing the Feed, I keep getting the message "Either the feed didn’t load or there was a server error."
+- **Solution 1:** Update your WiFi credentials. Sometimes, you just need to hit Save again on either the SSID or password.
+- **Solution 2:** Ensure your WiFi Devboard is plugged in, then restart the app.
+- **Solution 3:** Ensure your WiFi Devboard is plugged in, then restart your Flipper device.
+   
+4. The Feed is empty.
+- **Solution 1:** Update your WiFi credentials. Sometimes, you just need to hit Save again on either the SSID or password.
+- **Solution 2:** Ensure your WiFi Devboard is plugged in, then restart the app.
+- **Solution 3:** Ensure your WiFi Devboard is plugged in, then restart your Flipper device.
+
+5. Out of memory when starting the app or after visiting the feed and post views back-to-back.
+- **Solution:** Restart your Flipper device.

+ 47 - 0
flip_social/app.c

@@ -0,0 +1,47 @@
+// app.c
+#include <jsmn.h>                // Include cJSON
+#include <uart_text_input.h>     // Include the text input widget
+#include <flip_social_e.h>       // Include the FlipSocialApp structure
+#include <flip_social_storage.h> // Include the storage functions
+#include "flip_social_draw.h"
+#include "flip_social_feed.h"
+#include "flip_social_explore.h"
+#include "flip_social_friends.h"
+#include "flip_social_messages.h"
+#include <flip_social_callback.h> // Include the callback functions
+#include <flip_social_i.h>        // Include the initialization functions
+#include <flip_social_free.h>     // Include the cleanup functions
+
+/**
+ * @brief Entry point for the Hello World application.
+ * @details Initializes the app, runs the view dispatcher, and cleans up upon exit.
+ * @param p Input parameter - unused
+ * @return 0 to indicate success, -1 on failure
+ */
+int32_t main_flip_social(void *p)
+{
+    UNUSED(p);
+
+    // Initialize the Hello World application
+    app_instance = flip_social_app_alloc();
+    if (!app_instance)
+    {
+        // Allocation failed
+        return -1; // Indicate failure
+    }
+
+    if (!flipper_http_ping())
+    {
+        FURI_LOG_E(TAG, "Failed to ping the device");
+        return -1;
+    }
+
+    // Run the view dispatcher
+    view_dispatcher_run(app_instance->view_dispatcher);
+
+    // Free the resources used by the Hello World application
+    flip_social_app_free(app_instance);
+
+    // Return 0 to indicate success
+    return 0;
+}

BIN
flip_social/app.png


BIN
flip_social/app_new.png


+ 14 - 0
flip_social/application.fam

@@ -0,0 +1,14 @@
+App(
+    appid="flip_social",
+    name="FlipSocial",
+    apptype=FlipperAppType.EXTERNAL,
+    entry_point="main_flip_social",
+    stack_size=4 * 1024,
+    fap_icon="app_new.png",
+    fap_category="GPIO",
+    fap_icon_assets="assets",
+    fap_author="jblanked",
+    fap_weburl="https://github.com/jblanked/FlipSocial",
+    fap_version="0.5",
+    fap_description="Social media platform for the Flipper Zero.",
+)

BIN
flip_social/assets/.DS_Store


BIN
flip_social/assets/ButtonBACK_10x8.png


BIN
flip_social/assets/ButtonLeft_4x7.png


BIN
flip_social/assets/ButtonOK_7x7.png


BIN
flip_social/assets/ButtonRight_4x7.png


BIN
flip_social/assets/ButtonUp_7x4.png


+ 21 - 0
flip_social/assets/CHANGELOG.md

@@ -0,0 +1,21 @@
+## 0.5
+- Improved memory allocation
+- Improved Feed Page
+- Raspberry Pi Pico W Support
+
+## 0.4
+- Added direct messaging.
+- Updated app flow for a smoother user experience.
+- Reduced recent feed posts from 128 to 50.
+- Improved memory allocation.
+
+## 0.3
+- Added an Explore page to discover and add other users as friends.
+- Added a Friends page to view and remove friends.
+- Enhanced error handling.
+
+## 0.2
+- Stability improvements.
+
+## 0.1
+- Initial release.

BIN
flip_social/assets/KeyBackspaceSelected_16x9.png


BIN
flip_social/assets/KeyBackspace_16x9.png


BIN
flip_social/assets/KeySaveSelected_24x11.png


BIN
flip_social/assets/KeySave_24x11.png


+ 87 - 0
flip_social/assets/README.md

@@ -0,0 +1,87 @@
+# FlipSocial
+The first social media app for Flipper Zero. Connect with other users directly on your device through WiFi.
+
+The highlight of this app is customizable pre-saves, which, as explained below, aim to address the challenges of typing with the directional pad.
+
+FlipSocial uses the FlipperHTTP flash for the WiFi Devboard, first introduced in the WebCrawler app: [WebCrawler](https://github.com/jblanked/WebCrawler-FlipperZero/tree/main/assets/FlipperHTTP)
+
+## Requirements
+- WiFi Dev Board for Flipper Zero with FlipperHTTP Flash: [FlipperHTTP](https://github.com/jblanked/WebCrawler-FlipperZero/tree/main/assets/FlipperHTTP)
+- WiFi Access Point
+
+## Features
+- Login/Logout
+- Registration
+- Feed
+- Profile
+- Customizable Pre-Saves
+- Explore (NEW)
+- Friends (NEW)
+- Direct Messaging (NEW)
+
+**Login/Logout:** Log in to your account to view and post on the Feed. You can also change your password and log out when needed.
+
+**Registration:** Create an account with just a username and password—no email or personal information required or collected.
+
+**Feed:** View up to 50 of the latest posts, create your own posts, and "Flip" a post—FlipSocial’s version of liking or favoriting a post.
+
+**Customizable Pre-Saves:** The biggest challenge with a social media app on the Flipper Zero is using only the directional pad for input. To address this, I implemented a pre-saved text system. The pre-saves are stored in a pre_saved_messages.txt file on your SD card. You can edit the pre-saves by opening qFlipper, downloading the file from the /apps_data/flip_social/ folder, adding your pre-saves (separated by new lines), and then copying it back to your SD card. You can also create pre-saves directly within the app.
+
+**Explore:** Discover other users and add them as friends.
+
+**Friends:** View and remove friends.
+
+**Direct Messaging:** Send direct messages to other Flipper users and view your conversations.
+
+## Roadmap
+**v0.2**
+- Stability Patch
+
+**v0.3**
+- Explore Page
+- Friends
+
+**v0.4**
+- Direct Messaging
+
+**v0.5**
+- Improved memory allocation
+- Improved Feed Page
+- Raspberry Pi Pico W Support
+
+**v0.6**
+- Improved User Profile (Bio, friend count, block)
+
+**v0.7**
+- Improved Explore Page
+
+**v0.8**
+- Create and Post Pixel Art
+
+**v1.0**
+- Official Release
+
+## Contribution
+This is a big project, and I welcome all contributors, especially developers interested in animations and graphics. Fork the repository, create a pull request, and I will review your edits.
+
+## Known Bugs
+1. When clicking any button other than the BACK button in the Feed view, post creation view, messages view, or the friends view, the app doesn't respond to inputs.
+- **Solution:** Restart your Flipper device.
+   
+2. When trying to log in, the app shows "Awaiting response..." and nothing happens for more than 30 seconds.
+- **Solution 1:** Update your WiFi credentials. Sometimes, you just need to hit Save again on either the SSID or password.
+- **Solution 2:** Ensure your WiFi Devboard is plugged in, then restart the app.
+- **Solution 3:** Ensure your WiFi Devboard is plugged in, then restart your Flipper device.
+   
+3. When accessing the Feed, I keep getting the message "Either the feed didn’t load or there was a server error."
+- **Solution 1:** Update your WiFi credentials. Sometimes, you just need to hit Save again on either the SSID or password.
+- **Solution 2:** Ensure your WiFi Devboard is plugged in, then restart the app.
+- **Solution 3:** Ensure your WiFi Devboard is plugged in, then restart your Flipper device.
+   
+4. The Feed is empty.
+- **Solution 1:** Update your WiFi credentials. Sometimes, you just need to hit Save again on either the SSID or password.
+- **Solution 2:** Ensure your WiFi Devboard is plugged in, then restart the app.
+- **Solution 3:** Ensure your WiFi Devboard is plugged in, then restart your Flipper device.
+
+5. Out of memory when starting the app or after visiting the feed and post views back-to-back.
+- **Solution:** Restart your Flipper device.

BIN
flip_social/assets/WarningDolphin_45x42.png


BIN
flip_social/assets/flip-social-enter-message.png


BIN
flip_social/assets/flip-social-logged-out-menu.png


BIN
flip_social/assets/flip-social-main-menu.png


BIN
flip_social/assets/flip-social-post-1.png


BIN
flip_social/assets/flip-social-post-2.png


BIN
flip_social/assets/flip-social-register.png


+ 596 - 0
flip_social/easy_flipper.h

@@ -0,0 +1,596 @@
+#ifndef EASY_FLIPPER_H
+#define EASY_FLIPPER_H
+
+#include <malloc.h>
+#include <furi.h>
+#include <furi_hal.h>
+#include <gui/gui.h>
+#include <gui/view.h>
+#include <gui/modules/submenu.h>
+#include <gui/view_dispatcher.h>
+#include <gui/modules/menu.h>
+#include <gui/modules/submenu.h>
+#include <gui/modules/widget.h>
+#include <gui/modules/text_input.h>
+#include <gui/modules/text_box.h>
+#include <gui/modules/variable_item_list.h>
+#include <gui/modules/dialog_ex.h>
+#include <gui/modules/popup.h>
+#include <gui/modules/loading.h>
+#include <uart_text_input.h>
+#include <stdio.h>
+#include <string.h>
+#include <jsmn.h>
+
+#define EASY_TAG "EasyFlipper"
+
+/**
+ * @brief Navigation callback for exiting the application
+ * @param context The context - unused
+ * @return next view id (VIEW_NONE to exit the app)
+ */
+uint32_t easy_flipper_callback_exit_app(void *context)
+{
+    // Exit the application
+    if (!context)
+    {
+        FURI_LOG_E(EASY_TAG, "Context is NULL");
+        return VIEW_NONE;
+    }
+    UNUSED(context);
+    return VIEW_NONE; // Return VIEW_NONE to exit the app
+}
+
+/**
+ * @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)
+{
+    if (!buffer)
+    {
+        FURI_LOG_E(EASY_TAG, "Invalid arguments provided to set_buffer");
+        return false;
+    }
+    *buffer = (char *)malloc(buffer_size);
+    if (!*buffer)
+    {
+        FURI_LOG_E(EASY_TAG, "Failed to allocate buffer");
+        return false;
+    }
+    *buffer[0] = '\0';
+    return true;
+}
+
+/**
+ * @brief Initialize a View object
+ * @param view The View object to initialize
+ * @param view_id The ID/Index of the view
+ * @param draw_callback The draw callback function (set to NULL if not needed)
+ * @param input_callback The input callback function (set to NULL if not needed)
+ * @param previous_callback The previous callback function (can be set to NULL)
+ * @param view_dispatcher The ViewDispatcher object
+ * @return true if successful, false otherwise
+ */
+bool easy_flipper_set_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)
+    {
+        FURI_LOG_E(EASY_TAG, "Invalid arguments provided to set_view");
+        return false;
+    }
+    *view = view_alloc();
+    if (!*view)
+    {
+        FURI_LOG_E(EASY_TAG, "Failed to allocate View");
+        return false;
+    }
+    if (draw_callback)
+    {
+        view_set_draw_callback(*view, draw_callback);
+    }
+    if (input_callback)
+    {
+        view_set_input_callback(*view, input_callback);
+    }
+    if (context)
+    {
+        view_set_context(*view, context);
+    }
+    if (previous_callback)
+    {
+        view_set_previous_callback(*view, previous_callback);
+    }
+    view_dispatcher_add_view(*view_dispatcher, view_id, *view);
+    return true;
+}
+
+/**
+ * @brief Initialize a ViewDispatcher object
+ * @param view_dispatcher The ViewDispatcher object to initialize
+ * @param gui The GUI object
+ * @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)
+    {
+        FURI_LOG_E(EASY_TAG, "Invalid arguments provided to set_view_dispatcher");
+        return false;
+    }
+    *view_dispatcher = view_dispatcher_alloc();
+    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)
+    {
+        view_dispatcher_set_event_callback_context(*view_dispatcher, context);
+    }
+    return true;
+}
+
+/**
+ * @brief Initialize a Submenu object
+ * @note This does not set the items in the submenu
+ * @param submenu The Submenu object to initialize
+ * @param view_id The ID/Index of the view
+ * @param title The title/header of the submenu
+ * @param previous_callback The previous callback function (can be set to NULL)
+ * @param view_dispatcher The ViewDispatcher object
+ * @return true if successful, false otherwise
+ */
+bool easy_flipper_set_submenu(
+    Submenu **submenu,
+    int32_t view_id,
+    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)
+    {
+        FURI_LOG_E(EASY_TAG, "Failed to allocate Submenu");
+        return false;
+    }
+    if (title)
+    {
+        submenu_set_header(*submenu, title);
+    }
+    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));
+    return true;
+}
+/**
+ * @brief Initialize a Menu object
+ * @note This does not set the items in the menu
+ * @param menu The Menu object to initialize
+ * @param view_id The ID/Index of the view
+ * @param item_callback The item callback function
+ * @param previous_callback The previous callback function (can be set to NULL)
+ * @param view_dispatcher The ViewDispatcher object
+ * @return true if successful, false otherwise
+ */
+bool easy_flipper_set_menu(
+    Menu **menu,
+    int32_t view_id,
+    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)
+    {
+        FURI_LOG_E(EASY_TAG, "Failed to allocate Menu");
+        return false;
+    }
+    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));
+    return true;
+}
+
+/**
+ * @brief Initialize a Widget object
+ * @param widget The Widget object to initialize
+ * @param view_id The ID/Index of the view
+ * @param text The text to display in the widget
+ * @param previous_callback The previous callback function (can be set to NULL)
+ * @param view_dispatcher The ViewDispatcher object
+ * @return true if successful, false otherwise
+ */
+bool easy_flipper_set_widget(
+    Widget **widget,
+    int32_t view_id,
+    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)
+    {
+        FURI_LOG_E(EASY_TAG, "Failed to allocate Widget");
+        return false;
+    }
+    if (text)
+    {
+        widget_add_text_scroll_element(*widget, 0, 0, 128, 64, text);
+    }
+    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));
+    return true;
+}
+
+/**
+ * @brief Initialize a VariableItemList object
+ * @note This does not set the items in the VariableItemList
+ * @param variable_item_list The VariableItemList object to initialize
+ * @param view_id The ID/Index of the view
+ * @param enter_callback The enter callback function (can be set to NULL)
+ * @param previous_callback The previous callback function (can be set to NULL)
+ * @param view_dispatcher The ViewDispatcher object
+ * @param context The context to pass to the enter callback (usually the app)
+ * @return true if successful, false otherwise
+ */
+bool easy_flipper_set_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)
+    {
+        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)
+    {
+        FURI_LOG_E(EASY_TAG, "Failed to allocate VariableItemList");
+        return false;
+    }
+    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);
+    }
+    view_dispatcher_add_view(*view_dispatcher, view_id, variable_item_list_get_view(*variable_item_list));
+    return true;
+}
+
+/**
+ * @brief Initialize a TextInput object
+ * @param text_input The TextInput object to initialize
+ * @param view_id The ID/Index of the view
+ * @param previous_callback The previous callback function (can be set to NULL)
+ * @param view_dispatcher The ViewDispatcher object
+ * @return true if successful, false otherwise
+ */
+bool easy_flipper_set_text_input(
+    TextInput **text_input,
+    int32_t view_id,
+    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)
+    {
+        FURI_LOG_E(EASY_TAG, "Invalid arguments provided to set_text_input");
+        return false;
+    }
+    *text_input = text_input_alloc();
+    if (!*text_input)
+    {
+        FURI_LOG_E(EASY_TAG, "Failed to allocate TextInput");
+        return false;
+    }
+    if (previous_callback)
+    {
+        view_set_previous_callback(text_input_get_view(*text_input), previous_callback);
+    }
+    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);
+    }
+    view_dispatcher_add_view(*view_dispatcher, view_id, text_input_get_view(*text_input));
+    return true;
+}
+
+/**
+ * @brief Initialize a UART_TextInput object
+ * @param uart_text_input The UART_TextInput object to initialize
+ * @param view_id The ID/Index of the view
+ * @param previous_callback The previous callback function (can be set to NULL)
+ * @param view_dispatcher The ViewDispatcher object
+ * @return true if successful, false otherwise
+ */
+bool easy_flipper_set_uart_text_input(
+    UART_TextInput **uart_text_input,
+    int32_t view_id,
+    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)
+    {
+        FURI_LOG_E(EASY_TAG, "Invalid arguments provided to set_uart_text_input");
+        return false;
+    }
+    *uart_text_input = uart_text_input_alloc();
+    if (!*uart_text_input)
+    {
+        FURI_LOG_E(EASY_TAG, "Failed to allocate UART_TextInput");
+        return false;
+    }
+    if (previous_callback)
+    {
+        view_set_previous_callback(uart_text_input_get_view(*uart_text_input), previous_callback);
+    }
+    if (header_text)
+    {
+        uart_text_input_set_header_text(*uart_text_input, header_text);
+    }
+    if (uart_text_input_temp_buffer && uart_text_input_buffer_size && result_callback)
+    {
+        uart_text_input_set_result_callback(*uart_text_input, result_callback, context, uart_text_input_temp_buffer, uart_text_input_buffer_size, false);
+    }
+    view_dispatcher_add_view(*view_dispatcher, view_id, uart_text_input_get_view(*uart_text_input));
+    return true;
+}
+
+/**
+ * @brief Initialize a DialogEx object
+ * @param dialog_ex The DialogEx object to initialize
+ * @param view_id The ID/Index of the view
+ * @param header The header of the dialog
+ * @param header_x The x coordinate of the header
+ * @param header_y The y coordinate of the header
+ * @param text The text of the dialog
+ * @param text_x The x coordinate of the dialog
+ * @param text_y The y coordinate of the dialog
+ * @param left_button_text The text of the left button
+ * @param right_button_text The text of the right button
+ * @param center_button_text The text of the center button
+ * @param result_callback The result callback function
+ * @param previous_callback The previous callback function (can be set to NULL)
+ * @param view_dispatcher The ViewDispatcher object
+ * @param context The context to pass to the result callback
+ * @return true if successful, false otherwise
+ */
+bool easy_flipper_set_dialog_ex(
+    DialogEx **dialog_ex,
+    int32_t view_id,
+    char *header,
+    uint16_t header_x,
+    uint16_t header_y,
+    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)
+    {
+        FURI_LOG_E(EASY_TAG, "Invalid arguments provided to set_dialog_ex");
+        return false;
+    }
+    *dialog_ex = dialog_ex_alloc();
+    if (!*dialog_ex)
+    {
+        FURI_LOG_E(EASY_TAG, "Failed to allocate DialogEx");
+        return false;
+    }
+    if (header)
+    {
+        dialog_ex_set_header(*dialog_ex, header, header_x, header_y, AlignLeft, AlignTop);
+    }
+    if (text)
+    {
+        dialog_ex_set_text(*dialog_ex, text, text_x, text_y, AlignLeft, AlignTop);
+    }
+    if (left_button_text)
+    {
+        dialog_ex_set_left_button_text(*dialog_ex, left_button_text);
+    }
+    if (right_button_text)
+    {
+        dialog_ex_set_right_button_text(*dialog_ex, right_button_text);
+    }
+    if (center_button_text)
+    {
+        dialog_ex_set_center_button_text(*dialog_ex, center_button_text);
+    }
+    if (result_callback)
+    {
+        dialog_ex_set_result_callback(*dialog_ex, result_callback);
+    }
+    if (previous_callback)
+    {
+        view_set_previous_callback(dialog_ex_get_view(*dialog_ex), previous_callback);
+    }
+    if (context)
+    {
+        dialog_ex_set_context(*dialog_ex, context);
+    }
+    view_dispatcher_add_view(*view_dispatcher, view_id, dialog_ex_get_view(*dialog_ex));
+    return true;
+}
+
+/**
+ * @brief Initialize a Popup object
+ * @param popup The Popup object to initialize
+ * @param view_id The ID/Index of the view
+ * @param header The header of the dialog
+ * @param header_x The x coordinate of the header
+ * @param header_y The y coordinate of the header
+ * @param text The text of the dialog
+ * @param text_x The x coordinate of the dialog
+ * @param text_y The y coordinate of the dialog
+ * @param result_callback The result callback function
+ * @param previous_callback The previous callback function (can be set to NULL)
+ * @param view_dispatcher The ViewDispatcher object
+ * @param context The context to pass to the result callback
+ * @return true if successful, false otherwise
+ */
+bool easy_flipper_set_popup(
+    Popup **popup,
+    int32_t view_id,
+    char *header,
+    uint16_t header_x,
+    uint16_t header_y,
+    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)
+    {
+        FURI_LOG_E(EASY_TAG, "Invalid arguments provided to set_popup");
+        return false;
+    }
+    *popup = popup_alloc();
+    if (!*popup)
+    {
+        FURI_LOG_E(EASY_TAG, "Failed to allocate Popup");
+        return false;
+    }
+    if (header)
+    {
+        popup_set_header(*popup, header, header_x, header_y, AlignLeft, AlignTop);
+    }
+    if (text)
+    {
+        popup_set_text(*popup, text, text_x, text_y, AlignLeft, AlignTop);
+    }
+    if (result_callback)
+    {
+        popup_set_callback(*popup, result_callback);
+    }
+    if (previous_callback)
+    {
+        view_set_previous_callback(popup_get_view(*popup), previous_callback);
+    }
+    if (context)
+    {
+        popup_set_context(*popup, context);
+    }
+    view_dispatcher_add_view(*view_dispatcher, view_id, popup_get_view(*popup));
+    return true;
+}
+
+/**
+ * @brief Initialize a Loading object
+ * @param loading The Loading object to initialize
+ * @param view_id The ID/Index of the view
+ * @param previous_callback The previous callback function (can be set to NULL)
+ * @param view_dispatcher The ViewDispatcher object
+ * @return true if successful, false otherwise
+ */
+bool easy_flipper_set_loading(
+    Loading **loading,
+    int32_t view_id,
+    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)
+    {
+        FURI_LOG_E(EASY_TAG, "Failed to allocate Loading");
+        return false;
+    }
+    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));
+    return true;
+}
+
+/**
+ * @brief Set a char butter to a FuriString
+ * @param furi_string The FuriString object
+ * @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)
+    {
+        FURI_LOG_E(EASY_TAG, "Invalid arguments provided to set_buffer_to_furi_string");
+        return false;
+    }
+    *furi_string = furi_string_alloc();
+    if (!furi_string)
+    {
+        FURI_LOG_E(EASY_TAG, "Failed to allocate FuriString");
+        return false;
+    }
+    furi_string_set_str(*furi_string, buffer);
+    return true;
+}
+
+#endif // EASY_FLIPPER_H

+ 1062 - 0
flip_social/flip_social_callback.h

@@ -0,0 +1,1062 @@
+// flip_social_callback.h
+#ifndef FLIP_SOCIAL_CALLBACK_H
+#define FLIP_SOCIAL_CALLBACK_H
+
+/**
+ * @brief Navigation callback to go back to the submenu Logged out.
+ * @param context The context - unused
+ * @return next view id (FlipSocialViewLoggedOutSubmenu)
+ */
+static uint32_t flip_social_callback_to_submenu_logged_out(void *context)
+{
+    UNUSED(context);
+    return FlipSocialViewLoggedOutSubmenu;
+}
+
+/**
+ * @brief Navigation callback to go back to the submenu Logged in.
+ * @param context The context - unused
+ * @return next view id (FlipSocialViewLoggedInSubmenu)
+ */
+static uint32_t flip_social_callback_to_submenu_logged_in(void *context)
+{
+    UNUSED(context);
+    flip_social_free_explore();
+    flip_social_free_feed();
+    flip_social_free_friends();
+    flip_social_free_message_users();
+    flip_social_free_messages();
+    return FlipSocialViewLoggedInSubmenu;
+}
+
+/**
+ * @brief Navigation callback to bring the user back to the (Logged out) Login screen
+ * @param context The context - unused
+ * @return next view id (FlipSocialViewLoggedOutLogin)
+ */
+static uint32_t flip_social_callback_to_login_logged_out(void *context)
+{
+    UNUSED(context);
+    flip_social_sent_login_request = false;
+    flip_social_login_success = false;
+    return FlipSocialViewLoggedOutLogin;
+}
+
+/**
+ * @brief Navigation callback to bring the user back to the (Logged out) Register screen
+ * @param context The context - unused
+ * @return next view id (FlipSocialViewLoggedOutRegister)
+ */
+static uint32_t flip_social_callback_to_register_logged_out(void *context)
+{
+    UNUSED(context);
+    flip_social_sent_register_request = false;
+    flip_social_register_success = false;
+    return FlipSocialViewLoggedOutRegister;
+}
+
+/**
+ * @brief Navigation callback to bring the user back to the (Logged out) Wifi Settings screen
+ * @param context The context - unused
+ * @return next view id (FlipSocialViewLoggedOutWifiSettings)
+ */
+static uint32_t flip_social_callback_to_wifi_settings_logged_out(void *context)
+{
+    UNUSED(context);
+    return FlipSocialViewLoggedOutWifiSettings;
+}
+
+/**
+ * @brief Navigation callback to bring the user back to the (Logged in) Wifi Settings screen
+ * @param context The context - unused
+ * @return next view id (FlipSocialViewLoggedInSettingsWifi)
+ */
+static uint32_t flip_social_callback_to_wifi_settings_logged_in(void *context)
+{
+    UNUSED(context);
+    return FlipSocialViewLoggedInSettingsWifi;
+}
+
+/**
+ * @brief Navigation callback to bring the user back to the (Logged in) Settings screen
+ * @param context The context - unused
+ * @return next view id (FlipSocialViewLoggedInSettingsWifi)
+ */
+static uint32_t flip_social_callback_to_settings_logged_in(void *context)
+{
+    UNUSED(context);
+    return FlipSocialViewLoggedInSettings;
+}
+
+/**
+ * @brief Navigation callback to bring the user back to the (Logged in) Compose screen
+ * @param context The context - unused
+ * @return next view id (FlipSocialViewLoggedInCompose)
+ */
+static uint32_t flip_social_callback_to_compose_logged_in(void *context)
+{
+    UNUSED(context);
+    return FlipSocialViewLoggedInCompose;
+}
+
+/**
+ * @brief Navigation callback to bring the user back to the (Logged in) Profile screen
+ * @param context The context - unused
+ * @return next view id (FlipSocialViewLoggedInProfile)
+ */
+static uint32_t flip_social_callback_to_profile_logged_in(void *context)
+{
+    UNUSED(context);
+    return FlipSocialViewLoggedInProfile;
+}
+
+/**
+ * @brief Navigation callback to bring the user back to the Explore submenu
+ * @param context The context - unused
+ * @return next view id (FlipSocialViewLoggedInExploreSubmenu)
+ */
+static uint32_t flip_social_callback_to_explore_logged_in(void *context)
+{
+    UNUSED(context);
+    flip_social_dialog_stop = true;
+    last_explore_response = "";
+    flip_social_dialog_shown = false;
+    flip_social_explore->index = 0;
+    action = ActionNone;
+    return FlipSocialViewLoggedInExploreSubmenu;
+}
+
+/**
+ * @brief Navigation callback to bring the user back to the Friends submenu
+ * @param context The context - unused
+ * @return next view id (FlipSocialViewLoggedInFriendsSubmenu)
+ */
+static uint32_t flip_social_callback_to_friends_logged_in(void *context)
+{
+    UNUSED(context);
+    flip_social_dialog_stop = true;
+    last_explore_response = "";
+    flip_social_dialog_shown = false;
+    flip_social_friends->index = 0;
+    action = ActionNone;
+    return FlipSocialViewLoggedInFriendsSubmenu;
+}
+
+/**
+ * @brief Navigation callback to bring the user back to the Messages submenu
+ * @param context The context - unused
+ * @return next view id (FlipSocialViewLoggedInMessagesSubmenu)
+ */
+static uint32_t flip_social_callback_to_messages_logged_in(void *context)
+{
+    UNUSED(context);
+    return FlipSocialViewLoggedInMessagesSubmenu;
+}
+
+/**
+ * @brief Navigation callback to bring the user back to the User Choices screen
+ * @param context The context - unused
+ * @return next view id (FlipSocialViewLoggedInMessagesUserChoices)
+ */
+static uint32_t flip_social_callback_to_messages_user_choices(void *context)
+{
+    UNUSED(context);
+    return FlipSocialViewLoggedInMessagesUserChoices;
+}
+
+/**
+ * @brief Navigation callback for exiting the application
+ * @param context The context - unused
+ * @return next view id (VIEW_NONE to exit the app)
+ */
+static uint32_t flip_social_callback_exit_app(void *context)
+{
+    // Exit the application
+    if (!context)
+    {
+        FURI_LOG_E(TAG, "Context is NULL");
+        return VIEW_NONE;
+    }
+    UNUSED(context);
+    return VIEW_NONE; // Return VIEW_NONE to exit the app
+}
+
+/**
+ * @brief Handle ALL submenu item selections.
+ * @param context The context - FlipSocialApp object.
+ * @param index The FlipSocialSubmenuIndex item that was clicked.
+ * @return void
+ */
+static void flip_social_callback_submenu_choices(void *context, uint32_t index)
+{
+    FlipSocialApp *app = (FlipSocialApp *)context;
+    if (!app)
+    {
+        FURI_LOG_E(TAG, "FlipSocialApp is NULL");
+        return;
+    }
+    switch (index)
+    {
+    case FlipSocialSubmenuLoggedOutIndexLogin:
+        flip_social_sent_login_request = false;
+        flip_social_login_success = false;
+        view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedOutLogin);
+        break;
+    case FlipSocialSubmenuLoggedOutIndexRegister:
+        flip_social_sent_register_request = false;
+        flip_social_register_success = false;
+        view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedOutRegister);
+        break;
+    case FlipSocialSubmenuLoggedOutIndexAbout:
+        view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedOutAbout);
+        break;
+    case FlipSocialSubmenuLoggedOutIndexWifiSettings:
+        view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedOutWifiSettings);
+        break;
+    case FlipSocialSubmenuLoggedInIndexProfile:
+        view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedInProfile);
+        break;
+    case FlipSocialSubmenuLoggedInIndexMessages:
+        if (flipper_http_process_response_async(flip_social_get_message_users, flip_social_parse_json_message_users))
+        {
+            view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedInMessagesSubmenu);
+        }
+        break;
+    case FlipSocialSubmenuLoggedInIndexMessagesNewMessage:
+        if (flipper_http_process_response_async(flip_social_get_explore, flip_social_parse_json_message_user_choices))
+        {
+            view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedInMessagesUserChoices);
+        }
+        break;
+    case FlipSocialSubmenuLoggedInIndexFeed:
+        if (flipper_http_process_response_async(flip_social_get_feed, flip_social_parse_json_feed))
+        {
+            view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedInFeed);
+        }
+        else
+        {
+            // Set failure FlipSocialFeed object
+            if (!flip_social_temp_feed())
+            {
+                return;
+            }
+            view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedInFeed);
+        }
+        break;
+    case FlipSocialSubmenuExploreIndex:
+        if (flipper_http_process_response_async(flip_social_get_explore, flip_social_parse_json_explore))
+        {
+            view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedInExploreSubmenu);
+        }
+        break;
+    case FlipSocialSubmenuLoggedInIndexCompose:
+        view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedInCompose);
+        break;
+    case FlipSocialSubmenuLoggedInIndexSettings:
+        view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedInSettings);
+        break;
+    case FlipSocialSubmenuLoggedInSignOutButton:
+        app->is_logged_in = "false";
+
+        save_settings(app_instance->wifi_ssid_logged_out, app_instance->wifi_password_logged_out, app_instance->login_username_logged_out, app_instance->login_username_logged_in, app_instance->login_password_logged_out, app_instance->change_password_logged_in, app_instance->is_logged_in);
+
+        view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedOutSubmenu);
+        break;
+    case FlipSocialSubmenuComposeIndexAddPreSave:
+        view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedInComposeAddPreSaveInput);
+        break;
+    default:
+        // Handle the pre-saved message selection (has a max of 25 items)
+        if (index >= FlipSocialSubemnuComposeIndexStartIndex && index < FlipSocialSubemnuComposeIndexStartIndex + MAX_PRE_SAVED_MESSAGES)
+        {
+            flip_social_feed->index = index - FlipSocialSubemnuComposeIndexStartIndex;
+            view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedInProcessCompose);
+        }
+
+        // Handle the explore selection
+        else if (index >= FlipSocialSubmenuExploreIndexStartIndex && index < FlipSocialSubmenuExploreIndexStartIndex + MAX_EXPLORE_USERS)
+        {
+            flip_social_explore->index = index - FlipSocialSubmenuExploreIndexStartIndex;
+            view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedInExploreProccess);
+        }
+
+        // handle the friends selection
+        else if (index >= FlipSocialSubmenuLoggedInIndexFriendsStart && index < FlipSocialSubmenuLoggedInIndexFriendsStart + MAX_FRIENDS)
+        {
+            flip_social_friends->index = index - FlipSocialSubmenuLoggedInIndexFriendsStart;
+            view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedInFriendsProcess);
+        }
+
+        // handle the messages selection
+        else if (index >= FlipSocialSubmenuLoggedInIndexMessagesUsersStart && index < FlipSocialSubmenuLoggedInIndexMessagesUsersStart + MAX_MESSAGE_USERS)
+        {
+            flip_social_message_users->index = index - FlipSocialSubmenuLoggedInIndexMessagesUsersStart;
+            if (flipper_http_process_response_async(flip_social_get_messages_with_user, flip_social_parse_json_messages))
+            {
+                view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedInMessagesProcess);
+            }
+        }
+
+        // handle the messages user choices selection
+        else if (index >= FlipSocialSubmenuLoggedInIndexMessagesUserChoicesIndexStart && index < FlipSocialSubmenuLoggedInIndexMessagesUserChoicesIndexStart + MAX_EXPLORE_USERS)
+        {
+            flip_social_explore->index = index - FlipSocialSubmenuLoggedInIndexMessagesUserChoicesIndexStart;
+            view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedInMessagesNewMessageUserChoicesInput);
+        }
+        else
+        {
+            FURI_LOG_E(TAG, "Unknown submenu index");
+        }
+
+        break;
+    }
+}
+
+/**
+ * @brief Text input callback for when the user finishes entering their SSID on the wifi settings (logged out) screen.
+ * @param context The context - FlipSocialApp object.
+ * @return void
+ */
+static void flip_social_logged_out_wifi_settings_ssid_updated(void *context)
+{
+    FlipSocialApp *app = (FlipSocialApp *)context;
+    if (!app)
+    {
+        FURI_LOG_E(TAG, "FlipSocialApp is NULL");
+        return;
+    }
+    // Store the entered name
+    strncpy(app->wifi_ssid_logged_out, app->wifi_ssid_logged_out_temp_buffer, app->wifi_ssid_logged_out_temp_buffer_size);
+
+    // Store the entered name in the logged in name field
+    strncpy(app->wifi_ssid_logged_in, app->wifi_ssid_logged_out_temp_buffer, app->wifi_ssid_logged_out_temp_buffer_size);
+    strncpy(app->wifi_ssid_logged_in_temp_buffer, app->wifi_ssid_logged_out_temp_buffer, app->wifi_ssid_logged_out_temp_buffer_size);
+
+    // Ensure null-termination
+    app->wifi_ssid_logged_out[app->wifi_ssid_logged_out_temp_buffer_size - 1] = '\0';
+
+    // Update the name item text
+    if (app->variable_item_logged_out_wifi_settings_ssid)
+    {
+        variable_item_set_current_value_text(app->variable_item_logged_out_wifi_settings_ssid, app->wifi_ssid_logged_out);
+    }
+
+    // update the wifi settings
+    if (!flipper_http_save_wifi(app->wifi_ssid_logged_out, app->wifi_password_logged_out))
+    {
+        FURI_LOG_E(TAG, "Failed to save wifi settings via UART");
+        FURI_LOG_E(TAG, "Make sure the Flipper is connected to the Wifi Dev Board");
+    }
+
+    // Save the settings
+    save_settings(app_instance->wifi_ssid_logged_out, app_instance->wifi_password_logged_out, app_instance->login_username_logged_out, app_instance->login_username_logged_in, app_instance->login_password_logged_out, app_instance->change_password_logged_in, app_instance->is_logged_in);
+
+    view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedOutWifiSettings);
+}
+
+/**
+ * @brief Text input callback for when the user finishes entering their password on the wifi settings (logged out) screen.
+ * @param context The context - FlipSocialApp object.
+ * @return void
+ */
+static void flip_social_logged_out_wifi_settings_password_updated(void *context)
+{
+    FlipSocialApp *app = (FlipSocialApp *)context;
+    if (!app)
+    {
+        FURI_LOG_E(TAG, "FlipSocialApp is NULL");
+        return;
+    }
+    // Store the entered WiFi password
+    strncpy(app->wifi_password_logged_out, app->wifi_password_logged_out_temp_buffer, app->wifi_password_logged_out_temp_buffer_size);
+
+    // Store the entered WiFi password in the logged in password field
+    strncpy(app->wifi_password_logged_in, app->wifi_password_logged_out_temp_buffer, app->wifi_password_logged_out_temp_buffer_size);
+    strncpy(app->wifi_password_logged_in_temp_buffer, app->wifi_password_logged_out_temp_buffer, app->wifi_password_logged_out_temp_buffer_size);
+
+    // Ensure null-termination
+    app->wifi_password_logged_out[app->wifi_password_logged_out_temp_buffer_size - 1] = '\0';
+
+    // Update the password item text
+    if (app->variable_item_logged_out_wifi_settings_password)
+    {
+        variable_item_set_current_value_text(app->variable_item_logged_out_wifi_settings_password, app->wifi_password_logged_out);
+    }
+
+    // update the wifi settings
+    if (!flipper_http_save_wifi(app->wifi_ssid_logged_out, app->wifi_password_logged_out))
+    {
+        FURI_LOG_E(TAG, "Failed to save wifi settings via UART");
+        FURI_LOG_E(TAG, "Make sure the Flipper is connected to the Wifi Dev Board");
+    }
+
+    // Save the settings
+    save_settings(app_instance->wifi_ssid_logged_out, app_instance->wifi_password_logged_out, app_instance->login_username_logged_out, app_instance->login_username_logged_in, app_instance->login_password_logged_out, app_instance->change_password_logged_in, app_instance->is_logged_in);
+
+    view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedOutWifiSettings);
+}
+
+/**
+ * @brief Callback when the user selects a menu item in the wifi settings (logged out) screen.
+ * @param context The context - FlipSocialApp object.
+ * @param index The index of the selected item.
+ * @return void
+ */
+static void flip_social_text_input_logged_out_wifi_settings_item_selected(void *context, uint32_t index)
+{
+    FlipSocialApp *app = (FlipSocialApp *)context;
+    if (!app)
+    {
+        FURI_LOG_E(TAG, "FlipSocialApp is NULL");
+        return;
+    }
+    switch (index)
+    {
+    case 0: // Input SSID
+        view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedOutWifiSettingsSSIDInput);
+        break;
+    case 1: // Input Password
+        view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedOutWifiSettingsPasswordInput);
+        break;
+    default:
+        FURI_LOG_E(TAG, "Unknown configuration item index");
+        break;
+    }
+}
+
+/**
+ * @brief Text input callback for when the user finishes entering their username on the login (logged out) screen.
+ * @param context The context - FlipSocialApp object.
+ * @return void
+ */
+static void flip_social_logged_out_login_username_updated(void *context)
+{
+    FlipSocialApp *app = (FlipSocialApp *)context;
+    if (!app)
+    {
+        FURI_LOG_E(TAG, "FlipSocialApp is NULL");
+        return;
+    }
+    // Store the entered name
+    strncpy(app->login_username_logged_out, app->login_username_logged_out_temp_buffer, app->login_username_logged_out_temp_buffer_size);
+
+    // Store the entered name in the logged in username field
+    strncpy(app->login_username_logged_in, app->login_username_logged_out_temp_buffer, app->login_username_logged_out_temp_buffer_size);
+    strncpy(app->login_username_logged_in_temp_buffer, app->login_username_logged_out_temp_buffer, app->login_username_logged_out_temp_buffer_size);
+
+    // Ensure null-termination
+    app->login_username_logged_out[app->login_username_logged_out_temp_buffer_size - 1] = '\0';
+
+    // Update the name item text
+    if (app->variable_item_logged_out_login_username)
+    {
+        variable_item_set_current_value_text(app->variable_item_logged_out_login_username, app->login_username_logged_out);
+    }
+
+    // Save the settings
+    save_settings(app_instance->wifi_ssid_logged_out, app_instance->wifi_password_logged_out, app_instance->login_username_logged_out, app_instance->login_username_logged_in, app_instance->login_password_logged_out, app_instance->change_password_logged_in, app_instance->is_logged_in);
+
+    view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedOutLogin);
+}
+
+/**
+ * @brief Text input callback for when the user finishes entering their password on the login (logged out) screen.
+ * @param context The context - FlipSocialApp object.
+ * @return void
+ */
+
+static void flip_social_logged_out_login_password_updated(void *context)
+{
+    FlipSocialApp *app = (FlipSocialApp *)context;
+    if (!app)
+    {
+        FURI_LOG_E(TAG, "FlipSocialApp is NULL");
+        return;
+    }
+    // Store the entered password
+    strncpy(app->login_password_logged_out, app->login_password_logged_out_temp_buffer, app->login_password_logged_out_temp_buffer_size);
+
+    // Store the entered password in the change password field
+    strncpy(app->change_password_logged_in, app->login_password_logged_out_temp_buffer, app->login_password_logged_out_temp_buffer_size);
+    strncpy(app->change_password_logged_in_temp_buffer, app->login_password_logged_out_temp_buffer, app->login_password_logged_out_temp_buffer_size);
+
+    // Ensure null-termination
+    app->login_password_logged_out[app->login_password_logged_out_temp_buffer_size - 1] = '\0';
+
+    // Update the password item text
+    if (app->variable_item_logged_out_login_password)
+    {
+        // dont show the password on the screen (version 0.2)
+        // variable_item_set_current_value_text(app->variable_item_logged_out_login_password, app->login_password_logged_out);
+    }
+
+    // Save the settings
+    save_settings(app_instance->wifi_ssid_logged_out, app_instance->wifi_password_logged_out, app_instance->login_username_logged_out, app_instance->login_username_logged_in, app_instance->login_password_logged_out, app_instance->change_password_logged_in, app_instance->is_logged_in);
+
+    view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedOutLogin);
+}
+
+/**
+ * @brief Callback when the user selects a menu item in the login (logged out) screen.
+ * @param context The context - FlipSocialApp object.
+ * @param index The index of the selected item.
+ * @return void
+ */
+static void flip_social_text_input_logged_out_login_item_selected(void *context, uint32_t index)
+{
+    FlipSocialApp *app = (FlipSocialApp *)context;
+    if (!app)
+    {
+        FURI_LOG_E(TAG, "FlipSocialApp is NULL");
+        return;
+    }
+    switch (index)
+    {
+    case 0: // Input Username
+        view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedOutLoginUsernameInput);
+        break;
+    case 1: // Input Password
+        view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedOutLoginPasswordInput);
+        break;
+    case 2: // Login Button
+        view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedOutProcessLogin);
+        break;
+    default:
+        FURI_LOG_E(TAG, "Unknown configuration item index");
+        break;
+    }
+}
+
+/**
+ * @brief Text input callback for when the user finishes entering their username on the register (logged out) screen.
+ * @param context The context - FlipSocialApp object.
+ * @return void
+ */
+static void flip_social_logged_out_register_username_updated(void *context)
+{
+    FlipSocialApp *app = (FlipSocialApp *)context;
+    if (!app)
+    {
+        FURI_LOG_E(TAG, "FlipSocialApp is NULL");
+        return;
+    }
+    // Store the entered name
+    strncpy(app->register_username_logged_out, app->register_username_logged_out_temp_buffer, app->register_username_logged_out_temp_buffer_size);
+
+    // Ensure null-termination
+    app->register_username_logged_out[app->register_username_logged_out_temp_buffer_size - 1] = '\0';
+
+    // Update the name item text
+    if (app->variable_item_logged_out_register_username)
+    {
+        variable_item_set_current_value_text(app->variable_item_logged_out_register_username, app->register_username_logged_out);
+    }
+
+    view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedOutRegister);
+}
+
+/**
+ * @brief Text input callback for when the user finishes entering their password on the register (logged out) screen.
+ * @param context The context - FlipSocialApp object.
+ * @return void
+ */
+static void flip_social_logged_out_register_password_updated(void *context)
+{
+    FlipSocialApp *app = (FlipSocialApp *)context;
+    if (!app)
+    {
+        FURI_LOG_E(TAG, "FlipSocialApp is NULL");
+        return;
+    }
+    // Store the entered password
+    strncpy(app->register_password_logged_out, app->register_password_logged_out_temp_buffer, app->register_password_logged_out_temp_buffer_size);
+
+    // Ensure null-termination
+    app->register_password_logged_out[app->register_password_logged_out_temp_buffer_size - 1] = '\0';
+
+    // Update the password item text
+    if (app->variable_item_logged_out_register_password)
+    {
+        variable_item_set_current_value_text(app->variable_item_logged_out_register_password, app->register_password_logged_out);
+    }
+
+    view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedOutRegister);
+}
+
+/**
+ * @brief Text input callback for when the user finishes entering their password 2 on the register (logged out) screen.
+ * @param context The context - FlipSocialApp object.
+ * @return void
+ */
+static void flip_social_logged_out_register_password_2_updated(void *context)
+{
+    FlipSocialApp *app = (FlipSocialApp *)context;
+    if (!app)
+    {
+        FURI_LOG_E(TAG, "FlipSocialApp is NULL");
+        return;
+    }
+    // Store the entered password
+    strncpy(app->register_password_2_logged_out, app->register_password_2_logged_out_temp_buffer, app->register_password_2_logged_out_temp_buffer_size);
+
+    // Ensure null-termination
+    app->register_password_2_logged_out[app->register_password_2_logged_out_temp_buffer_size - 1] = '\0';
+
+    // Update the password item text
+    if (app->variable_item_logged_out_register_password_2)
+    {
+        variable_item_set_current_value_text(app->variable_item_logged_out_register_password_2, app->register_password_2_logged_out);
+    }
+
+    view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedOutRegister);
+}
+
+/**
+ * @brief Callback when the user selects a menu item in the register (logged out) screen.
+ * @param context The context - FlipSocialApp object.
+ * @param index The index of the selected item.
+ * @return void
+ */
+static void flip_social_text_input_logged_out_register_item_selected(void *context, uint32_t index)
+{
+    FlipSocialApp *app = (FlipSocialApp *)context;
+    if (!app)
+    {
+        FURI_LOG_E(TAG, "FlipSocialApp is NULL");
+        return;
+    }
+    switch (index)
+    {
+    case 0: // Input Username
+        view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedOutRegisterUsernameInput);
+        break;
+    case 1: // Input Password
+        view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedOutRegisterPasswordInput);
+        break;
+    case 2: // Input Password 2
+        view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedOutRegisterPassword2Input);
+        break;
+    case 3: // Register button
+        view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedOutProcessRegister);
+        break;
+    default:
+        FURI_LOG_E(TAG, "Unknown configuration item index");
+        break;
+    }
+}
+
+/**
+ * @brief Text input callback for when the user finishes entering their SSID on the wifi settings (logged in) screen.
+ * @param context The context - FlipSocialApp object.
+ * @return void
+ */
+static void flip_social_logged_in_wifi_settings_ssid_updated(void *context)
+{
+    FlipSocialApp *app = (FlipSocialApp *)context;
+    if (!app)
+    {
+        FURI_LOG_E(TAG, "FlipSocialApp is NULL");
+        return;
+    }
+    // Store the entered SSID
+    strncpy(app->wifi_ssid_logged_in, app->wifi_ssid_logged_in_temp_buffer, app->wifi_ssid_logged_in_temp_buffer_size);
+
+    // Store the entered SSID in the logged out SSID
+    strncpy(app->wifi_ssid_logged_out, app->wifi_ssid_logged_in, app->wifi_ssid_logged_in_temp_buffer_size);
+    strncpy(app->wifi_ssid_logged_out_temp_buffer, app->wifi_ssid_logged_in, app->wifi_ssid_logged_in_temp_buffer_size);
+
+    // Ensure null-termination
+    app->wifi_ssid_logged_in[app->wifi_ssid_logged_in_temp_buffer_size - 1] = '\0';
+
+    // Update the name item text
+    if (app->variable_item_logged_in_wifi_settings_ssid)
+    {
+        variable_item_set_current_value_text(app->variable_item_logged_in_wifi_settings_ssid, app->wifi_ssid_logged_in);
+    }
+
+    // Save the settings
+    save_settings(app_instance->wifi_ssid_logged_in, app_instance->wifi_password_logged_in, app_instance->login_username_logged_out, app_instance->login_username_logged_in, app_instance->login_password_logged_out, app_instance->change_password_logged_in, app_instance->is_logged_in);
+
+    // update the wifi settings
+    if (strlen(app->wifi_ssid_logged_in) > 0 && strlen(app->wifi_password_logged_in) > 0)
+    {
+        if (!flipper_http_save_wifi(app->wifi_ssid_logged_in, app->wifi_password_logged_in))
+        {
+            FURI_LOG_E(TAG, "Failed to save wifi settings via UART");
+            FURI_LOG_E(TAG, "Make sure the Flipper is connected to the Wifi Dev Board");
+        }
+    }
+
+    view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedInSettingsWifi);
+}
+
+/**
+ * @brief Text input callback for when the user finishes entering their password on the wifi settings (logged in) screen.
+ * @param context The context - FlipSocialApp object.
+ * @return void
+ */
+static void flip_social_logged_in_wifi_settings_password_updated(void *context)
+{
+    FlipSocialApp *app = (FlipSocialApp *)context;
+    if (!app)
+    {
+        FURI_LOG_E(TAG, "FlipSocialApp is NULL");
+        return;
+    }
+    // Store the entered password
+    strncpy(app->wifi_password_logged_in, app->wifi_password_logged_in_temp_buffer, app->wifi_password_logged_in_temp_buffer_size);
+
+    // Store the entered password in the logged out password
+    strncpy(app->login_password_logged_out, app->wifi_password_logged_in, app->wifi_password_logged_in_temp_buffer_size);
+    strncpy(app->login_password_logged_out_temp_buffer, app->wifi_password_logged_in, app->wifi_password_logged_in_temp_buffer_size);
+
+    // Ensure null-termination
+    app->wifi_password_logged_in[app->wifi_password_logged_in_temp_buffer_size - 1] = '\0';
+
+    // Update the password item text
+    if (app->variable_item_logged_in_wifi_settings_password)
+    {
+        // dont show the password on the screen (version 0.2)
+        // variable_item_set_current_value_text(app->variable_item_logged_in_wifi_settings_password, app->wifi_password_logged_in);
+    }
+
+    // Save the settings
+    save_settings(app_instance->wifi_ssid_logged_in, app_instance->wifi_password_logged_in, app_instance->login_username_logged_out, app_instance->login_username_logged_in, app_instance->login_password_logged_out, app_instance->change_password_logged_in, app_instance->is_logged_in);
+
+    // update the wifi settings
+    if (strlen(app->wifi_ssid_logged_in) > 0 && strlen(app->wifi_password_logged_in) > 0)
+    {
+        if (!flipper_http_save_wifi(app->wifi_ssid_logged_in, app->wifi_password_logged_in))
+        {
+            FURI_LOG_E(TAG, "Failed to save wifi settings via UART");
+            FURI_LOG_E(TAG, "Make sure the Flipper is connected to the Wifi Dev Board");
+        }
+    }
+
+    view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedInSettingsWifi);
+}
+
+/**
+ * @brief Callback when the user selects a menu item in the wifi settings (logged in) screen.
+ * @param context The context - FlipSocialApp object.
+ * @param index The index of the selected item.
+ * @return void
+ */
+static void flip_social_text_input_logged_in_wifi_settings_item_selected(void *context, uint32_t index)
+{
+    FlipSocialApp *app = (FlipSocialApp *)context;
+    if (!app)
+    {
+        FURI_LOG_E(TAG, "FlipSocialApp is NULL");
+        return;
+    }
+    switch (index)
+    {
+    case 0: // Input SSID
+        view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedInWifiSettingsSSIDInput);
+        break;
+    case 1: // Input Password
+        view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedInWifiSettingsPasswordInput);
+        break;
+    default:
+        FURI_LOG_E(TAG, "Unknown configuration item index");
+        break;
+    }
+}
+
+/**
+ * @brief Text input callback for when the user finishes entering their message on the compose (logged in) screen for Add Text
+ * @param context The context - FlipSocialApp object.
+ * @return void
+ */
+static void flip_social_logged_in_compose_pre_save_updated(void *context)
+{
+    FlipSocialApp *app = (FlipSocialApp *)context;
+    if (!app)
+    {
+        FURI_LOG_E(TAG, "FlipSocialApp is NULL");
+        return;
+    }
+
+    // check if the message is empty or if adding in the message would exceed the MAX_PRE_SAVED_MESSAGES
+    if (app->compose_pre_save_logged_in_temp_buffer_size == 0 || app->pre_saved_messages.count >= MAX_PRE_SAVED_MESSAGES)
+    {
+        FURI_LOG_E(TAG, "Message is empty or would exceed the maximum number of pre-saved messages");
+        view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedInCompose);
+        return;
+    }
+
+    // Store the entered message
+    strncpy(app->compose_pre_save_logged_in, app->compose_pre_save_logged_in_temp_buffer, app->compose_pre_save_logged_in_temp_buffer_size);
+
+    // Ensure null-termination
+    app->compose_pre_save_logged_in[app->compose_pre_save_logged_in_temp_buffer_size - 1] = '\0';
+
+    // add the item to the submenu
+    submenu_reset(app->submenu_compose);
+
+    // loop through the items and add them to the submenu
+    app->pre_saved_messages.messages[app->pre_saved_messages.count] = app->compose_pre_save_logged_in;
+    app->pre_saved_messages.count++;
+
+    submenu_add_item(app->submenu_compose, "Add Pre-Save", FlipSocialSubmenuComposeIndexAddPreSave, flip_social_callback_submenu_choices, app);
+    for (uint32_t i = 0; i < app->pre_saved_messages.count; i++)
+    {
+        submenu_add_item(app->submenu_compose, app->pre_saved_messages.messages[i], FlipSocialSubemnuComposeIndexStartIndex + i, flip_social_callback_submenu_choices, app);
+    }
+
+    // save playlist
+    save_playlist(&app->pre_saved_messages);
+
+    view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedInCompose);
+}
+
+/**
+ * @brief Text input callback for when the user finishes entering their message on the profile (logged in) screen for change password
+ * @param context The context - FlipSocialApp object.
+ * @return void
+ */
+static void flip_social_logged_in_profile_change_password_updated(void *context)
+{
+    FlipSocialApp *app = (FlipSocialApp *)context;
+    if (!app)
+    {
+        FURI_LOG_E(TAG, "FlipSocialApp is NULL");
+        return;
+    }
+    // Correct type: old_pass should be a pointer to a string (char *)
+    const char *old_password = app->login_password_logged_out;
+
+    // Store the entered message
+    strncpy(app->change_password_logged_in, app->change_password_logged_in_temp_buffer, app->change_password_logged_in_temp_buffer_size);
+
+    // store the entered password in the logged out password
+    strncpy(app->login_password_logged_out, app->change_password_logged_in, app->login_password_logged_out_temp_buffer_size);
+    strncpy(app->login_password_logged_out_temp_buffer, app->change_password_logged_in, app->login_password_logged_out_temp_buffer_size);
+
+    // Ensure null-termination
+    app->change_password_logged_in[app->change_password_logged_in_temp_buffer_size - 1] = '\0';
+
+    // Update the message item text
+    if (app->variable_item_logged_in_profile_change_password)
+    {
+        // dont show the password on the screen (version 0.2)
+        // variable_item_set_current_value_text(app->variable_item_logged_in_profile_change_password, app->change_password_logged_in);
+    }
+
+    // send post request to change password
+    char payload[256];
+    snprintf(payload, sizeof(payload), "{\"username\":\"%s\",\"old_password\":\"%s\",\"new_password\":\"%s\"}", app->login_username_logged_out, old_password, app->change_password_logged_in);
+    char *headers = jsmn("Content-Type", "application/json");
+    if (!flipper_http_post_request_with_headers("https://www.flipsocial.net/api/user/change-password/", headers, payload))
+    {
+        FURI_LOG_E(TAG, "Failed to send post request to change password");
+        FURI_LOG_E(TAG, "Make sure the Flipper is connected to the Wifi Dev Board");
+        free(headers);
+        return;
+    }
+    free(headers);
+    // Save the settings
+    save_settings(app_instance->wifi_ssid_logged_out, app_instance->wifi_password_logged_out, app_instance->login_username_logged_out, app_instance->login_username_logged_in, app_instance->login_password_logged_out, app_instance->change_password_logged_in, app_instance->is_logged_in);
+
+    view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedInProfile);
+}
+
+/**
+ * @brief Callback when a user selects a menu item in the profile (logged in) screen.
+ * @param context The context - FlipSocialApp object.
+ * @param index The index of the selected item.
+ * @return void
+ */
+static void flip_social_text_input_logged_in_profile_item_selected(void *context, uint32_t index)
+{
+    FlipSocialApp *app = (FlipSocialApp *)context;
+    if (!app)
+    {
+        FURI_LOG_E(TAG, "FlipSocialApp is NULL");
+        return;
+    }
+    switch (index)
+    {
+    case 0: // Change Username
+        // do nothing since username cannot be changed
+        break;
+    case 1: // Change Password
+        view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedInChangePasswordInput);
+        break;
+    case 2: // Friends
+        // get friends then switch to the friends screen
+        if (flip_social_get_friends()) // start the async friends request
+        {
+            furi_timer_start(fhttp.get_timeout_timer, TIMEOUT_DURATION_TICKS);
+        }
+        while (fhttp.state == RECEIVING && furi_timer_is_running(fhttp.get_timeout_timer) > 0)
+        {
+            // Wait for the friends to be received
+            furi_delay_ms(100);
+        }
+        furi_timer_stop(fhttp.get_timeout_timer);
+        if (!flip_social_parse_json_friends()) // parse the JSON before switching to the friends (synchonous)
+        {
+            FURI_LOG_E(TAG, "Failed to parse the JSON friends...");
+            return; // just return for now, no temporary friends yet
+            // show a popup message saving wifi is disconnected
+        }
+        furi_timer_stop(fhttp.get_timeout_timer);
+        view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedInFriendsSubmenu);
+        break;
+    default:
+        FURI_LOG_E(TAG, "Unknown configuration item index");
+        break;
+    }
+}
+
+/**
+ * @brief Callback when a user selects a menu item in the settings (logged in) screen.
+ * @param context The context - FlipSocialApp object.
+ * @param index The index of the selected item.
+ * @return void
+ */
+static void flip_social_text_input_logged_in_settings_item_selected(void *context, uint32_t index)
+{
+    FlipSocialApp *app = (FlipSocialApp *)context;
+    if (!app)
+    {
+        FURI_LOG_E(TAG, "FlipSocialApp is NULL");
+        return;
+    }
+    switch (index)
+    {
+    case 0: // About
+        view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedInSettingsAbout);
+        break;
+    case 1: // Wifi
+        view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedInSettingsWifi);
+        break;
+    default:
+        break;
+    }
+}
+
+/**
+ * @brief Text input callback for when the user finishes entering their message to send to the selected user choice (user choice messages view)
+ * @param context The context - FlipSocialApp object.
+ * @return void
+ */
+static void flip_social_logged_in_messages_user_choice_message_updated(void *context)
+{
+    FlipSocialApp *app = (FlipSocialApp *)context;
+    if (!app)
+    {
+        FURI_LOG_E(TAG, "FlipSocialApp is NULL");
+        return;
+    }
+
+    // check if the message is empty
+    if (app->message_user_choice_logged_in_temp_buffer_size == 0)
+    {
+        FURI_LOG_E(TAG, "Message is empty");
+        view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedInMessagesNewMessageUserChoicesInput);
+        return;
+    }
+
+    // Store the entered message
+    strncpy(app->message_user_choice_logged_in, app->message_user_choice_logged_in_temp_buffer, app->message_user_choice_logged_in_temp_buffer_size);
+
+    // Ensure null-termination
+    app->message_user_choice_logged_in[app->message_user_choice_logged_in_temp_buffer_size - 1] = '\0';
+
+    // send post request to send message
+    char url[128];
+    char payload[256];
+    snprintf(url, sizeof(url), "https://www.flipsocial.net/api/messages/%s/post/", app->login_username_logged_in);
+    snprintf(payload, sizeof(payload), "{\"receiver\":\"%s\",\"content\":\"%s\"}", flip_social_explore->usernames[flip_social_explore->index], app->message_user_choice_logged_in);
+    char *headers = jsmn("Content-Type", "application/json");
+
+    if (flipper_http_post_request_with_headers(url, headers, payload)) // start the async request
+    {
+        furi_timer_start(fhttp.get_timeout_timer, TIMEOUT_DURATION_TICKS);
+        fhttp.state = RECEIVING;
+        free(headers);
+    }
+    else
+    {
+        FURI_LOG_E(TAG, "Failed to send post request to send message");
+        FURI_LOG_E(TAG, "Make sure the Flipper is connected to the Wifi Dev Board");
+        free(headers);
+        return;
+    }
+    while (fhttp.state == RECEIVING && furi_timer_is_running(fhttp.get_timeout_timer) > 0)
+    {
+        // Wait for the request to be received
+        furi_delay_ms(100);
+    }
+    furi_timer_stop(fhttp.get_timeout_timer);
+
+    // add user to the message list
+    strncpy(flip_social_message_users->usernames[flip_social_message_users->count], flip_social_explore->usernames[flip_social_explore->index], strlen(flip_social_explore->usernames[flip_social_explore->index]));
+    flip_social_message_users->count++;
+
+    // redraw submenu
+    flip_social_update_messages_submenu();
+    view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedInMessagesSubmenu);
+}
+
+/**
+ * @brief Text input callback for when the user finishes entering their message to the selected user (messages view)
+ * @param context The context - FlipSocialApp object.
+ * @return void
+ */
+static void flip_social_logged_in_messages_new_message_updated(void *context)
+{
+    FlipSocialApp *app = (FlipSocialApp *)context;
+    if (!app)
+    {
+        FURI_LOG_E(TAG, "FlipSocialApp is NULL");
+        return;
+    }
+
+    // check if the message is empty
+    if (app->messages_new_message_logged_in_temp_buffer_size == 0)
+    {
+        FURI_LOG_E(TAG, "Message is empty");
+        view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedInMessagesNewMessageInput);
+        return;
+    }
+
+    // Store the entered message
+    strncpy(app->messages_new_message_logged_in, app->messages_new_message_logged_in_temp_buffer, app->messages_new_message_logged_in_temp_buffer_size);
+
+    // Ensure null-termination
+    app->messages_new_message_logged_in[app->messages_new_message_logged_in_temp_buffer_size - 1] = '\0';
+
+    // send post request to send message
+    char url[128];
+    char payload[256];
+    snprintf(url, sizeof(url), "https://www.flipsocial.net/api/messages/%s/post/", app->login_username_logged_in);
+    snprintf(payload, sizeof(payload), "{\"receiver\":\"%s\",\"content\":\"%s\"}", flip_social_message_users->usernames[flip_social_message_users->index], app->messages_new_message_logged_in);
+    char *headers = jsmn("Content-Type", "application/json");
+
+    if (flipper_http_post_request_with_headers(url, headers, payload)) // start the async request
+    {
+        furi_timer_start(fhttp.get_timeout_timer, TIMEOUT_DURATION_TICKS);
+        fhttp.state = RECEIVING;
+        free(headers);
+    }
+    else
+    {
+        FURI_LOG_E(TAG, "Failed to send post request to send message");
+        FURI_LOG_E(TAG, "Make sure the Flipper is connected to the Wifi Dev Board");
+        free(headers);
+        return;
+    }
+    while (fhttp.state == RECEIVING && furi_timer_is_running(fhttp.get_timeout_timer) > 0)
+    {
+        // Wait for the request to be received
+        furi_delay_ms(100);
+    }
+    furi_timer_stop(fhttp.get_timeout_timer);
+    view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedInMessagesSubmenu);
+}
+
+#endif // FLIP_SOCIAL_CALLBACK_H

+ 1008 - 0
flip_social/flip_social_draw.h

@@ -0,0 +1,1008 @@
+#ifndef FLIP_SOCIAL_DRAW_H
+#define FLIP_SOCIAL_DRAW_H
+
+bool flip_social_sent_login_request = false;
+bool flip_social_sent_register_request = false;
+bool flip_social_login_success = false;
+bool flip_social_register_success = false;
+bool flip_social_dialog_shown = false;
+bool flip_social_dialog_stop = false;
+char *last_explore_response = "";
+static bool flip_social_update_friends();
+
+bool flip_social_board_is_active(Canvas *canvas)
+{
+    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 false;
+    }
+    return true;
+}
+
+void flip_social_handle_error(Canvas *canvas)
+{
+    if (fhttp.received_data != NULL)
+    {
+        if (strstr(fhttp.received_data, "[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.received_data, "[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
+        {
+            canvas_draw_str(canvas, 0, 42, "Failed...");
+            canvas_draw_str(canvas, 0, 52, "Update your credentials.");
+            canvas_draw_str(canvas, 0, 62, "Press BACK to return.");
+        }
+    }
+    else
+    {
+        canvas_draw_str(canvas, 0, 42, "Failed...");
+        canvas_draw_str(canvas, 0, 52, "Update your credentials.");
+        canvas_draw_str(canvas, 0, 62, "Press BACK to return.");
+    }
+}
+
+static void on_input(const void *event, void *ctx)
+{
+    UNUSED(ctx);
+
+    InputKey key = ((InputEvent *)event)->key;
+    InputType type = ((InputEvent *)event)->type;
+
+    if (type != InputTypeRelease)
+    {
+        return;
+    }
+
+    switch (key)
+    {
+    case InputKeyOk:
+        action = ActionFlip;
+        break;
+    case InputKeyBack:
+        action = ActionBack;
+        break;
+    case InputKeyRight:
+        action = ActionNext;
+        break;
+    case InputKeyLeft:
+        action = ActionPrev;
+        break;
+    case InputKeyUp:
+        action = ActionPrev;
+        break;
+    case InputKeyDown:
+        action = ActionNext;
+        break;
+    default:
+        action = ActionNone;
+        break;
+    }
+}
+
+// Function to draw the message on the canvas with word wrapping
+void draw_user_message(Canvas *canvas, const char *user_message, int x, int y)
+{
+    if (user_message == NULL)
+    {
+        FURI_LOG_E(TAG, "User message is NULL.");
+        return;
+    }
+
+    size_t msg_length = strlen(user_message);
+    size_t start = 0;
+    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)
+    {
+        size_t remaining = msg_length - start;
+        size_t len = (remaining > MAX_LINE_LENGTH) ? MAX_LINE_LENGTH : remaining;
+
+        if (remaining > MAX_LINE_LENGTH)
+        {
+            // Find the last space within the first 'len' characters
+            size_t last_space = len;
+            while (last_space > 0 && user_message[start + last_space - 1] != ' ')
+            {
+                last_space--;
+            }
+
+            if (last_space > 0)
+            {
+                len = last_space; // Adjust len to the position of the last space
+            }
+        }
+
+        // Copy the substring to 'line' and null-terminate it
+        memcpy(line, user_message + start, len);
+        line[len] = '\0'; // Ensure the string is null-terminated
+
+        // Draw the string on the canvas
+        // Adjust the y-coordinate based on the line number
+        canvas_draw_str_aligned(canvas, x, y + line_num * 10, AlignLeft, AlignTop, line);
+
+        // Update the start position for the next line
+        start += len;
+
+        // Skip any spaces to avoid leading spaces on the next line
+        while (start < msg_length && user_message[start] == ' ')
+        {
+            start++;
+        }
+
+        // Increment the line number
+        line_num++;
+    }
+}
+
+static void flip_social_callback_draw_compose(Canvas *canvas, void *model)
+{
+    UNUSED(model);
+    if (!canvas)
+    {
+        FURI_LOG_E(TAG, "Canvas is NULL");
+        return;
+    }
+    if (!app_instance)
+    {
+        FURI_LOG_E(TAG, "FlipSocialApp is NULL");
+        return;
+    }
+
+    char *message = app_instance->pre_saved_messages.messages[flip_social_feed->index];
+
+    if (!flip_social_dialog_shown)
+    {
+        flip_social_dialog_shown = true;
+        app_instance->input_event_queue = furi_record_open(RECORD_INPUT_EVENTS);
+        app_instance->input_event = furi_pubsub_subscribe(app_instance->input_event_queue, on_input, NULL);
+    }
+
+    draw_user_message(canvas, message, 0, 2);
+
+    canvas_draw_icon(canvas, 0, 53, &I_ButtonLeft_4x7);
+    canvas_draw_str_aligned(canvas, 7, 54, AlignLeft, AlignTop, "Delete");
+    canvas_draw_icon(canvas, 52, 53, &I_ButtonBACK_10x8);
+    canvas_draw_str_aligned(canvas, 64, 54, AlignLeft, AlignTop, "Back");
+    canvas_draw_icon(canvas, 100, 53, &I_ButtonRight_4x7);
+    canvas_draw_str_aligned(canvas, 107, 54, AlignLeft, AlignTop, "Post");
+
+    // handle action
+    switch (action)
+    {
+    case ActionNone:
+        break;
+    case ActionBack:
+        flip_social_dialog_stop = true;
+        break;
+    case ActionNext:
+        // send message
+        if (message && app_instance->login_username_logged_in)
+        {
+            // Send the message
+            char command[128];
+            snprintf(command, sizeof(command), "{\"username\":\"%s\",\"content\":\"%s\"}",
+                     app_instance->login_username_logged_in, message);
+
+            bool success = flipper_http_post_request_with_headers(
+                "https://www.flipsocial.net/api/feed/post/",
+                "{\"Content-Type\":\"application/json\"}",
+                command);
+
+            if (!success)
+            {
+                FURI_LOG_E(TAG, "Failed to send HTTP request for feed");
+                furi_check(success); // Log the error with furi_check
+                return;              // Exit early to avoid further errors
+            }
+
+            fhttp.state = RECEIVING;
+            furi_timer_start(fhttp.get_timeout_timer, TIMEOUT_DURATION_TICKS);
+        }
+        else
+        {
+            FURI_LOG_E(TAG, "Message or username is NULL");
+            furi_check(false); // Log as an error and return
+            return;
+        }
+
+        int i = 0;
+        while (fhttp.state == RECEIVING && furi_timer_is_running(fhttp.get_timeout_timer) > 0)
+        {
+            // Wait for the feed to be received
+            furi_delay_ms(100);
+
+            char dots_str[64] = "Receiving";
+
+            // Append dots to the string based on the value of i
+            int dot_count = i % 4;
+            int len = strlen(dots_str);
+            snprintf(dots_str + len, sizeof(dots_str) - len, "%.*s", dot_count, "....");
+
+            // Draw the resulting string on the canvas
+            canvas_draw_str(canvas, 0, 30, dots_str);
+
+            i++;
+        }
+        flip_social_dialog_stop = true;
+        furi_timer_stop(fhttp.get_timeout_timer);
+        break;
+    case ActionPrev:
+        // delete message
+        app_instance->pre_saved_messages.messages[flip_social_feed->index] = NULL;
+
+        for (uint32_t i = flip_social_feed->index; i < app_instance->pre_saved_messages.count - 1; i++)
+        {
+            app_instance->pre_saved_messages.messages[i] = app_instance->pre_saved_messages.messages[i + 1];
+        }
+        app_instance->pre_saved_messages.count--;
+
+        // add the item to the submenu
+        submenu_reset(app_instance->submenu_compose);
+
+        submenu_add_item(app_instance->submenu_compose, "Add Pre-Save", FlipSocialSubmenuComposeIndexAddPreSave, flip_social_callback_submenu_choices, app_instance);
+
+        for (uint32_t i = 0; i < app_instance->pre_saved_messages.count; i++)
+        {
+            submenu_add_item(app_instance->submenu_compose, app_instance->pre_saved_messages.messages[i], FlipSocialSubemnuComposeIndexStartIndex + i, flip_social_callback_submenu_choices, app_instance);
+        }
+
+        // save playlist
+        save_playlist(&app_instance->pre_saved_messages);
+
+        flip_social_dialog_stop = true;
+        break;
+    default:
+        action = ActionNone;
+        break;
+    }
+
+    if (flip_social_dialog_stop)
+    {
+        furi_pubsub_unsubscribe(app_instance->input_event_queue, app_instance->input_event);
+        flip_social_dialog_shown = false;
+        flip_social_dialog_stop = false;
+        if (action == ActionNext)
+        {
+            action = ActionNone;
+            view_dispatcher_switch_to_view(app_instance->view_dispatcher, FlipSocialViewLoggedInFeed);
+        }
+        else if (action == ActionBack)
+        {
+            action = ActionNone;
+            view_dispatcher_switch_to_view(app_instance->view_dispatcher, FlipSocialViewLoggedInSubmenu);
+        }
+        else
+        {
+            action = ActionNone;
+            view_dispatcher_switch_to_view(app_instance->view_dispatcher, FlipSocialViewLoggedInCompose);
+        }
+    }
+}
+// function to draw the dialog canvas
+static void flip_social_canvas_draw_message(Canvas *canvas, char *user_username, char *user_message, bool is_flipped, bool show_prev, bool show_next, int flip_count)
+{
+    canvas_set_color(canvas, ColorBlack);
+    canvas_set_font(canvas, FontPrimary);
+    canvas_draw_str_aligned(canvas, 64, 5, AlignCenter, AlignCenter, user_username);
+    canvas_set_font(canvas, FontSecondary);
+
+    char flip_count_str[12];
+    if (flip_count == 1)
+    {
+        snprintf(flip_count_str, sizeof(flip_count_str), "%d Flip", flip_count);
+        canvas_draw_str_aligned(canvas, 106, 54, AlignLeft, AlignTop, flip_count_str);
+    }
+    else
+    {
+        snprintf(flip_count_str, sizeof(flip_count_str), "%d Flips", flip_count);
+
+        if (flip_count < 10)
+        {
+            canvas_draw_str_aligned(canvas, 100, 54, AlignLeft, AlignTop, flip_count_str);
+        }
+        else if (flip_count < 100)
+        {
+            canvas_draw_str_aligned(canvas, 94, 54, AlignLeft, AlignTop, flip_count_str);
+        }
+        else
+        {
+            canvas_draw_str_aligned(canvas, 88, 54, AlignLeft, AlignTop, flip_count_str);
+        }
+    }
+
+    draw_user_message(canvas, user_message, 0, 12);
+
+    // combine and shift icons/labels around if not show_prev or show_next
+    if (show_prev && show_next && !is_flipped)
+    {
+        canvas_draw_icon(canvas, 0, 54, &I_ButtonLeft_4x7);
+        canvas_draw_str_aligned(canvas, 6, 54, AlignLeft, AlignTop, "Prev");
+        canvas_draw_icon(canvas, 30, 54, &I_ButtonRight_4x7);
+        canvas_draw_str_aligned(canvas, 36, 54, AlignLeft, AlignTop, "Next");
+        canvas_draw_icon(canvas, 58, 54, &I_ButtonOK_7x7);
+        canvas_draw_str_aligned(canvas, 67, 54, AlignLeft, AlignTop, "Flip");
+    }
+    else if (show_prev && !show_next && !is_flipped)
+    {
+        canvas_draw_icon(canvas, 0, 54, &I_ButtonLeft_4x7);
+        canvas_draw_str_aligned(canvas, 6, 54, AlignLeft, AlignTop, "Prev");
+        canvas_draw_icon(canvas, 28, 54, &I_ButtonOK_7x7);
+        canvas_draw_str_aligned(canvas, 37, 54, AlignLeft, AlignTop, "Flip");
+    }
+    else if (!show_prev && show_next && !is_flipped)
+    {
+        canvas_draw_icon(canvas, 0, 54, &I_ButtonRight_4x7);
+        canvas_draw_str_aligned(canvas, 6, 54, AlignLeft, AlignTop, "Next");
+        canvas_draw_icon(canvas, 28, 54, &I_ButtonOK_7x7);
+        canvas_draw_str_aligned(canvas, 37, 54, AlignLeft, AlignTop, "Flip");
+    }
+    else if (show_prev && show_next && is_flipped)
+    {
+        canvas_draw_icon(canvas, 0, 54, &I_ButtonLeft_4x7);
+        canvas_draw_str_aligned(canvas, 6, 54, AlignLeft, AlignTop, "Prev");
+        canvas_draw_icon(canvas, 28, 54, &I_ButtonRight_4x7);
+        canvas_draw_str_aligned(canvas, 34, 54, AlignLeft, AlignTop, "Next");
+        canvas_draw_icon(canvas, 54, 54, &I_ButtonOK_7x7);
+        canvas_draw_str_aligned(canvas, 63, 54, AlignLeft, AlignTop, "UnFlip");
+    }
+    else if (show_prev && !show_next && is_flipped)
+    {
+        canvas_draw_icon(canvas, 0, 54, &I_ButtonLeft_4x7);
+        canvas_draw_str_aligned(canvas, 6, 54, AlignLeft, AlignTop, "Prev");
+        canvas_draw_icon(canvas, 28, 54, &I_ButtonOK_7x7);
+        canvas_draw_str_aligned(canvas, 37, 54, AlignLeft, AlignTop, "UnFlip");
+    }
+    else if (!show_prev && show_next && is_flipped)
+    {
+        canvas_draw_icon(canvas, 0, 54, &I_ButtonRight_4x7);
+        canvas_draw_str_aligned(canvas, 6, 54, AlignLeft, AlignTop, "Next");
+        canvas_draw_icon(canvas, 28, 54, &I_ButtonOK_7x7);
+        canvas_draw_str_aligned(canvas, 37, 54, AlignLeft, AlignTop, "UnFlip");
+    }
+    else if (!show_prev && !show_next && is_flipped)
+    {
+        canvas_draw_icon(canvas, 0, 54, &I_ButtonOK_7x7);
+        canvas_draw_str_aligned(canvas, 9, 54, AlignLeft, AlignTop, "UnFlip");
+    }
+    else
+    {
+        canvas_draw_icon(canvas, 0, 54, &I_ButtonOK_7x7);
+        canvas_draw_str_aligned(canvas, 9, 54, AlignLeft, AlignTop, "Flip");
+    }
+}
+// Callback function to handle the feed dialog
+static void flip_social_callback_draw_feed(Canvas *canvas, void *model)
+{
+    UNUSED(model);
+    if (!canvas)
+    {
+        FURI_LOG_E(TAG, "Canvas is NULL");
+        return;
+    }
+    if (!app_instance)
+    {
+        FURI_LOG_E(TAG, "FlipSocialApp is NULL");
+        return;
+    }
+
+    if (!flip_social_dialog_shown)
+    {
+        flip_social_dialog_shown = true;
+        app_instance->input_event_queue = furi_record_open(RECORD_INPUT_EVENTS);
+        app_instance->input_event = furi_pubsub_subscribe(app_instance->input_event_queue, on_input, NULL);
+    }
+
+    // handle action
+    switch (action)
+    {
+    case ActionNone:
+        flip_social_canvas_draw_message(canvas, flip_social_feed->usernames[flip_social_feed->index], flip_social_feed->messages[flip_social_feed->index], flip_social_feed->is_flipped[flip_social_feed->index], flip_social_feed->index > 0, flip_social_feed->index < flip_social_feed->count - 1, flip_social_feed->flips[flip_social_feed->index]);
+        break;
+    case ActionNext:
+        canvas_clear(canvas);
+        if (flip_social_feed->index < flip_social_feed->count - 1)
+        {
+            flip_social_feed->index++;
+        }
+        flip_social_canvas_draw_message(canvas, flip_social_feed->usernames[flip_social_feed->index], flip_social_feed->messages[flip_social_feed->index], flip_social_feed->is_flipped[flip_social_feed->index], flip_social_feed->index > 0, flip_social_feed->index < flip_social_feed->count - 1, flip_social_feed->flips[flip_social_feed->index]);
+        action = ActionNone;
+        break;
+    case ActionPrev:
+        canvas_clear(canvas);
+        if (flip_social_feed->index > 0)
+        {
+            flip_social_feed->index--;
+        }
+        flip_social_canvas_draw_message(canvas, flip_social_feed->usernames[flip_social_feed->index], flip_social_feed->messages[flip_social_feed->index], flip_social_feed->is_flipped[flip_social_feed->index], flip_social_feed->index > 0, flip_social_feed->index < flip_social_feed->count - 1, flip_social_feed->flips[flip_social_feed->index]);
+        action = ActionNone;
+        break;
+    case ActionFlip:
+        canvas_clear(canvas);
+        // Moved to above the is_flipped check
+        if (!flip_social_feed->is_flipped[flip_social_feed->index])
+        {
+            // increase the flip count
+            flip_social_feed->flips[flip_social_feed->index]++;
+        }
+        else
+        {
+            // decrease the flip count
+            flip_social_feed->flips[flip_social_feed->index]--;
+        }
+        // change the flip status
+        flip_social_feed->is_flipped[flip_social_feed->index] = !flip_social_feed->is_flipped[flip_social_feed->index];
+        // send post request to flip the message
+        if (app_instance->login_username_logged_in == NULL)
+        {
+            FURI_LOG_E(TAG, "Username is NULL");
+            return;
+        }
+        char payload[256];
+        snprintf(payload, sizeof(payload), "{\"username\":\"%s\",\"post_id\":\"%u\"}", app_instance->login_username_logged_in, flip_social_feed->ids[flip_social_feed->index]);
+        flipper_http_post_request_with_headers("https://www.flipsocial.net/api/feed/flip/", "{\"Content-Type\":\"application/json\"}", payload);
+        flip_social_canvas_draw_message(canvas, flip_social_feed->usernames[flip_social_feed->index], flip_social_feed->messages[flip_social_feed->index], flip_social_feed->is_flipped[flip_social_feed->index], flip_social_feed->index > 0, flip_social_feed->index < flip_social_feed->count - 1, flip_social_feed->flips[flip_social_feed->index]);
+        action = ActionNone;
+        break;
+    case ActionBack:
+        canvas_clear(canvas);
+        flip_social_dialog_stop = true;
+        flip_social_feed->index = 0;
+        action = ActionNone;
+        break;
+    default:
+        break;
+    }
+
+    if (flip_social_dialog_stop)
+    {
+        furi_pubsub_unsubscribe(app_instance->input_event_queue, app_instance->input_event);
+        flip_social_dialog_shown = false;
+        flip_social_dialog_stop = false;
+        action = ActionNone;
+    }
+}
+/**
+ * @brief Navigation callback for asynchonously handling the login process.
+ * @param canvas The canvas to draw on.
+ * @param model The model - unused
+ * @return void
+ */
+static void flip_social_callback_draw_login(Canvas *canvas, void *model)
+{
+    UNUSED(model);
+    if (!canvas)
+    {
+        FURI_LOG_E(TAG, "Canvas is NULL");
+        return;
+    }
+
+    canvas_set_font(canvas, FontSecondary);
+
+    if (!flip_social_board_is_active(canvas))
+    {
+        return;
+    }
+
+    canvas_draw_str(canvas, 0, 7, "Logging in...");
+
+    // Perform login request
+    if (!flip_social_sent_login_request)
+    {
+
+        if (!app_instance->login_username_logged_out || !app_instance->login_password_logged_out || strlen(app_instance->login_username_logged_out) == 0 || strlen(app_instance->login_password_logged_out) == 0)
+        {
+            canvas_clear(canvas);
+            canvas_draw_str(canvas, 0, 10, "Please enter your credentials.");
+            canvas_draw_str(canvas, 0, 60, "Press BACK to return.");
+            return;
+        }
+
+        flip_social_sent_login_request = true;
+
+        char buffer[256];
+        snprintf(buffer, sizeof(buffer), "{\"username\":\"%s\",\"password\":\"%s\"}", app_instance->login_username_logged_out, app_instance->login_password_logged_out);
+        flip_social_login_success = flipper_http_post_request_with_headers("https://www.flipsocial.net/api/user/login/", "{\"Content-Type\":\"application/json\"}", buffer);
+        if (flip_social_login_success)
+        {
+            fhttp.state = RECEIVING;
+            return;
+        }
+        else
+        {
+            fhttp.state = ISSUE;
+            return;
+        }
+    }
+    // handle response
+    if (flip_social_sent_login_request && flip_social_login_success)
+    {
+        canvas_set_font(canvas, FontSecondary);
+        canvas_draw_str(canvas, 0, 17, "Request Sent!");
+        canvas_draw_str(canvas, 0, 32, "Awaiting reponse...");
+
+        if (fhttp.state == IDLE && fhttp.received_data != NULL)
+        {
+            // read response
+            if (strstr(fhttp.received_data, "[SUCCESS]") != NULL || strstr(fhttp.received_data, "User found") != NULL)
+            {
+                canvas_draw_str(canvas, 0, 42, "Login successful!");
+                canvas_draw_str(canvas, 0, 62, "Welcome back!");
+
+                app_instance->is_logged_in = "true";
+
+                // set the logged_in_username and change_password_logged_in
+                if (app_instance->login_username_logged_out)
+                {
+                    strcpy(app_instance->login_username_logged_in, app_instance->login_username_logged_out);
+                }
+                if (app_instance->login_password_logged_out)
+                {
+                    app_instance->change_password_logged_in = app_instance->login_password_logged_out;
+                }
+
+                save_settings(app_instance->wifi_ssid_logged_out, app_instance->wifi_password_logged_out, app_instance->login_username_logged_out, app_instance->login_username_logged_in, app_instance->login_password_logged_out, app_instance->change_password_logged_in, app_instance->is_logged_in);
+
+                // send user to the logged in submenu
+                view_dispatcher_switch_to_view(app_instance->view_dispatcher, FlipSocialViewLoggedInSubmenu);
+            }
+            else if (strstr(fhttp.received_data, "User not found") != NULL)
+            {
+                canvas_clear(canvas);
+                canvas_draw_str(canvas, 0, 10, "Account not found...");
+                canvas_draw_str(canvas, 0, 60, "Press BACK to return.");
+            }
+            else
+            {
+                flip_social_handle_error(canvas);
+            }
+        }
+        else if ((fhttp.state == ISSUE || fhttp.state == INACTIVE) && fhttp.received_data != NULL)
+        {
+            flip_social_handle_error(canvas);
+        }
+        else if (fhttp.state == IDLE && fhttp.received_data == NULL)
+        {
+            flip_social_handle_error(canvas);
+        }
+    }
+    else if (flip_social_sent_login_request && !flip_social_login_success)
+    {
+        canvas_clear(canvas);
+        canvas_draw_str(canvas, 0, 10, "Failed sending request.");
+        canvas_draw_str(canvas, 0, 50, "Update your WiFi settings.");
+        canvas_draw_str(canvas, 0, 62, "Press BACK to return.");
+    }
+}
+
+/**
+ * @brief Navigation callback for asynchonously handling the register process.
+ * @param canvas The canvas to draw on.
+ * @param model The model - unused
+ * @return void
+ */
+static void flip_social_callback_draw_register(Canvas *canvas, void *model)
+{
+    UNUSED(model);
+    if (!canvas)
+    {
+        FURI_LOG_E(TAG, "Canvas is NULL");
+        return;
+    }
+
+    canvas_set_font(canvas, FontSecondary);
+
+    if (!flip_social_board_is_active(canvas))
+    {
+        return;
+    }
+
+    canvas_draw_str(canvas, 0, 7, "Registering...");
+
+    // Perform login request
+    if (!flip_social_sent_register_request)
+    {
+        // check if the username and password are valid
+        if (!app_instance->register_username_logged_out || !app_instance->register_password_logged_out || strlen(app_instance->register_username_logged_out) == 0 || strlen(app_instance->register_password_logged_out) == 0)
+        {
+            canvas_clear(canvas);
+            canvas_draw_str(canvas, 0, 10, "Please enter your credentials.");
+            canvas_draw_str(canvas, 0, 60, "Press BACK to return.");
+            return;
+        }
+
+        // check if both passwords match
+        if (strcmp(app_instance->register_password_logged_out, app_instance->register_password_2_logged_out) != 0)
+        {
+            canvas_clear(canvas);
+            canvas_draw_str(canvas, 0, 10, "Passwords do not match.");
+            canvas_draw_str(canvas, 0, 60, "Press BACK to return.");
+            return;
+        }
+
+        char buffer[128];
+        snprintf(buffer, sizeof(buffer), "{\"username\":\"%s\",\"password\":\"%s\"}", app_instance->register_username_logged_out, app_instance->register_password_logged_out);
+        flip_social_register_success = flipper_http_post_request_with_headers("https://www.flipsocial.net/api/user/register/", "{\"Content-Type\":\"application/json\"}", buffer);
+
+        flip_social_sent_register_request = true;
+        if (flip_social_register_success)
+        {
+            // Set the state to RECEIVING to ensure we continue to see the receiving message
+            fhttp.state = RECEIVING;
+        }
+        else
+        {
+            fhttp.state = ISSUE;
+        }
+    }
+    // handle response
+    if (flip_social_sent_register_request && flip_social_register_success)
+    {
+        canvas_set_font(canvas, FontSecondary);
+        canvas_draw_str(canvas, 0, 17, "Request Sent!");
+        canvas_draw_str(canvas, 0, 32, "Awaiting reponse...");
+
+        if (fhttp.state == IDLE)
+        {
+            // read response
+            if (fhttp.received_data != NULL && (strstr(fhttp.received_data, "[SUCCESS]") != NULL || strstr(fhttp.received_data, "User created") != NULL))
+            {
+                canvas_draw_str(canvas, 0, 42, "Registeration successful!");
+                canvas_draw_str(canvas, 0, 62, "Welcome to FlipSocial!");
+
+                // set the login credentials
+                if (app_instance->login_username_logged_out)
+                {
+                    app_instance->login_username_logged_out = app_instance->register_username_logged_out;
+                }
+                if (app_instance->login_password_logged_out)
+                {
+                    app_instance->login_password_logged_out = app_instance->register_password_logged_out;
+                    app_instance->change_password_logged_in = app_instance->register_password_logged_out;
+                }
+                if (app_instance->login_username_logged_in)
+                {
+                    app_instance->login_username_logged_in = app_instance->register_username_logged_out;
+                }
+
+                app_instance->is_logged_in = "true";
+
+                // save the credentials
+                save_settings(app_instance->wifi_ssid_logged_out, app_instance->wifi_password_logged_out, app_instance->login_username_logged_out, app_instance->login_username_logged_in, app_instance->login_password_logged_out, app_instance->change_password_logged_in, app_instance->is_logged_in);
+
+                // send user to the logged in submenu
+                view_dispatcher_switch_to_view(app_instance->view_dispatcher, FlipSocialViewLoggedInSubmenu);
+            }
+            else if (strstr(fhttp.received_data, "Username or password not provided") != NULL)
+            {
+                canvas_clear(canvas);
+                canvas_draw_str(canvas, 0, 10, "Please enter your credentials.");
+                canvas_draw_str(canvas, 0, 60, "Press BACK to return.");
+            }
+            else if (strstr(fhttp.received_data, "User already exists") != NULL || strstr(fhttp.received_data, "Multiple users found") != NULL)
+            {
+                canvas_draw_str(canvas, 0, 42, "Registration failed...");
+                canvas_draw_str(canvas, 0, 52, "Username already exists.");
+                canvas_draw_str(canvas, 0, 62, "Press BACK to return.");
+            }
+            else
+            {
+                canvas_draw_str(canvas, 0, 42, "Registration failed...");
+                canvas_draw_str(canvas, 0, 52, "Update your credentials.");
+                canvas_draw_str(canvas, 0, 62, "Press BACK to return.");
+            }
+        }
+        else if (fhttp.state == ISSUE || fhttp.state == INACTIVE)
+        {
+            flip_social_handle_error(canvas);
+        }
+    }
+    else if (flip_social_sent_register_request && !flip_social_register_success)
+    {
+        canvas_clear(canvas);
+        canvas_draw_str(canvas, 0, 10, "Failed sending request.");
+        canvas_draw_str(canvas, 0, 50, "Update your WiFi settings.");
+        canvas_draw_str(canvas, 0, 62, "Press BACK to return.");
+    }
+}
+
+// function to draw the dialog canvas
+static void flip_social_canvas_draw_explore(Canvas *canvas, char *user_username, char *content)
+{
+    canvas_set_color(canvas, ColorBlack);
+    canvas_set_font(canvas, FontPrimary);
+    canvas_draw_str_aligned(canvas, 64, 5, AlignCenter, AlignCenter, user_username);
+    canvas_set_font(canvas, FontSecondary);
+
+    draw_user_message(canvas, content, 0, 12);
+
+    canvas_set_font(canvas, FontSecondary);
+    canvas_draw_icon(canvas, 0, 53, &I_ButtonLeft_4x7);
+    canvas_draw_str_aligned(canvas, 9, 54, AlignLeft, AlignTop, "Remove");
+    canvas_draw_icon(canvas, 98, 53, &I_ButtonRight_4x7);
+    canvas_draw_str_aligned(canvas, 107, 54, AlignLeft, AlignTop, "Add");
+
+    if (strlen(content) > 0)
+    {
+        last_explore_response = content;
+    }
+}
+
+// Callback function to handle the explore dialog
+static void flip_social_callback_draw_explore(Canvas *canvas, void *model)
+{
+    UNUSED(model);
+    if (!canvas)
+    {
+        FURI_LOG_E(TAG, "Canvas is NULL");
+        return;
+    }
+    if (!app_instance)
+    {
+        FURI_LOG_E(TAG, "FlipSocialApp is NULL");
+        return;
+    }
+
+    if (!flip_social_dialog_shown)
+    {
+        flip_social_dialog_shown = true;
+        app_instance->input_event_queue = furi_record_open(RECORD_INPUT_EVENTS);
+        app_instance->input_event = furi_pubsub_subscribe(app_instance->input_event_queue, on_input, NULL);
+    }
+    flip_social_canvas_draw_explore(canvas, flip_social_explore->usernames[flip_social_explore->index], last_explore_response);
+
+    // handle action
+    switch (action)
+    {
+    case ActionNext:
+        // add friend
+        char add_payload[128];
+        snprintf(add_payload, sizeof(add_payload), "{\"username\":\"%s\",\"friend\":\"%s\"}", app_instance->login_username_logged_in, flip_social_explore->usernames[flip_social_explore->index]);
+        flipper_http_post_request_with_headers("https://www.flipsocial.net/api/user/add-friend/", "{\"Content-Type\":\"application/json\"}", add_payload);
+        canvas_clear(canvas);
+        flip_social_canvas_draw_explore(canvas, flip_social_explore->usernames[flip_social_explore->index], "Added!");
+        action = ActionNone;
+        break;
+    case ActionPrev:
+        // remove friend
+        char remove_payload[128];
+        snprintf(remove_payload, sizeof(remove_payload), "{\"username\":\"%s\",\"friend\":\"%s\"}", app_instance->login_username_logged_in, flip_social_explore->usernames[flip_social_explore->index]);
+        flipper_http_post_request_with_headers("https://www.flipsocial.net/api/user/remove-friend/", "{\"Content-Type\":\"application/json\"}", remove_payload);
+        canvas_clear(canvas);
+        flip_social_canvas_draw_explore(canvas, flip_social_explore->usernames[flip_social_explore->index], "Removed!");
+        action = ActionNone;
+        break;
+    case ActionBack:
+        canvas_clear(canvas);
+        flip_social_dialog_stop = true;
+        last_explore_response = "";
+        flip_social_dialog_shown = false;
+        flip_social_explore->index = 0;
+        action = ActionNone;
+        break;
+    default:
+        break;
+    }
+
+    if (flip_social_dialog_stop)
+    {
+        furi_pubsub_unsubscribe(app_instance->input_event_queue, app_instance->input_event);
+        flip_social_dialog_shown = false;
+        flip_social_dialog_stop = false;
+        action = ActionNone;
+    }
+}
+
+// Callback function to handle the friends dialog
+static void flip_social_callback_draw_friends(Canvas *canvas, void *model)
+{
+    UNUSED(model);
+    if (!canvas)
+    {
+        FURI_LOG_E(TAG, "Canvas is NULL");
+        return;
+    }
+    if (!app_instance)
+    {
+        FURI_LOG_E(TAG, "FlipSocialApp is NULL");
+        return;
+    }
+
+    if (!flip_social_dialog_shown)
+    {
+        flip_social_dialog_shown = true;
+        app_instance->input_event_queue = furi_record_open(RECORD_INPUT_EVENTS);
+        app_instance->input_event = furi_pubsub_subscribe(app_instance->input_event_queue, on_input, NULL);
+    }
+    flip_social_canvas_draw_explore(canvas, flip_social_friends->usernames[flip_social_friends->index], last_explore_response);
+
+    // handle action
+    switch (action)
+    {
+    case ActionNext:
+        // add friend
+        char add_payload[128];
+        snprintf(add_payload, sizeof(add_payload), "{\"username\":\"%s\",\"friend\":\"%s\"}", app_instance->login_username_logged_in, flip_social_friends->usernames[flip_social_friends->index]);
+        if (flipper_http_post_request_with_headers("https://www.flipsocial.net/api/user/add-friend/", "{\"Content-Type\":\"application/json\"}", add_payload))
+        {
+            canvas_clear(canvas);
+            flip_social_canvas_draw_explore(canvas, flip_social_friends->usernames[flip_social_friends->index], "Added!");
+
+            // add the friend to the friends list
+            flip_social_friends->usernames[flip_social_friends->count] = flip_social_friends->usernames[flip_social_friends->index];
+            flip_social_friends->count++;
+            if (!flip_social_update_friends())
+            {
+                FURI_LOG_E(TAG, "Failed to update friends");
+            }
+        }
+        action = ActionNone;
+        break;
+    case ActionPrev:
+        // remove friend
+        char remove_payload[128];
+        snprintf(remove_payload, sizeof(remove_payload), "{\"username\":\"%s\",\"friend\":\"%s\"}", app_instance->login_username_logged_in, flip_social_friends->usernames[flip_social_friends->index]);
+        if (flipper_http_post_request_with_headers("https://www.flipsocial.net/api/user/remove-friend/", "{\"Content-Type\":\"application/json\"}", remove_payload))
+        {
+            canvas_clear(canvas);
+            flip_social_canvas_draw_explore(canvas, flip_social_friends->usernames[flip_social_friends->index], "Removed!");
+
+            // remove the friend from the friends list
+            for (int i = flip_social_friends->index; i < flip_social_friends->count - 1; i++)
+            {
+                flip_social_friends->usernames[i] = flip_social_friends->usernames[i + 1];
+            }
+            flip_social_friends->count--;
+            if (!flip_social_update_friends())
+            {
+                FURI_LOG_E(TAG, "Failed to update friends");
+            }
+        }
+        action = ActionNone;
+        break;
+    case ActionBack:
+        canvas_clear(canvas);
+        flip_social_dialog_stop = true;
+        last_explore_response = "";
+        flip_social_dialog_shown = false;
+        flip_social_friends->index = 0;
+        action = ActionNone;
+        break;
+    default:
+        break;
+    }
+
+    if (flip_social_dialog_stop)
+    {
+        furi_pubsub_unsubscribe(app_instance->input_event_queue, app_instance->input_event);
+        flip_social_dialog_shown = false;
+        flip_social_dialog_stop = false;
+        action = ActionNone;
+    }
+}
+
+static void flip_social_canvas_draw_user_message(Canvas *canvas, char *user_username, char *user_message, bool show_prev, bool show_next)
+{
+    canvas_set_color(canvas, ColorBlack);
+    canvas_set_font(canvas, FontPrimary);
+    canvas_draw_str_aligned(canvas, 64, 5, AlignCenter, AlignCenter, user_username);
+    canvas_set_font(canvas, FontSecondary);
+
+    draw_user_message(canvas, user_message, 0, 12);
+
+    canvas_set_font(canvas, FontSecondary);
+    if (show_prev)
+    {
+        canvas_draw_icon(canvas, 0, 53, &I_ButtonLeft_4x7);
+        canvas_draw_str_aligned(canvas, 9, 54, AlignLeft, AlignTop, "Prev");
+    }
+
+    canvas_draw_icon(canvas, 47, 53, &I_ButtonOK_7x7);
+    canvas_draw_str_aligned(canvas, 56, 54, AlignLeft, AlignTop, "Create");
+
+    if (show_next)
+    {
+        canvas_draw_icon(canvas, 98, 53, &I_ButtonRight_4x7);
+        canvas_draw_str_aligned(canvas, 107, 54, AlignLeft, AlignTop, "Next");
+    }
+}
+
+// Callback function to handle the messages dialog
+static void flip_social_callback_draw_messages(Canvas *canvas, void *model)
+{
+    UNUSED(model);
+    if (!canvas)
+    {
+        FURI_LOG_E(TAG, "Canvas is NULL");
+        return;
+    }
+    if (!app_instance)
+    {
+        FURI_LOG_E(TAG, "FlipSocialApp is NULL");
+        return;
+    }
+
+    if (!flip_social_dialog_shown)
+    {
+        flip_social_dialog_shown = true;
+        app_instance->input_event_queue = furi_record_open(RECORD_INPUT_EVENTS);
+        app_instance->input_event = furi_pubsub_subscribe(app_instance->input_event_queue, on_input, NULL);
+    }
+
+    // handle action
+    switch (action)
+    {
+    case ActionNone:
+        flip_social_canvas_draw_user_message(canvas, flip_social_messages->usernames[flip_social_messages->index], flip_social_messages->messages[flip_social_messages->index], flip_social_messages->index > 0, flip_social_messages->index < flip_social_messages->count - 1);
+        action = ActionNone;
+        break;
+    case ActionNext:
+        // view next message (if any)
+        canvas_clear(canvas);
+        if (flip_social_messages->index < flip_social_messages->count - 1)
+        {
+            flip_social_messages->index++;
+        }
+        flip_social_canvas_draw_user_message(canvas, flip_social_messages->usernames[flip_social_messages->index], flip_social_messages->messages[flip_social_messages->index], flip_social_messages->index > 0, flip_social_messages->index < flip_social_messages->count - 1);
+        action = ActionNone;
+        break;
+    case ActionPrev:
+        // view previous message (if any)
+        canvas_clear(canvas);
+        if (flip_social_messages->index > 0)
+        {
+            flip_social_messages->index--;
+        }
+        flip_social_canvas_draw_user_message(canvas, flip_social_messages->usernames[flip_social_messages->index], flip_social_messages->messages[flip_social_messages->index], flip_social_messages->index > 0, flip_social_messages->index < flip_social_messages->count - 1);
+        action = ActionNone;
+        break;
+    case ActionBack:
+        //  go back to the previous view
+        flip_social_dialog_stop = true;
+        action = ActionNone;
+        break;
+    case ActionFlip:
+        // go to the input view
+        flip_social_dialog_stop = true;
+        break;
+    default:
+        action = ActionNone;
+        break;
+    }
+
+    if (flip_social_dialog_stop)
+    {
+        furi_pubsub_unsubscribe(app_instance->input_event_queue, app_instance->input_event);
+        flip_social_dialog_shown = false;
+        flip_social_dialog_stop = false;
+        if (action == ActionFlip)
+        {
+            action = ActionNone;
+            view_dispatcher_switch_to_view(app_instance->view_dispatcher, FlipSocialViewLoggedInMessagesNewMessageInput);
+        }
+        else
+        {
+            action = ActionNone;
+            view_dispatcher_switch_to_view(app_instance->view_dispatcher, FlipSocialViewLoggedInMessagesSubmenu);
+        }
+    }
+}
+
+#endif // FLIP_SOCIAL_DRAW_H

+ 310 - 0
flip_social/flip_social_e.h

@@ -0,0 +1,310 @@
+// flip_social_e.h
+#ifndef FLIP_SOCIAL_E
+#define FLIP_SOCIAL_E
+#include <easy_flipper.h>
+#include <dialogs/dialogs.h>
+#include <storage/storage.h>
+#include <flipper_http.h>
+#include <input/input.h>
+#include <flip_social_icons.h>
+#define TAG "FlipSocial"
+
+#define MAX_PRE_SAVED_MESSAGES 25 // Maximum number of pre-saved messages
+#define MAX_MESSAGE_LENGTH 100    // Maximum length of a message in the feed
+#define MAX_EXPLORE_USERS 50      // Maximum number of users to explore
+#define MAX_USER_LENGTH 32        // Maximum length of a username
+#define MAX_FRIENDS 50            // Maximum number of friends
+#define MAX_TOKENS 450            // Adjust based on expected JSON tokens
+#define MAX_FEED_ITEMS 41         // Maximum number of feed items
+#define MAX_LINE_LENGTH 30
+#define MAX_MESSAGE_USERS 20 // Maximum number of users to display in the submenu
+#define MAX_MESSAGES 20      // Maximum number of meesages between each user
+
+// Define the submenu items for our Hello World application
+typedef enum
+{
+    FlipSocialSubmenuLoggedOutIndexLogin,        // click to go to the login screen
+    FlipSocialSubmenuLoggedOutIndexRegister,     // click to go to the register screen
+    FlipSocialSubmenuLoggedOutIndexAbout,        // click to go to the about screen
+    FlipSocialSubmenuLoggedOutIndexWifiSettings, // click to go to the wifi settings screen
+    //
+    FlipSocialSubmenuLoggedInIndexProfile,  // click to go to the profile screen
+    FlipSocialSubmenuExploreIndex,          // click to go to the explore
+    FlipSocialSubmenuLoggedInIndexFeed,     // click to go to the feed screen
+    FlipSocialSubmenuLoggedInIndexMessages, // click to go to the messages screen
+    FlipSocialSubmenuLoggedInIndexCompose,  // click to go to the compose screen
+    FlipSocialSubmenuLoggedInIndexSettings, // click to go to the settings screen
+    FlipSocialSubmenuLoggedInSignOutButton, // click to sign out
+    //
+    FlipSocialSubmenuLoggedInIndexMessagesNewMessage, // click to add a new message
+    //
+    FlipSocialSubmenuComposeIndexAddPreSave,       // click to add a pre-saved message
+    FlipSocialSubemnuComposeIndexStartIndex = 100, // starting index for the first pre saved message
+    //
+    FlipSocialSubmenuExploreIndexStartIndex = 150, // starting index for the users to explore
+    //
+    FlipSocialSubmenuLoggedInIndexFriendsStart = 200, // starting index for the friends
+    //
+    FlipSocialSubmenuLoggedInIndexMessagesUsersStart = 250, // starting index for the messages
+    //
+    FlipSocialSubmenuLoggedInIndexMessagesUserChoicesIndexStart = 300, // click to select a user to message
+} FlipSocialSubmenuIndex;
+
+typedef enum
+{
+    ActionNone,
+    ActionBack,
+    ActionNext,
+    ActionPrev,
+    ActionFlip,
+} Action;
+
+static Action action = ActionNone;
+
+// Define the ScriptPlaylist structure
+typedef struct
+{
+    char *messages[MAX_PRE_SAVED_MESSAGES];
+    size_t count;
+} PreSavedPlaylist;
+
+typedef struct
+{
+    char *usernames[MAX_FEED_ITEMS];
+    char *messages[MAX_FEED_ITEMS];
+    bool is_flipped[MAX_FEED_ITEMS];
+    int ids[MAX_FEED_ITEMS];
+    int flips[MAX_FEED_ITEMS];
+    size_t count;
+    size_t index;
+} FlipSocialFeed;
+
+typedef struct
+{
+    char *usernames[MAX_EXPLORE_USERS];
+    int count;
+    int index;
+} FlipSocialModel;
+
+typedef struct
+{
+    char *usernames[MAX_MESSAGE_USERS];
+    int count;
+    int index;
+} FlipSocialModel2;
+
+typedef struct
+{
+    char *usernames[MAX_MESSAGES];
+    char *messages[MAX_MESSAGES];
+    int count;
+    int index;
+} FlipSocialMessage;
+
+// Define views for our Hello World application
+typedef enum
+{
+    FlipSocialViewLoggedOutSubmenu,      // The menu if the user is not logged in
+    FlipSocialViewLoggedOutLogin,        // The login screen
+    FlipSocialViewLoggedOutRegister,     // The register screen
+    FlipSocialViewLoggedOutAbout,        // The about screen
+    FlipSocialViewLoggedOutWifiSettings, // The wifi settings screen
+    //
+    FlipSocialViewLoggedOutLoginUsernameInput,        // Text input screen for username input on login screen
+    FlipSocialViewLoggedOutLoginPasswordInput,        // Text input screen for password input on login screen
+    FlipSocialViewLoggedOutRegisterUsernameInput,     // Text input screen for username input on register screen
+    FlipSocialViewLoggedOutRegisterPasswordInput,     // Text input screen for password input on register screen
+    FlipSocialViewLoggedOutRegisterPassword2Input,    // Text input screen for password 2 input on register screen
+    FlipSocialViewLoggedOutWifiSettingsSSIDInput,     // Text input screen for SSID input on wifi screen
+    FlipSocialViewLoggedOutWifiSettingsPasswordInput, // Text input screen for Password input on wifi screen
+    FlipSocialViewLoggedOutProcessLogin,              // The screen displayed after clicking login
+    FlipSocialViewLoggedOutProcessRegister,           // The screen displayed after clicking register
+    //
+    FlipSocialViewLoggedInSubmenu,  // The menu if the user is logged in
+    FlipSocialViewLoggedInProfile,  // The profile screen
+    FlipSocialViewLoggedInFeed,     // The feed screen
+    FlipSocialViewLoggedInCompose,  // The compose screen
+    FlipSocialViewLoggedInSettings, // The settings screen
+    //
+    FlipSocialViewLoggedInChangePasswordInput,    // Text input screen for password input on change password screen
+    FlipSocialViewLoggedInComposeAddPreSaveInput, // Text input screen for add text input on compose screen
+    //
+    FlipSocialViewLoggedInMessagesNewMessageInput,            // Text input screen for new message input on messages screen
+    FlipSocialViewLoggedInMessagesNewMessageUserChoicesInput, // Text input screen for new message input on messages screen
+    FlipSocialViewLoggedInMessagesUserChoices,                // the view after clicking [New Message] - select a user to message, then direct to input view
+    //
+    FlipSocialViewLoggedInSettingsAbout,             // The about screen
+    FlipSocialViewLoggedInSettingsWifi,              // The wifi settings screen
+    FlipSocialViewLoggedInWifiSettingsSSIDInput,     // Text input screen for SSID input on wifi screen
+    FlipSocialViewLoggedInWifiSettingsPasswordInput, // Text input screen for Password input on wifi screen
+    FlipSocialViewLoggedInProcessCompose,            // The dialog view to delete or send the clicked pre-saved text
+    //
+    FlipSocialViewLoggedInSignOut, // The view after clicking the sign out button
+    //
+    FlipSocialViewLoggedInExploreSubmenu,  // The view after clicking the explore button
+    FlipSocialViewLoggedInExploreProccess, // The view after clicking on a user in the explore screen
+    FlipSocialViewLoggedInFriendsSubmenu,  // The view after clicking the friends button on the profile screen
+    FlipSocialViewLoggedInFriendsProcess,  // The view after clicking on a friend in the friends screen
+    FlipSocialViewLoggedInMessagesSubmenu, // The view after clicking the messages button on the profile screen
+    FlipSocialViewLoggedInMessagesProcess, // The view after clicking on a user in the messages screen
+} FlipSocialView;
+
+// Define the application structure
+typedef struct
+{
+    ViewDispatcher *view_dispatcher;        // Switches between our views
+    Submenu *submenu_logged_out;            // The application submenu (logged out)
+    Submenu *submenu_logged_in;             // The application submenu (logged in)
+    Submenu *submenu_compose;               // The application submenu (compose)
+    Submenu *submenu_explore;               // The application submenu (explore)
+    Submenu *submenu_friends;               // The application submenu (friends)
+    Submenu *submenu_messages;              // The application submenu (messages)
+    Submenu *submenu_messages_user_choices; // The application submenu (messages user choices)
+    Widget *widget_logged_out_about;        // The about screen (logged out)
+    Widget *widget_logged_in_about;         // The about screen (logged in)
+
+    View *view_process_login;    // The screen displayed after clicking login
+    View *view_process_register; // The screen displayed after clicking register
+    View *view_process_feed;     // Dialog for the feed screen
+    View *view_process_compose;  // Dialog for the compose screen (delete or send)
+    View *view_process_explore;  // Dialog for the explore screen (view user profile - add or delete friend)
+    View *view_process_friends;  // Dialog for the friends screen (view user profile - add or delete friend)
+    View *view_process_messages; // Dialog for the messages screen (next, previous, send message)
+
+    VariableItemList *variable_item_list_logged_out_wifi_settings; // The wifi settings menu
+    VariableItemList *variable_item_list_logged_out_login;         // The login menu
+    VariableItemList *variable_item_list_logged_out_register;      // The register menu
+    //
+    VariableItemList *variable_item_list_logged_in_profile;       // The profile menu
+    VariableItemList *variable_item_list_logged_in_settings;      // The settings menu
+    VariableItemList *variable_item_list_logged_in_settings_wifi; // The wifi settings menu
+
+    UART_TextInput *text_input_logged_out_wifi_settings_ssid;     // Text input for ssid input on wifi settings screen
+    UART_TextInput *text_input_logged_out_wifi_settings_password; // Text input for password input on wifi settings screen
+    UART_TextInput *text_input_logged_out_login_username;         // Text input for username input on login screen
+    UART_TextInput *text_input_logged_out_login_password;         // Text input for password input on login screen
+    UART_TextInput *text_input_logged_out_register_username;      // Text input for username input on register screen
+    UART_TextInput *text_input_logged_out_register_password;      // Text input for password input on register screen
+    UART_TextInput *text_input_logged_out_register_password_2;    // Text input for password 2 input on register screen
+    //
+    UART_TextInput *text_input_logged_in_change_password;        // Text input for password input on change password screen
+    UART_TextInput *text_input_logged_in_compose_pre_save_input; // Text input for pre save input on compose screen
+    UART_TextInput *text_input_logged_in_wifi_settings_ssid;     // Text input for ssid input on wifi settings screen
+    UART_TextInput *text_input_logged_in_wifi_settings_password; // Text input for password input on wifi settings screen
+    //
+    UART_TextInput *text_input_logged_in_messages_new_message;              // Text input for new message input on messages screen
+    UART_TextInput *text_input_logged_in_messages_new_message_user_choices; //
+
+    VariableItem *variable_item_logged_out_wifi_settings_ssid;     // Reference to the ssid configuration item
+    VariableItem *variable_item_logged_out_wifi_settings_password; // Reference to the password configuration item
+    VariableItem *variable_item_logged_out_login_username;         // Reference to the username configuration item
+    VariableItem *variable_item_logged_out_login_password;         // Reference to the password configuration item
+    VariableItem *variable_item_logged_out_login_button;           // Reference to the login button configuration item
+    VariableItem *variable_item_logged_out_register_username;      // Reference to the username configuration item
+    VariableItem *variable_item_logged_out_register_password;      // Reference to the password configuration item
+    VariableItem *variable_item_logged_out_register_password_2;    // Reference to the password 2 configuration item
+    VariableItem *variable_item_logged_out_register_button;        // Reference to the register button configuration item
+    //
+    VariableItem *variable_item_logged_in_profile_username;        // Reference to the username configuration item
+    VariableItem *variable_item_logged_in_profile_change_password; // Reference to the change password configuration item
+    VariableItem *variable_item_logged_in_settings_about;          // Reference to the about configuration item
+    VariableItem *variable_item_logged_in_settings_wifi;           // Reference to the wifi settings configuration item
+    VariableItem *variable_item_logged_in_wifi_settings_ssid;      // Reference to the ssid configuration item
+    VariableItem *variable_item_logged_in_wifi_settings_password;  // Reference to the password configuration item
+    //
+    VariableItem *variable_item_logged_in_profile_friends; // Reference to the friends configuration item
+    //
+    FuriPubSub *input_event_queue;
+    FuriPubSubSubscription *input_event;
+
+    PreSavedPlaylist pre_saved_messages; // Pre-saved messages for the feed screen
+
+    char *is_logged_in;         // Store the login status
+    uint32_t is_logged_in_size; // Size of the login status buffer
+
+    char *login_username_logged_in;                     // Store the entered login username
+    char *login_username_logged_in_temp_buffer;         // Temporary buffer for login username text input
+    uint32_t login_username_logged_in_temp_buffer_size; // Size of the login username temporary buffer
+
+    char *wifi_ssid_logged_out;                     // Store the entered wifi ssid
+    char *wifi_ssid_logged_out_temp_buffer;         // Temporary buffer for wifi ssid text input
+    uint32_t wifi_ssid_logged_out_temp_buffer_size; // Size of the wifi ssid temporary buffer
+
+    char *wifi_password_logged_out;                     // Store the entered wifi password
+    char *wifi_password_logged_out_temp_buffer;         // Temporary buffer for wifi_password text input
+    uint32_t wifi_password_logged_out_temp_buffer_size; // Size of the wifi_password temporary buffer
+
+    char *login_username_logged_out;                     // Store the entered login username
+    char *login_username_logged_out_temp_buffer;         // Temporary buffer for login username text input
+    uint32_t login_username_logged_out_temp_buffer_size; // Size of the login username temporary buffer
+
+    char *login_password_logged_out;                     // Store the entered login password
+    char *login_password_logged_out_temp_buffer;         // Temporary buffer for login password text input
+    uint32_t login_password_logged_out_temp_buffer_size; // Size of the login password temporary buffer
+
+    char *register_username_logged_out;                     // Store the entered register username
+    char *register_username_logged_out_temp_buffer;         // Temporary buffer for register username text input
+    uint32_t register_username_logged_out_temp_buffer_size; // Size of the register username temporary buffer
+
+    char *register_password_logged_out;                     // Store the entered register password
+    char *register_password_logged_out_temp_buffer;         // Temporary buffer for register password text input
+    uint32_t register_password_logged_out_temp_buffer_size; // Size of the register password temporary buffer
+
+    char *register_password_2_logged_out;                     // Store the entered register password 2
+    char *register_password_2_logged_out_temp_buffer;         // Temporary buffer for register password 2 text input
+    uint32_t register_password_2_logged_out_temp_buffer_size; // Size of the register password 2 temporary buffer
+
+    //
+    char *change_password_logged_in;                     // Store the entered change password
+    char *change_password_logged_in_temp_buffer;         // Temporary buffer for change password text input
+    uint32_t change_password_logged_in_temp_buffer_size; // Size of the change password temporary buffer
+
+    char *compose_pre_save_logged_in;                     // Store the entered add text
+    char *compose_pre_save_logged_in_temp_buffer;         // Temporary buffer for add text text input
+    uint32_t compose_pre_save_logged_in_temp_buffer_size; // Size of the add text temporary buffer
+
+    char *wifi_ssid_logged_in;                     // Store the entered wifi ssid
+    char *wifi_ssid_logged_in_temp_buffer;         // Temporary buffer for wifi ssid text input
+    uint32_t wifi_ssid_logged_in_temp_buffer_size; // Size of the wifi ssid temporary buffer
+
+    char *wifi_password_logged_in;                     // Store the entered wifi password
+    char *wifi_password_logged_in_temp_buffer;         // Temporary buffer for wifi_password text input
+    uint32_t wifi_password_logged_in_temp_buffer_size; // Size of the wifi_password temporary buffer
+
+    //
+    char *messages_new_message_logged_in;                     // Store the entered new message
+    char *messages_new_message_logged_in_temp_buffer;         // Temporary buffer for new message text input
+    uint32_t messages_new_message_logged_in_temp_buffer_size; // Size of the new message temporary buffer
+
+    char *message_user_choice_logged_in;                     // Store the entered message to send to the selected user
+    char *message_user_choice_logged_in_temp_buffer;         // Temporary buffer for message to send to the selected user
+    uint32_t message_user_choice_logged_in_temp_buffer_size; // Size of the message to send to the selected user temporary buffer
+} FlipSocialApp;
+
+static FlipSocialFeed *flip_social_feed = NULL;            // Store the feed
+static FlipSocialModel *flip_social_friends = NULL;        // Store the friends
+static FlipSocialModel2 *flip_social_message_users = NULL; // Store the users that have sent messages to the logged in user
+static FlipSocialModel *flip_social_explore = NULL;        // Store the users to explore
+static FlipSocialMessage *flip_social_messages = NULL;     // Store the messages between the logged in user and the selected user
+
+// include strndup (otherwise NULL pointer dereference)
+char *strndup(const char *s, size_t n)
+{
+    char *result;
+    size_t len = strlen(s);
+
+    if (n < len)
+        len = n;
+
+    result = (char *)malloc(len + 1);
+    if (!result)
+        return NULL;
+
+    result[len] = '\0';
+    return (char *)memcpy(result, s, len);
+}
+
+static FlipSocialApp *app_instance = NULL;
+static void flip_social_logged_in_compose_pre_save_updated(void *context);
+static void flip_social_callback_submenu_choices(void *context, uint32_t index);
+#endif

+ 141 - 0
flip_social/flip_social_explore.h

@@ -0,0 +1,141 @@
+#ifndef FLIP_SOCIAL_EXPLORE_H
+#define FLIP_SOCIAL_EXPLORE_H
+
+static FlipSocialModel *flip_social_explore_alloc()
+{
+    // Allocate memory for each username only if not already allocated
+    FlipSocialModel *explore = malloc(sizeof(FlipSocialModel));
+    if (explore == NULL)
+    {
+        FURI_LOG_E(TAG, "Failed to allocate memory for explore model.");
+        return NULL;
+    }
+    for (size_t i = 0; i < MAX_EXPLORE_USERS; i++)
+    {
+        if (explore->usernames[i] == NULL)
+        {
+            explore->usernames[i] = malloc(MAX_USER_LENGTH);
+            if (explore->usernames[i] == NULL)
+            {
+                FURI_LOG_E(TAG, "Failed to allocate memory for username %zu", i);
+                return NULL; // Return false on memory allocation failure
+            }
+        }
+    }
+    return explore;
+}
+
+static void flip_social_free_explore()
+{
+    if (!flip_social_explore)
+    {
+        FURI_LOG_E(TAG, "Explore model is NULL");
+        return;
+    }
+    for (int i = 0; i < flip_social_explore->count; i++)
+    {
+        free(flip_social_explore->usernames[i]);
+    }
+}
+
+// for now we're just listing the current users
+// as the feed is upgraded, then we can port more to the explore view
+static bool flip_social_get_explore()
+{
+    // will return true unless the devboard is not connected
+    bool success = flipper_http_get_request_with_headers("https://www.flipsocial.net/api/user/users/", jsmn("Content-Type", "application/json"));
+    if (!success)
+    {
+        FURI_LOG_E(TAG, "Failed to send HTTP request for explore");
+        return false;
+    }
+    fhttp.state = RECEIVING;
+    return true;
+}
+
+static bool flip_social_parse_json_explore()
+{
+    if (fhttp.received_data == NULL)
+    {
+        FURI_LOG_E(TAG, "No data received.");
+        return false;
+    }
+
+    // Allocate memory for each username only if not already allocated
+    flip_social_explore = flip_social_explore_alloc();
+    if (flip_social_explore == NULL)
+    {
+        FURI_LOG_E(TAG, "Failed to allocate memory for explore usernames.");
+        return false;
+    }
+
+    // Remove newlines
+    char *pos = fhttp.received_data;
+    while ((pos = strchr(pos, '\n')) != NULL)
+    {
+        *pos = ' ';
+    }
+
+    // Initialize explore count
+    flip_social_explore->count = 0;
+
+    // Extract the users array from the JSON
+    char *json_users = get_json_value("users", fhttp.received_data, MAX_TOKENS);
+    if (json_users == NULL)
+    {
+        FURI_LOG_E(TAG, "Failed to parse users array.");
+        return false;
+    }
+
+    // Manual tokenization for comma-separated values
+    char *start = json_users + 1; // Skip the opening bracket
+    char *end;
+    while ((end = strchr(start, ',')) != NULL && flip_social_explore->count < MAX_EXPLORE_USERS)
+    {
+        *end = '\0'; // Null-terminate the current token
+
+        // Remove quotes
+        if (*start == '"')
+            start++;
+        if (*(end - 1) == '"')
+            *(end - 1) = '\0';
+
+        // Copy username to pre-allocated memory
+        strncpy(flip_social_explore->usernames[flip_social_explore->count], start, MAX_USER_LENGTH - 1);
+        flip_social_explore->usernames[flip_social_explore->count][MAX_USER_LENGTH - 1] = '\0'; // Ensure null termination
+        flip_social_explore->count++;
+        start = end + 1;
+    }
+
+    // Handle the last token
+    if (*start != '\0' && flip_social_explore->count < MAX_EXPLORE_USERS)
+    {
+        if (*start == '"')
+            start++;
+        if (*(start + strlen(start) - 1) == ']')
+            *(start + strlen(start) - 1) = '\0';
+        if (*(start + strlen(start) - 1) == '"')
+            *(start + strlen(start) - 1) = '\0';
+
+        strncpy(flip_social_explore->usernames[flip_social_explore->count], start, MAX_USER_LENGTH - 1);
+        flip_social_explore->usernames[flip_social_explore->count][MAX_USER_LENGTH - 1] = '\0'; // Ensure null termination
+        flip_social_explore->count++;
+    }
+
+    // Add submenu items for the users
+    submenu_reset(app_instance->submenu_explore);
+    submenu_set_header(app_instance->submenu_explore, "Explore");
+    for (int i = 0; i < flip_social_explore->count; i++)
+    {
+        submenu_add_item(app_instance->submenu_explore, flip_social_explore->usernames[i], FlipSocialSubmenuExploreIndexStartIndex + i, flip_social_callback_submenu_choices, app_instance);
+    }
+
+    // Free the json_users
+    free(json_users);
+    free(start);
+    free(end);
+
+    return true;
+}
+
+#endif // FLIP_SOCIAL_EXPLORE_H

+ 209 - 0
flip_social/flip_social_feed.h

@@ -0,0 +1,209 @@
+#ifndef FLIP_SOCIAL_FEED_H
+#define FLIP_SOCIAL_FEED_H
+
+// Set failure FlipSocialFeed object
+static bool flip_social_temp_feed()
+{
+    if (flip_social_feed == NULL)
+    {
+        flip_social_feed = malloc(sizeof(FlipSocialFeed));
+        if (flip_social_feed == NULL)
+        {
+            FURI_LOG_E(TAG, "Failed to allocate memory for feed");
+            return false;
+        }
+    }
+    for (int i = 0; i < 3; i++)
+    {
+        if (flip_social_feed->usernames[i] == NULL)
+        {
+            flip_social_feed->usernames[i] = malloc(MAX_USER_LENGTH);
+            if (flip_social_feed->usernames[i] == NULL)
+            {
+                FURI_LOG_E(TAG, "Failed to allocate memory for username %zu", i);
+                return false;
+            }
+        }
+        if (flip_social_feed->messages[i] == NULL)
+        {
+            flip_social_feed->messages[i] = malloc(MAX_MESSAGE_LENGTH);
+            if (flip_social_feed->messages[i] == NULL)
+            {
+                FURI_LOG_E(TAG, "Failed to allocate memory for message %zu", i);
+                return false;
+            }
+        }
+    }
+    flip_social_feed->usernames[0] = "JBlanked";
+    flip_social_feed->usernames[1] = "FlipperKing";
+    flip_social_feed->usernames[2] = "FlipperQueen";
+    //
+    flip_social_feed->messages[0] = "Welcome. This is a temp message. Either the feed didn't load or there was a server error.";
+    flip_social_feed->messages[1] = "I am the Chosen Flipper.";
+    flip_social_feed->messages[2] = "No one can flip like me.";
+    //
+    flip_social_feed->is_flipped[0] = true;
+    flip_social_feed->is_flipped[1] = false;
+    flip_social_feed->is_flipped[2] = true;
+    //
+    flip_social_feed->ids[0] = 0;
+    flip_social_feed->ids[1] = 1;
+    flip_social_feed->ids[2] = 2;
+    //
+    flip_social_feed->flips[0] = 51;
+    flip_social_feed->flips[1] = 8;
+    flip_social_feed->flips[2] = 23;
+    //
+    flip_social_feed->count = 3;
+    flip_social_feed->index = 0;
+
+    return true;
+}
+
+// Allocate memory for each feed item if not already allocated
+static FlipSocialFeed *flip_social_feed_alloc()
+{
+    // Initialize the feed
+    FlipSocialFeed *feed = (FlipSocialFeed *)malloc(sizeof(FlipSocialFeed));
+    if (!feed)
+    {
+        FURI_LOG_E(TAG, "Failed to allocate memory for feed");
+        return feed;
+    }
+    for (size_t i = 0; i < MAX_FEED_ITEMS; i++)
+    {
+        if (feed->usernames[i] == NULL)
+        {
+            feed->usernames[i] = malloc(MAX_USER_LENGTH);
+            if (feed->usernames[i] == NULL)
+            {
+                FURI_LOG_E(TAG, "Failed to allocate memory for username %zu", i);
+                return NULL;
+            }
+        }
+        if (feed->messages[i] == NULL)
+        {
+            feed->messages[i] = malloc(MAX_MESSAGE_LENGTH);
+            if (feed->messages[i] == NULL)
+            {
+                FURI_LOG_E(TAG, "Failed to allocate memory for message %zu", i);
+                return NULL;
+            }
+        }
+    }
+    return feed;
+}
+
+static void flip_social_free_feed()
+{
+    if (!flip_social_feed)
+    {
+        FURI_LOG_E(TAG, "Feed model is NULL");
+        return;
+    }
+    for (uint32_t i = 0; i < flip_social_feed->count; i++)
+    {
+        free(flip_social_feed->usernames[i]);
+    }
+}
+
+static bool flip_social_get_feed()
+{
+    // Get the feed from the server
+    if (app_instance->login_username_logged_out == NULL)
+    {
+        FURI_LOG_E(TAG, "Username is NULL");
+        return false;
+    }
+    char command[128];
+    snprintf(command, 128, "https://www.flipsocial.net/api/feed/40/%s/extended/", app_instance->login_username_logged_out);
+    bool success = flipper_http_get_request_with_headers(command, jsmn("Content-Type", "application/json"));
+    if (!success)
+    {
+        FURI_LOG_E(TAG, "Failed to send HTTP request for feed");
+        return false;
+    }
+    fhttp.state = RECEIVING;
+    return true;
+}
+
+static bool flip_social_parse_json_feed()
+{
+    if (fhttp.received_data == NULL)
+    {
+        FURI_LOG_E(TAG, "No data received.");
+        return false;
+    }
+
+    // Allocate memory for each feed item if not already allocated
+    flip_social_feed = flip_social_feed_alloc();
+    if (flip_social_feed == NULL)
+    {
+        return false;
+    }
+    // Remove newlines
+    char *pos = fhttp.received_data;
+    while ((pos = strchr(pos, '\n')) != NULL)
+    {
+        *pos = ' ';
+    }
+
+    // Initialize feed count
+    flip_social_feed->count = 0;
+
+    // Iterate through the feed array
+    for (int i = 0; i < MAX_FEED_ITEMS; i++)
+    {
+        // Parse each item in the array
+        char *item = get_json_array_value("feed", i, fhttp.received_data, MAX_TOKENS);
+        if (item == NULL)
+        {
+            break;
+        }
+
+        // Extract individual fields from the JSON object
+        char *username = get_json_value("username", item, MAX_TOKENS);
+        char *message = get_json_value("message", item, MAX_TOKENS);
+        char *flipped = get_json_value("flipped", item, MAX_TOKENS);
+        char *flips = get_json_value("flip_count", item, MAX_TOKENS);
+        char *id = get_json_value("id", item, MAX_TOKENS);
+
+        if (username == NULL || message == NULL || flipped == NULL || id == NULL)
+        {
+            FURI_LOG_E(TAG, "Failed to parse item fields.");
+            free(item);
+            free(username);
+            free(message);
+            free(flipped);
+            free(flips);
+            free(id);
+            continue;
+        }
+
+        // Safely copy strings with bounds checking
+        strncpy(flip_social_feed->usernames[i], username, MAX_USER_LENGTH - 1);
+        flip_social_feed->usernames[i][MAX_USER_LENGTH - 1] = '\0';
+
+        strncpy(flip_social_feed->messages[i], message, MAX_MESSAGE_LENGTH - 1);
+        flip_social_feed->messages[i][MAX_MESSAGE_LENGTH - 1] = '\0';
+
+        // Store boolean and integer values
+        flip_social_feed->is_flipped[i] = strstr(flipped, "true") != NULL;
+        flip_social_feed->ids[i] = atoi(id);
+        flip_social_feed->flips[i] = atoi(flips);
+
+        flip_social_feed->count++;
+
+        // Free allocated memory
+        free(item);
+        free(username);
+        free(message);
+        free(flipped);
+        free(flips);
+        free(id);
+    }
+
+    return flip_social_feed->count > 0;
+}
+
+#endif // FLIP_SOCIAL_FEED_H

+ 294 - 0
flip_social/flip_social_free.h

@@ -0,0 +1,294 @@
+// flip_social_free.h
+#ifndef FLIP_SOCIAL_FREE_H
+#define FLIP_SOCIAL_FREE_H
+
+/**
+ * @brief Function to free the resources used by FlipSocialApp.
+ * @details Cleans up all allocated resources before exiting the application.
+ * @param app The FlipSocialApp object to free.
+ * @return void
+ */
+static void flip_social_app_free(FlipSocialApp *app)
+{
+    if (!app)
+    {
+        FURI_LOG_E(TAG, "FlipSocialApp is NULL");
+        return;
+    }
+    if (!app->view_dispatcher)
+    {
+        FURI_LOG_E(TAG, "ViewDispatcher is NULL");
+        return;
+    }
+
+    // Free Submenu(s)
+    if (app->submenu_logged_out)
+    {
+        view_dispatcher_remove_view(app->view_dispatcher, FlipSocialViewLoggedOutSubmenu);
+        submenu_free(app->submenu_logged_out);
+    }
+    if (app->submenu_logged_in)
+    {
+        view_dispatcher_remove_view(app->view_dispatcher, FlipSocialViewLoggedInSubmenu);
+        submenu_free(app->submenu_logged_in);
+    }
+    if (app->submenu_compose)
+    {
+        view_dispatcher_remove_view(app->view_dispatcher, FlipSocialViewLoggedInCompose);
+        submenu_free(app->submenu_compose);
+    }
+    if (app->submenu_explore)
+    {
+        view_dispatcher_remove_view(app->view_dispatcher, FlipSocialViewLoggedInExploreSubmenu);
+        submenu_free(app->submenu_explore);
+    }
+    if (app->submenu_friends)
+    {
+        view_dispatcher_remove_view(app->view_dispatcher, FlipSocialViewLoggedInFriendsSubmenu);
+        submenu_free(app->submenu_friends);
+    }
+    if (app->submenu_messages)
+    {
+        view_dispatcher_remove_view(app->view_dispatcher, FlipSocialViewLoggedInMessagesSubmenu);
+        submenu_free(app->submenu_messages);
+    }
+    if (app->submenu_messages_user_choices)
+    {
+        view_dispatcher_remove_view(app->view_dispatcher, FlipSocialViewLoggedInMessagesUserChoices);
+        submenu_free(app->submenu_messages_user_choices);
+    }
+
+    // Free Variable Item List(s)
+    if (app->variable_item_list_logged_out_wifi_settings)
+    {
+        view_dispatcher_remove_view(app->view_dispatcher, FlipSocialViewLoggedOutWifiSettings);
+        variable_item_list_free(app->variable_item_list_logged_out_wifi_settings);
+    }
+    if (app->variable_item_list_logged_out_login)
+    {
+        view_dispatcher_remove_view(app->view_dispatcher, FlipSocialViewLoggedOutLogin);
+        variable_item_list_free(app->variable_item_list_logged_out_login);
+    }
+    if (app->variable_item_list_logged_out_register)
+    {
+        view_dispatcher_remove_view(app->view_dispatcher, FlipSocialViewLoggedOutRegister);
+        variable_item_list_free(app->variable_item_list_logged_out_register);
+    }
+    if (app->variable_item_list_logged_in_profile)
+    {
+        view_dispatcher_remove_view(app->view_dispatcher, FlipSocialViewLoggedInProfile);
+        variable_item_list_free(app->variable_item_list_logged_in_profile);
+    }
+    if (app->variable_item_list_logged_in_settings)
+    {
+        view_dispatcher_remove_view(app->view_dispatcher, FlipSocialViewLoggedInSettings);
+        variable_item_list_free(app->variable_item_list_logged_in_settings);
+    }
+    if (app->variable_item_list_logged_in_settings_wifi)
+    {
+        view_dispatcher_remove_view(app->view_dispatcher, FlipSocialViewLoggedInSettingsWifi);
+        variable_item_list_free(app->variable_item_list_logged_in_settings_wifi);
+    }
+
+    // Free Text Input(s)
+    if (app->text_input_logged_out_wifi_settings_ssid)
+    {
+        view_dispatcher_remove_view(app->view_dispatcher, FlipSocialViewLoggedOutWifiSettingsSSIDInput);
+        uart_text_input_free(app->text_input_logged_out_wifi_settings_ssid);
+    }
+    if (app->text_input_logged_out_wifi_settings_password)
+    {
+        view_dispatcher_remove_view(app->view_dispatcher, FlipSocialViewLoggedOutWifiSettingsPasswordInput);
+        uart_text_input_free(app->text_input_logged_out_wifi_settings_password);
+    }
+    if (app->text_input_logged_out_login_username)
+    {
+        view_dispatcher_remove_view(app->view_dispatcher, FlipSocialViewLoggedOutLoginUsernameInput);
+        uart_text_input_free(app->text_input_logged_out_login_username);
+    }
+    if (app->text_input_logged_out_login_password)
+    {
+        view_dispatcher_remove_view(app->view_dispatcher, FlipSocialViewLoggedOutLoginPasswordInput);
+        uart_text_input_free(app->text_input_logged_out_login_password);
+    }
+    if (app->text_input_logged_out_register_username)
+    {
+        view_dispatcher_remove_view(app->view_dispatcher, FlipSocialViewLoggedOutRegisterUsernameInput);
+        uart_text_input_free(app->text_input_logged_out_register_username);
+    }
+    if (app->text_input_logged_out_register_password)
+    {
+        view_dispatcher_remove_view(app->view_dispatcher, FlipSocialViewLoggedOutRegisterPasswordInput);
+        uart_text_input_free(app->text_input_logged_out_register_password);
+    }
+    if (app->text_input_logged_out_register_password_2)
+    {
+        view_dispatcher_remove_view(app->view_dispatcher, FlipSocialViewLoggedOutRegisterPassword2Input);
+        uart_text_input_free(app->text_input_logged_out_register_password_2);
+    }
+    if (app->text_input_logged_in_change_password)
+    {
+        view_dispatcher_remove_view(app->view_dispatcher, FlipSocialViewLoggedInChangePasswordInput);
+        uart_text_input_free(app->text_input_logged_in_change_password);
+    }
+    if (app->text_input_logged_in_compose_pre_save_input)
+    {
+        view_dispatcher_remove_view(app->view_dispatcher, FlipSocialViewLoggedInComposeAddPreSaveInput);
+        uart_text_input_free(app->text_input_logged_in_compose_pre_save_input);
+    }
+    if (app->text_input_logged_in_wifi_settings_ssid)
+    {
+        view_dispatcher_remove_view(app->view_dispatcher, FlipSocialViewLoggedInWifiSettingsSSIDInput);
+        uart_text_input_free(app->text_input_logged_in_wifi_settings_ssid);
+    }
+    if (app->text_input_logged_in_wifi_settings_password)
+    {
+        view_dispatcher_remove_view(app->view_dispatcher, FlipSocialViewLoggedInWifiSettingsPasswordInput);
+        uart_text_input_free(app->text_input_logged_in_wifi_settings_password);
+    }
+    if (app->text_input_logged_in_messages_new_message)
+    {
+        view_dispatcher_remove_view(app->view_dispatcher, FlipSocialViewLoggedInMessagesNewMessageInput);
+        uart_text_input_free(app->text_input_logged_in_messages_new_message);
+    }
+    if (app->text_input_logged_in_messages_new_message_user_choices)
+    {
+        view_dispatcher_remove_view(app->view_dispatcher, FlipSocialViewLoggedInMessagesNewMessageUserChoicesInput);
+        uart_text_input_free(app->text_input_logged_in_messages_new_message_user_choices);
+    }
+
+    // Free Widget(s)
+    if (app->widget_logged_out_about)
+    {
+        view_dispatcher_remove_view(app->view_dispatcher, FlipSocialViewLoggedOutAbout);
+        widget_free(app->widget_logged_out_about);
+    }
+    if (app->widget_logged_in_about)
+    {
+        view_dispatcher_remove_view(app->view_dispatcher, FlipSocialViewLoggedInSettingsAbout);
+        widget_free(app->widget_logged_in_about);
+    }
+
+    // Free View(s)
+    if (app->view_process_login)
+    {
+        view_dispatcher_remove_view(app->view_dispatcher, FlipSocialViewLoggedOutProcessLogin);
+        view_free(app->view_process_login);
+    }
+    if (app->view_process_register)
+    {
+        view_dispatcher_remove_view(app->view_dispatcher, FlipSocialViewLoggedOutProcessRegister);
+        view_free(app->view_process_register);
+    }
+    if (app->view_process_feed)
+    {
+        view_dispatcher_remove_view(app->view_dispatcher, FlipSocialViewLoggedInFeed);
+        view_free(app->view_process_feed);
+    }
+    if (app->view_process_compose)
+    {
+        view_dispatcher_remove_view(app->view_dispatcher, FlipSocialViewLoggedInProcessCompose);
+        view_free(app->view_process_compose);
+    }
+    if (app->view_process_explore)
+    {
+        view_dispatcher_remove_view(app->view_dispatcher, FlipSocialViewLoggedInExploreProccess);
+        view_free(app->view_process_explore);
+    }
+    if (app->view_process_friends)
+    {
+        view_dispatcher_remove_view(app->view_dispatcher, FlipSocialViewLoggedInFriendsProcess);
+        view_free(app->view_process_friends);
+    }
+    if (app->view_process_messages)
+    {
+        view_dispatcher_remove_view(app->view_dispatcher, FlipSocialViewLoggedInMessagesProcess);
+        view_free(app->view_process_messages);
+    }
+
+    if (app->view_dispatcher)
+        view_dispatcher_free(app->view_dispatcher);
+
+    // Free the app structure members
+    if (app->wifi_ssid_logged_out)
+        free(app->wifi_ssid_logged_out);
+    if (app->wifi_ssid_logged_out_temp_buffer)
+        free(app->wifi_ssid_logged_out_temp_buffer);
+    if (app->wifi_password_logged_out)
+        free(app->wifi_password_logged_out);
+    if (app->wifi_password_logged_out_temp_buffer)
+        free(app->wifi_password_logged_out_temp_buffer);
+    if (app->login_username_logged_out)
+        free(app->login_username_logged_out);
+    if (app->login_username_logged_out_temp_buffer)
+        free(app->login_username_logged_out_temp_buffer);
+    if (app->login_password_logged_out)
+        free(app->login_password_logged_out);
+    if (app->login_password_logged_out_temp_buffer)
+        free(app->login_password_logged_out_temp_buffer);
+    if (app->register_username_logged_out)
+        free(app->register_username_logged_out);
+    if (app->register_username_logged_out_temp_buffer)
+        free(app->register_username_logged_out_temp_buffer);
+    if (app->register_password_logged_out)
+        free(app->register_password_logged_out);
+    if (app->register_password_logged_out_temp_buffer)
+        free(app->register_password_logged_out_temp_buffer);
+    if (app->register_password_2_logged_out)
+        free(app->register_password_2_logged_out);
+    if (app->register_password_2_logged_out_temp_buffer)
+        free(app->register_password_2_logged_out_temp_buffer);
+    if (app->change_password_logged_in)
+        free(app->change_password_logged_in);
+    if (app->change_password_logged_in_temp_buffer)
+        free(app->change_password_logged_in_temp_buffer);
+    if (app->compose_pre_save_logged_in)
+        free(app->compose_pre_save_logged_in);
+    if (app->compose_pre_save_logged_in_temp_buffer)
+        free(app->compose_pre_save_logged_in_temp_buffer);
+    if (app->wifi_ssid_logged_in)
+        free(app->wifi_ssid_logged_in);
+    if (app->wifi_ssid_logged_in_temp_buffer)
+        free(app->wifi_ssid_logged_in_temp_buffer);
+    if (app->wifi_password_logged_in)
+        free(app->wifi_password_logged_in);
+    if (app->wifi_password_logged_in_temp_buffer)
+        free(app->wifi_password_logged_in_temp_buffer);
+    if (app->is_logged_in)
+        free(app->is_logged_in);
+    if (app->login_username_logged_in)
+        free(app->login_username_logged_in);
+    if (app->login_username_logged_in_temp_buffer)
+        free(app->login_username_logged_in_temp_buffer);
+    if (app->messages_new_message_logged_in)
+        free(app->messages_new_message_logged_in);
+    if (app->messages_new_message_logged_in_temp_buffer)
+        free(app->messages_new_message_logged_in_temp_buffer);
+    if (app->message_user_choice_logged_in)
+        free(app->message_user_choice_logged_in);
+    if (app->message_user_choice_logged_in_temp_buffer)
+        free(app->message_user_choice_logged_in_temp_buffer);
+
+    if (app->input_event && app->input_event_queue)
+        furi_pubsub_unsubscribe(app->input_event_queue, app->input_event);
+
+    // free received_data
+    if (fhttp.received_data)
+        free(fhttp.received_data);
+
+    // free playlist and explore page
+    flip_social_free_explore();
+    flip_social_free_feed();
+    flip_social_free_friends();
+    flip_social_free_message_users();
+    flip_social_free_messages();
+
+    // DeInit UART
+    flipper_http_deinit();
+
+    // Free the app structure
+    if (app_instance)
+        free(app_instance);
+}
+
+#endif // FLIP_SOCIAL_FREE_H

+ 158 - 0
flip_social/flip_social_friends.h

@@ -0,0 +1,158 @@
+#ifndef FLIP_SOCIAL_FRIENDS
+#define FLIP_SOCIAL_FRIENDS
+
+static FlipSocialModel *flip_social_friends_alloc()
+{
+    // Allocate memory for each username only if not already allocated
+    FlipSocialModel *friends = malloc(sizeof(FlipSocialModel));
+    for (size_t i = 0; i < MAX_FRIENDS; i++)
+    {
+        if (friends->usernames[i] == NULL)
+        {
+            friends->usernames[i] = malloc(MAX_USER_LENGTH);
+            if (friends->usernames[i] == NULL)
+            {
+                FURI_LOG_E(TAG, "Failed to allocate memory for username %zu", i);
+                return NULL; // Return false on memory allocation failure
+            }
+        }
+    }
+    return friends;
+}
+
+static void flip_social_free_friends()
+{
+    if (!flip_social_friends)
+    {
+        FURI_LOG_E(TAG, "Friends model is NULL");
+        return;
+    }
+    for (int i = 0; i < flip_social_friends->count; i++)
+    {
+        free(flip_social_friends->usernames[i]);
+    }
+}
+
+// for now we're just listing the current users
+// as the feed is upgraded, then we can port more to the friends view
+static bool flip_social_get_friends()
+{
+    // will return true unless the devboard is not connected
+    char url[100];
+    snprintf(url, 100, "https://www.flipsocial.net/api/user/friends/%s/", app_instance->login_username_logged_in);
+    bool success = flipper_http_get_request_with_headers(url, jsmn("Content-Type", "application/json"));
+    if (!success)
+    {
+        FURI_LOG_E(TAG, "Failed to send HTTP request for friends");
+        return false;
+    }
+    fhttp.state = RECEIVING;
+    return true;
+}
+
+static bool flip_social_update_friends()
+{
+    if (!app_instance->submenu_friends)
+    {
+        FURI_LOG_E(TAG, "Friends submenu is NULL");
+        return false;
+    }
+    if (!flip_social_friends)
+    {
+        FURI_LOG_E(TAG, "Friends model is NULL");
+        return false;
+    }
+    // Add submenu items for the users
+    submenu_reset(app_instance->submenu_friends);
+    submenu_set_header(app_instance->submenu_friends, "Friends");
+    for (int i = 0; i < flip_social_friends->count; i++)
+    {
+        submenu_add_item(app_instance->submenu_friends, flip_social_friends->usernames[i], FlipSocialSubmenuLoggedInIndexFriendsStart + i, flip_social_callback_submenu_choices, app_instance);
+    }
+    return true;
+}
+
+static bool flip_social_parse_json_friends()
+{
+    if (fhttp.received_data == NULL)
+    {
+        FURI_LOG_E(TAG, "No data received.");
+        return false;
+    }
+
+    // Allocate memory for each username only if not already allocated
+    flip_social_friends = flip_social_friends_alloc();
+    if (flip_social_friends == NULL)
+    {
+        FURI_LOG_E(TAG, "Failed to allocate memory for friends usernames.");
+        return false;
+    }
+
+    // Remove newlines
+    char *pos = fhttp.received_data;
+    while ((pos = strchr(pos, '\n')) != NULL)
+    {
+        *pos = ' ';
+    }
+
+    // Initialize friends count
+    flip_social_friends->count = 0;
+
+    // Extract the users array from the JSON
+    char *json_users = get_json_value("friends", fhttp.received_data, MAX_TOKENS);
+    if (json_users == NULL)
+    {
+        FURI_LOG_E(TAG, "Failed to parse friends array.");
+        return false;
+    }
+
+    // Manual tokenization for comma-separated values
+    char *start = json_users + 1; // Skip the opening bracket
+    char *end;
+    while ((end = strchr(start, ',')) != NULL && flip_social_friends->count < MAX_FRIENDS)
+    {
+        *end = '\0'; // Null-terminate the current token
+
+        // Remove quotes
+        if (*start == '"')
+            start++;
+        if (*(end - 1) == '"')
+            *(end - 1) = '\0';
+
+        // Copy username to pre-allocated memory
+        strncpy(flip_social_friends->usernames[flip_social_friends->count], start, MAX_USER_LENGTH - 1);
+        flip_social_friends->usernames[flip_social_friends->count][MAX_USER_LENGTH - 1] = '\0'; // Ensure null termination
+        flip_social_friends->count++;
+        start = end + 1;
+    }
+
+    // Handle the last token
+    if (*start != '\0' && flip_social_friends->count < MAX_FRIENDS)
+    {
+        if (*start == '"')
+            start++;
+        if (*(start + strlen(start) - 1) == ']')
+            *(start + strlen(start) - 1) = '\0';
+        if (*(start + strlen(start) - 1) == '"')
+            *(start + strlen(start) - 1) = '\0';
+
+        strncpy(flip_social_friends->usernames[flip_social_friends->count], start, MAX_USER_LENGTH - 1);
+        flip_social_friends->usernames[flip_social_friends->count][MAX_USER_LENGTH - 1] = '\0'; // Ensure null termination
+        flip_social_friends->count++;
+    }
+
+    // Add submenu items for the friends
+    if (!flip_social_update_friends())
+    {
+        FURI_LOG_E(TAG, "Failed to update friends submenu");
+        return false;
+    }
+
+    // Free the json_users
+    free(json_users);
+    free(start);
+    free(end);
+
+    return true;
+}
+#endif // FLIP_SOCIAL_FRIENDS

+ 542 - 0
flip_social/flip_social_i.h

@@ -0,0 +1,542 @@
+// flip_social.i.h
+#ifndef FLIP_SOCIAL_I
+#define FLIP_SOCIAL_I
+
+/**
+ * @brief Function to allocate resources for the FlipSocialApp.
+ * @details Initializes all components and views of the application.
+ * @return Pointer to the initialized FlipSocialApp, or NULL on failure.
+ */
+static FlipSocialApp *flip_social_app_alloc()
+{
+    // Initiailize the app
+    FlipSocialApp *app = (FlipSocialApp *)malloc(sizeof(FlipSocialApp));
+
+    // Initialize gui
+    Gui *gui = furi_record_open(RECORD_GUI);
+
+    // Initialize UART
+    if (!flipper_http_init(flipper_http_rx_callback, app))
+    {
+        FURI_LOG_E(TAG, "Failed to initialize UART");
+        return NULL;
+    }
+
+    // Allocate ViewDispatcher
+    if (!easy_flipper_set_view_dispatcher(&app->view_dispatcher, gui, app))
+    {
+        return NULL;
+    }
+
+    // Allocate the text input buffers
+    app->wifi_ssid_logged_out_temp_buffer_size = MAX_USER_LENGTH;
+    app->wifi_password_logged_out_temp_buffer_size = MAX_USER_LENGTH;
+    app->login_username_logged_out_temp_buffer_size = MAX_USER_LENGTH;
+    app->login_password_logged_out_temp_buffer_size = MAX_USER_LENGTH;
+    app->register_username_logged_out_temp_buffer_size = MAX_USER_LENGTH;
+    app->register_password_logged_out_temp_buffer_size = MAX_USER_LENGTH;
+    app->register_password_2_logged_out_temp_buffer_size = MAX_USER_LENGTH;
+    app->change_password_logged_in_temp_buffer_size = MAX_USER_LENGTH;
+    app->compose_pre_save_logged_in_temp_buffer_size = 100;
+    app->wifi_ssid_logged_in_temp_buffer_size = MAX_USER_LENGTH;
+    app->wifi_password_logged_in_temp_buffer_size = MAX_USER_LENGTH;
+    app->is_logged_in_size = 8;
+    app->login_username_logged_in_temp_buffer_size = MAX_USER_LENGTH;
+    app->messages_new_message_logged_in_temp_buffer_size = 100;
+    app->message_user_choice_logged_in_temp_buffer_size = 100;
+    if (!easy_flipper_set_buffer(&app->wifi_ssid_logged_out_temp_buffer, app->wifi_ssid_logged_out_temp_buffer_size))
+    {
+        return NULL;
+    }
+    if (!easy_flipper_set_buffer(&app->wifi_password_logged_out_temp_buffer, app->wifi_password_logged_out_temp_buffer_size))
+    {
+        return NULL;
+    }
+    if (!easy_flipper_set_buffer(&app->login_username_logged_out_temp_buffer, app->login_username_logged_out_temp_buffer_size))
+    {
+        return NULL;
+    }
+    if (!easy_flipper_set_buffer(&app->login_password_logged_out_temp_buffer, app->login_password_logged_out_temp_buffer_size))
+    {
+        return NULL;
+    }
+    if (!easy_flipper_set_buffer(&app->register_username_logged_out_temp_buffer, app->register_username_logged_out_temp_buffer_size))
+    {
+        return NULL;
+    }
+    if (!easy_flipper_set_buffer(&app->register_password_logged_out_temp_buffer, app->register_password_logged_out_temp_buffer_size))
+    {
+        return NULL;
+    }
+    if (!easy_flipper_set_buffer(&app->register_password_2_logged_out_temp_buffer, app->register_password_2_logged_out_temp_buffer_size))
+    {
+        return NULL;
+    }
+    if (!easy_flipper_set_buffer(&app->change_password_logged_in_temp_buffer, app->change_password_logged_in_temp_buffer_size))
+    {
+        return NULL;
+    }
+    if (!easy_flipper_set_buffer(&app->compose_pre_save_logged_in_temp_buffer, app->compose_pre_save_logged_in_temp_buffer_size))
+    {
+        return NULL;
+    }
+    if (!easy_flipper_set_buffer(&app->wifi_ssid_logged_in_temp_buffer, app->wifi_ssid_logged_in_temp_buffer_size))
+    {
+        return NULL;
+    }
+    if (!easy_flipper_set_buffer(&app->wifi_password_logged_in_temp_buffer, app->wifi_password_logged_in_temp_buffer_size))
+    {
+        return NULL;
+    }
+    if (!easy_flipper_set_buffer(&app->is_logged_in, app->is_logged_in_size))
+    {
+        return NULL;
+    }
+    if (!easy_flipper_set_buffer(&app->login_username_logged_in_temp_buffer, app->login_username_logged_in_temp_buffer_size))
+    {
+        return NULL;
+    }
+
+    if (!easy_flipper_set_buffer(&app->wifi_ssid_logged_out, app->wifi_ssid_logged_out_temp_buffer_size))
+    {
+        return NULL;
+    }
+    if (!easy_flipper_set_buffer(&app->wifi_password_logged_out, app->wifi_password_logged_out_temp_buffer_size))
+    {
+        return NULL;
+    }
+    if (!easy_flipper_set_buffer(&app->login_username_logged_out, app->login_username_logged_out_temp_buffer_size))
+    {
+        return NULL;
+    }
+    if (!easy_flipper_set_buffer(&app->login_password_logged_out, app->login_password_logged_out_temp_buffer_size))
+    {
+        return NULL;
+    }
+    if (!easy_flipper_set_buffer(&app->register_username_logged_out, app->register_username_logged_out_temp_buffer_size))
+    {
+        return NULL;
+    }
+    if (!easy_flipper_set_buffer(&app->register_password_logged_out, app->register_password_logged_out_temp_buffer_size))
+    {
+        return NULL;
+    }
+    if (!easy_flipper_set_buffer(&app->register_password_2_logged_out, app->register_password_2_logged_out_temp_buffer_size))
+    {
+        return NULL;
+    }
+    if (!easy_flipper_set_buffer(&app->change_password_logged_in, app->change_password_logged_in_temp_buffer_size))
+    {
+        return NULL;
+    }
+    if (!easy_flipper_set_buffer(&app->compose_pre_save_logged_in, app->compose_pre_save_logged_in_temp_buffer_size))
+    {
+        return NULL;
+    }
+    if (!easy_flipper_set_buffer(&app->wifi_ssid_logged_in, app->wifi_ssid_logged_in_temp_buffer_size))
+    {
+        return NULL;
+    }
+    if (!easy_flipper_set_buffer(&app->wifi_password_logged_in, app->wifi_password_logged_in_temp_buffer_size))
+    {
+        return NULL;
+    }
+    if (!easy_flipper_set_buffer(&app->login_username_logged_in, app->login_username_logged_in_temp_buffer_size))
+    {
+        return NULL;
+    }
+    //
+    if (!easy_flipper_set_buffer(&app->messages_new_message_logged_in, app->messages_new_message_logged_in_temp_buffer_size))
+    {
+        return NULL;
+    }
+    if (!easy_flipper_set_buffer(&app->messages_new_message_logged_in_temp_buffer, app->messages_new_message_logged_in_temp_buffer_size))
+    {
+        return NULL;
+    }
+    if (!easy_flipper_set_buffer(&app->message_user_choice_logged_in, app->message_user_choice_logged_in_temp_buffer_size))
+    {
+        return NULL;
+    }
+    if (!easy_flipper_set_buffer(&app->message_user_choice_logged_in_temp_buffer, app->message_user_choice_logged_in_temp_buffer_size))
+    {
+        return NULL;
+    }
+
+    // Allocate Submenu(s)
+    if (!easy_flipper_set_submenu(&app->submenu_logged_out, FlipSocialViewLoggedOutSubmenu, "FlipSocial v0.5", flip_social_callback_exit_app, &app->view_dispatcher))
+    {
+        return NULL;
+    }
+    if (!easy_flipper_set_submenu(&app->submenu_logged_in, FlipSocialViewLoggedInSubmenu, "FlipSocial v0.5", flip_social_callback_exit_app, &app->view_dispatcher))
+    {
+        return NULL;
+    }
+    if (!easy_flipper_set_submenu(&app->submenu_compose, FlipSocialViewLoggedInCompose, "Create A Post", flip_social_callback_to_submenu_logged_in, &app->view_dispatcher))
+    {
+        return NULL;
+    }
+    if (!easy_flipper_set_submenu(&app->submenu_explore, FlipSocialViewLoggedInExploreSubmenu, "Explore", flip_social_callback_to_submenu_logged_in, &app->view_dispatcher))
+    {
+        return NULL;
+    }
+    if (!easy_flipper_set_submenu(&app->submenu_friends, FlipSocialViewLoggedInFriendsSubmenu, "Friends", flip_social_callback_to_profile_logged_in, &app->view_dispatcher))
+    {
+        return NULL;
+    }
+    if (!easy_flipper_set_submenu(&app->submenu_messages, FlipSocialViewLoggedInMessagesSubmenu, "Messages", flip_social_callback_to_submenu_logged_in, &app->view_dispatcher))
+    {
+        return NULL;
+    }
+    if (!easy_flipper_set_submenu(&app->submenu_messages_user_choices, FlipSocialViewLoggedInMessagesUserChoices, "Users", flip_social_callback_to_messages_logged_in, &app->view_dispatcher))
+    {
+        return NULL;
+    }
+
+    submenu_add_item(app->submenu_logged_out, "Login", FlipSocialSubmenuLoggedOutIndexLogin, flip_social_callback_submenu_choices, app);
+    submenu_add_item(app->submenu_logged_out, "Register", FlipSocialSubmenuLoggedOutIndexRegister, flip_social_callback_submenu_choices, app);
+    submenu_add_item(app->submenu_logged_out, "About", FlipSocialSubmenuLoggedOutIndexAbout, flip_social_callback_submenu_choices, app);
+    submenu_add_item(app->submenu_logged_out, "Settings", FlipSocialSubmenuLoggedOutIndexWifiSettings, flip_social_callback_submenu_choices, app);
+    //
+    submenu_add_item(app->submenu_logged_in, "Explore", FlipSocialSubmenuExploreIndex, flip_social_callback_submenu_choices, app);
+    submenu_add_item(app->submenu_logged_in, "Feed", FlipSocialSubmenuLoggedInIndexFeed, flip_social_callback_submenu_choices, app);
+    submenu_add_item(app->submenu_logged_in, "Post", FlipSocialSubmenuLoggedInIndexCompose, flip_social_callback_submenu_choices, app);
+    submenu_add_item(app->submenu_logged_in, "Messages", FlipSocialSubmenuLoggedInIndexMessages, flip_social_callback_submenu_choices, app);
+    submenu_add_item(app->submenu_logged_in, "Profile", FlipSocialSubmenuLoggedInIndexProfile, flip_social_callback_submenu_choices, app);
+    submenu_add_item(app->submenu_logged_in, "Settings", FlipSocialSubmenuLoggedInIndexSettings, flip_social_callback_submenu_choices, app);
+    submenu_add_item(app->submenu_logged_in, "Sign Out", FlipSocialSubmenuLoggedInSignOutButton, flip_social_callback_submenu_choices, app);
+    //
+    submenu_add_item(app->submenu_compose, "Add Pre-Save", FlipSocialSubmenuComposeIndexAddPreSave, flip_social_callback_submenu_choices, app);
+    //
+
+    // Allocate View(s)
+    if (!easy_flipper_set_view(&app->view_process_login, FlipSocialViewLoggedOutProcessLogin, flip_social_callback_draw_login, NULL, flip_social_callback_to_login_logged_out, &app->view_dispatcher, app))
+    {
+        return NULL;
+    }
+    if (!easy_flipper_set_view(&app->view_process_register, FlipSocialViewLoggedOutProcessRegister, flip_social_callback_draw_register, NULL, flip_social_callback_to_register_logged_out, &app->view_dispatcher, app))
+    {
+        return NULL;
+    }
+    if (!easy_flipper_set_view(&app->view_process_feed, FlipSocialViewLoggedInFeed, flip_social_callback_draw_feed, NULL, flip_social_callback_to_submenu_logged_in, &app->view_dispatcher, app))
+    {
+        return NULL;
+    }
+    if (!easy_flipper_set_view(&app->view_process_compose, FlipSocialViewLoggedInProcessCompose, flip_social_callback_draw_compose, NULL, flip_social_callback_to_compose_logged_in, &app->view_dispatcher, app))
+    {
+        return NULL;
+    }
+    if (!easy_flipper_set_view(&app->view_process_explore, FlipSocialViewLoggedInExploreProccess, flip_social_callback_draw_explore, NULL, flip_social_callback_to_explore_logged_in, &app->view_dispatcher, app))
+    {
+        return NULL;
+    }
+    if (!easy_flipper_set_view(&app->view_process_friends, FlipSocialViewLoggedInFriendsProcess, flip_social_callback_draw_friends, NULL, flip_social_callback_to_friends_logged_in, &app->view_dispatcher, app))
+    {
+        return NULL;
+    }
+    if (!easy_flipper_set_view(&app->view_process_messages, FlipSocialViewLoggedInMessagesProcess, flip_social_callback_draw_messages, NULL, flip_social_callback_to_messages_logged_in, &app->view_dispatcher, app))
+    {
+        return NULL;
+    }
+
+    // Setup Variable Item List(s)
+    if (!easy_flipper_set_variable_item_list(&app->variable_item_list_logged_out_wifi_settings, FlipSocialViewLoggedOutWifiSettings, flip_social_text_input_logged_out_wifi_settings_item_selected, flip_social_callback_to_submenu_logged_out, &app->view_dispatcher, app))
+    {
+        return NULL;
+    }
+    if (!easy_flipper_set_variable_item_list(&app->variable_item_list_logged_out_login, FlipSocialViewLoggedOutLogin, flip_social_text_input_logged_out_login_item_selected, flip_social_callback_to_submenu_logged_out, &app->view_dispatcher, app))
+    {
+        return NULL;
+    }
+    if (!easy_flipper_set_variable_item_list(&app->variable_item_list_logged_out_register, FlipSocialViewLoggedOutRegister, flip_social_text_input_logged_out_register_item_selected, flip_social_callback_to_submenu_logged_out, &app->view_dispatcher, app))
+    {
+        return NULL;
+    }
+    if (!easy_flipper_set_variable_item_list(&app->variable_item_list_logged_in_profile, FlipSocialViewLoggedInProfile, flip_social_text_input_logged_in_profile_item_selected, flip_social_callback_to_submenu_logged_in, &app->view_dispatcher, app))
+    {
+        return NULL;
+    }
+    if (!easy_flipper_set_variable_item_list(&app->variable_item_list_logged_in_settings, FlipSocialViewLoggedInSettings, flip_social_text_input_logged_in_settings_item_selected, flip_social_callback_to_submenu_logged_in, &app->view_dispatcher, app))
+    {
+        return NULL;
+    }
+    if (!easy_flipper_set_variable_item_list(&app->variable_item_list_logged_in_settings_wifi, FlipSocialViewLoggedInSettingsWifi, flip_social_text_input_logged_in_wifi_settings_item_selected, flip_social_callback_to_settings_logged_in, &app->view_dispatcher, app))
+    {
+        return NULL;
+    }
+
+    app->variable_item_logged_out_wifi_settings_ssid = variable_item_list_add(app->variable_item_list_logged_out_wifi_settings, "SSID", 1, NULL, NULL);
+    app->variable_item_logged_out_wifi_settings_password = variable_item_list_add(app->variable_item_list_logged_out_wifi_settings, "Password", 1, NULL, NULL);
+    //
+    app->variable_item_logged_out_login_username = variable_item_list_add(app->variable_item_list_logged_out_login, "Username", 1, NULL, NULL);
+    app->variable_item_logged_out_login_password = variable_item_list_add(app->variable_item_list_logged_out_login, "Password", 1, NULL, NULL);
+    app->variable_item_logged_out_login_button = variable_item_list_add(app->variable_item_list_logged_out_login, "Login", 0, NULL, NULL);
+    //
+    app->variable_item_logged_out_register_username = variable_item_list_add(app->variable_item_list_logged_out_register, "Username", 1, NULL, NULL);
+    app->variable_item_logged_out_register_password = variable_item_list_add(app->variable_item_list_logged_out_register, "Password", 1, NULL, NULL);
+    app->variable_item_logged_out_register_password_2 = variable_item_list_add(app->variable_item_list_logged_out_register, "Confirm Password", 1, NULL, NULL);
+    app->variable_item_logged_out_register_button = variable_item_list_add(app->variable_item_list_logged_out_register, "Register", 0, NULL, NULL);
+    //
+    app->variable_item_logged_in_profile_username = variable_item_list_add(app->variable_item_list_logged_in_profile, "Username", 0, NULL, NULL);
+    app->variable_item_logged_in_profile_change_password = variable_item_list_add(app->variable_item_list_logged_in_profile, "Change Password", 0, NULL, NULL);
+    app->variable_item_logged_in_profile_friends = variable_item_list_add(app->variable_item_list_logged_in_profile, "Friends", 0, NULL, NULL);
+    //
+    app->variable_item_logged_in_settings_about = variable_item_list_add(app->variable_item_list_logged_in_settings, "About", 0, NULL, NULL);
+    app->variable_item_logged_in_settings_wifi = variable_item_list_add(app->variable_item_list_logged_in_settings, "WiFi", 0, NULL, NULL);
+    //
+    app->variable_item_logged_in_wifi_settings_ssid = variable_item_list_add(app->variable_item_list_logged_in_settings_wifi, "SSID", 1, NULL, NULL);
+    app->variable_item_logged_in_wifi_settings_password = variable_item_list_add(app->variable_item_list_logged_in_settings_wifi, "Password", 1, NULL, NULL);
+
+    // Setup Text Input(s)
+    if (!easy_flipper_set_uart_text_input(&app->text_input_logged_out_wifi_settings_ssid, FlipSocialViewLoggedOutWifiSettingsSSIDInput, "Enter SSID", app->wifi_ssid_logged_out_temp_buffer, app->wifi_ssid_logged_out_temp_buffer_size, flip_social_logged_out_wifi_settings_ssid_updated, flip_social_callback_to_wifi_settings_logged_out, &app->view_dispatcher, app))
+    {
+        return NULL;
+    }
+    if (!easy_flipper_set_uart_text_input(&app->text_input_logged_out_wifi_settings_password, FlipSocialViewLoggedOutWifiSettingsPasswordInput, "Enter Password", app->wifi_password_logged_out_temp_buffer, app->wifi_password_logged_out_temp_buffer_size, flip_social_logged_out_wifi_settings_password_updated, flip_social_callback_to_wifi_settings_logged_out, &app->view_dispatcher, app))
+    {
+        return NULL;
+    }
+    if (!easy_flipper_set_uart_text_input(&app->text_input_logged_out_login_username, FlipSocialViewLoggedOutLoginUsernameInput, "Enter Username", app->login_username_logged_out_temp_buffer, app->login_username_logged_out_temp_buffer_size, flip_social_logged_out_login_username_updated, flip_social_callback_to_login_logged_out, &app->view_dispatcher, app))
+    {
+        return NULL;
+    }
+    if (!easy_flipper_set_uart_text_input(&app->text_input_logged_out_login_password, FlipSocialViewLoggedOutLoginPasswordInput, "Enter Password", app->login_password_logged_out_temp_buffer, app->login_password_logged_out_temp_buffer_size, flip_social_logged_out_login_password_updated, flip_social_callback_to_login_logged_out, &app->view_dispatcher, app))
+    {
+        return NULL;
+    }
+    if (!easy_flipper_set_uart_text_input(&app->text_input_logged_out_register_username, FlipSocialViewLoggedOutRegisterUsernameInput, "Enter Username", app->register_username_logged_out_temp_buffer, app->register_username_logged_out_temp_buffer_size, flip_social_logged_out_register_username_updated, flip_social_callback_to_register_logged_out, &app->view_dispatcher, app))
+    {
+        return NULL;
+    }
+    if (!easy_flipper_set_uart_text_input(&app->text_input_logged_out_register_password, FlipSocialViewLoggedOutRegisterPasswordInput, "Enter Password", app->register_password_logged_out_temp_buffer, app->register_password_logged_out_temp_buffer_size, flip_social_logged_out_register_password_updated, flip_social_callback_to_register_logged_out, &app->view_dispatcher, app))
+    {
+        return NULL;
+    }
+    if (!easy_flipper_set_uart_text_input(&app->text_input_logged_out_register_password_2, FlipSocialViewLoggedOutRegisterPassword2Input, "Confirm Password", app->register_password_2_logged_out_temp_buffer, app->register_password_2_logged_out_temp_buffer_size, flip_social_logged_out_register_password_2_updated, flip_social_callback_to_register_logged_out, &app->view_dispatcher, app))
+    {
+        return NULL;
+    }
+    //
+    if (!easy_flipper_set_uart_text_input(&app->text_input_logged_in_change_password, FlipSocialViewLoggedInChangePasswordInput, "Enter New Password", app->change_password_logged_in_temp_buffer, app->change_password_logged_in_temp_buffer_size, flip_social_logged_in_profile_change_password_updated, flip_social_callback_to_profile_logged_in, &app->view_dispatcher, app))
+    {
+        return NULL;
+    }
+    if (!easy_flipper_set_uart_text_input(&app->text_input_logged_in_compose_pre_save_input, FlipSocialViewLoggedInComposeAddPreSaveInput, "Enter Pre-Save Message", app->compose_pre_save_logged_in_temp_buffer, app->compose_pre_save_logged_in_temp_buffer_size, flip_social_logged_in_compose_pre_save_updated, flip_social_callback_to_compose_logged_in, &app->view_dispatcher, app))
+    {
+        return NULL;
+    }
+    if (!easy_flipper_set_uart_text_input(&app->text_input_logged_in_wifi_settings_ssid, FlipSocialViewLoggedInWifiSettingsSSIDInput, "Enter SSID", app->wifi_ssid_logged_in_temp_buffer, app->wifi_ssid_logged_in_temp_buffer_size, flip_social_logged_in_wifi_settings_ssid_updated, flip_social_callback_to_wifi_settings_logged_in, &app->view_dispatcher, app))
+    {
+        return NULL;
+    }
+    if (!easy_flipper_set_uart_text_input(&app->text_input_logged_in_wifi_settings_password, FlipSocialViewLoggedInWifiSettingsPasswordInput, "Enter Password", app->wifi_password_logged_in_temp_buffer, app->wifi_password_logged_in_temp_buffer_size, flip_social_logged_in_wifi_settings_password_updated, flip_social_callback_to_wifi_settings_logged_in, &app->view_dispatcher, app))
+    {
+        return NULL;
+    }
+    //
+    if (!easy_flipper_set_uart_text_input(&app->text_input_logged_in_messages_new_message, FlipSocialViewLoggedInMessagesNewMessageInput, "Enter Message", app->messages_new_message_logged_in_temp_buffer, app->messages_new_message_logged_in_temp_buffer_size, flip_social_logged_in_messages_new_message_updated, flip_social_callback_to_messages_logged_in, &app->view_dispatcher, app))
+    {
+        return NULL;
+    }
+    if (!easy_flipper_set_uart_text_input(&app->text_input_logged_in_messages_new_message_user_choices, FlipSocialViewLoggedInMessagesNewMessageUserChoicesInput, "Enter Message", app->message_user_choice_logged_in_temp_buffer, app->message_user_choice_logged_in_temp_buffer_size, flip_social_logged_in_messages_user_choice_message_updated, flip_social_callback_to_messages_user_choices, &app->view_dispatcher, app))
+    {
+        return NULL;
+    }
+
+    // Setup About(s)
+    if (!easy_flipper_set_widget(&app->widget_logged_out_about, FlipSocialViewLoggedOutAbout, "Welcome to FlipSocial\n---\nThe social media app for\nFlipper Zero, created by\nJBlanked: www.flipsocial.net\n---\nPress BACK to return.", flip_social_callback_to_submenu_logged_out, &app->view_dispatcher))
+    {
+        return NULL;
+    }
+    if (!easy_flipper_set_widget(&app->widget_logged_in_about, FlipSocialViewLoggedInSettingsAbout, "Welcome to FlipSocial\n---\nThe social media app for\nFlipper Zero, created by\nJBlanked: www.flipsocial.net\n---\nPress BACK to return.", flip_social_callback_to_settings_logged_in, &app->view_dispatcher))
+    {
+        return NULL;
+    }
+
+    // load the playlist
+    if (load_playlist(&app->pre_saved_messages))
+    {
+        // Update the playlist submenu
+        for (uint32_t i = 0; i < app->pre_saved_messages.count; i++)
+        {
+            submenu_add_item(app->submenu_compose, app->pre_saved_messages.messages[i], FlipSocialSubemnuComposeIndexStartIndex + i, flip_social_callback_submenu_choices, app);
+        }
+    }
+
+    // Load the settings
+    if (!load_settings(app->wifi_ssid_logged_out,
+                       app->wifi_ssid_logged_out_temp_buffer_size,
+                       app->wifi_password_logged_out,
+                       app->wifi_password_logged_out_temp_buffer_size,
+                       app->login_username_logged_out,
+                       app->login_username_logged_out_temp_buffer_size,
+                       app->login_username_logged_in,
+                       app->login_username_logged_in_temp_buffer_size,
+                       app->login_password_logged_out,
+                       app->login_password_logged_out_temp_buffer_size,
+                       app->change_password_logged_in,
+                       app->change_password_logged_in_temp_buffer_size,
+                       app->is_logged_in,
+                       app->is_logged_in_size))
+
+    {
+        FURI_LOG_E(TAG, "Failed to load settings");
+
+        if (app->is_logged_in == NULL)
+        {
+            app->is_logged_in = (char *)malloc(app->is_logged_in_size);
+            app->is_logged_in = "false";
+        }
+        app_instance = app;
+        view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedOutSubmenu);
+    }
+    else
+    {
+        // Copy items into their temp buffers with safety checks
+        if (app->wifi_ssid_logged_out && app->wifi_ssid_logged_out_temp_buffer)
+        {
+            strncpy(app->wifi_ssid_logged_out_temp_buffer, app->wifi_ssid_logged_out, app->wifi_ssid_logged_out_temp_buffer_size - 1);
+            app->wifi_ssid_logged_out_temp_buffer[app->wifi_ssid_logged_out_temp_buffer_size - 1] = '\0';
+        }
+        if (app->wifi_password_logged_out && app->wifi_password_logged_out_temp_buffer)
+        {
+            strncpy(app->wifi_password_logged_out_temp_buffer, app->wifi_password_logged_out, app->wifi_password_logged_out_temp_buffer_size - 1);
+            app->wifi_password_logged_out_temp_buffer[app->wifi_password_logged_out_temp_buffer_size - 1] = '\0';
+        }
+        if (app->login_username_logged_out && app->login_username_logged_out_temp_buffer)
+        {
+            strncpy(app->login_username_logged_out_temp_buffer, app->login_username_logged_out, app->login_username_logged_out_temp_buffer_size - 1);
+            app->login_username_logged_out_temp_buffer[app->login_username_logged_out_temp_buffer_size - 1] = '\0';
+        }
+        if (app->login_password_logged_out && app->login_password_logged_out_temp_buffer)
+        {
+            strncpy(app->login_password_logged_out_temp_buffer, app->login_password_logged_out, app->login_password_logged_out_temp_buffer_size - 1);
+            app->login_password_logged_out_temp_buffer[app->login_password_logged_out_temp_buffer_size - 1] = '\0';
+        }
+        if (app->register_username_logged_out && app->register_username_logged_out_temp_buffer)
+        {
+            strncpy(app->register_username_logged_out_temp_buffer, app->register_username_logged_out, app->register_username_logged_out_temp_buffer_size - 1);
+            app->register_username_logged_out_temp_buffer[app->register_username_logged_out_temp_buffer_size - 1] = '\0';
+        }
+        if (app->register_password_logged_out && app->register_password_logged_out_temp_buffer)
+        {
+            strncpy(app->register_password_logged_out_temp_buffer, app->register_password_logged_out, app->register_password_logged_out_temp_buffer_size - 1);
+            app->register_password_logged_out_temp_buffer[app->register_password_logged_out_temp_buffer_size - 1] = '\0';
+        }
+        if (app->register_password_2_logged_out && app->register_password_2_logged_out_temp_buffer)
+        {
+            strncpy(app->register_password_2_logged_out_temp_buffer, app->register_password_2_logged_out, app->register_password_2_logged_out_temp_buffer_size - 1);
+            app->register_password_2_logged_out_temp_buffer[app->register_password_2_logged_out_temp_buffer_size - 1] = '\0';
+        }
+        if (app->change_password_logged_in && app->change_password_logged_in_temp_buffer)
+        {
+            strncpy(app->change_password_logged_in_temp_buffer, app->change_password_logged_in, app->change_password_logged_in_temp_buffer_size - 1);
+            app->change_password_logged_in_temp_buffer[app->change_password_logged_in_temp_buffer_size - 1] = '\0';
+        }
+        if (app->compose_pre_save_logged_in && app->compose_pre_save_logged_in_temp_buffer)
+        {
+            strncpy(app->compose_pre_save_logged_in_temp_buffer, app->compose_pre_save_logged_in, app->compose_pre_save_logged_in_temp_buffer_size - 1);
+            app->compose_pre_save_logged_in_temp_buffer[app->compose_pre_save_logged_in_temp_buffer_size - 1] = '\0';
+        }
+        if (app->wifi_ssid_logged_in && app->wifi_ssid_logged_in_temp_buffer)
+        {
+            strncpy(app->wifi_ssid_logged_in_temp_buffer, app->wifi_ssid_logged_in, app->wifi_ssid_logged_in_temp_buffer_size - 1);
+            app->wifi_ssid_logged_in_temp_buffer[app->wifi_ssid_logged_in_temp_buffer_size - 1] = '\0';
+        }
+        if (app->wifi_password_logged_in && app->wifi_password_logged_in_temp_buffer)
+        {
+            strncpy(app->wifi_password_logged_in_temp_buffer, app->wifi_password_logged_in, app->wifi_password_logged_in_temp_buffer_size - 1);
+            app->wifi_password_logged_in_temp_buffer[app->wifi_password_logged_in_temp_buffer_size - 1] = '\0';
+        }
+        if (app->login_username_logged_in && app->login_username_logged_in_temp_buffer)
+        {
+            strncpy(app->login_username_logged_in_temp_buffer, app->login_username_logged_in, app->login_username_logged_in_temp_buffer_size - 1);
+            app->login_username_logged_in_temp_buffer[app->login_username_logged_in_temp_buffer_size - 1] = '\0';
+        }
+
+        // if login username is empty but logged out isnt, copy it over
+        if (strlen(app->login_username_logged_out) > 0 && strlen(app->login_username_logged_in) == 0)
+        {
+            strncpy(app->login_username_logged_in, app->login_username_logged_out, app->login_username_logged_in_temp_buffer_size - 1);
+            strncpy(app->login_username_logged_in_temp_buffer, app->login_username_logged_out, app->login_username_logged_in_temp_buffer_size - 1);
+            app->login_username_logged_in[app->login_username_logged_in_temp_buffer_size - 1] = '\0';
+            app->login_username_logged_in_temp_buffer[app->login_username_logged_in_temp_buffer_size - 1] = '\0';
+        }
+        // logout username is empty but logged in isnt, copy it over
+        if (strlen(app->login_username_logged_in) > 0 && strlen(app->login_username_logged_out) == 0)
+        {
+            strncpy(app->login_username_logged_out, app->login_username_logged_in, app->login_username_logged_in_temp_buffer_size - 1);
+            strncpy(app->login_username_logged_out_temp_buffer, app->login_username_logged_in, app->login_username_logged_in_temp_buffer_size - 1);
+            app->login_username_logged_out[app->login_username_logged_in_temp_buffer_size - 1] = '\0';
+            app->login_username_logged_out_temp_buffer[app->login_username_logged_in_temp_buffer_size - 1] = '\0';
+        }
+        // if login password is empty but logged out isnt, copy it over
+        if (strlen(app->login_password_logged_out) > 0 && strlen(app->change_password_logged_in) == 0)
+        {
+            strncpy(app->change_password_logged_in, app->login_password_logged_out, app->login_password_logged_out_temp_buffer_size - 1);
+            strncpy(app->change_password_logged_in_temp_buffer, app->login_password_logged_out, app->login_password_logged_out_temp_buffer_size - 1);
+            app->change_password_logged_in[app->login_password_logged_out_temp_buffer_size - 1] = '\0';
+            app->change_password_logged_in_temp_buffer[app->login_password_logged_out_temp_buffer_size - 1] = '\0';
+        }
+        // if logout password is empty but logged in isnt, copy it over
+        if (strlen(app->change_password_logged_in) > 0 && strlen(app->login_password_logged_out) == 0)
+        {
+            strncpy(app->login_password_logged_out, app->change_password_logged_in, app->login_password_logged_out_temp_buffer_size - 1);
+            strncpy(app->login_password_logged_out_temp_buffer, app->change_password_logged_in, app->login_password_logged_out_temp_buffer_size - 1);
+            app->login_password_logged_out[app->login_password_logged_out_temp_buffer_size - 1] = '\0';
+            app->login_password_logged_out_temp_buffer[app->login_password_logged_out_temp_buffer_size - 1] = '\0';
+        }
+        // if wifi password is empty but logged out isnt, copy it over
+        if (strlen(app->wifi_password_logged_out) > 0 && strlen(app->wifi_password_logged_in) == 0)
+        {
+            strncpy(app->wifi_password_logged_in, app->wifi_password_logged_out, app->wifi_password_logged_in_temp_buffer_size - 1);
+            strncpy(app->wifi_password_logged_in_temp_buffer, app->wifi_password_logged_out, app->wifi_password_logged_in_temp_buffer_size - 1);
+            app->wifi_password_logged_in[app->wifi_password_logged_in_temp_buffer_size - 1] = '\0';
+            app->wifi_password_logged_in_temp_buffer[app->wifi_password_logged_in_temp_buffer_size - 1] = '\0';
+        }
+        // ifi wifi password is empty but logged in isnt, copy it over
+        if (strlen(app->wifi_password_logged_in) > 0 && strlen(app->wifi_password_logged_out) == 0)
+        {
+            strncpy(app->wifi_password_logged_out, app->wifi_password_logged_in, app->wifi_password_logged_in_temp_buffer_size - 1);
+            strncpy(app->wifi_password_logged_out_temp_buffer, app->wifi_password_logged_in, app->wifi_password_logged_in_temp_buffer_size - 1);
+            app->wifi_password_logged_out[app->wifi_password_logged_in_temp_buffer_size - 1] = '\0';
+            app->wifi_password_logged_out_temp_buffer[app->wifi_password_logged_in_temp_buffer_size - 1] = '\0';
+        }
+        // if wifi ssid is empty but logged out isnt, copy it over
+        if (strlen(app->wifi_ssid_logged_out) > 0 && strlen(app->wifi_ssid_logged_in) == 0)
+        {
+            strncpy(app->wifi_ssid_logged_in, app->wifi_ssid_logged_out, app->wifi_ssid_logged_in_temp_buffer_size - 1);
+            strncpy(app->wifi_ssid_logged_in_temp_buffer, app->wifi_ssid_logged_out, app->wifi_ssid_logged_in_temp_buffer_size - 1);
+            app->wifi_ssid_logged_in[app->wifi_ssid_logged_in_temp_buffer_size - 1] = '\0';
+            app->wifi_ssid_logged_in_temp_buffer[app->wifi_ssid_logged_in_temp_buffer_size - 1] = '\0';
+        }
+        // if wifi ssid is empty but logged in isnt, copy it over
+        if (strlen(app->wifi_ssid_logged_in) > 0 && strlen(app->wifi_ssid_logged_out) == 0)
+        {
+            strncpy(app->wifi_ssid_logged_out, app->wifi_ssid_logged_in, app->wifi_ssid_logged_in_temp_buffer_size - 1);
+            strncpy(app->wifi_ssid_logged_out_temp_buffer, app->wifi_ssid_logged_in, app->wifi_ssid_logged_in_temp_buffer_size - 1);
+            app->wifi_ssid_logged_out[app->wifi_ssid_logged_in_temp_buffer_size - 1] = '\0';
+            app->wifi_ssid_logged_out_temp_buffer[app->wifi_ssid_logged_in_temp_buffer_size - 1] = '\0';
+        }
+
+        // set variable item text (ommit the passwords)
+        variable_item_set_current_value_text(app->variable_item_logged_in_wifi_settings_ssid, app->wifi_ssid_logged_in);
+        variable_item_set_current_value_text(app->variable_item_logged_out_wifi_settings_ssid, app->wifi_ssid_logged_out);
+        variable_item_set_current_value_text(app->variable_item_logged_out_login_username, app->login_username_logged_out);
+        variable_item_set_current_value_text(app->variable_item_logged_in_profile_username, app->login_username_logged_in);
+        //
+
+        if (app->is_logged_in != NULL && strcmp(app->is_logged_in, "true") == 0)
+        {
+            view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedInSubmenu);
+        }
+        else
+        {
+            view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedOutSubmenu);
+        }
+    }
+
+    return app;
+}
+
+#endif // FLIP_SOCIAL_I

+ 396 - 0
flip_social/flip_social_messages.h

@@ -0,0 +1,396 @@
+#ifndef FLIP_SOCIAL_MESSAGES_H
+#define FLIP_SOCIAL_MESSAGES_H
+
+static FlipSocialModel2 *flip_social_messages_alloc()
+{
+    // Allocate memory for each username only if not already allocated
+    FlipSocialModel2 *users = malloc(sizeof(FlipSocialModel2));
+    if (users == NULL)
+    {
+        FURI_LOG_E(TAG, "Failed to allocate memory for message users");
+        return NULL;
+    }
+    for (size_t i = 0; i < MAX_MESSAGE_USERS; i++)
+    {
+        if (users->usernames[i] == NULL)
+        {
+            users->usernames[i] = malloc(MAX_USER_LENGTH);
+            if (users->usernames[i] == NULL)
+            {
+                FURI_LOG_E(TAG, "Failed to allocate memory for username %zu", i);
+                return NULL; // Return false on memory allocation failure
+            }
+        }
+    }
+    return users;
+}
+
+static FlipSocialMessage *flip_social_user_messages_alloc()
+{
+    // Allocate memory for each username only if not already allocated
+    FlipSocialMessage *messages = malloc(sizeof(FlipSocialMessage));
+    if (messages == NULL)
+    {
+        FURI_LOG_E(TAG, "Failed to allocate memory for messages");
+        return NULL;
+    }
+    for (size_t i = 0; i < MAX_MESSAGE_USERS; i++)
+    {
+        if (messages->usernames[i] == NULL)
+        {
+            messages->usernames[i] = malloc(MAX_USER_LENGTH);
+            if (messages->usernames[i] == NULL)
+            {
+                FURI_LOG_E(TAG, "Failed to allocate memory for username %zu", i);
+                return NULL; // Return false on memory allocation failure
+            }
+        }
+        if (messages->messages[i] == NULL)
+        {
+            messages->messages[i] = malloc(MAX_MESSAGE_LENGTH);
+            if (messages->messages[i] == NULL)
+            {
+                FURI_LOG_E(TAG, "Failed to allocate memory for message %zu", i);
+                return NULL; // Return false on memory allocation failure
+            }
+        }
+    }
+    return messages;
+}
+
+static void flip_social_free_message_users()
+{
+    if (flip_social_message_users == NULL)
+    {
+        FURI_LOG_E(TAG, "Message users model is NULL");
+        return;
+    }
+    for (int i = 0; i < flip_social_message_users->count; i++)
+    {
+        free(flip_social_message_users->usernames[i]);
+    }
+}
+
+static void flip_social_free_messages()
+{
+    if (flip_social_messages == NULL)
+    {
+        FURI_LOG_E(TAG, "Messages model is NULL");
+        return;
+    }
+    for (int i = 0; i < flip_social_messages->count; i++)
+    {
+        free(flip_social_messages->usernames[i]);
+        free(flip_social_messages->messages[i]);
+    }
+}
+
+static bool flip_social_update_messages_submenu()
+{
+    if (app_instance->submenu_messages == NULL)
+    {
+        FURI_LOG_E(TAG, "Submenu is NULL");
+        return false;
+    }
+    if (flip_social_message_users == NULL)
+    {
+        FURI_LOG_E(TAG, "Message users model is NULL");
+        return false;
+    }
+    submenu_reset(app_instance->submenu_messages);
+    submenu_set_header(app_instance->submenu_messages, "Messages");
+    submenu_add_item(app_instance->submenu_messages, "[New Message]", FlipSocialSubmenuLoggedInIndexMessagesNewMessage, flip_social_callback_submenu_choices, app_instance);
+    for (int i = 0; i < flip_social_message_users->count; i++)
+    {
+        submenu_add_item(app_instance->submenu_messages, flip_social_message_users->usernames[i], FlipSocialSubmenuLoggedInIndexMessagesUsersStart + i, flip_social_callback_submenu_choices, app_instance);
+    }
+    return true;
+}
+
+static bool flip_social_update_submenu_user_choices()
+{
+    if (app_instance->submenu_messages_user_choices == NULL)
+    {
+        FURI_LOG_E(TAG, "Submenu is NULL");
+        return false;
+    }
+    if (flip_social_explore == NULL)
+    {
+        FURI_LOG_E(TAG, "Explore model is NULL");
+        return false;
+    }
+    submenu_reset(app_instance->submenu_messages_user_choices);
+    submenu_set_header(app_instance->submenu_messages_user_choices, "Users");
+    for (int i = 0; i < flip_social_explore->count; i++)
+    {
+        submenu_add_item(app_instance->submenu_messages_user_choices, flip_social_explore->usernames[i], FlipSocialSubmenuLoggedInIndexMessagesUserChoicesIndexStart + i, flip_social_callback_submenu_choices, app_instance);
+    }
+    return true;
+}
+
+// Get all the users that have sent messages to the logged in user
+static bool flip_social_get_message_users()
+{
+    if (app_instance->login_username_logged_out == NULL)
+    {
+        FURI_LOG_E(TAG, "Username is NULL");
+        return false;
+    }
+    char command[128];
+    snprintf(command, 128, "https://www.flipsocial.net/api/messages/%s/get/list/", app_instance->login_username_logged_out);
+    bool success = flipper_http_get_request_with_headers(command, "{\"Content-Type\":\"application/json\"}");
+    if (!success)
+    {
+        FURI_LOG_E(TAG, "Failed to send HTTP request for messages");
+        return false;
+    }
+    fhttp.state = RECEIVING;
+    return true;
+}
+
+// Get all the messages between the logged in user and the selected user
+static bool flip_social_get_messages_with_user()
+{
+    if (app_instance->login_username_logged_out == NULL)
+    {
+        FURI_LOG_E(TAG, "Username is NULL");
+        return false;
+    }
+    char command[128];
+    snprintf(command, 128, "https://www.flipsocial.net/api/messages/%s/get/%s/", app_instance->login_username_logged_out, flip_social_message_users->usernames[flip_social_message_users->index]);
+    bool success = flipper_http_get_request_with_headers(command, "{\"Content-Type\":\"application/json\"}");
+    if (!success)
+    {
+        FURI_LOG_E(TAG, "Failed to send HTTP request for messages");
+        return false;
+    }
+    fhttp.state = RECEIVING;
+    return true;
+}
+
+// Parse the users that have sent messages to the logged-in user
+static bool flip_social_parse_json_message_users()
+{
+    if (fhttp.received_data == NULL)
+    {
+        FURI_LOG_E(TAG, "No data received.");
+        return false;
+    }
+
+    // Allocate memory for each username only if not already allocated
+    flip_social_message_users = flip_social_messages_alloc();
+    if (flip_social_message_users == NULL)
+    {
+        FURI_LOG_E(TAG, "Failed to allocate memory for message users.");
+        return false;
+    }
+
+    // Remove newlines
+    char *pos = fhttp.received_data;
+    while ((pos = strchr(pos, '\n')) != NULL)
+    {
+        *pos = ' ';
+    }
+
+    // Initialize message users count
+    flip_social_message_users->count = 0;
+
+    // Extract the users array from the JSON
+    char *json_users = get_json_value("users", fhttp.received_data, MAX_TOKENS);
+    if (json_users == NULL)
+    {
+        FURI_LOG_E(TAG, "Failed to parse users array.");
+        return false;
+    }
+
+    // Manual tokenization for comma-separated values
+    char *start = json_users + 1; // Skip the opening bracket
+    char *end;
+    while ((end = strchr(start, ',')) != NULL && flip_social_message_users->count < MAX_MESSAGE_USERS)
+    {
+        *end = '\0'; // Null-terminate the current token
+
+        // Remove quotes
+        if (*start == '"')
+            start++;
+        if (*(end - 1) == '"')
+            *(end - 1) = '\0';
+
+        // Copy username to pre-allocated memory
+        strncpy(flip_social_message_users->usernames[flip_social_message_users->count], start, MAX_USER_LENGTH - 1);
+        flip_social_message_users->usernames[flip_social_message_users->count][MAX_USER_LENGTH - 1] = '\0'; // Ensure null termination
+        flip_social_message_users->count++;
+        start = end + 1;
+    }
+
+    // Handle the last token
+    if (*start != '\0' && flip_social_message_users->count < MAX_MESSAGE_USERS)
+    {
+        if (*start == '"')
+            start++;
+        if (*(start + strlen(start) - 1) == ']')
+            *(start + strlen(start) - 1) = '\0';
+        if (*(start + strlen(start) - 1) == '"')
+            *(start + strlen(start) - 1) = '\0';
+
+        strncpy(flip_social_message_users->usernames[flip_social_message_users->count], start, MAX_USER_LENGTH - 1);
+        flip_social_message_users->usernames[flip_social_message_users->count][MAX_USER_LENGTH - 1] = '\0'; // Ensure null termination
+        flip_social_message_users->count++;
+    }
+
+    // Add submenu items for the users
+    flip_social_update_messages_submenu();
+
+    // Free the JSON data
+    free(json_users);
+    free(start);
+    free(end);
+
+    return true;
+}
+
+// Parse the users that the logged in user can message
+static bool flip_social_parse_json_message_user_choices()
+{
+    if (fhttp.received_data == NULL)
+    {
+        FURI_LOG_E(TAG, "No data received.");
+        return false;
+    }
+
+    // Allocate memory for each username only if not already allocated
+    flip_social_explore = flip_social_explore_alloc();
+    if (flip_social_explore == NULL)
+    {
+        FURI_LOG_E(TAG, "Failed to allocate memory for explore usernames.");
+        return false;
+    }
+
+    // Remove newlines
+    char *pos = fhttp.received_data;
+    while ((pos = strchr(pos, '\n')) != NULL)
+    {
+        *pos = ' ';
+    }
+
+    // Initialize explore count
+    flip_social_explore->count = 0;
+
+    // Extract the users array from the JSON
+    char *json_users = get_json_value("users", fhttp.received_data, MAX_TOKENS);
+    if (json_users == NULL)
+    {
+        FURI_LOG_E(TAG, "Failed to parse users array.");
+        return false;
+    }
+
+    // Manual tokenization for comma-separated values
+    char *start = json_users + 1; // Skip the opening bracket
+    char *end;
+    while ((end = strchr(start, ',')) != NULL && flip_social_explore->count < MAX_EXPLORE_USERS)
+    {
+        *end = '\0'; // Null-terminate the current token
+
+        // Remove quotes
+        if (*start == '"')
+            start++;
+        if (*(end - 1) == '"')
+            *(end - 1) = '\0';
+
+        // Copy username to pre-allocated memory
+        strncpy(flip_social_explore->usernames[flip_social_explore->count], start, MAX_USER_LENGTH - 1);
+        flip_social_explore->usernames[flip_social_explore->count][MAX_USER_LENGTH - 1] = '\0'; // Ensure null termination
+        flip_social_explore->count++;
+        start = end + 1;
+    }
+
+    // Handle the last token
+    if (*start != '\0' && flip_social_explore->count < MAX_EXPLORE_USERS)
+    {
+        if (*start == '"')
+            start++;
+        if (*(start + strlen(start) - 1) == ']')
+            *(start + strlen(start) - 1) = '\0';
+        if (*(start + strlen(start) - 1) == '"')
+            *(start + strlen(start) - 1) = '\0';
+
+        strncpy(flip_social_explore->usernames[flip_social_explore->count], start, MAX_USER_LENGTH - 1);
+        flip_social_explore->usernames[flip_social_explore->count][MAX_USER_LENGTH - 1] = '\0'; // Ensure null termination
+        flip_social_explore->count++;
+    }
+
+    // Add submenu items for the users
+    flip_social_update_submenu_user_choices();
+
+    // Free the JSON data
+    free(json_users);
+    free(start);
+    free(end);
+
+    return true;
+}
+
+// parse messages between the logged in user and the selected user
+static bool flip_social_parse_json_messages()
+{
+    if (fhttp.received_data == NULL)
+    {
+        FURI_LOG_E(TAG, "No data received.");
+        return false;
+    }
+
+    // Allocate memory for each message only if not already allocated
+    flip_social_messages = flip_social_user_messages_alloc();
+    if (!flip_social_messages)
+    {
+        FURI_LOG_E(TAG, "Failed to allocate memory for messages.");
+        return false;
+    }
+
+    // Remove newlines
+    char *pos = fhttp.received_data;
+    while ((pos = strchr(pos, '\n')) != NULL)
+    {
+        *pos = ' ';
+    }
+
+    // Initialize messages count
+    flip_social_messages->count = 0;
+
+    // Iterate through the messages array
+    for (int i = 0; i < MAX_MESSAGES; i++)
+    {
+        // Parse each item in the array
+        char *item = get_json_array_value("conversations", i, fhttp.received_data, MAX_TOKENS);
+        if (item == NULL)
+        {
+            break;
+        }
+
+        // Extract individual fields from the JSON object
+        char *sender = get_json_value("sender", item, MAX_TOKENS);
+        char *content = get_json_value("content", item, MAX_TOKENS);
+
+        if (sender == NULL || content == NULL)
+        {
+            FURI_LOG_E(TAG, "Failed to parse item fields.");
+            free(item);
+            continue;
+        }
+
+        // Store parsed values
+        strncpy(flip_social_messages->usernames[i], sender, MAX_USER_LENGTH - 1);
+        flip_social_messages->usernames[i][MAX_USER_LENGTH - 1] = '\0';
+        strncpy(flip_social_messages->messages[i], content, MAX_MESSAGE_LENGTH - 1);
+        flip_social_messages->messages[i][MAX_MESSAGE_LENGTH - 1] = '\0';
+        flip_social_messages->count++;
+
+        free(item);
+        free(sender);
+        free(content);
+    }
+
+    return true;
+}
+
+#endif // FLIP_SOCIAL_MESSAGES_H

+ 381 - 0
flip_social/flip_social_storage.h

@@ -0,0 +1,381 @@
+// flip_social_storage.h
+#ifndef FLIP_SOCIAL_STORAGE_H
+#define FLIP_SOCIAL_STORAGE_H
+
+#include <furi.h>
+#include <storage/storage.h>
+
+#define SETTINGS_PATH STORAGE_EXT_PATH_PREFIX "/apps_data/flip_social/settings.bin"
+#define PRE_SAVED_MESSAGES_PATH STORAGE_EXT_PATH_PREFIX "/apps_data/flip_social/pre_saved_messages.txt"
+
+// Function to save the playlist
+void save_playlist(const PreSavedPlaylist *playlist)
+{
+    if (!playlist)
+    {
+        FURI_LOG_E(TAG, "Playlist is NULL");
+        return;
+    }
+    // Create the directory for saving settings
+    char directory_path[128];
+    snprintf(directory_path, sizeof(directory_path), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_social");
+
+    // Create the directory
+    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, PRE_SAVED_MESSAGES_PATH, FSAM_WRITE, FSOM_CREATE_ALWAYS))
+    {
+        FURI_LOG_E(TAG, "Failed to open settings file for writing: %s", PRE_SAVED_MESSAGES_PATH);
+        storage_file_free(file);
+        furi_record_close(RECORD_STORAGE);
+        return;
+    }
+
+    // Write each playlist message on a separate line
+    for (size_t i = 0; i < playlist->count; ++i)
+    {
+        // Add a newline character after each message
+        if (storage_file_write(file, playlist->messages[i], strlen(playlist->messages[i])) != strlen(playlist->messages[i]))
+        {
+            FURI_LOG_E(TAG, "Failed to write playlist message %zu", i);
+        }
+
+        // Write a newline after each message
+        if (storage_file_write(file, "\n", 1) != 1)
+        {
+            FURI_LOG_E(TAG, "Failed to write newline after message %zu", i);
+        }
+    }
+
+    storage_file_close(file);
+    storage_file_free(file);
+    furi_record_close(RECORD_STORAGE);
+}
+
+// Function to load the playlist
+// Function to load the playlist
+bool load_playlist(PreSavedPlaylist *playlist)
+{
+    // Ensure playlist is not NULL
+    if (!playlist)
+    {
+        FURI_LOG_E(TAG, "Playlist is NULL");
+        return false;
+    }
+
+    // Ensure playlist->messages is not NULL and allocate memory for each message
+    for (size_t i = 0; i < MAX_PRE_SAVED_MESSAGES; ++i)
+    {
+        if (!playlist->messages[i]) // Check if memory is already allocated
+        {
+            playlist->messages[i] = (char *)malloc(MAX_MESSAGE_LENGTH * sizeof(char));
+            if (!playlist->messages[i])
+            {
+                FURI_LOG_E(TAG, "Failed to allocate memory for message %zu", i);
+                return false; // Return false on memory allocation failure
+            }
+        }
+    }
+
+    // Open the storage
+    Storage *storage = furi_record_open(RECORD_STORAGE);
+    File *file = storage_file_alloc(storage);
+
+    if (!storage_file_open(file, PRE_SAVED_MESSAGES_PATH, FSAM_READ, FSOM_OPEN_EXISTING))
+    {
+        FURI_LOG_E(TAG, "Failed to open pre-saved messages file for reading: %s", PRE_SAVED_MESSAGES_PATH);
+        storage_file_free(file);
+        furi_record_close(RECORD_STORAGE);
+        return false; // Return false if the file does not exist
+    }
+
+    // Initialize the playlist count
+    playlist->count = 0;
+
+    // Read the file byte by byte to simulate reading lines
+    char ch;
+    size_t message_pos = 0;
+    bool message_started = false;
+
+    while (storage_file_read(file, &ch, 1) == 1) // Read one character at a time
+    {
+        message_started = true;
+
+        if (ch == '\n' || message_pos >= (MAX_MESSAGE_LENGTH - 1)) // End of line or message is too long
+        {
+            playlist->messages[playlist->count][message_pos] = '\0'; // Null-terminate the message
+            playlist->count++;                                       // Move to the next message
+            message_pos = 0;                                         // Reset for the next message
+            message_started = false;
+
+            // Ensure the playlist count does not exceed the maximum
+            if (playlist->count >= MAX_PRE_SAVED_MESSAGES)
+            {
+                FURI_LOG_W(TAG, "Reached maximum playlist messages");
+                break;
+            }
+        }
+        else
+        {
+            playlist->messages[playlist->count][message_pos++] = ch; // Add character to current message
+        }
+    }
+
+    // Handle the case where the last message does not end with a newline
+    if (message_started && message_pos > 0)
+    {
+        playlist->messages[playlist->count][message_pos] = '\0'; // Null-terminate the last message
+        playlist->count++;                                       // Increment the count for the last message
+
+        // Ensure the playlist count does not exceed the maximum
+        if (playlist->count >= MAX_PRE_SAVED_MESSAGES)
+        {
+            FURI_LOG_W(TAG, "Reached maximum playlist messages");
+        }
+    }
+
+    // Close the file and storage
+    storage_file_close(file);
+    storage_file_free(file);
+    furi_record_close(RECORD_STORAGE);
+
+    return true;
+}
+static void save_settings(
+    const char *ssid,
+    const char *password,
+    const char *login_username_logged_out,
+    const char *login_username_logged_in,
+    const char *login_password_logged_out,
+    const char *change_password_logged_in,
+    const char *is_logged_in)
+{
+    // Create the directory for saving settings
+    char directory_path[128];
+    snprintf(directory_path, sizeof(directory_path), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_social");
+
+    // Create the directory
+    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))
+    {
+        FURI_LOG_E(TAG, "Failed to open settings file for writing: %s", SETTINGS_PATH);
+        storage_file_free(file);
+        furi_record_close(RECORD_STORAGE);
+        return;
+    }
+
+    // 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)
+    {
+        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)
+    {
+        FURI_LOG_E(TAG, "Failed to write password");
+    }
+
+    // Save the login_username_logged_out length and data
+    size_t username_out_length = strlen(login_username_logged_out) + 1; // Include null terminator
+    if (storage_file_write(file, &username_out_length, sizeof(size_t)) != sizeof(size_t) ||
+        storage_file_write(file, login_username_logged_out, username_out_length) != username_out_length)
+    {
+        FURI_LOG_E(TAG, "Failed to write login_username_logged_out");
+    }
+
+    // Save the login_username_logged_in length and data
+    size_t username_in_length = strlen(login_username_logged_in) + 1; // Include null terminator
+    if (storage_file_write(file, &username_in_length, sizeof(size_t)) != sizeof(size_t) ||
+        storage_file_write(file, login_username_logged_in, username_in_length) != username_in_length)
+    {
+        FURI_LOG_E(TAG, "Failed to write login_username_logged_in");
+    }
+
+    // Save the login_password_logged_out length and data
+    size_t password_out_length = strlen(login_password_logged_out) + 1; // Include null terminator
+    if (storage_file_write(file, &password_out_length, sizeof(size_t)) != sizeof(size_t) ||
+        storage_file_write(file, login_password_logged_out, password_out_length) != password_out_length)
+    {
+        FURI_LOG_E(TAG, "Failed to write login_password_logged_out");
+    }
+
+    // Save the change_password_logged_in length and data
+    size_t change_password_length = strlen(change_password_logged_in) + 1; // Include null terminator
+    if (storage_file_write(file, &change_password_length, sizeof(size_t)) != sizeof(size_t) ||
+        storage_file_write(file, change_password_logged_in, change_password_length) != change_password_length)
+    {
+        FURI_LOG_E(TAG, "Failed to write change_password_logged_in");
+    }
+
+    // Save the is_logged_in length and data
+    size_t is_logged_in_length = strlen(is_logged_in) + 1; // Include null terminator
+    if (storage_file_write(file, &is_logged_in_length, sizeof(size_t)) != sizeof(size_t) ||
+        storage_file_write(file, is_logged_in, is_logged_in_length) != is_logged_in_length)
+    {
+        FURI_LOG_E(TAG, "Failed to write is_logged_in");
+    }
+
+    storage_file_close(file);
+    storage_file_free(file);
+    furi_record_close(RECORD_STORAGE);
+}
+
+static bool load_settings(
+    char *ssid,
+    size_t ssid_size,
+    char *password,
+    size_t password_size,
+    char *login_username_logged_out,
+    size_t username_out_size,
+    char *login_username_logged_in,
+    size_t username_in_size,
+    char *login_password_logged_out,
+    size_t password_out_size,
+    char *change_password_logged_in,
+    size_t change_password_size,
+    char *is_logged_in,
+    size_t is_logged_in_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);
+        return false; // Return false if the file does not exist
+    }
+
+    // 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)
+    {
+        FURI_LOG_E(TAG, "Failed to read SSID");
+        storage_file_close(file);
+        storage_file_free(file);
+        furi_record_close(RECORD_STORAGE);
+        return false;
+    }
+    else
+    {
+        ssid[ssid_length - 1] = '\0'; // Ensure null-termination
+    }
+
+    // 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)
+    {
+        FURI_LOG_E(TAG, "Failed to read password");
+        storage_file_close(file);
+        storage_file_free(file);
+        furi_record_close(RECORD_STORAGE);
+        return false;
+    }
+    else
+    {
+        password[password_length - 1] = '\0'; // Ensure null-termination
+    }
+
+    // Load the login_username_logged_out
+    size_t username_out_length;
+    if (storage_file_read(file, &username_out_length, sizeof(size_t)) != sizeof(size_t) || username_out_length > username_out_size ||
+        storage_file_read(file, login_username_logged_out, username_out_length) != username_out_length)
+    {
+        FURI_LOG_E(TAG, "Failed to read login_username_logged_out");
+        // storage_file_close(file);
+        // storage_file_free(file);
+        // furi_record_close(RECORD_STORAGE);
+        // return false;
+    }
+    else
+    {
+        login_username_logged_out[username_out_length - 1] = '\0'; // Ensure null-termination
+    }
+
+    // Load the login_username_logged_in
+    size_t username_in_length;
+    if (storage_file_read(file, &username_in_length, sizeof(size_t)) != sizeof(size_t) || username_in_length > username_in_size ||
+        storage_file_read(file, login_username_logged_in, username_in_length) != username_in_length)
+    {
+        FURI_LOG_E(TAG, "Failed to read login_username_logged_in");
+        // storage_file_close(file);
+        // storage_file_free(file);
+        // furi_record_close(RECORD_STORAGE);
+        // return false;
+    }
+    else
+    {
+        login_username_logged_in[username_in_length - 1] = '\0'; // Ensure null-termination
+    }
+
+    // Load the login_password_logged_out
+    size_t password_out_length;
+    if (storage_file_read(file, &password_out_length, sizeof(size_t)) != sizeof(size_t) || password_out_length > password_out_size ||
+        storage_file_read(file, login_password_logged_out, password_out_length) != password_out_length)
+    {
+        FURI_LOG_E(TAG, "Failed to read login_password_logged_out");
+        // storage_file_close(file);
+        // storage_file_free(file);
+        // furi_record_close(RECORD_STORAGE);
+        // return false;
+    }
+    else
+    {
+        login_password_logged_out[password_out_length - 1] = '\0'; // Ensure null-termination
+    }
+
+    // Load the change_password_logged_in
+    size_t change_password_length;
+    if (storage_file_read(file, &change_password_length, sizeof(size_t)) != sizeof(size_t) || change_password_length > change_password_size ||
+        storage_file_read(file, change_password_logged_in, change_password_length) != change_password_length)
+    {
+        FURI_LOG_E(TAG, "Failed to read change_password_logged_in");
+        // storage_file_close(file);
+        // storage_file_free(file);
+        // furi_record_close(RECORD_STORAGE);
+        //  return false;
+    }
+    else
+    {
+        change_password_logged_in[change_password_length - 1] = '\0'; // Ensure null-termination
+    }
+
+    // Load the is_logged_in
+    size_t is_logged_in_length;
+    if (storage_file_read(file, &is_logged_in_length, sizeof(size_t)) != sizeof(size_t) || is_logged_in_length > is_logged_in_size ||
+        storage_file_read(file, is_logged_in, is_logged_in_length) != is_logged_in_length)
+    {
+        FURI_LOG_E(TAG, "Failed to read is_logged_in");
+        // storage_file_close(file);
+        // storage_file_free(file);
+        // furi_record_close(RECORD_STORAGE);
+        //  return false;
+    }
+    else
+    {
+        is_logged_in[is_logged_in_length - 1] = '\0'; // Ensure null-termination
+    }
+
+    storage_file_close(file);
+    storage_file_free(file);
+    furi_record_close(RECORD_STORAGE);
+
+    return true;
+}
+
+#endif // FLIP_SOCIAL_STORAGE_H

+ 1197 - 0
flip_social/flipper_http.h

@@ -0,0 +1,1197 @@
+// flipper_http.h
+#ifndef FLIPPER_HTTP_H
+#define FLIPPER_HTTP_H
+
+#include <furi.h>
+#include <furi_hal.h>
+#include <furi_hal_gpio.h>
+#include <furi_hal_serial.h>
+#include <storage/storage.h>
+
+// STORAGE_EXT_PATH_PREFIX is defined in the Furi SDK as /ext
+
+#define HTTP_TAG "FlipSocial"             // change this to your app name
+#define http_tag "flip_social"            // change this to your app id
+#define UART_CH (FuriHalSerialIdUsart)    // UART channel
+#define TIMEOUT_DURATION_TICKS (6 * 1000) // 6 seconds
+#define BAUDRATE (115200)                 // UART baudrate
+#define RX_BUF_SIZE 128                   // UART RX buffer size
+#define RX_LINE_BUFFER_SIZE 8192          // UART RX line buffer size (increase for large responses)
+
+// Forward declaration for callback
+typedef void (*FlipperHTTP_Callback)(const char *line, void *context);
+
+// Functions
+bool flipper_http_init(FlipperHTTP_Callback callback, void *context);
+void flipper_http_deinit();
+//---
+void flipper_http_rx_callback(const char *line, void *context);
+bool flipper_http_send_data(const char *data);
+//---
+bool flipper_http_connect_wifi();
+bool flipper_http_disconnect_wifi();
+bool flipper_http_ping();
+bool flipper_http_scan_wifi();
+bool flipper_http_save_wifi(const char *ssid, const char *password);
+//---
+bool flipper_http_get_request(const char *url);
+bool flipper_http_get_request_with_headers(const char *url, const char *headers);
+bool flipper_http_post_request_with_headers(const char *url, const char *headers, const char *payload);
+bool flipper_http_put_request_with_headers(const char *url, const char *headers, const char *payload);
+bool flipper_http_delete_request_with_headers(const char *url, const char *headers, const char *payload);
+//---
+bool flipper_http_save_received_data(size_t bytes_received, const char line_buffer[]);
+static char *trim(const char *str);
+//
+bool flipper_http_process_response_async(
+    bool (*http_request)(void),
+    bool (*parse_json)(void));
+
+// State variable to track the UART state
+typedef enum
+{
+    INACTIVE,  // Inactive state
+    IDLE,      // Default state
+    RECEIVING, // Receiving data
+    SENDING,   // Sending data
+    ISSUE,     // Issue with connection
+} SerialState;
+
+// Event Flags for UART Worker Thread
+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
+    uint8_t rx_buf[RX_BUF_SIZE];            // Buffer for received data
+    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
+
+    // variable to store the last received data from the UART
+    char *last_response;
+
+    // Timer-related members
+    FuriTimer *get_timeout_timer; // Timer for HTTP request timeout
+    char *received_data;          // Buffer to store received data
+
+    bool started_receiving_get; // Indicates if a GET request has 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 started_receiving_put; // Indicates if a PUT request has 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
+} FlipperHTTP;
+
+FlipperHTTP fhttp;
+
+// fhttp.received_data holds the received data from HTTP requests
+// fhttp.last_response holds the last received data from the UART, which could be [GET/END], [POST/END], [PUT/END], [DELETE/END], etc
+
+// Timer callback function
+/**
+ * @brief      Callback function for the GET timeout timer.
+ * @return     0
+ * @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)
+{
+    UNUSED(context);
+    FURI_LOG_E(HTTP_TAG, "Timeout reached: 2 seconds without receiving the end.");
+
+    // Reset the state
+    fhttp.started_receiving_get = false;
+    fhttp.started_receiving_post = false;
+    fhttp.started_receiving_put = false;
+    fhttp.started_receiving_delete = false;
+
+    // Free received data if any
+    if (fhttp.received_data)
+    {
+        free(fhttp.received_data);
+        fhttp.received_data = NULL;
+    }
+
+    // Update UART state
+    fhttp.state = ISSUE;
+}
+
+// UART RX Handler Callback (Interrupt Context)
+/**
+ * @brief      A private callback function to handle received data asynchronously.
+ * @return     void
+ * @param      handle    The UART handle.
+ * @param      event     The event type.
+ * @param      context   The context to pass to the callback.
+ * @note       This function will handle received data asynchronously via the callback.
+ */
+static void _flipper_http_rx_callback(FuriHalSerialHandle *handle, FuriHalSerialRxEvent event, void *context)
+{
+    UNUSED(context);
+    if (event == FuriHalSerialRxEventData)
+    {
+        uint8_t data = furi_hal_serial_async_rx(handle);
+        furi_stream_buffer_send(fhttp.flipper_http_stream, &data, 1, 0);
+        furi_thread_flags_set(fhttp.rx_thread_id, WorkerEvtRxDone);
+    }
+}
+
+// UART worker thread
+/**
+ * @brief      Worker thread to handle UART data asynchronously.
+ * @return     0
+ * @param      context   The context to pass to the callback.
+ * @note       This function will handle received data asynchronously via the callback.
+ */
+static int32_t flipper_http_worker(void *context)
+{
+    UNUSED(context);
+    size_t rx_line_pos = 0;
+    char *rx_line_buffer = (char *)malloc(RX_LINE_BUFFER_SIZE);
+
+    if (!rx_line_buffer)
+    {
+        // Handle malloc failure
+        FURI_LOG_E(HTTP_TAG, "Failed to allocate memory for rx_line_buffer");
+        return -1;
+    }
+
+    while (1)
+    {
+        uint32_t events = furi_thread_flags_wait(WorkerEvtStop | WorkerEvtRxDone, FuriFlagWaitAny, FuriWaitForever);
+        if (events & WorkerEvtStop)
+            break;
+        if (events & WorkerEvtRxDone)
+        {
+            size_t len = furi_stream_buffer_receive(fhttp.flipper_http_stream, fhttp.rx_buf, RX_BUF_SIZE + 1, 0);
+            for (size_t i = 0; i < len; i++)
+            {
+                char c = fhttp.rx_buf[i];
+                if (c == '\n' || rx_line_pos >= RX_LINE_BUFFER_SIZE - 1)
+                {
+                    rx_line_buffer[rx_line_pos] = '\0';
+                    // Invoke the callback with the complete line
+                    if (fhttp.handle_rx_line_cb)
+                    {
+                        fhttp.handle_rx_line_cb(rx_line_buffer, fhttp.callback_context);
+                    }
+                    // Reset the line buffer
+                    rx_line_pos = 0;
+                }
+                else
+                {
+                    rx_line_buffer[rx_line_pos++] = c;
+                }
+            }
+        }
+    }
+
+    // Free the allocated memory before exiting the thread
+    free(rx_line_buffer);
+
+    return 0;
+}
+
+// UART initialization function
+/**
+ * @brief      Initialize UART.
+ * @return     true if the UART was initialized successfully, false otherwise.
+ * @param      callback  The callback function to handle received data (ex. 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)
+{
+    if (!context)
+    {
+        FURI_LOG_E(HTTP_TAG, "Invalid context provided to flipper_http_init.");
+        return false;
+    }
+    if (!callback)
+    {
+        FURI_LOG_E(HTTP_TAG, "Invalid callback provided to flipper_http_init.");
+        return false;
+    }
+    fhttp.flipper_http_stream = furi_stream_buffer_alloc(RX_BUF_SIZE, 1);
+    if (!fhttp.flipper_http_stream)
+    {
+        FURI_LOG_E(HTTP_TAG, "Failed to allocate UART stream buffer.");
+        return false;
+    }
+
+    fhttp.rx_thread = furi_thread_alloc();
+    if (!fhttp.rx_thread)
+    {
+        FURI_LOG_E(HTTP_TAG, "Failed to allocate UART thread.");
+        furi_stream_buffer_free(fhttp.flipper_http_stream);
+        return false;
+    }
+
+    furi_thread_set_name(fhttp.rx_thread, "FlipperHTTP_RxThread");
+    furi_thread_set_stack_size(fhttp.rx_thread, 1024);
+    furi_thread_set_context(fhttp.rx_thread, &fhttp);
+    furi_thread_set_callback(fhttp.rx_thread, flipper_http_worker);
+
+    fhttp.handle_rx_line_cb = callback;
+    fhttp.callback_context = context;
+
+    furi_thread_start(fhttp.rx_thread);
+    fhttp.rx_thread_id = furi_thread_get_id(fhttp.rx_thread);
+
+    // handle when the UART control is busy to avoid furi_check failed
+    if (furi_hal_serial_control_is_busy(UART_CH))
+    {
+        FURI_LOG_E(HTTP_TAG, "UART control is busy.");
+        return false;
+    }
+
+    fhttp.serial_handle = furi_hal_serial_control_acquire(UART_CH);
+    if (!fhttp.serial_handle)
+    {
+        FURI_LOG_E(HTTP_TAG, "Failed to acquire UART control - handle is NULL");
+        // Cleanup resources
+        furi_thread_free(fhttp.rx_thread);
+        furi_stream_buffer_free(fhttp.flipper_http_stream);
+        return false;
+    }
+
+    // Initialize UART with acquired handle
+    furi_hal_serial_init(fhttp.serial_handle, BAUDRATE);
+
+    // Enable RX direction
+    furi_hal_serial_enable_direction(fhttp.serial_handle, FuriHalSerialDirectionRx);
+
+    // Start asynchronous RX with the callback
+    furi_hal_serial_async_rx_start(fhttp.serial_handle, _flipper_http_rx_callback, &fhttp, false);
+
+    // Wait for the TX to complete to ensure UART is ready
+    furi_hal_serial_tx_wait_complete(fhttp.serial_handle);
+
+    // Allocate the timer for handling timeouts
+    fhttp.get_timeout_timer = furi_timer_alloc(
+        get_timeout_timer_callback, // Callback function
+        FuriTimerTypeOnce,          // One-shot timer
+        &fhttp                      // Context passed to callback
+    );
+
+    if (!fhttp.get_timeout_timer)
+    {
+        FURI_LOG_E(HTTP_TAG, "Failed to allocate HTTP request timeout timer.");
+        // Cleanup resources
+        furi_hal_serial_async_rx_stop(fhttp.serial_handle);
+        furi_hal_serial_disable_direction(fhttp.serial_handle, FuriHalSerialDirectionRx);
+        furi_hal_serial_control_release(fhttp.serial_handle);
+        furi_hal_serial_deinit(fhttp.serial_handle);
+        furi_thread_flags_set(fhttp.rx_thread_id, WorkerEvtStop);
+        furi_thread_join(fhttp.rx_thread);
+        furi_thread_free(fhttp.rx_thread);
+        furi_stream_buffer_free(fhttp.flipper_http_stream);
+        return false;
+    }
+
+    // Set the timer thread priority if needed
+    furi_timer_set_thread_priority(FuriTimerThreadPriorityElevated);
+
+    // FURI_LOG_I(HTTP_TAG, "UART initialized successfully.");
+    return true;
+}
+
+// Deinitialize UART
+/**
+ * @brief      Deinitialize UART.
+ * @return     void
+ * @note       This function will stop the asynchronous RX, release the serial handle, and free the resources.
+ */
+void flipper_http_deinit()
+{
+    if (fhttp.serial_handle == NULL)
+    {
+        FURI_LOG_E(HTTP_TAG, "UART handle is NULL. Already deinitialized?");
+        return;
+    }
+    // Stop asynchronous RX
+    furi_hal_serial_async_rx_stop(fhttp.serial_handle);
+
+    // Release and deinitialize the serial handle
+    furi_hal_serial_disable_direction(fhttp.serial_handle, FuriHalSerialDirectionRx);
+    furi_hal_serial_control_release(fhttp.serial_handle);
+    furi_hal_serial_deinit(fhttp.serial_handle);
+
+    // Signal the worker thread to stop
+    furi_thread_flags_set(fhttp.rx_thread_id, WorkerEvtStop);
+    // Wait for the thread to finish
+    furi_thread_join(fhttp.rx_thread);
+    // Free the thread resources
+    furi_thread_free(fhttp.rx_thread);
+
+    // Free the stream buffer
+    furi_stream_buffer_free(fhttp.flipper_http_stream);
+
+    // Free the timer
+    if (fhttp.get_timeout_timer)
+    {
+        furi_timer_free(fhttp.get_timeout_timer);
+        fhttp.get_timeout_timer = NULL;
+    }
+
+    // Free received data if any
+    if (fhttp.received_data)
+    {
+        free(fhttp.received_data);
+        fhttp.received_data = NULL;
+    }
+
+    // Free the last response
+    if (fhttp.last_response)
+    {
+        free(fhttp.last_response);
+        fhttp.last_response = NULL;
+    }
+
+    // FURI_LOG_I("FlipperHTTP", "UART deinitialized successfully.");
+}
+
+// Function to send data over UART with newline termination
+/**
+ * @brief      Send data over UART with newline termination.
+ * @return     true if the data was sent successfully, false otherwise.
+ * @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)
+{
+    size_t data_length = strlen(data);
+    if (data_length == 0)
+    {
+        FURI_LOG_E("FlipperHTTP", "Attempted to send empty data.");
+        return false;
+    }
+
+    // Create a buffer with data + '\n'
+    size_t send_length = data_length + 1; // +1 for '\n'
+    if (send_length > 256)
+    { // Ensure buffer size is sufficient
+        FURI_LOG_E("FlipperHTTP", "Data too long to send over FHTTP.");
+        return false;
+    }
+
+    char send_buffer[257]; // 256 + 1 for safety
+    strncpy(send_buffer, data, 256);
+    send_buffer[data_length] = '\n';     // Append newline
+    send_buffer[data_length + 1] = '\0'; // Null-terminate
+
+    if (fhttp.state == INACTIVE && ((strstr(send_buffer, "[PING]") == NULL) && (strstr(send_buffer, "[WIFI/CONNECT]") == NULL)))
+    {
+        FURI_LOG_E("FlipperHTTP", "Cannot send data while INACTIVE.");
+        fhttp.last_response = "Cannot send data while INACTIVE.";
+        return false;
+    }
+
+    fhttp.state = SENDING;
+    furi_hal_serial_tx(fhttp.serial_handle, (const uint8_t *)send_buffer, send_length);
+
+    // Uncomment below line to log the data sent over UART
+    // FURI_LOG_I("FlipperHTTP", "Sent data over UART: %s", send_buffer);
+    fhttp.state = IDLE;
+    return true;
+}
+
+// Function to send a PING request
+/**
+ * @brief      Send a PING request to check if the Wifi Dev Board is connected.
+ * @return     true if the request was successful, false otherwise.
+ * @note       The received data will be handled asynchronously via the callback.
+ * @note       This is best used to check if the Wifi Dev Board is connected.
+ * @note       The state will remain INACTIVE until a PONG is received.
+ */
+bool flipper_http_ping()
+{
+    const char *command = "[PING]";
+    if (!flipper_http_send_data(command))
+    {
+        FURI_LOG_E("FlipperHTTP", "Failed to send PING command.");
+        return false;
+    }
+    // set state as INACTIVE to be made IDLE if PONG is received
+    fhttp.state = INACTIVE;
+    // The response will be handled asynchronously via the callback
+    return true;
+}
+
+// Function to scan for WiFi networks
+/**
+ * @brief      Send a command to scan for WiFi networks.
+ * @return     true if the request was successful, false otherwise.
+ * @note       The received data will be handled asynchronously via the callback.
+ */
+bool flipper_http_scan_wifi()
+{
+    const char *command = "[WIFI/SCAN]";
+    if (!flipper_http_send_data(command))
+    {
+        FURI_LOG_E("FlipperHTTP", "Failed to send WiFi scan command.");
+        return false;
+    }
+
+    // The response will be handled asynchronously via the callback
+    return true;
+}
+
+// Function to save WiFi settings (returns true if successful)
+/**
+ * @brief      Send a command to save WiFi settings.
+ * @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)
+{
+    if (!ssid || !password)
+    {
+        FURI_LOG_E("FlipperHTTP", "Invalid arguments provided to flipper_http_save_wifi.");
+        return false;
+    }
+    char buffer[256];
+    int ret = snprintf(buffer, sizeof(buffer), "[WIFI/SAVE]{\"ssid\":\"%s\",\"password\":\"%s\"}", ssid, password);
+    if (ret < 0 || ret >= (int)sizeof(buffer))
+    {
+        FURI_LOG_E("FlipperHTTP", "Failed to format WiFi save command.");
+        return false;
+    }
+
+    if (!flipper_http_send_data(buffer))
+    {
+        FURI_LOG_E("FlipperHTTP", "Failed to send WiFi save command.");
+        return false;
+    }
+
+    // The response will be handled asynchronously via the callback
+    return true;
+}
+
+// Function to disconnect from WiFi (returns true if successful)
+/**
+ * @brief      Send a command to disconnect from WiFi.
+ * @return     true if the request was successful, false otherwise.
+ * @note       The received data will be handled asynchronously via the callback.
+ */
+bool flipper_http_disconnect_wifi()
+{
+    const char *command = "[WIFI/DISCONNECT]";
+    if (!flipper_http_send_data(command))
+    {
+        FURI_LOG_E("FlipperHTTP", "Failed to send WiFi disconnect command.");
+        return false;
+    }
+
+    // The response will be handled asynchronously via the callback
+    return true;
+}
+
+// Function to connect to WiFi (returns true if successful)
+/**
+ * @brief      Send a command to connect to WiFi.
+ * @return     true if the request was successful, false otherwise.
+ * @note       The received data will be handled asynchronously via the callback.
+ */
+bool flipper_http_connect_wifi()
+{
+    const char *command = "[WIFI/CONNECT]";
+    if (!flipper_http_send_data(command))
+    {
+        FURI_LOG_E("FlipperHTTP", "Failed to send WiFi connect command.");
+        return false;
+    }
+
+    // The response will be handled asynchronously via the callback
+    return true;
+}
+
+// Function to send a GET request
+/**
+ * @brief      Send a GET request to the specified URL.
+ * @return     true if the request was successful, false otherwise.
+ * @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)
+{
+    if (!url)
+    {
+        FURI_LOG_E("FlipperHTTP", "Invalid arguments provided to flipper_http_get_request.");
+        return false;
+    }
+
+    // Prepare GET request command
+    char command[256];
+    int ret = snprintf(command, sizeof(command), "[GET]%s", url);
+    if (ret < 0 || ret >= (int)sizeof(command))
+    {
+        FURI_LOG_E("FlipperHTTP", "Failed to format GET request command.");
+        return false;
+    }
+
+    // Send GET request via UART
+    if (!flipper_http_send_data(command))
+    {
+        FURI_LOG_E("FlipperHTTP", "Failed to send GET request command.");
+        return false;
+    }
+
+    // The response will be handled asynchronously via the callback
+    return true;
+}
+// Function to send a GET request with headers
+/**
+ * @brief      Send a GET request to the specified URL.
+ * @return     true if the request was successful, false otherwise.
+ * @param      url  The URL to send the GET request to.
+ * @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)
+{
+    if (!url || !headers)
+    {
+        FURI_LOG_E("FlipperHTTP", "Invalid arguments provided to flipper_http_get_request_with_headers.");
+        return false;
+    }
+
+    // Prepare GET request command with headers
+    char command[256];
+    int ret = snprintf(command, sizeof(command), "[GET/HTTP]{\"url\":\"%s\",\"headers\":%s}", url, headers);
+    if (ret < 0 || ret >= (int)sizeof(command))
+    {
+        FURI_LOG_E("FlipperHTTP", "Failed to format GET request command with headers.");
+        return false;
+    }
+
+    // Send GET request via UART
+    if (!flipper_http_send_data(command))
+    {
+        FURI_LOG_E("FlipperHTTP", "Failed to send GET request command with headers.");
+        return false;
+    }
+
+    // The response will be handled asynchronously via the callback
+    return true;
+}
+// Function to send a POST request with headers
+/**
+ * @brief      Send a POST request to the specified URL.
+ * @return     true if the request was successful, false otherwise.
+ * @param      url  The URL to send the POST request to.
+ * @param      headers  The headers to send with the POST request.
+ * @param      data  The data to send with the POST request.
+ * @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)
+{
+    if (!url || !headers || !payload)
+    {
+        FURI_LOG_E("FlipperHTTP", "Invalid arguments provided to flipper_http_post_request_with_headers.");
+        return false;
+    }
+
+    // Prepare POST request command with headers and data
+    char command[256];
+    int ret = snprintf(command, sizeof(command), "[POST/HTTP]{\"url\":\"%s\",\"headers\":%s,\"payload\":%s}", url, headers, payload);
+    if (ret < 0 || ret >= (int)sizeof(command))
+    {
+        FURI_LOG_E("FlipperHTTP", "Failed to format POST request command with headers and data.");
+        return false;
+    }
+
+    // Send POST request via UART
+    if (!flipper_http_send_data(command))
+    {
+        FURI_LOG_E("FlipperHTTP", "Failed to send POST request command with headers and data.");
+        return false;
+    }
+
+    // The response will be handled asynchronously via the callback
+    return true;
+}
+// Function to send a PUT request with headers
+/**
+ * @brief      Send a PUT request to the specified URL.
+ * @return     true if the request was successful, false otherwise.
+ * @param      url  The URL to send the PUT request to.
+ * @param      headers  The headers to send with the PUT request.
+ * @param      data  The data to send with the PUT request.
+ * @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)
+{
+    if (!url || !headers || !payload)
+    {
+        FURI_LOG_E("FlipperHTTP", "Invalid arguments provided to flipper_http_put_request_with_headers.");
+        return false;
+    }
+
+    // Prepare PUT request command with headers and data
+    char command[256];
+    int ret = snprintf(command, sizeof(command), "[PUT/HTTP]{\"url\":\"%s\",\"headers\":%s,\"payload\":%s}", url, headers, payload);
+    if (ret < 0 || ret >= (int)sizeof(command))
+    {
+        FURI_LOG_E("FlipperHTTP", "Failed to format PUT request command with headers and data.");
+        return false;
+    }
+
+    // Send PUT request via UART
+    if (!flipper_http_send_data(command))
+    {
+        FURI_LOG_E("FlipperHTTP", "Failed to send PUT request command with headers and data.");
+        return false;
+    }
+
+    // The response will be handled asynchronously via the callback
+    return true;
+}
+// Function to send a DELETE request with headers
+/**
+ * @brief      Send a DELETE request to the specified URL.
+ * @return     true if the request was successful, false otherwise.
+ * @param      url  The URL to send the DELETE request to.
+ * @param      headers  The headers to send with the DELETE request.
+ * @param      data  The data to send with the DELETE request.
+ * @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)
+{
+    if (!url || !headers || !payload)
+    {
+        FURI_LOG_E("FlipperHTTP", "Invalid arguments provided to flipper_http_delete_request_with_headers.");
+        return false;
+    }
+
+    // Prepare DELETE request command with headers and data
+    char command[256];
+    int ret = snprintf(command, sizeof(command), "[DELETE/HTTP]{\"url\":\"%s\",\"headers\":%s,\"payload\":%s}", url, headers, payload);
+    if (ret < 0 || ret >= (int)sizeof(command))
+    {
+        FURI_LOG_E("FlipperHTTP", "Failed to format DELETE request command with headers and data.");
+        return false;
+    }
+
+    // Send DELETE request via UART
+    if (!flipper_http_send_data(command))
+    {
+        FURI_LOG_E("FlipperHTTP", "Failed to send DELETE request command with headers and data.");
+        return false;
+    }
+
+    // The response will be handled asynchronously via the callback
+    return true;
+}
+// Function to handle received data asynchronously
+/**
+ * @brief      Callback function to handle received data asynchronously.
+ * @return     void
+ * @param      line     The received line.
+ * @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)
+{
+
+    if (!line || !context)
+    {
+        FURI_LOG_E(HTTP_TAG, "Invalid arguments provided to flipper_http_rx_callback.");
+        return;
+    }
+
+    // Trim the received line to check if it's empty
+    char *trimmed_line = trim(line);
+    if (trimmed_line != NULL && trimmed_line[0] != '\0')
+    {
+        fhttp.last_response = (char *)line;
+    }
+    free(trimmed_line); // Free the allocated memory for trimmed_line
+
+    if (fhttp.state != INACTIVE && fhttp.state != ISSUE)
+    {
+        fhttp.state = RECEIVING;
+    }
+
+    // Uncomment below line to log the data received over UART
+    FURI_LOG_I(HTTP_TAG, "Received UART line: %s", line);
+
+    // Check if we've started receiving data from a GET request
+    if (fhttp.started_receiving_get)
+    {
+        // Restart the timeout timer each time new data is received
+        furi_timer_restart(fhttp.get_timeout_timer, TIMEOUT_DURATION_TICKS);
+
+        if (strstr(line, "[GET/END]") != NULL)
+        {
+            FURI_LOG_I(HTTP_TAG, "GET request completed.");
+            // Stop the timer since we've completed the GET request
+            furi_timer_stop(fhttp.get_timeout_timer);
+
+            if (fhttp.received_data)
+            {
+                // uncomment if you want to save the received data to the external storage
+                // flipper_http_save_received_data(strlen(fhttp.received_data), fhttp.received_data);
+                fhttp.started_receiving_get = false;
+                fhttp.just_started_get = false;
+                fhttp.state = IDLE;
+                return;
+            }
+            else
+            {
+                FURI_LOG_E(HTTP_TAG, "No data received.");
+                fhttp.started_receiving_get = false;
+                fhttp.just_started_get = false;
+                fhttp.state = IDLE;
+                return;
+            }
+        }
+
+        // Append the new line to the existing data
+        if (fhttp.received_data == NULL)
+        {
+            fhttp.received_data = (char *)malloc(strlen(line) + 2); // +2 for newline and null terminator
+            if (fhttp.received_data)
+            {
+                strcpy(fhttp.received_data, line);
+                fhttp.received_data[strlen(line)] = '\n';     // Add newline
+                fhttp.received_data[strlen(line) + 1] = '\0'; // Null terminator
+            }
+        }
+        else
+        {
+            size_t current_len = strlen(fhttp.received_data);
+            size_t new_size = current_len + strlen(line) + 2; // +2 for newline and null terminator
+            fhttp.received_data = (char *)realloc(fhttp.received_data, new_size);
+            if (fhttp.received_data)
+            {
+                memcpy(fhttp.received_data + current_len, line, strlen(line)); // Copy line at the end of the current data
+                fhttp.received_data[current_len + strlen(line)] = '\n';        // Add newline
+                fhttp.received_data[current_len + strlen(line) + 1] = '\0';    // Null terminator
+            }
+        }
+
+        if (!fhttp.just_started_get)
+        {
+            fhttp.just_started_get = true;
+        }
+        return;
+    }
+
+    // Check if we've started receiving data from a POST request
+    else if (fhttp.started_receiving_post)
+    {
+        // Restart the timeout timer each time new data is received
+        furi_timer_restart(fhttp.get_timeout_timer, TIMEOUT_DURATION_TICKS);
+
+        if (strstr(line, "[POST/END]") != NULL)
+        {
+            FURI_LOG_I(HTTP_TAG, "POST request completed.");
+            // Stop the timer since we've completed the POST request
+            furi_timer_stop(fhttp.get_timeout_timer);
+
+            if (fhttp.received_data)
+            {
+                // uncomment if you want to save the received data to the external storage
+                // flipper_http_save_received_data(strlen(fhttp.received_data), fhttp.received_data);
+                fhttp.started_receiving_post = false;
+                fhttp.just_started_post = false;
+                fhttp.state = IDLE;
+                return;
+            }
+            else
+            {
+                FURI_LOG_E(HTTP_TAG, "No data received.");
+                fhttp.started_receiving_post = false;
+                fhttp.just_started_post = false;
+                fhttp.state = IDLE;
+                return;
+            }
+        }
+
+        // Append the new line to the existing data
+        if (fhttp.received_data == NULL)
+        {
+            fhttp.received_data = (char *)malloc(strlen(line) + 2); // +2 for newline and null terminator
+            if (fhttp.received_data)
+            {
+                strcpy(fhttp.received_data, line);
+                fhttp.received_data[strlen(line)] = '\n';     // Add newline
+                fhttp.received_data[strlen(line) + 1] = '\0'; // Null terminator
+            }
+        }
+        else
+        {
+            size_t current_len = strlen(fhttp.received_data);
+            size_t new_size = current_len + strlen(line) + 2; // +2 for newline and null terminator
+            fhttp.received_data = (char *)realloc(fhttp.received_data, new_size);
+            if (fhttp.received_data)
+            {
+                memcpy(fhttp.received_data + current_len, line, strlen(line)); // Copy line at the end of the current data
+                fhttp.received_data[current_len + strlen(line)] = '\n';        // Add newline
+                fhttp.received_data[current_len + strlen(line) + 1] = '\0';    // Null terminator
+            }
+        }
+
+        if (!fhttp.just_started_post)
+        {
+            fhttp.just_started_post = true;
+        }
+        return;
+    }
+
+    // Check if we've started receiving data from a PUT request
+    else if (fhttp.started_receiving_put)
+    {
+        // Restart the timeout timer each time new data is received
+        furi_timer_restart(fhttp.get_timeout_timer, TIMEOUT_DURATION_TICKS);
+
+        if (strstr(line, "[PUT/END]") != NULL)
+        {
+            FURI_LOG_I(HTTP_TAG, "PUT request completed.");
+            // Stop the timer since we've completed the PUT request
+            furi_timer_stop(fhttp.get_timeout_timer);
+
+            if (fhttp.received_data)
+            {
+                // uncomment if you want to save the received data to the external storage
+                // flipper_http_save_received_data(strlen(fhttp.received_data), fhttp.received_data);
+                fhttp.started_receiving_put = false;
+                fhttp.just_started_put = false;
+                fhttp.state = IDLE;
+                return;
+            }
+            else
+            {
+                FURI_LOG_E(HTTP_TAG, "No data received.");
+                fhttp.started_receiving_put = false;
+                fhttp.just_started_put = false;
+                fhttp.state = IDLE;
+                return;
+            }
+        }
+
+        // Append the new line to the existing data
+        if (fhttp.received_data == NULL)
+        {
+            fhttp.received_data = (char *)malloc(strlen(line) + 2); // +2 for newline and null terminator
+            if (fhttp.received_data)
+            {
+                strcpy(fhttp.received_data, line);
+                fhttp.received_data[strlen(line)] = '\n';     // Add newline
+                fhttp.received_data[strlen(line) + 1] = '\0'; // Null terminator
+            }
+        }
+        else
+        {
+            size_t current_len = strlen(fhttp.received_data);
+            size_t new_size = current_len + strlen(line) + 2; // +2 for newline and null terminator
+            fhttp.received_data = (char *)realloc(fhttp.received_data, new_size);
+            if (fhttp.received_data)
+            {
+                memcpy(fhttp.received_data + current_len, line, strlen(line)); // Copy line at the end of the current data
+                fhttp.received_data[current_len + strlen(line)] = '\n';        // Add newline
+                fhttp.received_data[current_len + strlen(line) + 1] = '\0';    // Null terminator
+            }
+        }
+
+        if (!fhttp.just_started_put)
+        {
+            fhttp.just_started_put = true;
+        }
+        return;
+    }
+
+    // Check if we've started receiving data from a DELETE request
+    else if (fhttp.started_receiving_delete)
+    {
+        // Restart the timeout timer each time new data is received
+        furi_timer_restart(fhttp.get_timeout_timer, TIMEOUT_DURATION_TICKS);
+
+        if (strstr(line, "[DELETE/END]") != NULL)
+        {
+            FURI_LOG_I(HTTP_TAG, "DELETE request completed.");
+            // Stop the timer since we've completed the DELETE request
+            furi_timer_stop(fhttp.get_timeout_timer);
+
+            if (fhttp.received_data)
+            {
+                // uncomment if you want to save the received data to the external storage
+                // flipper_http_save_received_data(strlen(fhttp.received_data), fhttp.received_data);
+                fhttp.started_receiving_delete = false;
+                fhttp.just_started_delete = false;
+                fhttp.state = IDLE;
+                return;
+            }
+            else
+            {
+                FURI_LOG_E(HTTP_TAG, "No data received.");
+                fhttp.started_receiving_delete = false;
+                fhttp.just_started_delete = false;
+                fhttp.state = IDLE;
+                return;
+            }
+        }
+
+        // Append the new line to the existing data
+        if (fhttp.received_data == NULL)
+        {
+            fhttp.received_data = (char *)malloc(strlen(line) + 2); // +2 for newline and null terminator
+            if (fhttp.received_data)
+            {
+                strcpy(fhttp.received_data, line);
+                fhttp.received_data[strlen(line)] = '\n';     // Add newline
+                fhttp.received_data[strlen(line) + 1] = '\0'; // Null terminator
+            }
+        }
+        else
+        {
+            size_t current_len = strlen(fhttp.received_data);
+            size_t new_size = current_len + strlen(line) + 2; // +2 for newline and null terminator
+            fhttp.received_data = (char *)realloc(fhttp.received_data, new_size);
+            if (fhttp.received_data)
+            {
+                memcpy(fhttp.received_data + current_len, line, strlen(line)); // Copy line at the end of the current data
+                fhttp.received_data[current_len + strlen(line)] = '\n';        // Add newline
+                fhttp.received_data[current_len + strlen(line) + 1] = '\0';    // Null terminator
+            }
+        }
+
+        if (!fhttp.just_started_delete)
+        {
+            fhttp.just_started_delete = true;
+        }
+        return;
+    }
+
+    // Handle different types of responses
+    if (strstr(line, "[SUCCESS]") != NULL || strstr(line, "[CONNECTED]") != NULL)
+    {
+        // FURI_LOG_I(HTTP_TAG, "Operation succeeded.");
+    }
+    else if (strstr(line, "[INFO]") != NULL)
+    {
+        FURI_LOG_I(HTTP_TAG, "Received info: %s", line);
+
+        if (fhttp.state == INACTIVE && strstr(line, "[INFO] Already connected to Wifi.") != NULL)
+        {
+            fhttp.state = IDLE;
+        }
+    }
+    else if (strstr(line, "[GET/SUCCESS]") != NULL)
+    {
+        FURI_LOG_I(HTTP_TAG, "GET request succeeded.");
+        fhttp.started_receiving_get = true;
+        furi_timer_start(fhttp.get_timeout_timer, TIMEOUT_DURATION_TICKS);
+        fhttp.state = RECEIVING;
+        fhttp.received_data = NULL;
+        return;
+    }
+    else if (strstr(line, "[POST/SUCCESS]") != NULL)
+    {
+        FURI_LOG_I(HTTP_TAG, "POST request succeeded.");
+        fhttp.started_receiving_post = true;
+        furi_timer_start(fhttp.get_timeout_timer, TIMEOUT_DURATION_TICKS);
+        fhttp.state = RECEIVING;
+        fhttp.received_data = NULL;
+        return;
+    }
+    else if (strstr(line, "[PUT/SUCCESS]") != NULL)
+    {
+        FURI_LOG_I(HTTP_TAG, "PUT request succeeded.");
+        fhttp.started_receiving_put = true;
+        furi_timer_start(fhttp.get_timeout_timer, TIMEOUT_DURATION_TICKS);
+        fhttp.state = RECEIVING;
+        fhttp.received_data = NULL;
+        return;
+    }
+    else if (strstr(line, "[DELETE/SUCCESS]") != NULL)
+    {
+        FURI_LOG_I(HTTP_TAG, "DELETE request succeeded.");
+        fhttp.started_receiving_delete = true;
+        furi_timer_start(fhttp.get_timeout_timer, TIMEOUT_DURATION_TICKS);
+        fhttp.state = RECEIVING;
+        fhttp.received_data = NULL;
+        return;
+    }
+    else if (strstr(line, "[DISCONNECTED]") != NULL)
+    {
+        FURI_LOG_I(HTTP_TAG, "WiFi disconnected successfully.");
+    }
+    else if (strstr(line, "[ERROR]") != NULL)
+    {
+        FURI_LOG_E(HTTP_TAG, "Received error: %s", line);
+        fhttp.state = ISSUE;
+        return;
+    }
+    else if (strstr(line, "[PONG]") != NULL)
+    {
+        FURI_LOG_I(HTTP_TAG, "Received PONG response: Wifi Dev Board is still alive.");
+
+        // send command to connect to WiFi
+        if (fhttp.state == INACTIVE)
+        {
+            fhttp.state = IDLE;
+            return;
+        }
+    }
+
+    if (fhttp.state == INACTIVE && strstr(line, "[PONG]") != NULL)
+    {
+        fhttp.state = IDLE;
+    }
+    else if (fhttp.state == INACTIVE && strstr(line, "[PONG]") == NULL)
+    {
+        fhttp.state = INACTIVE;
+    }
+    else
+    {
+        fhttp.state = IDLE;
+    }
+}
+// Function to save received data to a file
+/**
+ * @brief      Save the received data to a file.
+ * @return     true if the data was saved successfully, false otherwise.
+ * @param      bytes_received  The number of bytes received.
+ * @param      line_buffer     The buffer containing the received data.
+ * @note       The data will be saved to a file in the STORAGE_EXT_PATH_PREFIX "/apps_data/" http_tag "/received_data.txt" directory.
+ */
+bool flipper_http_save_received_data(size_t bytes_received, const char line_buffer[])
+{
+    const char *output_file_path = STORAGE_EXT_PATH_PREFIX "/apps_data/" http_tag "/received_data.txt";
+
+    // Ensure the directory exists
+    char directory_path[128];
+    snprintf(directory_path, sizeof(directory_path), STORAGE_EXT_PATH_PREFIX "/apps_data/" http_tag);
+
+    Storage *_storage = NULL;
+    File *_file = NULL;
+    // Open the storage if not opened already
+    // Initialize storage and create the directory if it doesn't exist
+    _storage = furi_record_open(RECORD_STORAGE);
+    storage_common_mkdir(_storage, directory_path); // Create directory if it doesn't exist
+    _file = storage_file_alloc(_storage);
+
+    // Open file for writing and append data line by line
+    if (!storage_file_open(_file, output_file_path, FSAM_WRITE, FSOM_CREATE_ALWAYS))
+    {
+        FURI_LOG_E(HTTP_TAG, "Failed to open output file for writing.");
+        storage_file_free(_file);
+        furi_record_close(RECORD_STORAGE);
+        return false;
+    }
+
+    // Write each line received from the UART to the file
+    if (bytes_received > 0 && _file)
+    {
+        storage_file_write(_file, line_buffer, bytes_received);
+        storage_file_write(_file, "\n", 1); // Add a newline after each line
+    }
+    else
+    {
+        FURI_LOG_E(HTTP_TAG, "No data received.");
+        return false;
+    }
+
+    if (_file)
+    {
+        storage_file_close(_file);
+        storage_file_free(_file);
+        _file = NULL;
+    }
+    if (_storage)
+    {
+        furi_record_close(RECORD_STORAGE);
+        _storage = NULL;
+    }
+
+    return true;
+}
+// Function to trim leading and trailing spaces and newlines from a constant string
+char *trim(const char *str)
+{
+    const char *end;
+    char *trimmed_str;
+    size_t len;
+
+    // Trim leading space
+    while (isspace((unsigned char)*str))
+        str++;
+
+    // All spaces?
+    if (*str == 0)
+        return strdup(""); // Return an empty string if all spaces
+
+    // Trim trailing space
+    end = str + strlen(str) - 1;
+    while (end > str && isspace((unsigned char)*end))
+        end--;
+
+    // Set length for the trimmed string
+    len = end - str + 1;
+
+    // Allocate space for the trimmed string and null terminator
+    trimmed_str = (char *)malloc(len + 1);
+    if (trimmed_str == NULL)
+    {
+        return NULL; // Handle memory allocation failure
+    }
+
+    // Copy the trimmed part of the string into trimmed_str
+    strncpy(trimmed_str, str, len);
+    trimmed_str[len] = '\0'; // Null terminate the string
+
+    return trimmed_str;
+}
+
+/**
+ * @brief Process requests and parse JSON data asynchronously
+ * @param http_request The function to send the request
+ * @param parse_json The function to parse the JSON
+ * @return true if successful, false otherwise
+ */
+bool flipper_http_process_response_async(
+    bool (*http_request)(void),
+    bool (*parse_json)(void))
+{
+    if (http_request()) // start the async request
+    {
+        furi_timer_start(fhttp.get_timeout_timer, TIMEOUT_DURATION_TICKS);
+        fhttp.state = RECEIVING;
+    }
+    else
+    {
+        FURI_LOG_E(HTTP_TAG, "Failed to send request");
+        return false;
+    }
+    while (fhttp.state == RECEIVING && furi_timer_is_running(fhttp.get_timeout_timer) > 0)
+    {
+        // Wait for the request to be received
+        furi_delay_ms(100);
+    }
+    furi_timer_stop(fhttp.get_timeout_timer);
+    if (!parse_json()) // parse the JSON before switching to the view (synchonous)
+    {
+        FURI_LOG_E(HTTP_TAG, "Failed to parse the JSON...");
+        return false;
+    }
+    furi_timer_stop(fhttp.get_timeout_timer);
+    return true;
+}
+
+#endif // FLIPPER_HTTP_H

+ 865 - 0
flip_social/jsmn.h

@@ -0,0 +1,865 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2010 Serge Zaitsev
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+#ifndef JSMN_H
+#define JSMN_H
+
+#include <stddef.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+#ifdef JSMN_STATIC
+#define JSMN_API static
+#else
+#define JSMN_API extern
+#endif
+
+  /**
+   * JSON type identifier. Basic types are:
+   * 	o Object
+   * 	o Array
+   * 	o String
+   * 	o Other primitive: number, boolean (true/false) or null
+   */
+  typedef enum
+  {
+    JSMN_UNDEFINED = 0,
+    JSMN_OBJECT = 1 << 0,
+    JSMN_ARRAY = 1 << 1,
+    JSMN_STRING = 1 << 2,
+    JSMN_PRIMITIVE = 1 << 3
+  } jsmntype_t;
+
+  enum jsmnerr
+  {
+    /* Not enough tokens were provided */
+    JSMN_ERROR_NOMEM = -1,
+    /* Invalid character inside JSON string */
+    JSMN_ERROR_INVAL = -2,
+    /* The string is not a full JSON packet, more bytes expected */
+    JSMN_ERROR_PART = -3
+  };
+
+  /**
+   * JSON token description.
+   * type		type (object, array, string etc.)
+   * start	start position in JSON data string
+   * end		end position in JSON data string
+   */
+  typedef struct jsmntok
+  {
+    jsmntype_t type;
+    int start;
+    int end;
+    int size;
+#ifdef JSMN_PARENT_LINKS
+    int parent;
+#endif
+  } jsmntok_t;
+
+  /**
+   * JSON parser. Contains an array of token blocks available. Also stores
+   * the string being parsed now and current position in that string.
+   */
+  typedef struct jsmn_parser
+  {
+    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);
+
+  /**
+   * 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);
+
+#ifndef JSMN_HEADER
+  /**
+   * 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;
+    if (parser->toknext >= num_tokens)
+    {
+      return NULL;
+    }
+    tok = &tokens[parser->toknext++];
+    tok->start = tok->end = -1;
+    tok->size = 0;
+#ifdef JSMN_PARENT_LINKS
+    tok->parent = -1;
+#endif
+    return tok;
+  }
+
+  /**
+   * Fills token type and boundaries.
+   */
+  static void jsmn_fill_token(jsmntok_t *token, const jsmntype_t type,
+                              const int start, const int end)
+  {
+    token->type = type;
+    token->start = start;
+    token->end = end;
+    token->size = 0;
+  }
+
+  /**
+   * Fills next available token with JSON primitive.
+   */
+  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])
+      {
+#ifndef JSMN_STRICT
+      /* In strict mode primitive must be followed by "," or "}" or "]" */
+      case ':':
+#endif
+      case '\t':
+      case '\r':
+      case '\n':
+      case ' ':
+      case ',':
+      case ']':
+      case '}':
+        goto found;
+      default:
+        /* to quiet a warning from gcc*/
+        break;
+      }
+      if (js[parser->pos] < 32 || js[parser->pos] >= 127)
+      {
+        parser->pos = start;
+        return JSMN_ERROR_INVAL;
+      }
+    }
+#ifdef JSMN_STRICT
+    /* In strict mode primitive must be followed by a comma/object/array */
+    parser->pos = start;
+    return JSMN_ERROR_PART;
+#endif
+
+  found:
+    if (tokens == NULL)
+    {
+      parser->pos--;
+      return 0;
+    }
+    token = jsmn_alloc_token(parser, tokens, num_tokens);
+    if (token == NULL)
+    {
+      parser->pos = start;
+      return JSMN_ERROR_NOMEM;
+    }
+    jsmn_fill_token(token, JSMN_PRIMITIVE, start, parser->pos);
+#ifdef JSMN_PARENT_LINKS
+    token->parent = parser->toksuper;
+#endif
+    parser->pos--;
+    return 0;
+  }
+
+  /**
+   * Fills next token with JSON string.
+   */
+  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++)
+    {
+      char c = js[parser->pos];
+
+      /* Quote: end of string */
+      if (c == '\"')
+      {
+        if (tokens == NULL)
+        {
+          return 0;
+        }
+        token = jsmn_alloc_token(parser, tokens, num_tokens);
+        if (token == NULL)
+        {
+          parser->pos = start;
+          return JSMN_ERROR_NOMEM;
+        }
+        jsmn_fill_token(token, JSMN_STRING, start + 1, parser->pos);
+#ifdef JSMN_PARENT_LINKS
+        token->parent = parser->toksuper;
+#endif
+        return 0;
+      }
+
+      /* Backslash: Quoted symbol expected */
+      if (c == '\\' && parser->pos + 1 < len)
+      {
+        int i;
+        parser->pos++;
+        switch (js[parser->pos])
+        {
+        /* Allowed escaped symbols */
+        case '\"':
+        case '/':
+        case '\\':
+        case 'b':
+        case 'f':
+        case 'r':
+        case 'n':
+        case 't':
+          break;
+        /* Allows escaped symbol \uXXXX */
+        case 'u':
+          parser->pos++;
+          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 */
+              parser->pos = start;
+              return JSMN_ERROR_INVAL;
+            }
+            parser->pos++;
+          }
+          parser->pos--;
+          break;
+        /* Unexpected symbol */
+        default:
+          parser->pos = start;
+          return JSMN_ERROR_INVAL;
+        }
+      }
+    }
+    parser->pos = start;
+    return JSMN_ERROR_PART;
+  }
+
+  /**
+   * Parse JSON string and fill tokens.
+   */
+  JSMN_API 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;
+    int count = parser->toknext;
+
+    for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++)
+    {
+      char c;
+      jsmntype_t type;
+
+      c = js[parser->pos];
+      switch (c)
+      {
+      case '{':
+      case '[':
+        count++;
+        if (tokens == NULL)
+        {
+          break;
+        }
+        token = jsmn_alloc_token(parser, tokens, num_tokens);
+        if (token == NULL)
+        {
+          return JSMN_ERROR_NOMEM;
+        }
+        if (parser->toksuper != -1)
+        {
+          jsmntok_t *t = &tokens[parser->toksuper];
+#ifdef JSMN_STRICT
+          /* In strict mode an object or array can't become a key */
+          if (t->type == JSMN_OBJECT)
+          {
+            return JSMN_ERROR_INVAL;
+          }
+#endif
+          t->size++;
+#ifdef JSMN_PARENT_LINKS
+          token->parent = parser->toksuper;
+#endif
+        }
+        token->type = (c == '{' ? JSMN_OBJECT : JSMN_ARRAY);
+        token->start = parser->pos;
+        parser->toksuper = parser->toknext - 1;
+        break;
+      case '}':
+      case ']':
+        if (tokens == NULL)
+        {
+          break;
+        }
+        type = (c == '}' ? JSMN_OBJECT : JSMN_ARRAY);
+#ifdef JSMN_PARENT_LINKS
+        if (parser->toknext < 1)
+        {
+          return JSMN_ERROR_INVAL;
+        }
+        token = &tokens[parser->toknext - 1];
+        for (;;)
+        {
+          if (token->start != -1 && token->end == -1)
+          {
+            if (token->type != type)
+            {
+              return JSMN_ERROR_INVAL;
+            }
+            token->end = parser->pos + 1;
+            parser->toksuper = token->parent;
+            break;
+          }
+          if (token->parent == -1)
+          {
+            if (token->type != type || parser->toksuper == -1)
+            {
+              return JSMN_ERROR_INVAL;
+            }
+            break;
+          }
+          token = &tokens[token->parent];
+        }
+#else
+        for (i = parser->toknext - 1; i >= 0; i--)
+        {
+          token = &tokens[i];
+          if (token->start != -1 && token->end == -1)
+          {
+            if (token->type != type)
+            {
+              return JSMN_ERROR_INVAL;
+            }
+            parser->toksuper = -1;
+            token->end = parser->pos + 1;
+            break;
+          }
+        }
+        /* Error if unmatched closing bracket */
+        if (i == -1)
+        {
+          return JSMN_ERROR_INVAL;
+        }
+        for (; i >= 0; i--)
+        {
+          token = &tokens[i];
+          if (token->start != -1 && token->end == -1)
+          {
+            parser->toksuper = i;
+            break;
+          }
+        }
+#endif
+        break;
+      case '\"':
+        r = jsmn_parse_string(parser, js, len, tokens, num_tokens);
+        if (r < 0)
+        {
+          return r;
+        }
+        count++;
+        if (parser->toksuper != -1 && tokens != NULL)
+        {
+          tokens[parser->toksuper].size++;
+        }
+        break;
+      case '\t':
+      case '\r':
+      case '\n':
+      case ' ':
+        break;
+      case ':':
+        parser->toksuper = parser->toknext - 1;
+        break;
+      case ',':
+        if (tokens != NULL && parser->toksuper != -1 &&
+            tokens[parser->toksuper].type != JSMN_ARRAY &&
+            tokens[parser->toksuper].type != JSMN_OBJECT)
+        {
+#ifdef JSMN_PARENT_LINKS
+          parser->toksuper = tokens[parser->toksuper].parent;
+#else
+          for (i = parser->toknext - 1; i >= 0; i--)
+          {
+            if (tokens[i].type == JSMN_ARRAY || tokens[i].type == JSMN_OBJECT)
+            {
+              if (tokens[i].start != -1 && tokens[i].end == -1)
+              {
+                parser->toksuper = i;
+                break;
+              }
+            }
+          }
+#endif
+        }
+        break;
+#ifdef JSMN_STRICT
+      /* In strict mode primitives are: numbers and booleans */
+      case '-':
+      case '0':
+      case '1':
+      case '2':
+      case '3':
+      case '4':
+      case '5':
+      case '6':
+      case '7':
+      case '8':
+      case '9':
+      case 't':
+      case 'f':
+      case 'n':
+        /* 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))
+          {
+            return JSMN_ERROR_INVAL;
+          }
+        }
+#else
+      /* In non-strict mode every unquoted value is a primitive */
+      default:
+#endif
+        r = jsmn_parse_primitive(parser, js, len, tokens, num_tokens);
+        if (r < 0)
+        {
+          return r;
+        }
+        count++;
+        if (parser->toksuper != -1 && tokens != NULL)
+        {
+          tokens[parser->toksuper].size++;
+        }
+        break;
+
+#ifdef JSMN_STRICT
+      /* Unexpected char in strict mode */
+      default:
+        return JSMN_ERROR_INVAL;
+#endif
+      }
+    }
+
+    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)
+        {
+          return JSMN_ERROR_PART;
+        }
+      }
+    }
+
+    return count;
+  }
+
+  /**
+   * Creates a new parser based over a given buffer with an array of tokens
+   * available.
+   */
+  JSMN_API void jsmn_init(jsmn_parser *parser)
+  {
+    parser->pos = 0;
+    parser->toknext = 0;
+    parser->toksuper = -1;
+  }
+
+#endif /* JSMN_HEADER */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* JSMN_H */
+
+#ifndef JB_JSMN_EDIT
+#define JB_JSMN_EDIT
+/* Added in by JBlanked on 2024-10-16 for use in Flipper Zero SDK*/
+
+#include <string.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <stdio.h>
+#include <string.h>
+#include <furi.h>
+
+// 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)
+  {
+    return NULL; // Handle memory allocation failure
+  }
+  snprintf(result, length, "{\"%s\":\"%s\"}", key, value);
+  return result; // Caller is responsible for freeing this memory
+}
+
+// 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)
+  {
+    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)
+{
+  // Parse the JSON feed
+  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)
+    {
+      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)
+    {
+      // Handle parsing errors
+      FURI_LOG_E("JSMM.H", "Failed to parse JSON: %d", ret);
+      free(tokens);
+      return NULL;
+    }
+
+    // Ensure that the root element is an 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)
+      {
+        // 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)
+        {
+          FURI_LOG_E("JSMM.H", "Failed to allocate memory for value.");
+          free(tokens);
+          return NULL;
+        }
+        strncpy(value, json_data + tokens[i + 1].start, length);
+        value[length] = '\0'; // Null-terminate the string
+
+        free(tokens); // Free the token array
+        return value; // Return the extracted value
+      }
+    }
+
+    // Free the token array if key was not found
+    free(tokens);
+  }
+  else
+  {
+    FURI_LOG_E("JSMM.H", "JSON data is NULL");
+  }
+  FURI_LOG_E("JSMM.H", "Failed to find the key in the JSON.");
+  return NULL; // Return NULL if something goes wrong
+}
+
+// Revised get_json_array_value function
+char *get_json_array_value(char *key, uint32_t index, char *json_data, uint32_t max_tokens)
+{
+  // Retrieve the array string for the given key
+  char *array_str = get_json_value(key, json_data, max_tokens);
+  if (array_str == NULL)
+  {
+    FURI_LOG_E("JSMM.H", "Failed to get array for key: %s", key);
+    return NULL;
+  }
+
+  // Initialize the JSON parser
+  jsmn_parser parser;
+  jsmn_init(&parser);
+
+  // Allocate memory for JSON tokens
+  jsmntok_t *tokens = malloc(sizeof(jsmntok_t) * max_tokens);
+  if (tokens == NULL)
+  {
+    FURI_LOG_E("JSMM.H", "Failed to allocate memory for JSON tokens.");
+    free(array_str);
+    return NULL;
+  }
+
+  // Parse the JSON array
+  int ret = jsmn_parse(&parser, array_str, strlen(array_str), tokens, max_tokens);
+  if (ret < 0)
+  {
+    FURI_LOG_E("JSMM.H", "Failed to parse JSON array: %d", ret);
+    free(tokens);
+    free(array_str);
+    return NULL;
+  }
+
+  // Ensure the root element is an array
+  if (ret < 1 || tokens[0].type != JSMN_ARRAY)
+  {
+    FURI_LOG_E("JSMM.H", "Value for key '%s' is not an array.", key);
+    free(tokens);
+    free(array_str);
+    return NULL;
+  }
+
+  // Check if the index is within bounds
+  if (index >= (uint32_t)tokens[0].size)
+  {
+    FURI_LOG_E("JSMM.H", "Index %lu out of bounds for array with size %d.", (unsigned long)index, tokens[0].size);
+    free(tokens);
+    free(array_str);
+    return NULL;
+  }
+
+  // Locate the token corresponding to the desired array element
+  int current_token = 1; // Start after the array token
+  for (uint32_t i = 0; i < index; i++)
+  {
+    if (tokens[current_token].type == JSMN_OBJECT)
+    {
+      // For objects, skip all key-value pairs
+      current_token += 1 + 2 * tokens[current_token].size;
+    }
+    else if (tokens[current_token].type == JSMN_ARRAY)
+    {
+      // For nested arrays, skip all elements
+      current_token += 1 + tokens[current_token].size;
+    }
+    else
+    {
+      // For primitive types, simply move to the next token
+      current_token += 1;
+    }
+
+    // Safety check to prevent out-of-bounds
+    if (current_token >= ret)
+    {
+      FURI_LOG_E("JSMM.H", "Unexpected end of tokens while traversing array.");
+      free(tokens);
+      free(array_str);
+      return NULL;
+    }
+  }
+
+  // Extract the array element
+  jsmntok_t element = tokens[current_token];
+  int length = element.end - element.start;
+  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);
+    return NULL;
+  }
+
+  // Copy the element value to a new string
+  strncpy(value, array_str + element.start, length);
+  value[length] = '\0'; // Null-terminate the string
+
+  // Clean up
+  free(tokens);
+  free(array_str);
+
+  return value;
+}
+
+// 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)
+{
+  // Retrieve the array string for the given key
+  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;
+  }
+
+  // Initialize the JSON parser
+  jsmn_parser parser;
+  jsmn_init(&parser);
+
+  // Allocate memory for JSON tokens
+  jsmntok_t *tokens = malloc(sizeof(jsmntok_t) * max_tokens); // Allocate on the heap
+  if (tokens == NULL)
+  {
+    FURI_LOG_E("JSMM.H", "Failed to allocate memory for JSON tokens.");
+    free(array_str);
+    return NULL;
+  }
+
+  // Parse the JSON array
+  int ret = jsmn_parse(&parser, array_str, strlen(array_str), tokens, max_tokens);
+  if (ret < 0)
+  {
+    FURI_LOG_E("JSMM.H", "Failed to parse JSON array: %d", ret);
+    free(tokens);
+    free(array_str);
+    return NULL;
+  }
+
+  // Ensure the root element is an array
+  if (tokens[0].type != JSMN_ARRAY)
+  {
+    FURI_LOG_E("JSMM.H", "Value for key '%s' is not an array.", key);
+    free(tokens);
+    free(array_str);
+    return NULL;
+  }
+
+  // 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)
+  {
+    FURI_LOG_E("JSMM.H", "Failed to allocate memory for array of values.");
+    free(tokens);
+    free(array_str);
+    return NULL;
+  }
+
+  int actual_num_values = 0;
+
+  // 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)
+    {
+      FURI_LOG_E("JSMM.H", "Unexpected end of tokens while traversing array.");
+      break;
+    }
+
+    jsmntok_t element = tokens[current_token];
+
+    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;
+      continue;
+    }
+
+    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)
+    {
+      FURI_LOG_E("JSMM.H", "Failed to allocate memory for array element.");
+      for (int j = 0; j < actual_num_values; j++)
+      {
+        free(values[j]);
+      }
+      free(values);
+      free(tokens);
+      free(array_str);
+      return NULL;
+    }
+
+    strncpy(value, array_str + element.start, length);
+    value[length] = '\0'; // Null-terminate the string
+
+    values[actual_num_values] = value;
+    actual_num_values++;
+
+    // Skip all tokens related to this object to avoid misparsing
+    current_token += 1 + (2 * element.size); // Each key-value pair consumes two tokens
+  }
+
+  *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)
+    {
+      values = reduced_values;
+    }
+
+    // Free the remaining values
+    for (int i = actual_num_values; i < array_size; i++)
+    {
+      free(values[i]);
+    }
+  }
+
+  // Clean up
+  free(tokens);
+  free(array_str);
+  return values;
+}
+
+#endif /* JB_JSMN_EDIT */

+ 803 - 0
flip_social/uart_text_input.h

@@ -0,0 +1,803 @@
+// from https://github.com/xMasterX/all-the-plugins/blob/dev/base_pack/uart_terminal/uart_text_input.c
+// all credits to xMasterX for the code
+#ifndef UART_TEXT_INPUT_H
+#define UART_TEXT_INPUT_H
+
+#include <gui/elements.h>
+#include "flip_social_icons.h"
+#include <furi.h>
+#include <furi.h>
+#include <furi_hal.h>
+#include <gui/gui.h>
+#include <gui/view.h>
+#include <core/common_defines.h>
+
+/** Text input anonymous structure */
+typedef struct UART_TextInput UART_TextInput;
+typedef void (*UART_TextInputCallback)(void *context);
+typedef bool (*UART_TextInputValidatorCallback)(const char *text, FuriString *error, void *context);
+
+UART_TextInputValidatorCallback
+uart_text_input_get_validator_callback(UART_TextInput *uart_text_input);
+
+void uart_text_input_reset(UART_TextInput *uart_text_input);
+
+struct UART_TextInput
+{
+    View *view;
+    FuriTimer *timer;
+};
+
+typedef struct
+{
+    const char text;
+    const uint8_t x;
+    const uint8_t y;
+} UART_TextInputKey;
+
+typedef struct
+{
+    const char *header;
+    char *text_buffer;
+    size_t text_buffer_size;
+    bool clear_default_text;
+
+    UART_TextInputCallback callback;
+    void *callback_context;
+
+    uint8_t selected_row;
+    uint8_t selected_column;
+
+    UART_TextInputValidatorCallback validator_callback;
+    void *validator_callback_context;
+    FuriString *validator_text;
+    bool valadator_message_visible;
+} UART_TextInputModel;
+
+static const uint8_t keyboard_origin_x = 1;
+static const uint8_t keyboard_origin_y = 29;
+static const uint8_t keyboard_row_count = 4;
+
+#define mode_AT "Send AT command to UART"
+
+#define ENTER_KEY '\r'
+#define BACKSPACE_KEY '\b'
+
+static const UART_TextInputKey keyboard_keys_row_1[] = {
+    {'{', 1, 0},
+    {'(', 9, 0},
+    {'[', 17, 0},
+    {'|', 25, 0},
+    {'@', 33, 0},
+    {'&', 41, 0},
+    {'#', 49, 0},
+    {';', 57, 0},
+    {'^', 65, 0},
+    {'*', 73, 0},
+    {'`', 81, 0},
+    {'"', 89, 0},
+    {'~', 97, 0},
+    {'\'', 105, 0},
+    {'.', 113, 0},
+    {'/', 120, 0},
+};
+
+static const UART_TextInputKey keyboard_keys_row_2[] = {
+    {'q', 1, 10},
+    {'w', 9, 10},
+    {'e', 17, 10},
+    {'r', 25, 10},
+    {'t', 33, 10},
+    {'y', 41, 10},
+    {'u', 49, 10},
+    {'i', 57, 10},
+    {'o', 65, 10},
+    {'p', 73, 10},
+    {'0', 81, 10},
+    {'1', 89, 10},
+    {'2', 97, 10},
+    {'3', 105, 10},
+    {'=', 113, 10},
+    {'-', 120, 10},
+};
+
+static const UART_TextInputKey keyboard_keys_row_3[] = {
+    {'a', 1, 21},
+    {'s', 9, 21},
+    {'d', 18, 21},
+    {'f', 25, 21},
+    {'g', 33, 21},
+    {'h', 41, 21},
+    {'j', 49, 21},
+    {'k', 57, 21},
+    {'l', 65, 21},
+    {BACKSPACE_KEY, 72, 13},
+    {'4', 89, 21},
+    {'5', 97, 21},
+    {'6', 105, 21},
+    {'$', 113, 21},
+    {'%', 120, 21},
+
+};
+
+static const UART_TextInputKey keyboard_keys_row_4[] = {
+    {'z', 1, 33},
+    {'x', 9, 33},
+    {'c', 18, 33},
+    {'v', 25, 33},
+    {'b', 33, 33},
+    {'n', 41, 33},
+    {'m', 49, 33},
+    {'_', 57, 33},
+    {ENTER_KEY, 64, 24},
+    {'7', 89, 33},
+    {'8', 97, 33},
+    {'9', 105, 33},
+    {'!', 113, 33},
+    {'+', 120, 33},
+};
+
+static uint8_t get_row_size(uint8_t row_index)
+{
+    uint8_t row_size = 0;
+
+    switch (row_index + 1)
+    {
+    case 1:
+        row_size = sizeof(keyboard_keys_row_1) / sizeof(UART_TextInputKey);
+        break;
+    case 2:
+        row_size = sizeof(keyboard_keys_row_2) / sizeof(UART_TextInputKey);
+        break;
+    case 3:
+        row_size = sizeof(keyboard_keys_row_3) / sizeof(UART_TextInputKey);
+        break;
+    case 4:
+        row_size = sizeof(keyboard_keys_row_4) / sizeof(UART_TextInputKey);
+        break;
+    }
+
+    return row_size;
+}
+
+static const UART_TextInputKey *get_row(uint8_t row_index)
+{
+    const UART_TextInputKey *row = NULL;
+
+    switch (row_index + 1)
+    {
+    case 1:
+        row = keyboard_keys_row_1;
+        break;
+    case 2:
+        row = keyboard_keys_row_2;
+        break;
+    case 3:
+        row = keyboard_keys_row_3;
+        break;
+    case 4:
+        row = keyboard_keys_row_4;
+        break;
+    }
+
+    return row;
+}
+
+static char get_selected_char(UART_TextInputModel *model)
+{
+    return get_row(model->selected_row)[model->selected_column].text;
+}
+
+static bool char_is_lowercase(char letter)
+{
+    return (letter >= 0x61 && letter <= 0x7A);
+}
+
+static bool char_is_uppercase(char letter)
+{
+    return (letter >= 0x41 && letter <= 0x5A);
+}
+
+static char char_to_lowercase(const char letter)
+{
+    switch (letter)
+    {
+    case ' ':
+        return 0x5f;
+        break;
+    case ')':
+        return 0x28;
+        break;
+    case '}':
+        return 0x7b;
+        break;
+    case ']':
+        return 0x5b;
+        break;
+    case '\\':
+        return 0x2f;
+        break;
+    case ':':
+        return 0x3b;
+        break;
+    case ',':
+        return 0x2e;
+        break;
+    case '?':
+        return 0x21;
+        break;
+    case '>':
+        return 0x3c;
+        break;
+    }
+    if (char_is_uppercase(letter))
+    {
+        return (letter + 0x20);
+    }
+    else
+    {
+        return letter;
+    }
+}
+
+static char char_to_uppercase(const char letter)
+{
+    switch (letter)
+    {
+    case '_':
+        return 0x20;
+        break;
+    case '(':
+        return 0x29;
+        break;
+    case '{':
+        return 0x7d;
+        break;
+    case '[':
+        return 0x5d;
+        break;
+    case '/':
+        return 0x5c;
+        break;
+    case ';':
+        return 0x3a;
+        break;
+    case '.':
+        return 0x2c;
+        break;
+    case '!':
+        return 0x3f;
+        break;
+    case '<':
+        return 0x3e;
+        break;
+    }
+    if (char_is_lowercase(letter))
+    {
+        return (letter - 0x20);
+    }
+    else
+    {
+        return letter;
+    }
+}
+
+static void uart_text_input_backspace_cb(UART_TextInputModel *model)
+{
+    uint8_t text_length = model->clear_default_text ? 1 : strlen(model->text_buffer);
+    if (text_length > 0)
+    {
+        model->text_buffer[text_length - 1] = 0;
+    }
+}
+
+static void uart_text_input_view_draw_callback(Canvas *canvas, void *_model)
+{
+    UART_TextInputModel *model = _model;
+    // uint8_t text_length = model->text_buffer ? strlen(model->text_buffer) : 0;
+    uint8_t needed_string_width = canvas_width(canvas) - 8;
+    uint8_t start_pos = 4;
+
+    const char *text = model->text_buffer;
+
+    canvas_clear(canvas);
+    canvas_set_color(canvas, ColorBlack);
+
+    canvas_draw_str(canvas, 2, 7, model->header);
+    elements_slightly_rounded_frame(canvas, 1, 8, 126, 12);
+
+    if (canvas_string_width(canvas, text) > needed_string_width)
+    {
+        canvas_draw_str(canvas, start_pos, 17, "...");
+        start_pos += 6;
+        needed_string_width -= 8;
+    }
+
+    while (text != 0 && canvas_string_width(canvas, text) > needed_string_width)
+    {
+        text++;
+    }
+
+    if (model->clear_default_text)
+    {
+        elements_slightly_rounded_box(
+            canvas, start_pos - 1, 14, canvas_string_width(canvas, text) + 2, 10);
+        canvas_set_color(canvas, ColorWhite);
+    }
+    else
+    {
+        canvas_draw_str(canvas, start_pos + canvas_string_width(canvas, text) + 1, 18, "|");
+        canvas_draw_str(canvas, start_pos + canvas_string_width(canvas, text) + 2, 18, "|");
+    }
+    canvas_draw_str(canvas, start_pos, 17, text);
+
+    canvas_set_font(canvas, FontKeyboard);
+
+    for (uint8_t row = 0; row <= keyboard_row_count; row++)
+    {
+        const uint8_t column_count = get_row_size(row);
+        const UART_TextInputKey *keys = get_row(row);
+
+        for (size_t column = 0; column < column_count; column++)
+        {
+            if (keys[column].text == ENTER_KEY)
+            {
+                canvas_set_color(canvas, ColorBlack);
+                if (model->selected_row == row && model->selected_column == column)
+                {
+                    canvas_draw_icon(
+                        canvas,
+                        keyboard_origin_x + keys[column].x,
+                        keyboard_origin_y + keys[column].y,
+                        &I_KeySaveSelected_24x11);
+                }
+                else
+                {
+                    canvas_draw_icon(
+                        canvas,
+                        keyboard_origin_x + keys[column].x,
+                        keyboard_origin_y + keys[column].y,
+                        &I_KeySave_24x11);
+                }
+            }
+            else if (keys[column].text == BACKSPACE_KEY)
+            {
+                canvas_set_color(canvas, ColorBlack);
+                if (model->selected_row == row && model->selected_column == column)
+                {
+                    canvas_draw_icon(
+                        canvas,
+                        keyboard_origin_x + keys[column].x,
+                        keyboard_origin_y + keys[column].y,
+                        &I_KeyBackspaceSelected_16x9);
+                }
+                else
+                {
+                    canvas_draw_icon(
+                        canvas,
+                        keyboard_origin_x + keys[column].x,
+                        keyboard_origin_y + keys[column].y,
+                        &I_KeyBackspace_16x9);
+                }
+            }
+            else
+            {
+                if (model->selected_row == row && model->selected_column == column)
+                {
+                    canvas_set_color(canvas, ColorBlack);
+                    canvas_draw_box(
+                        canvas,
+                        keyboard_origin_x + keys[column].x - 1,
+                        keyboard_origin_y + keys[column].y - 8,
+                        7,
+                        10);
+                    canvas_set_color(canvas, ColorWhite);
+                }
+                else
+                {
+                    canvas_set_color(canvas, ColorBlack);
+                }
+                if (0 == strcmp(model->header, mode_AT))
+                {
+                    canvas_draw_glyph(
+                        canvas,
+                        keyboard_origin_x + keys[column].x,
+                        keyboard_origin_y + keys[column].y,
+                        char_to_uppercase(keys[column].text));
+                }
+                else
+                {
+                    canvas_draw_glyph(
+                        canvas,
+                        keyboard_origin_x + keys[column].x,
+                        keyboard_origin_y + keys[column].y,
+                        keys[column].text);
+                }
+            }
+        }
+    }
+    if (model->valadator_message_visible)
+    {
+        canvas_set_font(canvas, FontSecondary);
+        canvas_set_color(canvas, ColorWhite);
+        canvas_draw_box(canvas, 8, 10, 110, 48);
+        canvas_set_color(canvas, ColorBlack);
+        canvas_draw_icon(canvas, 10, 14, &I_WarningDolphin_45x42);
+        canvas_draw_rframe(canvas, 8, 8, 112, 50, 3);
+        canvas_draw_rframe(canvas, 9, 9, 110, 48, 2);
+        elements_multiline_text(canvas, 62, 20, furi_string_get_cstr(model->validator_text));
+        canvas_set_font(canvas, FontKeyboard);
+    }
+}
+
+static void
+uart_text_input_handle_up(UART_TextInput *uart_text_input, UART_TextInputModel *model)
+{
+    UNUSED(uart_text_input);
+    if (model->selected_row > 0)
+    {
+        model->selected_row--;
+        if (model->selected_column > get_row_size(model->selected_row) - 6)
+        {
+            model->selected_column = model->selected_column + 1;
+        }
+    }
+}
+
+static void
+uart_text_input_handle_down(UART_TextInput *uart_text_input, UART_TextInputModel *model)
+{
+    UNUSED(uart_text_input);
+    if (model->selected_row < keyboard_row_count - 1)
+    {
+        model->selected_row++;
+        if (model->selected_column > get_row_size(model->selected_row) - 4)
+        {
+            model->selected_column = model->selected_column - 1;
+        }
+    }
+}
+
+static void
+uart_text_input_handle_left(UART_TextInput *uart_text_input, UART_TextInputModel *model)
+{
+    UNUSED(uart_text_input);
+    if (model->selected_column > 0)
+    {
+        model->selected_column--;
+    }
+    else
+    {
+        model->selected_column = get_row_size(model->selected_row) - 1;
+    }
+}
+
+static void
+uart_text_input_handle_right(UART_TextInput *uart_text_input, UART_TextInputModel *model)
+{
+    UNUSED(uart_text_input);
+    if (model->selected_column < get_row_size(model->selected_row) - 1)
+    {
+        model->selected_column++;
+    }
+    else
+    {
+        model->selected_column = 0;
+    }
+}
+
+static void uart_text_input_handle_ok(
+    UART_TextInput *uart_text_input,
+    UART_TextInputModel *model,
+    bool shift)
+{
+    char selected = get_selected_char(model);
+    uint8_t text_length = strlen(model->text_buffer);
+
+    if (0 == strcmp(model->header, mode_AT))
+    {
+        selected = char_to_uppercase(selected);
+    }
+
+    if (shift)
+    {
+        if (0 == strcmp(model->header, mode_AT))
+        {
+            selected = char_to_lowercase(selected);
+        }
+        else
+        {
+            selected = char_to_uppercase(selected);
+        }
+    }
+
+    if (selected == ENTER_KEY)
+    {
+        if (model->validator_callback &&
+            (!model->validator_callback(
+                model->text_buffer, model->validator_text, model->validator_callback_context)))
+        {
+            model->valadator_message_visible = true;
+            furi_timer_start(uart_text_input->timer, furi_kernel_get_tick_frequency() * 4);
+        }
+        else if (model->callback != 0 && text_length > 0)
+        {
+            model->callback(model->callback_context);
+        }
+    }
+    else if (selected == BACKSPACE_KEY)
+    {
+        uart_text_input_backspace_cb(model);
+    }
+    else
+    {
+        if (model->clear_default_text)
+        {
+            text_length = 0;
+        }
+        if (text_length < (model->text_buffer_size - 1))
+        {
+            model->text_buffer[text_length] = selected;
+            model->text_buffer[text_length + 1] = 0;
+        }
+    }
+    model->clear_default_text = false;
+}
+
+static bool uart_text_input_view_input_callback(InputEvent *event, void *context)
+{
+    UART_TextInput *uart_text_input = context;
+    furi_assert(uart_text_input);
+
+    bool consumed = false;
+
+    // Acquire model
+    UART_TextInputModel *model = view_get_model(uart_text_input->view);
+
+    if ((!(event->type == InputTypePress) && !(event->type == InputTypeRelease)) &&
+        model->valadator_message_visible)
+    {
+        model->valadator_message_visible = false;
+        consumed = true;
+    }
+    else if (event->type == InputTypeShort)
+    {
+        consumed = true;
+        switch (event->key)
+        {
+        case InputKeyUp:
+            uart_text_input_handle_up(uart_text_input, model);
+            break;
+        case InputKeyDown:
+            uart_text_input_handle_down(uart_text_input, model);
+            break;
+        case InputKeyLeft:
+            uart_text_input_handle_left(uart_text_input, model);
+            break;
+        case InputKeyRight:
+            uart_text_input_handle_right(uart_text_input, model);
+            break;
+        case InputKeyOk:
+            uart_text_input_handle_ok(uart_text_input, model, false);
+            break;
+        default:
+            consumed = false;
+            break;
+        }
+    }
+    else if (event->type == InputTypeLong)
+    {
+        consumed = true;
+        switch (event->key)
+        {
+        case InputKeyUp:
+            uart_text_input_handle_up(uart_text_input, model);
+            break;
+        case InputKeyDown:
+            uart_text_input_handle_down(uart_text_input, model);
+            break;
+        case InputKeyLeft:
+            uart_text_input_handle_left(uart_text_input, model);
+            break;
+        case InputKeyRight:
+            uart_text_input_handle_right(uart_text_input, model);
+            break;
+        case InputKeyOk:
+            uart_text_input_handle_ok(uart_text_input, model, true);
+            break;
+        case InputKeyBack:
+            uart_text_input_backspace_cb(model);
+            break;
+        default:
+            consumed = false;
+            break;
+        }
+    }
+    else if (event->type == InputTypeRepeat)
+    {
+        consumed = true;
+        switch (event->key)
+        {
+        case InputKeyUp:
+            uart_text_input_handle_up(uart_text_input, model);
+            break;
+        case InputKeyDown:
+            uart_text_input_handle_down(uart_text_input, model);
+            break;
+        case InputKeyLeft:
+            uart_text_input_handle_left(uart_text_input, model);
+            break;
+        case InputKeyRight:
+            uart_text_input_handle_right(uart_text_input, model);
+            break;
+        case InputKeyBack:
+            uart_text_input_backspace_cb(model);
+            break;
+        default:
+            consumed = false;
+            break;
+        }
+    }
+
+    // Commit model
+    view_commit_model(uart_text_input->view, consumed);
+
+    return consumed;
+}
+
+void uart_text_input_timer_callback(void *context)
+{
+    furi_assert(context);
+    UART_TextInput *uart_text_input = context;
+
+    with_view_model(
+        uart_text_input->view,
+        UART_TextInputModel * model,
+        { model->valadator_message_visible = false; },
+        true);
+}
+
+UART_TextInput *uart_text_input_alloc()
+{
+    UART_TextInput *uart_text_input = malloc(sizeof(UART_TextInput));
+    uart_text_input->view = view_alloc();
+    view_set_context(uart_text_input->view, uart_text_input);
+    view_allocate_model(uart_text_input->view, ViewModelTypeLocking, sizeof(UART_TextInputModel));
+    view_set_draw_callback(uart_text_input->view, uart_text_input_view_draw_callback);
+    view_set_input_callback(uart_text_input->view, uart_text_input_view_input_callback);
+
+    uart_text_input->timer =
+        furi_timer_alloc(uart_text_input_timer_callback, FuriTimerTypeOnce, uart_text_input);
+
+    with_view_model(
+        uart_text_input->view,
+        UART_TextInputModel * model,
+        { model->validator_text = furi_string_alloc(); },
+        false);
+
+    uart_text_input_reset(uart_text_input);
+
+    return uart_text_input;
+}
+
+void uart_text_input_free(UART_TextInput *uart_text_input)
+{
+    furi_assert(uart_text_input);
+    with_view_model(
+        uart_text_input->view,
+        UART_TextInputModel * model,
+        { furi_string_free(model->validator_text); },
+        false);
+
+    // Send stop command
+    furi_timer_stop(uart_text_input->timer);
+    // Release allocated memory
+    furi_timer_free(uart_text_input->timer);
+
+    view_free(uart_text_input->view);
+
+    free(uart_text_input);
+}
+
+void uart_text_input_reset(UART_TextInput *uart_text_input)
+{
+    furi_assert(uart_text_input);
+    with_view_model(
+        uart_text_input->view,
+        UART_TextInputModel * model,
+        {
+            model->text_buffer_size = 0;
+            model->header = "";
+            model->selected_row = 0;
+            model->selected_column = 0;
+            model->clear_default_text = false;
+            model->text_buffer = NULL;
+            model->text_buffer_size = 0;
+            model->callback = NULL;
+            model->callback_context = NULL;
+            model->validator_callback = NULL;
+            model->validator_callback_context = NULL;
+            furi_string_reset(model->validator_text);
+            model->valadator_message_visible = false;
+        },
+        true);
+}
+
+View *uart_text_input_get_view(UART_TextInput *uart_text_input)
+{
+    furi_assert(uart_text_input);
+    return uart_text_input->view;
+}
+
+void uart_text_input_set_result_callback(
+    UART_TextInput *uart_text_input,
+    UART_TextInputCallback callback,
+    void *callback_context,
+    char *text_buffer,
+    size_t text_buffer_size,
+    bool clear_default_text)
+{
+    with_view_model(
+        uart_text_input->view,
+        UART_TextInputModel * model,
+        {
+            model->callback = callback;
+            model->callback_context = callback_context;
+            model->text_buffer = text_buffer;
+            model->text_buffer_size = text_buffer_size;
+            model->clear_default_text = clear_default_text;
+            if (text_buffer && text_buffer[0] != '\0')
+            {
+                // Set focus on Save
+                model->selected_row = 2;
+                model->selected_column = 8;
+            }
+        },
+        true);
+}
+
+void uart_text_input_set_validator(
+    UART_TextInput *uart_text_input,
+    UART_TextInputValidatorCallback callback,
+    void *callback_context)
+{
+    with_view_model(
+        uart_text_input->view,
+        UART_TextInputModel * model,
+        {
+            model->validator_callback = callback;
+            model->validator_callback_context = callback_context;
+        },
+        true);
+}
+
+UART_TextInputValidatorCallback
+uart_text_input_get_validator_callback(UART_TextInput *uart_text_input)
+{
+    UART_TextInputValidatorCallback validator_callback = NULL;
+    with_view_model(
+        uart_text_input->view,
+        UART_TextInputModel * model,
+        { validator_callback = model->validator_callback; },
+        false);
+    return validator_callback;
+}
+
+void *uart_text_input_get_validator_callback_context(UART_TextInput *uart_text_input)
+{
+    void *validator_callback_context = NULL;
+    with_view_model(
+        uart_text_input->view,
+        UART_TextInputModel * model,
+        { validator_callback_context = model->validator_callback_context; },
+        false);
+    return validator_callback_context;
+}
+
+void uart_text_input_set_header_text(UART_TextInput *uart_text_input, const char *text)
+{
+    with_view_model(
+        uart_text_input->view, UART_TextInputModel * model, { model->header = text; }, true);
+}
+
+#endif // UART_TEXT_INPUT_H