jblanked 1 год назад
Родитель
Сommit
6089e6daeb

+ 57 - 0
README.md

@@ -0,0 +1,57 @@
+# 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 solve the dissatisfaction 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/WebCrawler-FlipperZero/tree/main/assets/FlipperHTTP
+
+## Features
+- Login/Logout
+- Registration
+- Feed
+- Profile
+- Customizable Pre-Saves
+- Direct Messaging (coming soon)
+
+**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 128 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. As a solution, 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 the pre-saves directly within the app.
+
+## Roadmap
+**v0.2**
+- Stability Patch
+- LED options
+
+**v0.3**
+- Explore Page
+- Friends
+
+**v0.4**
+- Direct Messaging
+- Privacy Settings
+
+## Contribution
+This is a big task, and I welcome all contributors, especially developers who are into 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 or the post creation 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.

+ 49 - 0
app.c

@@ -0,0 +1,49 @@
+// 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_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
+    FlipSocialApp *app = flip_social_app_alloc();
+    if (!app)
+    {
+        // Allocation failed
+        return -1; // Indicate failure
+    }
+
+    // send settings and connect wifi
+    if (!flipper_http_connect_wifi())
+    {
+        FURI_LOG_E(TAG, "Failed to connect to WiFi");
+        return -1;
+    }
+
+    if (!flipper_http_ping())
+    {
+        FURI_LOG_E(TAG, "Failed to ping the device");
+        return -1;
+    }
+
+    // Run the view dispatcher
+    view_dispatcher_run(app->view_dispatcher);
+
+    // Free the resources used by the Hello World application
+    flip_social_app_free(app);
+
+    // Return 0 to indicate success
+    return 0;
+}


+ 15 - 0
application.fam

@@ -0,0 +1,15 @@
+App(
+    appid="flip_social",
+    name="Flip Social",
+    apptype=FlipperAppType.EXTERNAL,
+    entry_point="main_flip_social",
+    stack_size=4 * 1024,
+    requires=[
+        "gui",
+    ],
+    order=10,
+    fap_icon="app.png",
+    fap_category="GPIO",
+    fap_icon_assets="assets",
+    fap_description="Social media platform for the Flipper Zero.",
+)

BIN
assets/.DS_Store


BIN
assets/ButtonBACK_10x8.png


BIN
assets/ButtonLeft_4x7.png


BIN
assets/ButtonOK_7x7.png


BIN
assets/ButtonRight_4x7.png


BIN
assets/ButtonUp_7x4.png


+ 2 - 0
assets/CHANGELOG.md

@@ -0,0 +1,2 @@
+## v0.1
+- Initial version.

BIN
assets/KeyBackspaceSelected_16x9.png


BIN
assets/KeyBackspace_16x9.png


BIN
assets/KeySaveSelected_24x11.png


BIN
assets/KeySave_24x11.png


+ 57 - 0
assets/README.md

@@ -0,0 +1,57 @@
+# 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 solve the dissatisfaction 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/WebCrawler-FlipperZero/tree/main/assets/FlipperHTTP
+
+## Features
+- Login/Logout
+- Registration
+- Feed
+- Profile
+- Customizable Pre-Saves
+- Direct Messaging (coming soon)
+
+**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 128 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. As a solution, 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 the pre-saves directly within the app.
+
+## Roadmap
+**v0.2**
+- Stability Patch
+- LED options
+
+**v0.3**
+- Explore Page
+- Friends
+
+**v0.4**
+- Direct Messaging
+- Privacy Settings
+
+## Contribution
+This is a big task, and I welcome all contributors, especially developers who are into 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 or the post creation 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.

BIN
assets/WarningDolphin_45x42.png


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


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


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


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


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


BIN
assets/flip-social-register.png


+ 1767 - 0
flip_social_callback.h

@@ -0,0 +1,1767 @@
+// flip_social_callback.h
+#ifndef FLIP_SOCIAL_CALLBACK_H
+#define FLIP_SOCIAL_CALLBACK_H
+static FlipSocialApp *app_instance = NULL;
+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;
+uint32_t flip_social_pre_saved_message_clicked_index = 0;
+static void flip_social_logged_in_compose_pre_save_updated(void *context);
+static void flip_social_callback_submenu_choices(void *context, uint32_t index);
+// include strndup
+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);
+}
+
+typedef struct
+{
+    char *usernames[128];
+    char *messages[128];
+    bool is_flipped[128];
+    uint32_t ids[128];
+    size_t count;
+    size_t index;
+} FlipSocialFeed;
+
+#define MAX_FEED_ITEMS 128
+#define MAX_LINE_LENGTH 30
+
+// temporary FlipSocialFeed object
+static FlipSocialFeed flip_social_feed = {
+    .usernames = {"JBlanked", "FlipperKing", "FlipperQueen"},
+    .messages = {"Welcome. This is a temp message. Either the feed didn't load or there was a server error.", "I am the Chosen Flipper.", "No one can flip like me."},
+    .is_flipped = {false, false, true},
+    .ids = {0, 1, 2},
+    .count = 3,
+    .index = 0};
+
+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[256];
+    snprintf(command, 128, "https://www.flipsocial.net/api/feed/20/%s/", 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 feed");
+        return false;
+    }
+    fhttp.state = RECEIVING;
+    return true;
+}
+
+#define MAX_TOKENS 128 // Adjust based on expected JSON size
+
+// 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;
+}
+
+bool flip_social_parse_json_feed()
+{
+    // Parse the JSON feed
+    if (fhttp.received_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(TAG, "Failed to allocate memory for JSON tokens.");
+            return false;
+        }
+
+        int ret = jsmn_parse(&parser, fhttp.received_data, strlen(fhttp.received_data), tokens, MAX_TOKENS);
+
+        if (ret < 0)
+        {
+            // Handle parsing errors
+            FURI_LOG_E(TAG, "Failed to parse JSON: %d", ret);
+            free(tokens);
+            return false;
+        }
+
+        // Ensure that the root element is an object
+        if (ret < 1 || tokens[0].type != JSMN_OBJECT)
+        {
+            FURI_LOG_E(TAG, "Root element is not an object.");
+            free(tokens);
+            return false;
+        }
+
+        // Initialize feed count
+        flip_social_feed.count = 0;
+
+        // Loop over all keys in the root object
+        int i = 0;
+        for (i = 1; i < ret; i++)
+        {
+            if (jsoneq(fhttp.received_data, &tokens[i], "feed") == 0)
+            {
+                // Found "feed" key
+                jsmntok_t *feed_array = &tokens[i + 1];
+
+                if (feed_array->type != JSMN_ARRAY)
+                {
+                    FURI_LOG_E(TAG, "'feed' is not an array.");
+                    break;
+                }
+
+                int j, k;
+                int feed_index = 0;
+
+                // Iterate over the feed array
+                for (j = 0; j < feed_array->size; j++)
+                {
+                    int idx = i + 2; // Position of the first feed item
+                    for (k = 0; k < j; k++)
+                    {
+                        // Skip tokens of previous feed items
+                        idx += tokens[idx].size * 2 + 1;
+                    }
+
+                    if (idx >= ret)
+                    {
+                        FURI_LOG_E(TAG, "Index out of bounds while accessing feed items.");
+                        break;
+                    }
+
+                    jsmntok_t *item = &tokens[idx];
+                    if (item->type != JSMN_OBJECT)
+                    {
+                        FURI_LOG_E(TAG, "Feed item is not an object.");
+                        continue;
+                    }
+
+                    // Variables to hold item data
+                    char *username = NULL;
+                    char *message = NULL;
+                    int flipped = 0;
+                    int id = 0;
+
+                    // Iterate over keys in the feed item
+                    int l;
+                    int item_size = item->size;
+                    int item_idx = idx + 1; // Position of the first key in the item
+
+                    for (l = 0; l < item_size; l++)
+                    {
+                        if (item_idx + 1 >= ret)
+                        {
+                            FURI_LOG_E(TAG, "Index out of bounds while accessing item properties.");
+                            break;
+                        }
+
+                        jsmntok_t *key = &tokens[item_idx];
+                        jsmntok_t *val = &tokens[item_idx + 1];
+
+                        if (jsoneq(fhttp.received_data, key, "username") == 0)
+                        {
+                            username = strndup(fhttp.received_data + val->start, val->end - val->start);
+                        }
+                        else if (jsoneq(fhttp.received_data, key, "message") == 0)
+                        {
+                            message = strndup(fhttp.received_data + val->start, val->end - val->start);
+                        }
+                        else if (jsoneq(fhttp.received_data, key, "flipped") == 0)
+                        {
+                            if (val->type == JSMN_PRIMITIVE)
+                            {
+                                if (strncmp(fhttp.received_data + val->start, "true", val->end - val->start) == 0)
+                                    flipped = 1;
+                                else
+                                    flipped = 0;
+                            }
+                        }
+                        else if (jsoneq(fhttp.received_data, key, "id") == 0)
+                        {
+                            if (val->type == JSMN_PRIMITIVE)
+                            {
+                                char id_str[16] = {0};
+                                uint32_t id_len = val->end - val->start;
+                                if (id_len >= sizeof(id_str))
+                                    id_len = sizeof(id_str) - 1;
+                                strncpy(id_str, fhttp.received_data + val->start, id_len);
+                                id = atoi(id_str);
+                            }
+                        }
+
+                        item_idx += 2; // Move to the next key-value pair
+                    }
+
+                    // Store the data in flip_social_feed
+                    if (username && message && feed_index < MAX_FEED_ITEMS)
+                    {
+                        flip_social_feed.usernames[feed_index] = username;
+                        flip_social_feed.messages[feed_index] = message;
+                        flip_social_feed.is_flipped[feed_index] = flipped;
+                        flip_social_feed.ids[feed_index] = id;
+                        feed_index++;
+                        flip_social_feed.count = feed_index;
+                    }
+                    else
+                    {
+                        // Free allocated memory if not stored
+                        if (username)
+                            free(username);
+                        if (message)
+                            free(message);
+                    }
+                }
+                break; // Feed processed
+            }
+        }
+
+        free(tokens); // Free the allocated tokens array
+    }
+    else
+    {
+        FURI_LOG_E(TAG, "No data received.");
+        return false;
+    }
+
+    return true;
+}
+
+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)
+{
+    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 < MAX_FEED_ITEMS)
+    {
+        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
+            }
+            // If no space is found, len remains MAX_LINE_LENGTH to force split
+        }
+
+        // Copy the substring to 'line' and null-terminate it
+        memcpy(line, user_message + start, len);
+        line[len] = '\0'; // Ensure the string is null-terminated
+
+        // Debug Logging: Print the current line being drawn
+        FURI_LOG_D(TAG, "Drawing line %d: \"%s\"", line_num + 1, line);
+
+        // Draw the string on the canvas
+        // Adjust the y-coordinate based on the line number
+        canvas_draw_str_aligned(canvas, 0, x + 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++;
+    }
+
+    // Handle any remaining text that wasn't processed due to exceeding MAX_FEED_ITEMS
+    if (start < msg_length)
+    {
+        FURI_LOG_E(TAG, "Message exceeds maximum number of lines (%d).", MAX_FEED_ITEMS);
+    }
+}
+
+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_pre_saved_message_clicked_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, 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[256];
+            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[256] = "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
+        // remove the message from app_instance->pre_saved_messages
+        app_instance->pre_saved_messages.messages[flip_social_pre_saved_message_clicked_index] = NULL;
+
+        for (uint32_t i = flip_social_pre_saved_message_clicked_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 == ActionBack || action == ActionNext)
+        {
+            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)
+{
+    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, 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");
+    }
+    if (!is_flipped)
+    {
+        canvas_draw_icon(canvas, 52, 53, &I_ButtonOK_7x7);
+        canvas_draw_str_aligned(canvas, 61, 54, AlignLeft, AlignTop, "Flip");
+    }
+    else
+    {
+        canvas_draw_icon(canvas, 47, 53, &I_ButtonOK_7x7);
+        canvas_draw_str_aligned(canvas, 56, 54, AlignLeft, AlignTop, "UnFlip");
+    }
+    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 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);
+        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);
+        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);
+        action = ActionNone;
+        break;
+    case ActionFlip:
+        canvas_clear(canvas);
+        flip_social_feed.is_flipped[flip_social_feed.index] = !flip_social_feed.is_flipped[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);
+        action = ActionNone;
+        // 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\":\"%lu\"}", 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);
+        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 (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 you board is connected,");
+        canvas_draw_str(canvas, 0, 42, "make sure you have flashed");
+        canvas_draw_str(canvas, 0, 52, "your Dev Board with the");
+        canvas_draw_str(canvas, 0, 62, "FlipperHTTP firmware.");
+        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/login/", "{\"Content-Type\":\"application/json\"}", buffer);
+        fhttp.state = RECEIVING;
+    }
+    // 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";
+
+                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 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, "Login 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) && 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
+            {
+                FURI_LOG_E(TAG, "Received an error: %s", fhttp.received_data);
+                canvas_draw_str(canvas, 0, 42, "Login failed...");
+                canvas_draw_str(canvas, 0, 52, "Update your credentials.");
+                canvas_draw_str(canvas, 0, 62, "Press BACK to return.");
+            }
+        }
+    }
+    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 (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 you board is connected,");
+        canvas_draw_str(canvas, 0, 42, "make sure you have flashed");
+        canvas_draw_str(canvas, 0, 52, "your Dev Board with the");
+        canvas_draw_str(canvas, 0, 62, "FlipperHTTP firmware.");
+        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[256];
+        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/register/", "{\"Content-Type\":\"application/json\"}", buffer);
+
+        flip_social_sent_register_request = true;
+        // Set the state to RECEIVING to ensure we continue to see the receiving message
+        fhttp.state = RECEIVING;
+    }
+    // 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;
+                }
+
+                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)
+        {
+            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, "Login failed...");
+                canvas_draw_str(canvas, 0, 52, "Update your credentials.");
+                canvas_draw_str(canvas, 0, 62, "Press BACK to return.");
+            }
+        }
+    }
+    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.");
+    }
+}
+
+/**
+ * @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 FlipSocialSubmenuLoggedInIndexFeed:
+        if (flip_social_get_feed()) // start the async feed 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 feed to be received
+            furi_delay_ms(100);
+        }
+        furi_timer_stop(fhttp.get_timeout_timer);
+
+        if (!flip_social_parse_json_feed()) // parse the JSON before switching to the feed (synchonous)
+        {
+            FURI_LOG_E(TAG, "Failed to parse the JSON feed. Using the temporary feed.");
+        }
+        view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedInFeed);
+        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:
+        uart_text_input_set_header_text(app->text_input_logged_in_compose_pre_save_input, "Enter your message:");
+        uart_text_input_set_result_callback(app->text_input_logged_in_compose_pre_save_input, flip_social_logged_in_compose_pre_save_updated, app, app->compose_pre_save_logged_in_temp_buffer, app->compose_pre_save_logged_in_temp_buffer_size, false);
+        view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedInComposeAddPreSaveInput);
+        break;
+    default:
+        // handle FlipSocialSubemnuComposeIndexStartIndex + i
+        // set temp variable to hold the index
+        flip_social_pre_saved_message_clicked_index = index - FlipSocialSubemnuComposeIndexStartIndex;
+        view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedInProcessCompose);
+        break;
+    }
+}
+
+/**
+ * @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_get_feed(); // start the feed request
+    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 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);
+
+    // 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 password
+    strncpy(app->wifi_password_logged_out, 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
+        // Initialize temp_buffer with the current name
+        if (app->wifi_ssid_logged_out && strlen(app->wifi_ssid_logged_out) > 0)
+        {
+            strncpy(app->wifi_ssid_logged_out_temp_buffer, app->wifi_ssid_logged_out, app->wifi_ssid_logged_out_temp_buffer_size - 1);
+        }
+        else
+        {
+            strncpy(app->wifi_ssid_logged_out_temp_buffer, "", app->wifi_ssid_logged_out_temp_buffer_size - 1);
+        }
+        uart_text_input_set_header_text(app->text_input_logged_out_wifi_settings_ssid, "Enter SSID");
+        uart_text_input_set_result_callback(app->text_input_logged_out_wifi_settings_ssid, flip_social_logged_out_wifi_settings_ssid_updated, app, app->wifi_ssid_logged_out_temp_buffer, app->wifi_ssid_logged_out_temp_buffer_size, false);
+        view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedOutWifiSettingsSSIDInput);
+        break;
+    case 1: // Input Password
+        // Initialize temp_buffer with the current password
+        if (app->wifi_password_logged_out && strlen(app->wifi_password_logged_out) > 0)
+        {
+            strncpy(app->wifi_password_logged_out_temp_buffer, app->wifi_password_logged_out, app->wifi_password_logged_out_temp_buffer_size - 1);
+        }
+        else
+        {
+            strncpy(app->wifi_password_logged_out_temp_buffer, "", app->wifi_password_logged_out_temp_buffer_size - 1);
+        }
+        uart_text_input_set_header_text(app->text_input_logged_out_wifi_settings_password, "Enter Password");
+        uart_text_input_set_result_callback(app->text_input_logged_out_wifi_settings_password, flip_social_logged_out_wifi_settings_password_updated, app, app->wifi_password_logged_out_temp_buffer, app->wifi_password_logged_out_temp_buffer_size, false);
+        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);
+
+    // 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);
+
+    // 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)
+    {
+        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
+        // Initialize temp_buffer with the current name
+        if (app->login_username_logged_out && strlen(app->login_username_logged_out) > 0)
+        {
+            strncpy(app->login_username_logged_out_temp_buffer, app->login_username_logged_out, app->login_username_logged_out_temp_buffer_size - 1);
+        }
+        else
+        {
+            strncpy(app->login_username_logged_out_temp_buffer, "", app->login_username_logged_out_temp_buffer_size - 1);
+        }
+        uart_text_input_set_header_text(app->text_input_logged_out_login_username, "Enter Username");
+        uart_text_input_set_result_callback(app->text_input_logged_out_login_username, flip_social_logged_out_login_username_updated, app, app->login_username_logged_out_temp_buffer, app->login_username_logged_out_temp_buffer_size, false);
+        view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedOutLoginUsernameInput);
+        break;
+    case 1: // Input Password
+        // Initialize temp_buffer with the current password
+        if (app->login_password_logged_out && strlen(app->login_password_logged_out) > 0)
+        {
+            strncpy(app->login_password_logged_out_temp_buffer, app->login_password_logged_out, app->login_password_logged_out_temp_buffer_size - 1);
+        }
+        else
+        {
+            strncpy(app->login_password_logged_out_temp_buffer, "", app->login_password_logged_out_temp_buffer_size - 1);
+        }
+        uart_text_input_set_header_text(app->text_input_logged_out_login_password, "Enter Password");
+        uart_text_input_set_result_callback(app->text_input_logged_out_login_password, flip_social_logged_out_login_password_updated, app, app->login_password_logged_out_temp_buffer, app->login_password_logged_out_temp_buffer_size, false);
+        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
+        // Initialize temp_buffer with the current name
+        if (app->register_username_logged_out && strlen(app->register_username_logged_out) > 0)
+        {
+            strncpy(app->register_username_logged_out_temp_buffer, app->register_username_logged_out, app->register_username_logged_out_temp_buffer_size - 1);
+        }
+        else
+        {
+            strncpy(app->register_username_logged_out_temp_buffer, "", app->register_username_logged_out_temp_buffer_size - 1);
+        }
+        uart_text_input_set_header_text(app->text_input_logged_out_register_username, "Enter Username");
+        uart_text_input_set_result_callback(app->text_input_logged_out_register_username, flip_social_logged_out_register_username_updated, app, app->register_username_logged_out_temp_buffer, app->register_username_logged_out_temp_buffer_size, false);
+        view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedOutRegisterUsernameInput);
+        break;
+    case 1: // Input Password
+        // Initialize temp_buffer with the current password
+        if (app->register_password_logged_out && strlen(app->register_password_logged_out) > 0)
+        {
+            strncpy(app->register_password_logged_out_temp_buffer, app->register_password_logged_out, app->register_password_logged_out_temp_buffer_size - 1);
+        }
+        else
+        {
+            strncpy(app->register_password_logged_out_temp_buffer, "", app->register_password_logged_out_temp_buffer_size - 1);
+        }
+        uart_text_input_set_header_text(app->text_input_logged_out_register_password, "Enter Password");
+        uart_text_input_set_result_callback(app->text_input_logged_out_register_password, flip_social_logged_out_register_password_updated, app, app->register_password_logged_out_temp_buffer, app->register_password_logged_out_temp_buffer_size, false);
+        view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedOutRegisterPasswordInput);
+        break;
+    case 2: // Input Password 2
+        // Initialize temp_buffer with the current password
+        if (app->register_password_2_logged_out && strlen(app->register_password_2_logged_out) > 0)
+        {
+            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);
+        }
+        else
+        {
+            strncpy(app->register_password_2_logged_out_temp_buffer, "", app->register_password_2_logged_out_temp_buffer_size - 1);
+        }
+        uart_text_input_set_header_text(app->text_input_logged_out_register_password_2, "Enter Password Again");
+        uart_text_input_set_result_callback(app->text_input_logged_out_register_password_2, flip_social_logged_out_register_password_2_updated, app, app->register_password_2_logged_out_temp_buffer, app->register_password_2_logged_out_temp_buffer_size, false);
+        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 name
+    strncpy(app->wifi_ssid_logged_in, app->wifi_ssid_logged_in_temp_buffer, 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);
+    }
+
+    // update the wifi settings
+    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");
+    }
+
+    // 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, 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);
+
+    // 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)
+    {
+        variable_item_set_current_value_text(app->variable_item_logged_in_wifi_settings_password, app->wifi_password_logged_in);
+    }
+
+    // update the wifi settings
+    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");
+    }
+
+    // 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, 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
+        // Initialize temp_buffer with the current name
+        if (app->wifi_ssid_logged_in && strlen(app->wifi_ssid_logged_in) > 0)
+        {
+            strncpy(app->wifi_ssid_logged_in_temp_buffer, app->wifi_ssid_logged_in, app->wifi_ssid_logged_in_temp_buffer_size - 1);
+        }
+        else
+        {
+            strncpy(app->wifi_ssid_logged_in_temp_buffer, "", app->wifi_ssid_logged_in_temp_buffer_size - 1);
+        }
+        uart_text_input_set_header_text(app->text_input_logged_in_wifi_settings_ssid, "Enter SSID");
+        uart_text_input_set_result_callback(app->text_input_logged_in_wifi_settings_ssid, flip_social_logged_in_wifi_settings_ssid_updated, app, app->wifi_ssid_logged_in_temp_buffer, app->wifi_ssid_logged_in_temp_buffer_size, false);
+        view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedInWifiSettingsSSIDInput);
+        break;
+    case 1: // Input Password
+        // Initialize temp_buffer with the current password
+        if (app->wifi_password_logged_in && strlen(app->wifi_password_logged_in) > 0)
+        {
+            strncpy(app->wifi_password_logged_in_temp_buffer, app->wifi_password_logged_in, app->wifi_password_logged_in_temp_buffer_size - 1);
+        }
+        else
+        {
+            strncpy(app->wifi_password_logged_in_temp_buffer, "", app->wifi_password_logged_in_temp_buffer_size - 1);
+        }
+        uart_text_input_set_header_text(app->text_input_logged_in_wifi_settings_password, "Enter Password");
+        uart_text_input_set_result_callback(app->text_input_logged_in_wifi_settings_password, flip_social_logged_in_wifi_settings_password_updated, app, app->wifi_password_logged_in_temp_buffer, app->wifi_password_logged_in_temp_buffer_size, false);
+        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);
+
+    // 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)
+    {
+        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);
+
+    if (!flipper_http_post_request_with_headers("https://www.flipsocial.net/api/change-password/", "Content-Type: application/json", 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");
+        return;
+    }
+
+    // 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
+        // Initialize temp_buffer with the current password
+        if (app->change_password_logged_in && strlen(app->change_password_logged_in) > 0)
+        {
+            strncpy(app->change_password_logged_in_temp_buffer, app->change_password_logged_in, app->change_password_logged_in_temp_buffer_size - 1);
+        }
+        else
+        {
+            strncpy(app->change_password_logged_in_temp_buffer, "", app->change_password_logged_in_temp_buffer_size - 1);
+        }
+        uart_text_input_set_header_text(app->text_input_logged_in_change_password, "Enter New Password");
+        uart_text_input_set_result_callback(app->text_input_logged_in_change_password, flip_social_logged_in_profile_change_password_updated, app, app->change_password_logged_in_temp_buffer, app->change_password_logged_in_temp_buffer_size, false);
+        view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedInChangePasswordInput);
+        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;
+    }
+}
+
+#endif // FLIP_SOCIAL_CALLBACK_H

+ 210 - 0
flip_social_e.h

@@ -0,0 +1,210 @@
+// flip_social_e.h
+#ifndef FLIP_SOCIAL_E
+#define FLIP_SOCIAL_E
+#include <furi.h>
+#include <furi_hal.h>
+#include <gui/gui.h>
+#include <gui/icon_i.h>
+#include <gui/view.h>
+#include <gui/view_dispatcher.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 <malloc.h>
+#include <dialogs/dialogs.h>
+#include <storage/storage.h>
+#include <flipper_http.h>
+#include <input/input.h>
+#include <flip_social_icons.h> // add <(YOUR-APP)_icons.h> so the compiler treats the .pngs are icons
+#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 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
+    FlipSocialSubmenuLoggedInIndexFeed,     // click to go to the feed screen
+    FlipSocialSubmenuLoggedInIndexCompose,  // click to go to the compose screen
+    FlipSocialSubmenuLoggedInIndexSettings, // click to go to the settings screen
+    FlipSocialSubmenuLoggedInSignOutButton, // click to sign out
+    //
+    FlipSocialSubmenuComposeIndexAddPreSave,      // click to add a pre-saved message
+    FlipSocialSubemnuComposeIndexStartIndex = 99, // starting index for the first pre saved message
+} FlipSocialSubmenuIndex;
+
+typedef enum
+{
+    ActionNone,
+    ActionBack,
+    ActionNext,
+    ActionPrev,
+    ActionFlip,
+} Action;
+
+Action action = ActionNone;
+
+// Define the ScriptPlaylist structure
+typedef struct
+{
+    char *messages[MAX_PRE_SAVED_MESSAGES];
+    size_t count;
+} PreSavedPlaylist;
+
+// 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
+    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
+} 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)
+    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)
+
+    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
+
+    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
+    //
+    FuriPubSub *input_event_queue;
+    FuriPubSubSubscription *input_event;
+
+    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
+
+    PreSavedPlaylist pre_saved_messages; // Pre-saved messages for the feed screen
+
+    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
+
+} FlipSocialApp;
+#endif

+ 242 - 0
flip_social_free.h

@@ -0,0 +1,242 @@
+// 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;
+    }
+
+    // Disconnect from WiFi
+    if (!flipper_http_disconnect_wifi())
+    {
+        FURI_LOG_E(TAG, "Failed to disconnect from WiFi");
+    }
+
+    // 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);
+    }
+
+    // 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);
+    }
+
+    // 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_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->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);
+
+    // DeInit UART
+    flipper_http_deinit();
+
+    // Free the app structure
+    if (app_instance)
+        free(app_instance);
+    if (app)
+        free(app);
+}
+
+#endif // FLIP_SOCIAL_FREE_H

+ 424 - 0
flip_social_i.h

@@ -0,0 +1,424 @@
+// 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));
+    if (!app)
+    {
+        // Allocation failed
+        FURI_LOG_E(TAG, "Failed to allocate FlipSocialApp");
+        return NULL;
+    }
+    memset(app, 0, sizeof(FlipSocialApp));
+
+    // Initialize gui
+    Gui *gui = furi_record_open(RECORD_GUI);
+    if (!gui)
+    {
+        // Failed to open GUI
+        FURI_LOG_E(TAG, "Failed to open GUI record");
+        return NULL;
+    }
+
+    // Initialize UART
+    if (!flipper_http_init(flipper_http_rx_callback, app))
+    {
+        FURI_LOG_E(TAG, "Failed to initialize UART");
+        return NULL;
+    }
+
+    // Allocate ViewDispatcher
+    app->view_dispatcher = view_dispatcher_alloc();
+    if (!app->view_dispatcher)
+    {
+        FURI_LOG_E(TAG, "Failed to allocate ViewDispatcher");
+        return NULL;
+    }
+    // Attach ViewDispatcher to GUI
+    view_dispatcher_attach_to_gui(app->view_dispatcher, gui, ViewDispatcherTypeFullscreen);
+    view_dispatcher_set_event_callback_context(app->view_dispatcher, app);
+
+    // initialize members
+    app->wifi_ssid_logged_out_temp_buffer_size = 128;
+    app->wifi_password_logged_out_temp_buffer_size = 128;
+    app->wifi_ssid_logged_out_temp_buffer = (char *)malloc(app->wifi_ssid_logged_out_temp_buffer_size);
+    app->wifi_password_logged_out_temp_buffer = (char *)malloc(app->wifi_password_logged_out_temp_buffer_size);
+    app->wifi_ssid_logged_out = (char *)malloc(app->wifi_ssid_logged_out_temp_buffer_size);
+    app->wifi_password_logged_out = (char *)malloc(app->wifi_password_logged_out_temp_buffer_size);
+
+    app->login_username_logged_out_temp_buffer_size = 128;
+    app->login_password_logged_out_temp_buffer_size = 128;
+    app->login_username_logged_out_temp_buffer = (char *)malloc(app->login_username_logged_out_temp_buffer_size);
+    app->login_password_logged_out_temp_buffer = (char *)malloc(app->login_password_logged_out_temp_buffer_size);
+    app->login_username_logged_out = (char *)malloc(app->login_username_logged_out_temp_buffer_size);
+    app->login_password_logged_out = (char *)malloc(app->login_password_logged_out_temp_buffer_size);
+
+    app->register_username_logged_out_temp_buffer_size = 128;
+    app->register_password_logged_out_temp_buffer_size = 128;
+    app->register_password_2_logged_out_temp_buffer_size = 128;
+    app->register_username_logged_out_temp_buffer = (char *)malloc(app->register_username_logged_out_temp_buffer_size);
+    app->register_password_logged_out_temp_buffer = (char *)malloc(app->register_password_logged_out_temp_buffer_size);
+    app->register_password_2_logged_out_temp_buffer = (char *)malloc(app->register_password_2_logged_out_temp_buffer_size);
+    app->register_username_logged_out = (char *)malloc(app->register_username_logged_out_temp_buffer_size);
+    app->register_password_logged_out = (char *)malloc(app->register_password_logged_out_temp_buffer_size);
+    app->register_password_2_logged_out = (char *)malloc(app->register_password_2_logged_out_temp_buffer_size);
+
+    app->change_password_logged_in_temp_buffer_size = 128;
+    app->compose_pre_save_logged_in_temp_buffer_size = 128;
+    app->wifi_ssid_logged_in_temp_buffer_size = 128;
+    app->wifi_password_logged_in_temp_buffer_size = 128;
+    app->is_logged_in_size = 128;
+    app->login_username_logged_in_temp_buffer_size = 128;
+
+    app->change_password_logged_in_temp_buffer = (char *)malloc(app->change_password_logged_in_temp_buffer_size);
+    app->compose_pre_save_logged_in_temp_buffer = (char *)malloc(app->compose_pre_save_logged_in_temp_buffer_size);
+    app->wifi_ssid_logged_in_temp_buffer = (char *)malloc(app->wifi_ssid_logged_in_temp_buffer_size);
+    app->wifi_password_logged_in_temp_buffer = (char *)malloc(app->wifi_password_logged_in_temp_buffer_size);
+    app->change_password_logged_in = (char *)malloc(app->change_password_logged_in_temp_buffer_size);
+    app->compose_pre_save_logged_in = (char *)malloc(app->compose_pre_save_logged_in_temp_buffer_size);
+    app->wifi_ssid_logged_in = (char *)malloc(app->wifi_ssid_logged_in_temp_buffer_size);
+    app->wifi_password_logged_in = (char *)malloc(app->wifi_password_logged_in_temp_buffer_size);
+    app->is_logged_in = (char *)malloc(app->is_logged_in_size);
+    app->login_username_logged_in = (char *)malloc(app->login_username_logged_in_temp_buffer_size);
+
+    if (!app->wifi_ssid_logged_out || !app->wifi_password_logged_out ||
+        !app->login_username_logged_out || !app->login_password_logged_out ||
+        !app->register_username_logged_out || !app->register_password_logged_out ||
+        !app->register_password_2_logged_out || !app->change_password_logged_in || !app->compose_pre_save_logged_in || !app->wifi_ssid_logged_in || !app->wifi_password_logged_in ||
+        !app->change_password_logged_in || !app->compose_pre_save_logged_in || !app->wifi_ssid_logged_in || !app->wifi_password_logged_in ||
+        !app->is_logged_in || !app->login_username_logged_in)
+
+    {
+        // Allocation failed
+        FURI_LOG_E(TAG, "Failed to allocate buffers");
+        return NULL;
+    }
+
+    // Initialize buffers with empty strings
+    app->wifi_ssid_logged_out_temp_buffer[0] = '\0';
+    app->wifi_password_logged_out_temp_buffer[0] = '\0';
+    app->login_username_logged_out_temp_buffer[0] = '\0';
+    app->login_password_logged_out_temp_buffer[0] = '\0';
+    app->register_username_logged_out_temp_buffer[0] = '\0';
+    app->register_password_logged_out_temp_buffer[0] = '\0';
+    app->register_password_2_logged_out_temp_buffer[0] = '\0';
+    app->change_password_logged_in_temp_buffer[0] = '\0';
+    app->compose_pre_save_logged_in_temp_buffer[0] = '\0';
+    app->wifi_ssid_logged_in_temp_buffer[0] = '\0';
+    app->wifi_password_logged_in_temp_buffer[0] = '\0';
+    app->change_password_logged_in[0] = '\0';
+    app->compose_pre_save_logged_in[0] = '\0';
+    app->wifi_ssid_logged_in[0] = '\0';
+    app->wifi_password_logged_in[0] = '\0';
+    app->is_logged_in[0] = '\0';
+    app->login_username_logged_in[0] = '\0';
+
+    // Setup Submenu(s)
+    app->submenu_logged_out = submenu_alloc();
+    app->submenu_logged_in = submenu_alloc();
+    app->submenu_compose = submenu_alloc();
+    if (!app->submenu_logged_out || !app->submenu_logged_in || !app->submenu_compose)
+    {
+        FURI_LOG_E(TAG, "Failed to allocate Submenus");
+        return NULL;
+    }
+    submenu_set_header(app->submenu_logged_out, "FlipSocial");
+    submenu_set_header(app->submenu_logged_in, "FlipSocial");
+    submenu_set_header(app->submenu_compose, "Create A Post");
+    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, "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, "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);
+
+    view_set_previous_callback(submenu_get_view(app->submenu_logged_out), flip_social_callback_exit_app);
+    view_set_previous_callback(submenu_get_view(app->submenu_logged_in), flip_social_callback_exit_app); // exit the app isntead of returning to the logged out screen
+    view_set_previous_callback(submenu_get_view(app->submenu_compose), flip_social_callback_to_submenu_logged_in);
+    view_dispatcher_add_view(app->view_dispatcher, FlipSocialViewLoggedOutSubmenu, submenu_get_view(app->submenu_logged_out));
+    view_dispatcher_add_view(app->view_dispatcher, FlipSocialViewLoggedInSubmenu, submenu_get_view(app->submenu_logged_in));
+    view_dispatcher_add_view(app->view_dispatcher, FlipSocialViewLoggedInCompose, submenu_get_view(app->submenu_compose));
+
+    app->view_process_login = view_alloc();
+    app->view_process_register = view_alloc();
+    app->view_process_feed = view_alloc();
+    app->view_process_compose = view_alloc();
+    if (!app->view_process_login || !app->view_process_register || !app->view_process_feed || !app->view_process_compose)
+    {
+        FURI_LOG_E(TAG, "Failed to allocate View");
+        return NULL;
+    }
+    view_set_draw_callback(app->view_process_login, flip_social_callback_draw_login);
+    view_set_draw_callback(app->view_process_register, flip_social_callback_draw_register);
+    view_set_draw_callback(app->view_process_feed, flip_social_callback_draw_feed);
+    view_set_draw_callback(app->view_process_compose, flip_social_callback_draw_compose);
+    view_set_context(app->view_process_login, app);
+    view_set_context(app->view_process_register, app);
+    view_set_context(app->view_process_feed, app);
+    view_set_context(app->view_process_compose, app);
+    view_set_previous_callback(app->view_process_login, flip_social_callback_to_login_logged_out);
+    view_set_previous_callback(app->view_process_register, flip_social_callback_to_register_logged_out);
+    view_set_previous_callback(app->view_process_feed, flip_social_callback_to_submenu_logged_in);
+    view_set_previous_callback(app->view_process_compose, flip_social_callback_to_compose_logged_in);
+    view_dispatcher_add_view(app->view_dispatcher, FlipSocialViewLoggedOutProcessLogin, app->view_process_login);
+    view_dispatcher_add_view(app->view_dispatcher, FlipSocialViewLoggedOutProcessRegister, app->view_process_register);
+    view_dispatcher_add_view(app->view_dispatcher, FlipSocialViewLoggedInFeed, app->view_process_feed);
+    view_dispatcher_add_view(app->view_dispatcher, FlipSocialViewLoggedInProcessCompose, app->view_process_compose);
+
+    // Setup Variable Item List(s)
+    app->variable_item_list_logged_out_wifi_settings = variable_item_list_alloc();
+    app->variable_item_list_logged_out_login = variable_item_list_alloc();
+    app->variable_item_list_logged_out_register = variable_item_list_alloc();
+    app->variable_item_list_logged_in_profile = variable_item_list_alloc();
+    app->variable_item_list_logged_in_settings = variable_item_list_alloc();
+    app->variable_item_list_logged_in_settings_wifi = variable_item_list_alloc();
+    if (!app->variable_item_list_logged_out_wifi_settings || !app->variable_item_list_logged_out_login ||
+        !app->variable_item_list_logged_out_register || !app->variable_item_list_logged_in_profile ||
+        !app->variable_item_list_logged_in_settings || !app->variable_item_list_logged_in_settings_wifi)
+    {
+        FURI_LOG_E(TAG, "Failed to allocate VariableItemList");
+        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_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);
+
+    variable_item_list_set_enter_callback(app->variable_item_list_logged_out_wifi_settings, flip_social_text_input_logged_out_wifi_settings_item_selected, app);
+    view_set_previous_callback(variable_item_list_get_view(app->variable_item_list_logged_out_wifi_settings), flip_social_callback_to_submenu_logged_out);
+    view_dispatcher_add_view(app->view_dispatcher, FlipSocialViewLoggedOutWifiSettings, variable_item_list_get_view(app->variable_item_list_logged_out_wifi_settings));
+    //
+    variable_item_list_set_enter_callback(app->variable_item_list_logged_out_login, flip_social_text_input_logged_out_login_item_selected, app);
+    view_set_previous_callback(variable_item_list_get_view(app->variable_item_list_logged_out_login), flip_social_callback_to_submenu_logged_out);
+    view_dispatcher_add_view(app->view_dispatcher, FlipSocialViewLoggedOutLogin, variable_item_list_get_view(app->variable_item_list_logged_out_login));
+    //
+    variable_item_list_set_enter_callback(app->variable_item_list_logged_out_register, flip_social_text_input_logged_out_register_item_selected, app);
+    view_set_previous_callback(variable_item_list_get_view(app->variable_item_list_logged_out_register), flip_social_callback_to_submenu_logged_out);
+    view_dispatcher_add_view(app->view_dispatcher, FlipSocialViewLoggedOutRegister, variable_item_list_get_view(app->variable_item_list_logged_out_register));
+    //
+    variable_item_list_set_enter_callback(app->variable_item_list_logged_in_profile, flip_social_text_input_logged_in_profile_item_selected, app);
+    view_set_previous_callback(variable_item_list_get_view(app->variable_item_list_logged_in_profile), flip_social_callback_to_submenu_logged_in);
+    view_dispatcher_add_view(app->view_dispatcher, FlipSocialViewLoggedInProfile, variable_item_list_get_view(app->variable_item_list_logged_in_profile));
+    //
+    variable_item_list_set_enter_callback(app->variable_item_list_logged_in_settings, flip_social_text_input_logged_in_settings_item_selected, app);
+    view_set_previous_callback(variable_item_list_get_view(app->variable_item_list_logged_in_settings), flip_social_callback_to_submenu_logged_in);
+    view_dispatcher_add_view(app->view_dispatcher, FlipSocialViewLoggedInSettings, variable_item_list_get_view(app->variable_item_list_logged_in_settings));
+    //
+    variable_item_list_set_enter_callback(app->variable_item_list_logged_in_settings_wifi, flip_social_text_input_logged_in_wifi_settings_item_selected, app);
+    view_set_previous_callback(variable_item_list_get_view(app->variable_item_list_logged_in_settings_wifi), flip_social_callback_to_settings_logged_in);
+    view_dispatcher_add_view(app->view_dispatcher, FlipSocialViewLoggedInSettingsWifi, variable_item_list_get_view(app->variable_item_list_logged_in_settings_wifi));
+
+    // Setup Text Input(s)
+    app->text_input_logged_out_wifi_settings_ssid = uart_text_input_alloc();
+    app->text_input_logged_out_wifi_settings_password = uart_text_input_alloc();
+    app->text_input_logged_out_login_username = uart_text_input_alloc();
+    app->text_input_logged_out_login_password = uart_text_input_alloc();
+    app->text_input_logged_out_register_username = uart_text_input_alloc();
+    app->text_input_logged_out_register_password = uart_text_input_alloc();
+    app->text_input_logged_out_register_password_2 = uart_text_input_alloc();
+    app->text_input_logged_in_change_password = uart_text_input_alloc();
+    app->text_input_logged_in_compose_pre_save_input = uart_text_input_alloc();
+    app->text_input_logged_in_wifi_settings_ssid = uart_text_input_alloc();
+    app->text_input_logged_in_wifi_settings_password = uart_text_input_alloc();
+    if (!app->text_input_logged_out_wifi_settings_ssid || !app->text_input_logged_out_wifi_settings_password || !app->text_input_logged_out_login_username ||
+        !app->text_input_logged_out_login_password || !app->text_input_logged_out_register_username || !app->text_input_logged_out_register_password ||
+        !app->text_input_logged_out_register_password_2 || !app->text_input_logged_in_change_password ||
+        !app->text_input_logged_in_compose_pre_save_input || !app->text_input_logged_in_wifi_settings_ssid ||
+        !app->text_input_logged_in_wifi_settings_password)
+    {
+        FURI_LOG_E(TAG, "Failed to allocate Text Input");
+        return NULL;
+    }
+    view_dispatcher_add_view(app->view_dispatcher, FlipSocialViewLoggedOutWifiSettingsSSIDInput, uart_text_input_get_view(app->text_input_logged_out_wifi_settings_ssid));
+    view_dispatcher_add_view(app->view_dispatcher, FlipSocialViewLoggedOutWifiSettingsPasswordInput, uart_text_input_get_view(app->text_input_logged_out_wifi_settings_password));
+    view_dispatcher_add_view(app->view_dispatcher, FlipSocialViewLoggedOutLoginUsernameInput, uart_text_input_get_view(app->text_input_logged_out_login_username));
+    view_dispatcher_add_view(app->view_dispatcher, FlipSocialViewLoggedOutLoginPasswordInput, uart_text_input_get_view(app->text_input_logged_out_login_password));
+    view_dispatcher_add_view(app->view_dispatcher, FlipSocialViewLoggedOutRegisterUsernameInput, uart_text_input_get_view(app->text_input_logged_out_register_username));
+    view_dispatcher_add_view(app->view_dispatcher, FlipSocialViewLoggedOutRegisterPasswordInput, uart_text_input_get_view(app->text_input_logged_out_register_password));
+    view_dispatcher_add_view(app->view_dispatcher, FlipSocialViewLoggedOutRegisterPassword2Input, uart_text_input_get_view(app->text_input_logged_out_register_password_2));
+    //
+    view_dispatcher_add_view(app->view_dispatcher, FlipSocialViewLoggedInChangePasswordInput, uart_text_input_get_view(app->text_input_logged_in_change_password));
+    view_dispatcher_add_view(app->view_dispatcher, FlipSocialViewLoggedInComposeAddPreSaveInput, uart_text_input_get_view(app->text_input_logged_in_compose_pre_save_input));
+    view_dispatcher_add_view(app->view_dispatcher, FlipSocialViewLoggedInWifiSettingsSSIDInput, uart_text_input_get_view(app->text_input_logged_in_wifi_settings_ssid));
+    view_dispatcher_add_view(app->view_dispatcher, FlipSocialViewLoggedInWifiSettingsPasswordInput, uart_text_input_get_view(app->text_input_logged_in_wifi_settings_password));
+
+    view_set_previous_callback(uart_text_input_get_view(app->text_input_logged_out_wifi_settings_ssid), flip_social_callback_to_wifi_settings_logged_out);
+    view_set_previous_callback(uart_text_input_get_view(app->text_input_logged_out_wifi_settings_password), flip_social_callback_to_wifi_settings_logged_out);
+    view_set_previous_callback(uart_text_input_get_view(app->text_input_logged_out_login_username), flip_social_callback_to_login_logged_out);
+    view_set_previous_callback(uart_text_input_get_view(app->text_input_logged_out_login_password), flip_social_callback_to_login_logged_out);
+    view_set_previous_callback(uart_text_input_get_view(app->text_input_logged_out_register_username), flip_social_callback_to_register_logged_out);
+    view_set_previous_callback(uart_text_input_get_view(app->text_input_logged_out_register_password), flip_social_callback_to_register_logged_out);
+    view_set_previous_callback(uart_text_input_get_view(app->text_input_logged_out_register_password_2), flip_social_callback_to_register_logged_out);
+    //
+    view_set_previous_callback(uart_text_input_get_view(app->text_input_logged_in_change_password), flip_social_callback_to_profile_logged_in);
+    view_set_previous_callback(uart_text_input_get_view(app->text_input_logged_in_compose_pre_save_input), flip_social_callback_to_compose_logged_in);
+    view_set_previous_callback(uart_text_input_get_view(app->text_input_logged_in_wifi_settings_ssid), flip_social_callback_to_wifi_settings_logged_in);
+    view_set_previous_callback(uart_text_input_get_view(app->text_input_logged_in_wifi_settings_password), flip_social_callback_to_wifi_settings_logged_in);
+
+    // Setup About(s)
+    app->widget_logged_out_about = widget_alloc();
+    app->widget_logged_in_about = widget_alloc();
+    if (!app->widget_logged_out_about || !app->widget_logged_in_about)
+    {
+        FURI_LOG_E(TAG, "Failed to allocate Widget");
+        return NULL;
+    }
+    widget_add_text_scroll_element(
+        app->widget_logged_out_about,
+        0,
+        0,
+        128,
+        64,
+        "Welcome to FlipSocial\n---\nThe social media app for\nFlipper Zero, created by\nJBlanked.\n---\nPress BACK to return.");
+    widget_add_text_scroll_element(
+        app->widget_logged_in_about,
+        0,
+        0,
+        128,
+        64,
+        "Welcome to FlipSocial\n---\nThe social media app for\nFlipper Zero, created by\nJBlanked.\n---\nPress BACK to return.");
+    view_set_previous_callback(widget_get_view(app->widget_logged_in_about), flip_social_callback_to_settings_logged_in);
+    view_set_previous_callback(widget_get_view(app->widget_logged_out_about), flip_social_callback_to_submenu_logged_out);
+    view_dispatcher_add_view(app->view_dispatcher, FlipSocialViewLoggedInSettingsAbout, widget_get_view(app->widget_logged_in_about));
+    view_dispatcher_add_view(app->view_dispatcher, FlipSocialViewLoggedOutAbout, widget_get_view(app->widget_logged_out_about));
+
+    // load the playlist
+    if (load_playlist(&app->pre_saved_messages))
+    {
+        // Update the 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 = "false";
+        }
+        app_instance = app;
+        view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedOutSubmenu);
+    }
+    else
+    {
+        // set variable item text
+        // Update variable items
+        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_in_wifi_settings_password, app->wifi_password_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_wifi_settings_password, app->wifi_password_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_out_login_password, app->login_password_logged_out);
+        variable_item_set_current_value_text(app->variable_item_logged_in_profile_username, app->login_username_logged_in);
+        variable_item_set_current_value_text(app->variable_item_logged_in_profile_change_password, app->change_password_logged_in);
+
+        // Copy items into their temp buffers with safety checks
+        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_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->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->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);
+            app->login_username_logged_in[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);
+            app->change_password_logged_in[app->login_password_logged_out_temp_buffer_size - 1] = '\0';
+        }
+
+        app_instance = app;
+        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

+ 364 - 0
flip_social_storage.h

@@ -0,0 +1,364 @@
+// 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[256];
+    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);
+        }
+    }
+
+    FURI_LOG_I(TAG, "Playlist saved: playlist_count=%zu", playlist->count);
+
+    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");
+        }
+    }
+
+    FURI_LOG_I(TAG, "Playlist loaded: playlist_count=%zu", playlist->count);
+
+    // 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[256];
+    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;
+    }
+    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;
+    }
+    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;
+    }
+    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;
+    }
+    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;
+    }
+    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;
+    }
+    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;
+    }
+    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

+ 1138 - 0
flipper_http.h

@@ -0,0 +1,1138 @@
+// 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 (2 * 1000) // 2 seconds
+#define BAUDRATE (115200)                 // UART baudrate
+#define RX_BUF_SIZE 1024                  // UART RX buffer size
+
+// 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_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);
+
+// Define GPIO pins for UART
+GpioPin test_pins[2] = {
+    {.port = GPIOA, .pin = LL_GPIO_PIN_7}, // USART1_RX
+    {.port = GPIOA, .pin = LL_GPIO_PIN_6}  // USART1_TX
+};
+
+// 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[256]; // Buffer to collect a line
+
+    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 * 10, 0);
+            for (size_t i = 0; i < len; i++)
+            {
+                char c = fhttp.rx_buf[i];
+                if (c == '\n' || rx_line_pos >= sizeof(rx_line_buffer) - 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;
+                }
+            }
+        }
+    }
+
+    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);
+
+    // Initialize GPIO pins for UART
+    furi_hal_gpio_init_simple(&test_pins[0], GpioModeInput);
+    furi_hal_gpio_init_simple(&test_pins[1], GpioModeOutputPushPull);
+
+    // 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 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.
+ * @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 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;
+}
+
+#endif // FLIPPER_HTTP_H

+ 471 - 0
jsmn.h

@@ -0,0 +1,471 @@
+/*
+ * 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 */

+ 803 - 0
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