ソースを参照

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

# Conflicts:
#	flip_social/flip_social.c
#	flip_social/flip_social.h
Willy-JL 1 年間 前
コミット
b4b358a181

+ 7 - 71
flip_social/README.md

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

+ 894 - 0
flip_social/alloc/alloc.c

@@ -0,0 +1,894 @@
+#include <alloc/alloc.h>
+bool went_to_friends = false;
+void auth_headers_alloc(void)
+{
+    if (!app_instance)
+    {
+        snprintf(auth_headers, sizeof(auth_headers), "{\"Content-Type\":\"application/json\"}");
+        return;
+    }
+
+    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)
+    {
+        snprintf(auth_headers, sizeof(auth_headers), "{\"Content-Type\":\"application/json\",\"username\":\"%s\",\"password\":\"%s\"}", app_instance->login_username_logged_out, app_instance->login_password_logged_out);
+    }
+    else if (app_instance->login_username_logged_in && app_instance->change_password_logged_in && strlen(app_instance->login_username_logged_in) > 0 && strlen(app_instance->change_password_logged_in) > 0)
+    {
+        snprintf(auth_headers, sizeof(auth_headers), "{\"Content-Type\":\"application/json\",\"username\":\"%s\",\"password\":\"%s\"}", app_instance->login_username_logged_in, app_instance->change_password_logged_in);
+    }
+    else
+    {
+        snprintf(auth_headers, sizeof(auth_headers), "{\"Content-Type\":\"application/json\"}");
+    }
+}
+
+FlipSocialFeedMini *flip_feed_info_alloc(void)
+{
+    FlipSocialFeedMini *feed_info = (FlipSocialFeedMini *)malloc(sizeof(FlipSocialFeedMini));
+    if (!feed_info)
+    {
+        FURI_LOG_E(TAG, "Failed to allocate memory for feed_info");
+        return NULL;
+    }
+    feed_info->count = 0;
+    feed_info->index = 0;
+    return feed_info;
+}
+bool messages_dialog_alloc(bool free_first)
+{
+    if (free_first)
+    {
+        flip_social_free_messages_dialog();
+    }
+    if (!app_instance->dialog_messages)
+    {
+        if (!easy_flipper_set_dialog_ex(
+                &app_instance->dialog_messages,
+                FlipSocialViewMessagesDialog,
+                flip_social_messages->usernames[flip_social_messages->index],
+                0,
+                0,
+                updated_user_message(flip_social_messages->messages[flip_social_messages->index]),
+                0,
+                10,
+                flip_social_messages->index != 0 ? "Prev" : NULL,
+                flip_social_messages->index != flip_social_messages->count - 1 ? "Next" : NULL,
+                "Create",
+                messages_dialog_callback,
+                flip_social_callback_to_messages_logged_in,
+                &app_instance->view_dispatcher,
+                app_instance))
+        {
+            return false;
+        }
+        return true;
+    }
+    return false;
+}
+char *updated_user_message(const char *user_message)
+{
+    if (user_message == NULL)
+    {
+        FURI_LOG_E(TAG, "User message is NULL.");
+        return NULL;
+    }
+
+    size_t msg_length = strlen(user_message);
+    size_t start = 0;
+    int line_num = 0;
+
+    // Allocate memory for the updated message
+    char *updated_message = malloc(MAX_MESSAGE_LENGTH + 10);
+    if (updated_message == NULL)
+    {
+        FURI_LOG_E(TAG, "Failed to allocate memory for updated_message.");
+        return NULL;
+    }
+    size_t current_pos = 0;    // Tracks the current position in updated_message
+    updated_message[0] = '\0'; // Initialize as empty string
+
+    while (start < msg_length && line_num < 4)
+    {
+        size_t remaining = msg_length - start;
+        size_t len = (remaining > MAX_LINE_LENGTH) ? MAX_LINE_LENGTH : remaining;
+
+        // Adjust length to the last space if the line exceeds MAX_LINE_LENGTH
+        if (remaining > MAX_LINE_LENGTH)
+        {
+            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
+            }
+        }
+
+        // Check if the new line fits in the updated_message buffer
+        if (current_pos + len + 1 >= (MAX_MESSAGE_LENGTH + 10))
+        {
+            FURI_LOG_E(TAG, "Updated message exceeds maximum length.");
+            // break and return what we have so far
+            break;
+        }
+
+        // Copy the line and append a newline character
+        memcpy(updated_message + current_pos, user_message + start, len);
+        current_pos += len;
+        updated_message[current_pos++] = '\n'; // Append newline
+
+        // 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++;
+    }
+
+    // Null-terminate the final string
+    if (current_pos < (MAX_MESSAGE_LENGTH + 10))
+    {
+        updated_message[current_pos] = '\0';
+    }
+    else
+    {
+        FURI_LOG_E(TAG, "Buffer overflow while null-terminating.");
+        free(updated_message);
+        return NULL;
+    }
+
+    return updated_message;
+}
+
+typedef enum
+{
+    ActionNone,
+    ActionBack,
+    ActionNext,
+    ActionPrev,
+    ActionFlip,
+} Action;
+
+static Action action = ActionNone;
+
+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;
+    }
+}
+
+// Make sure to define a suitable MAX_LINE_LENGTH
+// For example:
+
+#define MAX_LINES 6
+#define LINE_HEIGHT 8
+#define MAX_LINE_WIDTH_PX 128
+#define TEMP_BUF_SIZE 128
+
+static void draw_user_message(Canvas *canvas, const char *user_message, int x, int y)
+{
+    if (!user_message)
+    {
+        FURI_LOG_E(TAG, "User message is NULL.");
+        return;
+    }
+
+    // We will read through user_message and extract words manually
+    const char *p = user_message;
+
+    // Skip leading spaces
+    while (*p == ' ')
+        p++;
+
+    char line[TEMP_BUF_SIZE];
+    size_t line_len = 0;
+    line[0] = '\0';
+    int line_num = 0;
+
+    while (*p && line_num < MAX_LINES)
+    {
+        // Find the end of the next word
+        const char *word_start = p;
+        while (*p && *p != ' ')
+            p++;
+        size_t word_len = p - word_start;
+
+        // Extract the word into a temporary buffer
+        char word[TEMP_BUF_SIZE];
+        if (word_len > TEMP_BUF_SIZE - 1)
+        {
+            word_len = TEMP_BUF_SIZE - 1; // Just to avoid overflow if extremely large
+        }
+        memcpy(word, word_start, word_len);
+        word[word_len] = '\0';
+
+        // Skip trailing spaces for the next iteration
+        while (*p == ' ')
+            p++;
+
+        if (word_len == 0)
+        {
+            // Empty word (consecutive spaces?), just continue
+            continue;
+        }
+
+        // Check how the word fits into the current line
+        char test_line[TEMP_BUF_SIZE + 128];
+        if (line_len == 0)
+        {
+            // If line is empty, the line would just be this word
+            strncpy(test_line, word, sizeof(test_line) - 1);
+            test_line[sizeof(test_line) - 1] = '\0';
+        }
+        else
+        {
+            // If not empty, we add a space and then the word
+            snprintf(test_line, sizeof(test_line), "%s %s", line, word);
+        }
+
+        uint16_t width = canvas_string_width(canvas, test_line);
+        if (width <= MAX_LINE_WIDTH_PX)
+        {
+            // The word fits on this line
+            strcpy(line, test_line);
+            line_len = strlen(line);
+        }
+        else
+        {
+            // The word doesn't fit on this line
+            // First, draw the current line if it's not empty
+            if (line_len > 0)
+            {
+                canvas_draw_str_aligned(canvas, x, y + line_num * LINE_HEIGHT, AlignLeft, AlignTop, line);
+                line_num++;
+                if (line_num >= MAX_LINES)
+                    break;
+            }
+
+            // Now we try to put the current word on a new line
+            // Check if the word itself fits on an empty line
+            width = canvas_string_width(canvas, word);
+            if (width <= MAX_LINE_WIDTH_PX)
+            {
+                // The whole word fits on a new line
+                strcpy(line, word);
+                line_len = word_len;
+            }
+            else
+            {
+                // The word alone doesn't fit. We must truncate it.
+                // We'll find the largest substring of the word that fits.
+                size_t truncate_len = word_len;
+                while (truncate_len > 0)
+                {
+                    char truncated[TEMP_BUF_SIZE];
+                    strncpy(truncated, word, truncate_len);
+                    truncated[truncate_len] = '\0';
+                    if (canvas_string_width(canvas, truncated) <= MAX_LINE_WIDTH_PX)
+                    {
+                        // Found a substring that fits
+                        strcpy(line, truncated);
+                        line_len = truncate_len;
+                        break;
+                    }
+                    truncate_len--;
+                }
+
+                if (line_len == 0)
+                {
+                    // Could not fit a single character. Skip this word.
+                }
+            }
+        }
+    }
+
+    // Draw any remaining text in the buffer if we have lines left
+    if (line_len > 0 && line_num < MAX_LINES)
+    {
+        canvas_draw_str_aligned(canvas, x, y + line_num * LINE_HEIGHT, AlignLeft, AlignTop, line);
+    }
+}
+
+static void flip_social_feed_draw_callback(Canvas *canvas, void *model)
+{
+    UNUSED(model);
+    canvas_clear(canvas);
+    canvas_set_font_custom(canvas, FONT_SIZE_LARGE);
+    canvas_draw_str(canvas, 0, 7, flip_feed_item->username);
+    canvas_set_font_custom(canvas, FONT_SIZE_MEDIUM);
+    draw_user_message(canvas, flip_feed_item->message, 0, 12);
+    canvas_set_font_custom(canvas, FONT_SIZE_SMALL);
+    char flip_message[32];
+    snprintf(flip_message, sizeof(flip_message), "%u %s", flip_feed_item->flips, flip_feed_item->flips == 1 ? "flip" : "flips");
+    canvas_draw_str(canvas, 0, 60, flip_message); // Draw the number of flips
+    char flip_status[16];
+    snprintf(flip_status, sizeof(flip_status), flip_feed_item->is_flipped ? "Unflip" : "Flip");
+    canvas_draw_str(canvas, 32, 60, flip_status);                  // Draw the flip status
+    canvas_draw_str(canvas, 64, 60, flip_feed_item->date_created); // Draw the date
+}
+
+static bool flip_social_feed_input_callback(InputEvent *event, void *context)
+{
+    UNUSED(context);
+    furi_assert(app_instance);
+
+    // if back button is pressed
+    if (event->type == InputTypePress && event->key == InputKeyBack)
+    {
+        view_dispatcher_switch_to_view(app_instance->view_dispatcher, FlipSocialViewLoggedInSubmenu);
+        return true;
+    }
+
+    if (event->type == InputTypePress && event->key == InputKeyLeft) // Previous message
+    {
+        if (flip_feed_info->index > 0)
+        {
+            flip_feed_info->index--;
+        }
+        // switch view, free dialog, re-alloc dialog, switch back to dialog
+        view_dispatcher_switch_to_view(app_instance->view_dispatcher, FlipSocialViewWidgetResult);
+        flip_social_free_feed_view();
+        // load feed item
+        if (!flip_social_load_feed_post(flip_feed_info->ids[flip_feed_info->index]))
+        {
+            FURI_LOG_E(TAG, "Failed to load nexy feed post");
+            fhttp.state = ISSUE;
+            return false;
+        }
+        if (feed_view_alloc())
+        {
+            view_dispatcher_switch_to_view(app_instance->view_dispatcher, FlipSocialViewLoggedInFeed);
+        }
+        else
+        {
+            FURI_LOG_E(TAG, "Failed to allocate feed dialog");
+            fhttp.state = ISSUE;
+            return false;
+        }
+    }
+    else if (event->type == InputTypePress && event->key == InputKeyRight) // Next message
+    {
+        // if next message is the last message, then use flip_social_load_initial_feed
+        if (flip_feed_info->index == flip_feed_info->count - 1)
+        {
+            char series_index[16];
+            load_char("series_index", series_index, sizeof(series_index));
+            flip_feed_info->series_index = atoi(series_index) + 1;
+            char new_series_index[16];
+            snprintf(new_series_index, sizeof(new_series_index), "%d", flip_feed_info->series_index);
+
+            save_char("series_index", new_series_index);
+
+            if (!flip_social_load_initial_feed(true, flip_feed_info->series_index))
+            {
+                FURI_LOG_E(TAG, "Failed to load initial feed");
+                fhttp.state = ISSUE;
+                return false;
+            }
+            // switch view, free dialog, re-alloc dialog, switch back to dialog
+            view_dispatcher_switch_to_view(app_instance->view_dispatcher, FlipSocialViewWidgetResult);
+            flip_social_free_feed_view();
+            // load feed item
+            if (!flip_social_load_feed_post(flip_feed_info->ids[flip_feed_info->index]))
+            {
+                FURI_LOG_E(TAG, "Failed to load nexy feed post");
+                fhttp.state = ISSUE;
+                return false;
+            }
+            if (feed_view_alloc())
+            {
+                view_dispatcher_switch_to_view(app_instance->view_dispatcher, FlipSocialViewLoggedInFeed);
+            }
+            else
+            {
+                FURI_LOG_E(TAG, "Failed to allocate feed dialog");
+                fhttp.state = ISSUE;
+                return false;
+            }
+        }
+        if (flip_feed_info->index < flip_feed_info->count - 1)
+        {
+            flip_feed_info->index++;
+        }
+        // switch view, free dialog, re-alloc dialog, switch back to dialog
+        view_dispatcher_switch_to_view(app_instance->view_dispatcher, FlipSocialViewWidgetResult);
+        flip_social_free_feed_view();
+        // load feed item
+        if (!flip_social_load_feed_post(flip_feed_info->ids[flip_feed_info->index]))
+        {
+            FURI_LOG_E(TAG, "Failed to load nexy feed post");
+            fhttp.state = ISSUE;
+            return false;
+        }
+        if (feed_view_alloc())
+        {
+            view_dispatcher_switch_to_view(app_instance->view_dispatcher, FlipSocialViewLoggedInFeed);
+        }
+        else
+        {
+            FURI_LOG_E(TAG, "Failed to allocate feed dialog");
+            fhttp.state = ISSUE;
+            return false;
+        }
+    }
+    else if (event->type == InputTypePress && event->key == InputKeyOk) // Flip/Unflip
+    {
+        // Moved to above the is_flipped check
+        if (!flip_feed_item->is_flipped)
+        {
+            // increase the flip count
+            flip_feed_item->flips++;
+        }
+        else
+        {
+            // decrease the flip count
+            if (flip_feed_item->flips > 0)
+                flip_feed_item->flips--;
+        }
+        // change the flip status
+        flip_feed_item->is_flipped = !flip_feed_item->is_flipped;
+
+        // send post request to flip the message
+        if (app_instance->login_username_logged_in == NULL)
+        {
+            FURI_LOG_E(TAG, "Username is NULL");
+            return false;
+        }
+        if (!flipper_http_init(flipper_http_rx_callback, app_instance))
+        {
+            FURI_LOG_E(TAG, "Failed to initialize FlipperHTTP");
+            return false;
+        }
+        auth_headers_alloc();
+        char payload[256];
+        snprintf(payload, sizeof(payload), "{\"username\":\"%s\",\"post_id\":\"%u\"}", app_instance->login_username_logged_in, flip_feed_item->id);
+        if (flipper_http_post_request_with_headers("https://www.flipsocial.net/api/feed/flip/", auth_headers, payload))
+        {
+            // save feed item
+            char new_save[512];
+            snprintf(new_save, sizeof(new_save), "{\"id\":%u,\"username\":\"%s\",\"message\":\"%s\",\"flip_count\":%u,\"flipped\":%s,\"date_created\":\"%s\"}",
+                     flip_feed_item->id, flip_feed_item->username, flip_feed_item->message, flip_feed_item->flips, flip_feed_item->is_flipped ? "true" : "false", flip_feed_item->date_created);
+            char id[16];
+            snprintf(id, sizeof(id), "%u", flip_feed_item->id);
+            if (!flip_social_save_post(id, new_save))
+            {
+                FURI_LOG_E(TAG, "Failed to save the feed post");
+                flipper_http_deinit();
+                return false;
+            }
+        }
+        // switch view, free dialog, re-alloc dialog, switch back to dialog
+        view_dispatcher_switch_to_view(app_instance->view_dispatcher, FlipSocialViewWidgetResult);
+        flip_social_free_feed_view();
+        // load feed item
+        if (!flip_social_load_feed_post(flip_feed_info->ids[flip_feed_info->index]))
+        {
+            FURI_LOG_E(TAG, "Failed to load nexy feed post");
+            fhttp.state = ISSUE;
+            return false;
+        }
+        if (feed_view_alloc())
+        {
+            view_dispatcher_switch_to_view(app_instance->view_dispatcher, FlipSocialViewLoggedInFeed);
+        }
+        else
+        {
+            FURI_LOG_E(TAG, "Failed to allocate feed dialog");
+        }
+        flipper_http_deinit();
+    }
+    return false;
+}
+
+bool feed_view_alloc()
+{
+    if (!app_instance)
+    {
+        return false;
+    }
+    if (!flip_feed_item)
+    {
+        FURI_LOG_E(TAG, "Feed item is NULL");
+        return false;
+    }
+    flip_social_free_feed_view();
+    if (!app_instance->view_feed)
+    {
+        if (!easy_flipper_set_view(
+                &app_instance->view_feed,
+                FlipSocialViewLoggedInFeed,
+                flip_social_feed_draw_callback,
+                flip_social_feed_input_callback,
+                flip_social_callback_to_submenu_logged_in,
+                &app_instance->view_dispatcher,
+                app_instance))
+        {
+            return false;
+        }
+        return true;
+    }
+    return false;
+}
+
+bool alloc_text_input(uint32_t view_id)
+{
+    if (!app_instance)
+    {
+        return false;
+    }
+    if (!app_instance->text_input)
+    {
+        switch (view_id)
+        {
+        case FlipSocialViewLoggedOutWifiSettingsSSIDInput:
+            // memset(app_instance->wifi_ssid_logged_out_temp_buffer, 0, app_instance->wifi_ssid_logged_out_temp_buffer_size);
+            if (!easy_flipper_set_uart_text_input(&app_instance->text_input, FlipSocialViewTextInput, "Enter SSID", app_instance->wifi_ssid_logged_out_temp_buffer, app_instance->wifi_ssid_logged_out_temp_buffer_size, flip_social_logged_out_wifi_settings_ssid_updated, flip_social_callback_to_wifi_settings_logged_out, &app_instance->view_dispatcher, app_instance))
+            {
+                return false;
+            }
+            break;
+        case FlipSocialViewLoggedOutWifiSettingsPasswordInput:
+            // memset(app_instance->wifi_password_logged_out_temp_buffer, 0, app_instance->wifi_password_logged_out_temp_buffer_size);
+            if (!easy_flipper_set_uart_text_input(&app_instance->text_input, FlipSocialViewTextInput, "Enter Password", app_instance->wifi_password_logged_out_temp_buffer, app_instance->wifi_password_logged_out_temp_buffer_size, flip_social_logged_out_wifi_settings_password_updated, flip_social_callback_to_wifi_settings_logged_out, &app_instance->view_dispatcher, app_instance))
+            {
+                return false;
+            }
+            break;
+        case FlipSocialViewLoggedOutLoginUsernameInput:
+            // memset(app_instance->login_username_logged_out_temp_buffer, 0, app_instance->login_username_logged_out_temp_buffer_size);
+            if (!easy_flipper_set_uart_text_input(&app_instance->text_input, FlipSocialViewTextInput, "Enter Username", app_instance->login_username_logged_out_temp_buffer, app_instance->login_username_logged_out_temp_buffer_size, flip_social_logged_out_login_username_updated, flip_social_callback_to_login_logged_out, &app_instance->view_dispatcher, app_instance))
+            {
+                return false;
+            }
+            break;
+        case FlipSocialViewLoggedOutLoginPasswordInput:
+            // memset(app_instance->login_password_logged_out_temp_buffer, 0, app_instance->login_password_logged_out_temp_buffer_size);
+            if (!easy_flipper_set_uart_text_input(&app_instance->text_input, FlipSocialViewTextInput, "Enter Password", app_instance->login_password_logged_out_temp_buffer, app_instance->login_password_logged_out_temp_buffer_size, flip_social_logged_out_login_password_updated, flip_social_callback_to_login_logged_out, &app_instance->view_dispatcher, app_instance))
+            {
+                return false;
+            }
+            break;
+        case FlipSocialViewLoggedOutRegisterUsernameInput:
+            memset(app_instance->register_username_logged_out_temp_buffer, 0, app_instance->register_username_logged_out_temp_buffer_size);
+            if (!easy_flipper_set_uart_text_input(&app_instance->text_input, FlipSocialViewTextInput, "Enter Username", app_instance->register_username_logged_out_temp_buffer, app_instance->register_username_logged_out_temp_buffer_size, flip_social_logged_out_register_username_updated, flip_social_callback_to_register_logged_out, &app_instance->view_dispatcher, app_instance))
+            {
+                return false;
+            }
+            break;
+        case FlipSocialViewLoggedOutRegisterPasswordInput:
+            memset(app_instance->register_password_logged_out_temp_buffer, 0, app_instance->register_password_logged_out_temp_buffer_size);
+            if (!easy_flipper_set_uart_text_input(&app_instance->text_input, FlipSocialViewTextInput, "Enter Password", app_instance->register_password_logged_out_temp_buffer, app_instance->register_password_logged_out_temp_buffer_size, flip_social_logged_out_register_password_updated, flip_social_callback_to_register_logged_out, &app_instance->view_dispatcher, app_instance))
+            {
+                return false;
+            }
+            break;
+        case FlipSocialViewLoggedOutRegisterPassword2Input:
+            memset(app_instance->register_password_2_logged_out_temp_buffer, 0, app_instance->register_password_2_logged_out_temp_buffer_size);
+            if (!easy_flipper_set_uart_text_input(&app_instance->text_input, FlipSocialViewTextInput, "Confirm Password", app_instance->register_password_2_logged_out_temp_buffer, app_instance->register_password_2_logged_out_temp_buffer_size, flip_social_logged_out_register_password_2_updated, flip_social_callback_to_register_logged_out, &app_instance->view_dispatcher, app_instance))
+            {
+                return false;
+            }
+            break;
+        case FlipSocialViewLoggedInChangePasswordInput:
+            // memset(app_instance->change_password_logged_in_temp_buffer, 0, app_instance->change_password_logged_in_temp_buffer_size);
+            if (!easy_flipper_set_uart_text_input(&app_instance->text_input, FlipSocialViewTextInput, "Change Password", app_instance->change_password_logged_in_temp_buffer, app_instance->change_password_logged_in_temp_buffer_size, flip_social_logged_in_profile_change_password_updated, flip_social_callback_to_profile_logged_in, &app_instance->view_dispatcher, app_instance))
+            {
+                return false;
+            }
+            break;
+        case FlipSocialViewLoggedInChangeBioInput:
+            // memset(app_instance->change_bio_logged_in_temp_buffer, 0, app_instance->change_bio_logged_in_temp_buffer_size);
+            if (!easy_flipper_set_uart_text_input(&app_instance->text_input, FlipSocialViewTextInput, "Bio", app_instance->change_bio_logged_in_temp_buffer, app_instance->change_bio_logged_in_temp_buffer_size, flip_social_logged_in_profile_change_bio_updated, flip_social_callback_to_profile_logged_in, &app_instance->view_dispatcher, app_instance))
+            {
+                return false;
+            }
+            break;
+        case FlipSocialViewLoggedInComposeAddPreSaveInput:
+            memset(app_instance->compose_pre_save_logged_in_temp_buffer, 0, app_instance->compose_pre_save_logged_in_temp_buffer_size);
+            if (!easy_flipper_set_uart_text_input(&app_instance->text_input, FlipSocialViewTextInput, "Enter Pre-Save Message", app_instance->compose_pre_save_logged_in_temp_buffer, app_instance->compose_pre_save_logged_in_temp_buffer_size, flip_social_logged_in_compose_pre_save_updated, flip_social_callback_to_compose_logged_in, &app_instance->view_dispatcher, app_instance))
+            {
+                return false;
+            }
+            break;
+        case FlipSocialViewLoggedInWifiSettingsSSIDInput:
+            // memset(app_instance->wifi_ssid_logged_in_temp_buffer, 0, app_instance->wifi_ssid_logged_in_temp_buffer_size);
+            if (!easy_flipper_set_uart_text_input(&app_instance->text_input, FlipSocialViewTextInput, "Enter SSID", app_instance->wifi_ssid_logged_in_temp_buffer, app_instance->wifi_ssid_logged_in_temp_buffer_size, flip_social_logged_in_wifi_settings_ssid_updated, flip_social_callback_to_wifi_settings_logged_in, &app_instance->view_dispatcher, app_instance))
+            {
+                return false;
+            }
+            break;
+        case FlipSocialViewLoggedInWifiSettingsPasswordInput:
+            // memset(app_instance->wifi_password_logged_in_temp_buffer, 0, app_instance->wifi_password_logged_in_temp_buffer_size);
+            if (!easy_flipper_set_uart_text_input(&app_instance->text_input, FlipSocialViewTextInput, "Enter Password", app_instance->wifi_password_logged_in_temp_buffer, app_instance->wifi_password_logged_in_temp_buffer_size, flip_social_logged_in_wifi_settings_password_updated, flip_social_callback_to_wifi_settings_logged_in, &app_instance->view_dispatcher, app_instance))
+            {
+                return false;
+            }
+            break;
+        case FlipSocialViewLoggedInMessagesNewMessageInput:
+            memset(app_instance->messages_new_message_logged_in_temp_buffer, 0, app_instance->messages_new_message_logged_in_temp_buffer_size);
+            if (!easy_flipper_set_uart_text_input(&app_instance->text_input, FlipSocialViewTextInput, "Enter Message", app_instance->messages_new_message_logged_in_temp_buffer, app_instance->messages_new_message_logged_in_temp_buffer_size, flip_social_logged_in_messages_new_message_updated, flip_social_callback_to_messages_logged_in, &app_instance->view_dispatcher, app_instance))
+            {
+                return false;
+            }
+            break;
+        case FlipSocialViewLoggedInMessagesNewMessageUserChoicesInput:
+            memset(app_instance->message_user_choice_logged_in_temp_buffer, 0, app_instance->message_user_choice_logged_in_temp_buffer_size);
+            if (!easy_flipper_set_uart_text_input(&app_instance->text_input, FlipSocialViewTextInput, "Enter Message", app_instance->message_user_choice_logged_in_temp_buffer, app_instance->message_user_choice_logged_in_temp_buffer_size, flip_social_logged_in_messages_user_choice_message_updated, flip_social_callback_to_messages_user_choices, &app_instance->view_dispatcher, app_instance))
+            {
+                return false;
+            }
+            break;
+        case FlipSocialViewLoggedInExploreInput:
+            memset(app_instance->explore_logged_in_temp_buffer, 0, app_instance->explore_logged_in_temp_buffer_size);
+            if (!easy_flipper_set_uart_text_input(&app_instance->text_input, FlipSocialViewTextInput, "Enter Username or Keyword", app_instance->explore_logged_in_temp_buffer, app_instance->explore_logged_in_temp_buffer_size, flip_social_logged_in_explore_updated, flip_social_callback_to_submenu_logged_in, &app_instance->view_dispatcher, app_instance))
+            {
+                return false;
+            }
+            break;
+        case FlipSocialViewLoggedInMessageUsersInput:
+            memset(app_instance->message_users_logged_in_temp_buffer, 0, app_instance->message_users_logged_in_temp_buffer_size);
+            if (!easy_flipper_set_uart_text_input(&app_instance->text_input, FlipSocialViewTextInput, "Enter Username or Keyword", app_instance->message_users_logged_in_temp_buffer, app_instance->message_users_logged_in_temp_buffer_size, flip_social_logged_in_message_users_updated, flip_social_callback_to_submenu_logged_in, &app_instance->view_dispatcher, app_instance))
+            {
+                return false;
+            }
+            break;
+        default:
+            return false;
+        }
+    }
+    return true;
+}
+
+bool about_widget_alloc(bool is_logged_in)
+{
+    if (!is_logged_in)
+    {
+        if (!app_instance->widget_logged_out_about)
+        {
+            return easy_flipper_set_widget(&app_instance->widget_logged_out_about, FlipSocialViewLoggedOutAbout, "Welcome to FlipSocial\n---\nThe social media app for\nFlipper Zero, created by\nJBlanked: www.flipsocial.net\n---\nPress BACK to return.", flip_social_callback_to_submenu_logged_out, &app_instance->view_dispatcher);
+        }
+    }
+    else
+    {
+        if (!app_instance->widget_logged_in_about)
+        {
+            return easy_flipper_set_widget(&app_instance->widget_logged_in_about, FlipSocialViewLoggedInSettingsAbout, "Welcome to FlipSocial\n---\nThe social media app for\nFlipper Zero, created by\nJBlanked: www.flipsocial.net\n---\nPress BACK to return.", flip_social_callback_to_settings_logged_in, &app_instance->view_dispatcher);
+        }
+    }
+    return true;
+}
+
+bool alloc_submenu(uint32_t view_id)
+{
+    if (!app_instance)
+    {
+        return false;
+    }
+    if (!app_instance->submenu)
+    {
+        switch (view_id)
+        {
+        case FlipSocialViewLoggedInSettings:
+            if (!easy_flipper_set_submenu(&app_instance->submenu, FlipSocialViewSubmenu, "Settings", flip_social_callback_to_submenu_logged_in, &app_instance->view_dispatcher))
+            {
+                return false;
+            }
+            submenu_reset(app_instance->submenu);
+            submenu_add_item(app_instance->submenu, "About", FlipSocialSubmenuLoggedInIndexAbout, flip_social_callback_submenu_choices, app_instance);
+            submenu_add_item(app_instance->submenu, "WiFi", FlipSocialSubmenuLoggedInIndexWifiSettings, flip_social_callback_submenu_choices, app_instance);
+            submenu_add_item(app_instance->submenu, "User", FlipSocialSubmenuLoggedInIndexUserSettings, flip_social_callback_submenu_choices, app_instance);
+            break;
+        case FlipSocialViewLoggedInCompose:
+            if (!easy_flipper_set_submenu(&app_instance->submenu, FlipSocialViewSubmenu, "Create A Post", flip_social_callback_to_submenu_logged_in, &app_instance->view_dispatcher))
+            {
+                return false;
+            }
+            submenu_reset(app_instance->submenu);
+            submenu_add_item(app_instance->submenu, "Add Pre-Save", FlipSocialSubmenuComposeIndexAddPreSave, flip_social_callback_submenu_choices, app_instance);
+
+            // Load the playlist
+            if (load_playlist(&app_instance->pre_saved_messages))
+            {
+                // Update the playlist submenu
+                for (uint32_t i = 0; i < app_instance->pre_saved_messages.count; i++)
+                {
+                    if (app_instance->pre_saved_messages.messages[i][0] != '\0') // Check if the string is not empty
+                    {
+                        submenu_add_item(app_instance->submenu, app_instance->pre_saved_messages.messages[i], FlipSocialSubemnuComposeIndexStartIndex + i, flip_social_callback_submenu_choices, app_instance);
+                    }
+                }
+            }
+            break;
+        case FlipSocialViewLoggedInFriendsSubmenu:
+            if (!easy_flipper_set_submenu(&app_instance->submenu, FlipSocialViewSubmenu, "Friends", flip_social_callback_to_profile_logged_in, &app_instance->view_dispatcher))
+            {
+                FURI_LOG_E(TAG, "Failed to set submenu for friends");
+                return false;
+            }
+            submenu_reset(app_instance->submenu);
+            went_to_friends = true;
+            break;
+        case FlipSocialViewLoggedInMessagesUserChoices:
+            if (!easy_flipper_set_submenu(&app_instance->submenu, FlipSocialViewSubmenu, "Users", flip_social_callback_to_messages_logged_in, &app_instance->view_dispatcher))
+            {
+                FURI_LOG_E(TAG, "Failed to set submenu for user choices");
+                return false;
+            }
+            submenu_reset(app_instance->submenu);
+            break;
+        case FlipSocialViewLoggedInMessagesSubmenu:
+            if (!easy_flipper_set_submenu(&app_instance->submenu, FlipSocialViewSubmenu, "Messages", flip_social_callback_to_submenu_logged_in, &app_instance->view_dispatcher))
+            {
+                return false;
+            }
+            submenu_reset(app_instance->submenu);
+            break;
+        case FlipSocialViewLoggedInExploreSubmenu:
+            if (!easy_flipper_set_submenu(&app_instance->submenu, FlipSocialViewSubmenu, "Explore", flip_social_callback_to_submenu_logged_in, &app_instance->view_dispatcher))
+            {
+                return false;
+            }
+            submenu_reset(app_instance->submenu);
+            break;
+        }
+    }
+    return true;
+}
+static void flip_social_feed_type_change(VariableItem *item)
+{
+    uint8_t index = variable_item_get_current_value_index(item);
+    variable_item_set_current_value_text(item, flip_social_feed_type[index]);
+    flip_social_feed_type_index = index;
+    variable_item_set_current_value_index(item, index);
+
+    // save the feed type
+    save_char("user_feed_type", strstr(flip_social_feed_type[index], "Global") ? "global" : "friends");
+}
+static void flip_social_notification_type_change(VariableItem *item)
+{
+    uint8_t index = variable_item_get_current_value_index(item);
+    variable_item_set_current_value_text(item, flip_social_notification_type[index]);
+    flip_social_notification_type_index = index;
+    variable_item_set_current_value_index(item, index);
+
+    // save the notification type
+    save_char("user_notifications", strstr(flip_social_notification_type[index], "ON") ? "on" : "off");
+}
+
+bool alloc_variable_item_list(uint32_t view_id)
+{
+    if (!app_instance)
+    {
+        return false;
+    }
+    if (!app_instance->variable_item_list)
+    {
+        switch (view_id)
+        {
+        case FlipSocialViewLoggedOutWifiSettings:
+            if (!easy_flipper_set_variable_item_list(&app_instance->variable_item_list, FlipSocialViewVariableItemList, flip_social_text_input_logged_out_wifi_settings_item_selected, flip_social_callback_to_submenu_logged_out, &app_instance->view_dispatcher, app_instance))
+            {
+                return false;
+            }
+            app_instance->variable_item_logged_out_wifi_settings_ssid = variable_item_list_add(app_instance->variable_item_list, "SSID", 1, NULL, app_instance);
+            app_instance->variable_item_logged_out_wifi_settings_password = variable_item_list_add(app_instance->variable_item_list, "Password", 1, NULL, app_instance);
+            if (app_instance->wifi_ssid_logged_out)
+                variable_item_set_current_value_text(app_instance->variable_item_logged_out_wifi_settings_ssid, app_instance->wifi_ssid_logged_out);
+            return true;
+        case FlipSocialViewLoggedOutLogin:
+            if (!easy_flipper_set_variable_item_list(&app_instance->variable_item_list, FlipSocialViewVariableItemList, flip_social_text_input_logged_out_login_item_selected, flip_social_callback_to_submenu_logged_out, &app_instance->view_dispatcher, app_instance))
+            {
+                return false;
+            }
+            app_instance->variable_item_logged_out_login_username = variable_item_list_add(app_instance->variable_item_list, "Username", 1, NULL, app_instance);
+            app_instance->variable_item_logged_out_login_password = variable_item_list_add(app_instance->variable_item_list, "Password", 1, NULL, app_instance);
+            app_instance->variable_item_logged_out_login_button = variable_item_list_add(app_instance->variable_item_list, "Login", 0, NULL, app_instance);
+            if (app_instance->login_username_logged_out)
+                variable_item_set_current_value_text(app_instance->variable_item_logged_out_login_username, app_instance->login_username_logged_out);
+            return true;
+        case FlipSocialViewLoggedOutRegister:
+            if (!easy_flipper_set_variable_item_list(&app_instance->variable_item_list, FlipSocialViewVariableItemList, flip_social_text_input_logged_out_register_item_selected, flip_social_callback_to_submenu_logged_out, &app_instance->view_dispatcher, app_instance))
+            {
+                return false;
+            }
+            app_instance->variable_item_logged_out_register_username = variable_item_list_add(app_instance->variable_item_list, "Username", 1, NULL, app_instance);
+            app_instance->variable_item_logged_out_register_password = variable_item_list_add(app_instance->variable_item_list, "Password", 1, NULL, app_instance);
+            app_instance->variable_item_logged_out_register_password_2 = variable_item_list_add(app_instance->variable_item_list, "Confirm Password", 1, NULL, app_instance);
+            app_instance->variable_item_logged_out_register_button = variable_item_list_add(app_instance->variable_item_list, "Register", 0, NULL, app_instance);
+            return true;
+        case FlipSocialViewLoggedInProfile:
+            if (!easy_flipper_set_variable_item_list(&app_instance->variable_item_list, FlipSocialViewVariableItemList, flip_social_text_input_logged_in_profile_item_selected, flip_social_callback_to_submenu_logged_in, &app_instance->view_dispatcher, app_instance))
+            {
+                return false;
+            }
+            app_instance->variable_item_logged_in_profile_username = variable_item_list_add(app_instance->variable_item_list, "Username", 1, NULL, app_instance);
+            app_instance->variable_item_logged_in_profile_change_password = variable_item_list_add(app_instance->variable_item_list, "Password", 1, NULL, app_instance);
+            app_instance->variable_item_logged_in_profile_change_bio = variable_item_list_add(app_instance->variable_item_list, "Bio", 1, NULL, app_instance);
+            app_instance->variable_item_logged_in_profile_friends = variable_item_list_add(app_instance->variable_item_list, "Friends", 0, NULL, app_instance);
+            if (app_instance->login_username_logged_in)
+                variable_item_set_current_value_text(app_instance->variable_item_logged_in_profile_username, app_instance->login_username_logged_in);
+            if (app_instance->change_bio_logged_in)
+                variable_item_set_current_value_text(app_instance->variable_item_logged_in_profile_change_bio, app_instance->change_bio_logged_in);
+            return true;
+        case FlipSocialViewLoggedInSettingsWifi:
+            if (!easy_flipper_set_variable_item_list(&app_instance->variable_item_list, FlipSocialViewVariableItemList, flip_social_text_input_logged_in_wifi_settings_item_selected, flip_social_callback_to_settings_logged_in, &app_instance->view_dispatcher, app_instance))
+            {
+                return false;
+            }
+            app_instance->variable_item_logged_in_wifi_settings_ssid = variable_item_list_add(app_instance->variable_item_list, "SSID", 1, NULL, app_instance);
+            app_instance->variable_item_logged_in_wifi_settings_password = variable_item_list_add(app_instance->variable_item_list, "Password", 1, NULL, app_instance);
+            if (app_instance->wifi_ssid_logged_in)
+                variable_item_set_current_value_text(app_instance->variable_item_logged_in_wifi_settings_ssid, app_instance->wifi_ssid_logged_in);
+            return true;
+        case FlipSocialViewLoggedInSettingsUser:
+            if (!easy_flipper_set_variable_item_list(&app_instance->variable_item_list, FlipSocialViewVariableItemList, flip_social_logged_in_user_settings_item_selected, flip_social_callback_to_settings_logged_in, &app_instance->view_dispatcher, app_instance))
+            {
+                return false;
+            }
+            app_instance->variable_item_logged_in_user_settings_feed_type = variable_item_list_add(app_instance->variable_item_list, "Feed Type", 2, flip_social_feed_type_change, app_instance);
+            app_instance->variable_item_logged_in_user_settings_notifications = variable_item_list_add(app_instance->variable_item_list, "Notifications", 2, flip_social_notification_type_change, app_instance);
+            variable_item_set_current_value_text(app_instance->variable_item_logged_in_user_settings_feed_type, flip_social_feed_type[flip_social_feed_type_index]);
+            variable_item_set_current_value_index(app_instance->variable_item_logged_in_user_settings_feed_type, flip_social_feed_type_index);
+            variable_item_set_current_value_text(app_instance->variable_item_logged_in_user_settings_notifications, flip_social_notification_type[flip_social_notification_type_index]);
+            variable_item_set_current_value_index(app_instance->variable_item_logged_in_user_settings_notifications, flip_social_notification_type_index);
+            char user_feed_type[32];
+            char user_notifications[32];
+            if (load_char("user_feed_type", user_feed_type, sizeof(user_feed_type)))
+            {
+                flip_social_feed_type_index = strstr(user_feed_type, "friends") ? 1 : 0;
+                variable_item_set_current_value_text(app_instance->variable_item_logged_in_user_settings_feed_type, flip_social_feed_type[flip_social_feed_type_index]);
+                variable_item_set_current_value_index(app_instance->variable_item_logged_in_user_settings_feed_type, flip_social_feed_type_index);
+            }
+            if (load_char("user_notifications", user_notifications, sizeof(user_notifications)))
+            {
+                flip_social_notification_type_index = strstr(user_notifications, "on") ? 1 : 0;
+                variable_item_set_current_value_text(app_instance->variable_item_logged_in_user_settings_notifications, flip_social_notification_type[flip_social_notification_type_index]);
+                variable_item_set_current_value_index(app_instance->variable_item_logged_in_user_settings_notifications, flip_social_notification_type_index);
+            }
+            return true;
+        default:
+            return false;
+        }
+    }
+    return false;
+}

+ 14 - 0
flip_social/alloc/alloc.h

@@ -0,0 +1,14 @@
+#pragma once
+#include <flip_social.h>
+#include <callback/flip_social_callback.h>
+#include <alloc/free.h>
+void auth_headers_alloc(void);
+FlipSocialFeedMini *flip_feed_info_alloc(void);
+bool messages_dialog_alloc(bool free_first);
+bool feed_view_alloc();
+char *updated_user_message(const char *user_message);
+bool alloc_text_input(uint32_t view_id);
+bool about_widget_alloc(bool is_logged_in);
+bool alloc_variable_item_list(uint32_t view_id);
+bool alloc_submenu(uint32_t view_id);
+extern bool went_to_friends;

+ 5 - 140
flip_social/alloc/flip_social_alloc.c

@@ -8,13 +8,6 @@ FlipSocialApp *flip_social_app_alloc()
     // Initialize gui
     Gui *gui = furi_record_open(RECORD_GUI);
 
-    // Initialize UART
-    if (!flipper_http_init(flipper_http_rx_callback, app))
-    {
-        FURI_LOG_E(TAG, "Failed to initialize UART");
-        return NULL;
-    }
-
     // Allocate ViewDispatcher
     if (!easy_flipper_set_view_dispatcher(&app->view_dispatcher, gui, app))
     {
@@ -27,7 +20,7 @@ FlipSocialApp *flip_social_app_alloc()
         return NULL;
     }
     flip_social_loader_init(app->view_loader);
-    if (!easy_flipper_set_widget(&app->widget_result, FlipSocialViewWidgetResult, "Error, try again.", flip_social_callback_to_submenu_logged_out, &app->view_dispatcher))
+    if (!easy_flipper_set_widget(&app->widget_result, FlipSocialViewWidgetResult, "", flip_social_callback_to_submenu_logged_out, &app->view_dispatcher))
     {
         return NULL;
     }
@@ -199,16 +192,11 @@ FlipSocialApp *flip_social_app_alloc()
     }
 
     // Allocate Submenu(s)
-    if (!easy_flipper_set_submenu(&app->submenu_logged_out, FlipSocialViewLoggedOutSubmenu, "FlipSocial v0.8", flip_social_callback_exit_app, &app->view_dispatcher))
+    if (!easy_flipper_set_submenu(&app->submenu_logged_out, FlipSocialViewLoggedOutSubmenu, VERSION_TAG, flip_social_callback_exit_app, &app->view_dispatcher))
     {
         return NULL;
     }
-    if (!easy_flipper_set_submenu(&app->submenu_logged_in, FlipSocialViewLoggedInSubmenu, "FlipSocial v0.8", flip_social_callback_exit_app, &app->view_dispatcher))
-    {
-        return NULL;
-    }
-
-    if (!easy_flipper_set_submenu(&app->submenu_messages_user_choices, FlipSocialViewLoggedInMessagesUserChoices, "Users", flip_social_callback_to_messages_logged_in, &app->view_dispatcher))
+    if (!easy_flipper_set_submenu(&app->submenu_logged_in, FlipSocialViewLoggedInSubmenu, VERSION_TAG, flip_social_callback_exit_app, &app->view_dispatcher))
     {
         return NULL;
     }
@@ -226,123 +214,6 @@ FlipSocialApp *flip_social_app_alloc()
     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);
 
-    // Setup Variable Item List(s)
-    if (!easy_flipper_set_variable_item_list(&app->variable_item_list_logged_out_wifi_settings, FlipSocialViewLoggedOutWifiSettings, flip_social_text_input_logged_out_wifi_settings_item_selected, flip_social_callback_to_submenu_logged_out, &app->view_dispatcher, app))
-    {
-        return NULL;
-    }
-    if (!easy_flipper_set_variable_item_list(&app->variable_item_list_logged_out_login, FlipSocialViewLoggedOutLogin, flip_social_text_input_logged_out_login_item_selected, flip_social_callback_to_submenu_logged_out, &app->view_dispatcher, app))
-    {
-        return NULL;
-    }
-    if (!easy_flipper_set_variable_item_list(&app->variable_item_list_logged_out_register, FlipSocialViewLoggedOutRegister, flip_social_text_input_logged_out_register_item_selected, flip_social_callback_to_submenu_logged_out, &app->view_dispatcher, app))
-    {
-        return NULL;
-    }
-    if (!easy_flipper_set_variable_item_list(&app->variable_item_list_logged_in_profile, FlipSocialViewLoggedInProfile, flip_social_text_input_logged_in_profile_item_selected, flip_social_callback_to_submenu_logged_in, &app->view_dispatcher, app))
-    {
-        return NULL;
-    }
-    if (!easy_flipper_set_variable_item_list(&app->variable_item_list_logged_in_settings, FlipSocialViewLoggedInSettings, flip_social_text_input_logged_in_settings_item_selected, flip_social_callback_to_submenu_logged_in, &app->view_dispatcher, app))
-    {
-        return NULL;
-    }
-    if (!easy_flipper_set_variable_item_list(&app->variable_item_list_logged_in_settings_wifi, FlipSocialViewLoggedInSettingsWifi, flip_social_text_input_logged_in_wifi_settings_item_selected, flip_social_callback_to_settings_logged_in, &app->view_dispatcher, app))
-    {
-        return NULL;
-    }
-
-    app->variable_item_logged_out_wifi_settings_ssid = variable_item_list_add(app->variable_item_list_logged_out_wifi_settings, "SSID", 1, NULL, app);
-    app->variable_item_logged_out_wifi_settings_password = variable_item_list_add(app->variable_item_list_logged_out_wifi_settings, "Password", 1, NULL, app);
-    //
-    app->variable_item_logged_out_login_username = variable_item_list_add(app->variable_item_list_logged_out_login, "Username", 1, NULL, app);
-    app->variable_item_logged_out_login_password = variable_item_list_add(app->variable_item_list_logged_out_login, "Password", 1, NULL, app);
-    app->variable_item_logged_out_login_button = variable_item_list_add(app->variable_item_list_logged_out_login, "Login", 0, NULL, app);
-    //
-    app->variable_item_logged_out_register_username = variable_item_list_add(app->variable_item_list_logged_out_register, "Username", 1, NULL, app);
-    app->variable_item_logged_out_register_password = variable_item_list_add(app->variable_item_list_logged_out_register, "Password", 1, NULL, app);
-    app->variable_item_logged_out_register_password_2 = variable_item_list_add(app->variable_item_list_logged_out_register, "Confirm Password", 1, NULL, app);
-    app->variable_item_logged_out_register_button = variable_item_list_add(app->variable_item_list_logged_out_register, "Register", 0, NULL, app);
-    //
-    app->variable_item_logged_in_profile_username = variable_item_list_add(app->variable_item_list_logged_in_profile, "Username", 1, NULL, app);
-    app->variable_item_logged_in_profile_change_password = variable_item_list_add(app->variable_item_list_logged_in_profile, "Password", 1, NULL, app);
-    app->variable_item_logged_in_profile_change_bio = variable_item_list_add(app->variable_item_list_logged_in_profile, "Bio", 1, NULL, app);
-    app->variable_item_logged_in_profile_friends = variable_item_list_add(app->variable_item_list_logged_in_profile, "Friends", 0, NULL, app);
-    //
-    app->variable_item_logged_in_settings_about = variable_item_list_add(app->variable_item_list_logged_in_settings, "About", 0, NULL, app);
-    app->variable_item_logged_in_settings_wifi = variable_item_list_add(app->variable_item_list_logged_in_settings, "WiFi", 0, NULL, app);
-    //
-    app->variable_item_logged_in_wifi_settings_ssid = variable_item_list_add(app->variable_item_list_logged_in_settings_wifi, "SSID", 1, NULL, app);
-    app->variable_item_logged_in_wifi_settings_password = variable_item_list_add(app->variable_item_list_logged_in_settings_wifi, "Password", 1, NULL, app);
-
-    // Setup Text Input(s)
-    if (!easy_flipper_set_uart_text_input(&app->text_input_logged_out_wifi_settings_ssid, FlipSocialViewLoggedOutWifiSettingsSSIDInput, "Enter SSID", app->wifi_ssid_logged_out_temp_buffer, app->wifi_ssid_logged_out_temp_buffer_size, flip_social_logged_out_wifi_settings_ssid_updated, flip_social_callback_to_wifi_settings_logged_out, &app->view_dispatcher, app))
-    {
-        return NULL;
-    }
-    if (!easy_flipper_set_uart_text_input(&app->text_input_logged_out_wifi_settings_password, FlipSocialViewLoggedOutWifiSettingsPasswordInput, "Enter Password", app->wifi_password_logged_out_temp_buffer, app->wifi_password_logged_out_temp_buffer_size, flip_social_logged_out_wifi_settings_password_updated, flip_social_callback_to_wifi_settings_logged_out, &app->view_dispatcher, app))
-    {
-        return NULL;
-    }
-    if (!easy_flipper_set_uart_text_input(&app->text_input_logged_out_login_username, FlipSocialViewLoggedOutLoginUsernameInput, "Enter Username", app->login_username_logged_out_temp_buffer, app->login_username_logged_out_temp_buffer_size, flip_social_logged_out_login_username_updated, flip_social_callback_to_login_logged_out, &app->view_dispatcher, app))
-    {
-        return NULL;
-    }
-    if (!easy_flipper_set_uart_text_input(&app->text_input_logged_out_login_password, FlipSocialViewLoggedOutLoginPasswordInput, "Enter Password", app->login_password_logged_out_temp_buffer, app->login_password_logged_out_temp_buffer_size, flip_social_logged_out_login_password_updated, flip_social_callback_to_login_logged_out, &app->view_dispatcher, app))
-    {
-        return NULL;
-    }
-    if (!easy_flipper_set_uart_text_input(&app->text_input_logged_out_register_username, FlipSocialViewLoggedOutRegisterUsernameInput, "Enter Username", app->register_username_logged_out_temp_buffer, app->register_username_logged_out_temp_buffer_size, flip_social_logged_out_register_username_updated, flip_social_callback_to_register_logged_out, &app->view_dispatcher, app))
-    {
-        return NULL;
-    }
-    if (!easy_flipper_set_uart_text_input(&app->text_input_logged_out_register_password, FlipSocialViewLoggedOutRegisterPasswordInput, "Enter Password", app->register_password_logged_out_temp_buffer, app->register_password_logged_out_temp_buffer_size, flip_social_logged_out_register_password_updated, flip_social_callback_to_register_logged_out, &app->view_dispatcher, app))
-    {
-        return NULL;
-    }
-    if (!easy_flipper_set_uart_text_input(&app->text_input_logged_out_register_password_2, FlipSocialViewLoggedOutRegisterPassword2Input, "Confirm Password", app->register_password_2_logged_out_temp_buffer, app->register_password_2_logged_out_temp_buffer_size, flip_social_logged_out_register_password_2_updated, flip_social_callback_to_register_logged_out, &app->view_dispatcher, app))
-    {
-        return NULL;
-    }
-    //
-    if (!easy_flipper_set_uart_text_input(&app->text_input_logged_in_change_password, FlipSocialViewLoggedInChangePasswordInput, "Password", app->change_password_logged_in_temp_buffer, app->change_password_logged_in_temp_buffer_size, flip_social_logged_in_profile_change_password_updated, flip_social_callback_to_profile_logged_in, &app->view_dispatcher, app))
-    {
-        return NULL;
-    }
-    if (!easy_flipper_set_uart_text_input(&app->text_input_logged_in_change_bio, FlipSocialViewLoggedInChangeBioInput, "Bio", app->change_bio_logged_in_temp_buffer, app->change_bio_logged_in_temp_buffer_size, flip_social_logged_in_profile_change_bio_updated, flip_social_callback_to_profile_logged_in, &app->view_dispatcher, app))
-    {
-        return NULL;
-    }
-    if (!easy_flipper_set_uart_text_input(&app->text_input_logged_in_compose_pre_save_input, FlipSocialViewLoggedInComposeAddPreSaveInput, "Enter Pre-Save Message", app->compose_pre_save_logged_in_temp_buffer, app->compose_pre_save_logged_in_temp_buffer_size, flip_social_logged_in_compose_pre_save_updated, flip_social_callback_to_compose_logged_in, &app->view_dispatcher, app))
-    {
-        return NULL;
-    }
-    if (!easy_flipper_set_uart_text_input(&app->text_input_logged_in_wifi_settings_ssid, FlipSocialViewLoggedInWifiSettingsSSIDInput, "Enter SSID", app->wifi_ssid_logged_in_temp_buffer, app->wifi_ssid_logged_in_temp_buffer_size, flip_social_logged_in_wifi_settings_ssid_updated, flip_social_callback_to_wifi_settings_logged_in, &app->view_dispatcher, app))
-    {
-        return NULL;
-    }
-    if (!easy_flipper_set_uart_text_input(&app->text_input_logged_in_wifi_settings_password, FlipSocialViewLoggedInWifiSettingsPasswordInput, "Enter Password", app->wifi_password_logged_in_temp_buffer, app->wifi_password_logged_in_temp_buffer_size, flip_social_logged_in_wifi_settings_password_updated, flip_social_callback_to_wifi_settings_logged_in, &app->view_dispatcher, app))
-    {
-        return NULL;
-    }
-    //
-    if (!easy_flipper_set_uart_text_input(&app->text_input_logged_in_messages_new_message, FlipSocialViewLoggedInMessagesNewMessageInput, "Enter Message", app->messages_new_message_logged_in_temp_buffer, app->messages_new_message_logged_in_temp_buffer_size, flip_social_logged_in_messages_new_message_updated, flip_social_callback_to_messages_logged_in, &app->view_dispatcher, app))
-    {
-        return NULL;
-    }
-    if (!easy_flipper_set_uart_text_input(&app->text_input_logged_in_messages_new_message_user_choices, FlipSocialViewLoggedInMessagesNewMessageUserChoicesInput, "Enter Message", app->message_user_choice_logged_in_temp_buffer, app->message_user_choice_logged_in_temp_buffer_size, flip_social_logged_in_messages_user_choice_message_updated, flip_social_callback_to_messages_user_choices, &app->view_dispatcher, app))
-    {
-        return NULL;
-    }
-    if (!easy_flipper_set_uart_text_input(&app->text_input_logged_in_explore, FlipSocialViewLoggedInExploreInput, "Enter Username or Keyword", app->explore_logged_in_temp_buffer, app->explore_logged_in_temp_buffer_size, flip_social_logged_in_explore_updated, flip_social_callback_to_submenu_logged_in, &app->view_dispatcher, app))
-    {
-        return NULL;
-    }
-    if (!easy_flipper_set_uart_text_input(&app->text_input_logged_in_message_users, FlipSocialViewLoggedInMessageUsersInput, "Enter Username or Keyword", app->message_users_logged_in_temp_buffer, app->message_users_logged_in_temp_buffer_size, flip_social_logged_in_message_users_updated, flip_social_callback_to_submenu_logged_in, &app->view_dispatcher, app))
-    {
-        return NULL;
-    }
-
     // Load the settings
     if (!load_settings(app->wifi_ssid_logged_out,
                        app->wifi_ssid_logged_out_temp_buffer_size,
@@ -508,20 +379,14 @@ FlipSocialApp *flip_social_app_alloc()
 
         auth_headers_alloc();
 
-        // set variable item text (ommit the passwords)
-        variable_item_set_current_value_text(app->variable_item_logged_in_wifi_settings_ssid, app->wifi_ssid_logged_in);
-        variable_item_set_current_value_text(app->variable_item_logged_out_wifi_settings_ssid, app->wifi_ssid_logged_out);
-        variable_item_set_current_value_text(app->variable_item_logged_out_login_username, app->login_username_logged_out);
-        variable_item_set_current_value_text(app->variable_item_logged_in_profile_username, app->login_username_logged_in);
-        variable_item_set_current_value_text(app->variable_item_logged_in_profile_change_bio, app->change_bio_logged_in);
-        //
-
         if (app->is_logged_in != NULL && strcmp(app->is_logged_in, "true") == 0)
         {
+            save_char("is_logged_in", "true");
             view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedInSubmenu);
         }
         else
         {
+            save_char("is_logged_in", "false");
             view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedOutSubmenu);
         }
     }

+ 146 - 0
flip_social/alloc/free.c

@@ -0,0 +1,146 @@
+#include <alloc/free.h>
+void free_all(bool should_free_variable_item_list, bool should_free_submenu)
+{
+
+    if (should_free_submenu)
+    {
+        flip_social_free_explore();
+        free_submenu();
+    }
+    if (should_free_variable_item_list)
+    {
+        free_variable_item_list();
+    }
+    free_text_input();
+    flip_social_free_friends();
+    flip_social_free_messages();
+    flip_social_free_feed_view();
+    flip_social_free_compose_dialog();
+    flip_social_free_explore_dialog();
+    flip_social_free_friends_dialog();
+    flip_social_free_messages_dialog();
+    flip_feed_info_free();
+    free_about_widget(true);
+    free_about_widget(false);
+
+    if (went_to_friends)
+    {
+        flipper_http_deinit();
+        went_to_friends = false;
+    }
+}
+void free_text_input()
+{
+    if (app_instance->text_input)
+    {
+        uart_text_input_free(app_instance->text_input);
+        app_instance->text_input = NULL;
+        view_dispatcher_remove_view(app_instance->view_dispatcher, FlipSocialViewTextInput);
+    }
+}
+void flip_social_free_explore_dialog()
+{
+    if (app_instance->dialog_explore)
+    {
+        dialog_ex_free(app_instance->dialog_explore);
+        app_instance->dialog_explore = NULL;
+        view_dispatcher_remove_view(app_instance->view_dispatcher, FlipSocialViewExploreDialog);
+    }
+}
+void flip_social_free_friends_dialog()
+{
+    if (app_instance->dialog_friends)
+    {
+        dialog_ex_free(app_instance->dialog_friends);
+        app_instance->dialog_friends = NULL;
+        view_dispatcher_remove_view(app_instance->view_dispatcher, FlipSocialViewFriendsDialog);
+    }
+}
+void flip_social_free_messages_dialog()
+{
+    if (app_instance->dialog_messages)
+    {
+        dialog_ex_free(app_instance->dialog_messages);
+        app_instance->dialog_messages = NULL;
+        view_dispatcher_remove_view(app_instance->view_dispatcher, FlipSocialViewMessagesDialog);
+        return;
+    }
+}
+void flip_social_free_compose_dialog()
+{
+    if (app_instance->dialog_compose)
+    {
+        dialog_ex_free(app_instance->dialog_compose);
+        app_instance->dialog_compose = NULL;
+        view_dispatcher_remove_view(app_instance->view_dispatcher, FlipSocialViewComposeDialog);
+    }
+}
+void flip_social_free_feed_view()
+{
+    if (app_instance->view_feed)
+    {
+        view_free(app_instance->view_feed);
+        app_instance->view_feed = NULL;
+        view_dispatcher_remove_view(app_instance->view_dispatcher, FlipSocialViewLoggedInFeed);
+    }
+}
+
+void free_about_widget(bool is_logged_in)
+{
+    if (is_logged_in && app_instance->widget_logged_in_about)
+    {
+        widget_free(app_instance->widget_logged_in_about);
+        app_instance->widget_logged_in_about = NULL;
+        view_dispatcher_remove_view(app_instance->view_dispatcher, FlipSocialViewLoggedInSettingsAbout);
+    }
+    if (!is_logged_in && app_instance->widget_logged_out_about)
+    {
+        widget_free(app_instance->widget_logged_out_about);
+        app_instance->widget_logged_out_about = NULL;
+        view_dispatcher_remove_view(app_instance->view_dispatcher, FlipSocialViewLoggedOutAbout);
+    }
+}
+
+void flip_social_free_friends(void)
+{
+    if (!flip_social_friends)
+    {
+        return;
+    }
+    free(flip_social_friends);
+    flip_social_friends = NULL;
+}
+
+void flip_feed_info_free(void)
+{
+    if (!flip_feed_info)
+    {
+        return;
+    }
+    free(flip_feed_info);
+    flip_feed_info = NULL;
+}
+
+void free_variable_item_list(void)
+{
+    if (app_instance->variable_item_list)
+    {
+        variable_item_list_free(app_instance->variable_item_list);
+        app_instance->variable_item_list = NULL;
+        view_dispatcher_remove_view(app_instance->view_dispatcher, FlipSocialViewVariableItemList);
+    }
+}
+
+void free_submenu(void)
+{
+    if (!app_instance)
+    {
+        return;
+    }
+    if (app_instance->submenu)
+    {
+        submenu_free(app_instance->submenu);
+        app_instance->submenu = NULL;
+        view_dispatcher_remove_view(app_instance->view_dispatcher, FlipSocialViewSubmenu);
+    }
+}

+ 15 - 0
flip_social/alloc/free.h

@@ -0,0 +1,15 @@
+#pragma once
+#include <flip_social.h>
+#include <callback/flip_social_callback.h>
+void free_all(bool should_free_variable_item_list, bool should_free_submenu);
+void free_text_input();
+void flip_social_free_explore_dialog();
+void flip_social_free_friends_dialog();
+void flip_social_free_messages_dialog();
+void flip_social_free_compose_dialog();
+void flip_social_free_feed_view();
+void free_about_widget(bool is_logged_in);
+void flip_social_free_friends(void);
+void flip_feed_info_free(void);
+void free_variable_item_list(void);
+void free_submenu(void);

+ 41 - 23
flip_social/app.c

@@ -17,21 +17,24 @@ int32_t main_flip_social(void *p)
     if (!app_instance)
     {
         // Allocation failed
+        FURI_LOG_E(TAG, "Failed to allocate FlipSocialApp");
         return -1; // Indicate failure
     }
 
-    if (!flipper_http_ping())
+    // check if board is connected (Derek Jamison)
+    uint8_t counter = 10;
+    // initialize the http
+    if (flipper_http_init(flipper_http_rx_callback, app_instance))
     {
-        FURI_LOG_E(TAG, "Failed to ping the device");
-        return -1;
-    }
+        fhttp.state = INACTIVE; // set inactive for the ping
+
+        if (!flipper_http_ping())
+        {
+            FURI_LOG_E(TAG, "Failed to ping the device");
+            return -1;
+        }
 
-    // Thanks to Derek Jamison for the following edits
-    if (app_instance->wifi_ssid_logged_out != NULL &&
-        app_instance->wifi_password_logged_out != NULL)
-    {
         // Try to wait for pong response.
-        uint8_t counter = 10;
         while (fhttp.state == INACTIVE && --counter > 0)
         {
             FURI_LOG_D(TAG, "Waiting for PONG");
@@ -40,20 +43,35 @@ int32_t main_flip_social(void *p)
 
         if (counter == 0)
         {
-            DialogsApp *dialogs = furi_record_open(RECORD_DIALOGS);
-            DialogMessage *message = dialog_message_alloc();
-            dialog_message_set_header(
-                message, "[FlipperHTTP Error]", 64, 0, AlignCenter, AlignTop);
-            dialog_message_set_text(
-                message,
-                "Ensure your WiFi Developer\nBoard or Pico W is connected\nand the latest FlipperHTTP\nfirmware is installed.",
-                0,
-                63,
-                AlignLeft,
-                AlignBottom);
-            dialog_message_show(dialogs, message);
-            dialog_message_free(message);
-            furi_record_close(RECORD_DIALOGS);
+            easy_flipper_dialog("FlipperHTTP Error", "Ensure your WiFi Developer\nBoard or Pico W is connected\nand the latest FlipperHTTP\nfirmware is installed.");
+        }
+        else
+        {
+            save_char("is_connected", "true");
+        }
+
+        flipper_http_deinit();
+    }
+    else
+    {
+        easy_flipper_dialog("FlipperHTTP Error", "The UART is likely busy.\nEnsure you have the correct\nflash for your board then\nrestart your Flipper Zero.");
+    }
+
+    // if counter is not 0, check notifications
+    if (counter != 0)
+    {
+        char is_connected[5];
+        char is_logged_in[5];
+        char is_notifications[5];
+        load_char("is_connected", is_connected, 5);
+        load_char("is_logged_in", is_logged_in, 5);
+        load_char("user_notifications", is_notifications, 5);
+
+        if (strcmp(is_connected, "true") == 0 &&
+            strcmp(is_notifications, "on") == 0 &&
+            strcmp(is_logged_in, "true") == 0)
+        {
+            flip_social_home_notification();
         }
     }
 

+ 2 - 2
flip_social/application.fam

@@ -7,8 +7,8 @@ App(
     fap_icon="app_new.png",
     fap_category="GPIO/FlipperHTTP",
     fap_icon_assets="assets",
-    fap_author="jblanked",
+    fap_author="JBlanked",
     fap_weburl="https://github.com/jblanked/FlipSocial",
-    fap_version="0.8",
+    fap_version="1.0.2",
     fap_description="Social media platform for the Flipper Zero.",
 )

+ 18 - 0
flip_social/assets/CHANGELOG.md

@@ -1,3 +1,21 @@
+## 1.0.2
+- Fixed the Feed Type and Notifications toggles, in the User Settings, to work as intended. 
+- Added flip status to each feed post.
+
+## 1.0.1  
+- Fixed a freeze that occurred when sending a direct message.
+
+## 1.0 - Official Release
+- Final memory improvements.
+- Updated the New Message option in the Messages view to allow users to search for recipients to send messages to.
+- Updated the display of the Feed to use a custom view and custom font, and to show how long ago each post was created.
+- Organized the files saved/utilized in the apps_data folder.
+- Fixed bugs in the Direct Messaging View.
+- Fixed bugs in the Pre-Save View.
+- Added home announcements and notifications.
+- Added User Settings (Notifications and Feed Type). With the Feed Type option, users can choose between a private feed, which only consists of posts from their friends, and a global feed, which consists of posts from all Flippers. The Notifications settings allow users to turn home notifications on and off.
+- Updated the Feed to be "endless." Once you reach the end of the feed, previous posts will load in sets of 20.
+
 ## 0.8 - New Features
 - Added support for RPC_KEYBOARD (thanks to Derek Jamison).
 - Introduced a bio feature in the Profile section: View and update your bio.

+ 7 - 71
flip_social/assets/README.md

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

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


ファイルの差分が大きいため隠しています
+ 232 - 514
flip_social/callback/flip_social_callback.c


+ 7 - 4
flip_social/callback/flip_social_callback.h

@@ -7,6 +7,8 @@
 #include <explore/flip_social_explore.h>
 #include <feed/flip_social_feed.h>
 #include <flip_storage/flip_social_storage.h>
+#include <alloc/free.h>
+#include <alloc/alloc.h>
 
 /**
  * @brief Navigation callback to go back to the submenu Logged out.
@@ -252,6 +254,7 @@ void flip_social_logged_in_messages_new_message_updated(void *context);
  * @return void
  */
 void flip_social_text_input_logged_out_register_item_selected(void *context, uint32_t index);
+void flip_social_logged_in_user_settings_item_selected(void *context, uint32_t index);
 void flip_social_logged_in_explore_updated(void *context);
 void flip_social_logged_in_message_users_updated(void *context);
 
@@ -296,9 +299,9 @@ void flip_social_loader_draw_callback(Canvas *canvas, void *model);
 void flip_social_loader_init(View *view);
 
 void flip_social_loader_free_model(View *view);
-
 bool flip_social_custom_event_callback(void *context, uint32_t index);
-
-bool messages_dialog_alloc(bool free_first);
-bool feed_dialog_alloc();
+void messages_dialog_callback(DialogExResult result, void *context);
+void feed_dialog_callback(DialogExResult result, void *context);
+//
+bool flip_social_home_notification();
 #endif

+ 20 - 0
flip_social/easy_flipper/easy_flipper.c

@@ -1,5 +1,25 @@
 #include <easy_flipper/easy_flipper.h>
 
+void easy_flipper_dialog(
+    char *header,
+    char *text)
+{
+    DialogsApp *dialogs = furi_record_open(RECORD_DIALOGS);
+    DialogMessage *message = dialog_message_alloc();
+    dialog_message_set_header(
+        message, header, 64, 0, AlignCenter, AlignTop);
+    dialog_message_set_text(
+        message,
+        text,
+        0,
+        63,
+        AlignLeft,
+        AlignBottom);
+    dialog_message_show(dialogs, message);
+    dialog_message_free(message);
+    furi_record_close(RECORD_DIALOGS);
+}
+
 /**
  * @brief Navigation callback for exiting the application
  * @param context The context - unused

+ 6 - 0
flip_social/easy_flipper/easy_flipper.h

@@ -8,6 +8,7 @@
 #include <gui/view.h>
 #include <gui/modules/submenu.h>
 #include <gui/view_dispatcher.h>
+#include <gui/elements.h>
 #include <gui/modules/menu.h>
 #include <gui/modules/submenu.h>
 #include <gui/modules/widget.h>
@@ -20,9 +21,14 @@
 #include <stdio.h>
 #include <string.h>
 #include <jsmn/jsmn.h>
+#include <jsmn/jsmn_furi.h>
 
 #define EASY_TAG "EasyFlipper"
 
+void easy_flipper_dialog(
+    char *header,
+    char *text);
+
 /**
  * @brief Navigation callback for exiting the application
  * @param context The context - unused

+ 35 - 68
flip_social/explore/flip_social_explore.c

@@ -2,19 +2,6 @@
 
 FlipSocialModel *flip_social_explore_alloc(void)
 {
-    if (!app_instance)
-    {
-        return NULL;
-    }
-
-    if (!app_instance->submenu_explore)
-    {
-        if (!easy_flipper_set_submenu(&app_instance->submenu_explore, FlipSocialViewLoggedInExploreSubmenu, "Explore", flip_social_callback_to_submenu_logged_in, &app_instance->view_dispatcher))
-        {
-            return NULL;
-        }
-    }
-
     // Allocate memory for each username only if not already allocated
     FlipSocialModel *explore = malloc(sizeof(FlipSocialModel));
     if (explore == NULL)
@@ -22,55 +9,38 @@ FlipSocialModel *flip_social_explore_alloc(void)
         FURI_LOG_E(TAG, "Failed to allocate memory for explore model.");
         return NULL;
     }
-    for (size_t i = 0; i < MAX_EXPLORE_USERS; i++)
-    {
-        if (explore->usernames[i] == NULL)
-        {
-            explore->usernames[i] = malloc(MAX_USER_LENGTH);
-            if (explore->usernames[i] == NULL)
-            {
-                FURI_LOG_E(TAG, "Failed to allocate memory for username %zu", i);
-                return NULL; // Return false on memory allocation failure
-            }
-        }
-    }
     return explore;
 }
 
 void flip_social_free_explore(void)
 {
-    if (!app_instance)
-    {
-        return;
-    }
-    if (app_instance->submenu_explore)
-    {
-        submenu_free(app_instance->submenu_explore);
-        app_instance->submenu_explore = NULL;
-        view_dispatcher_remove_view(app_instance->view_dispatcher, FlipSocialViewLoggedInExploreSubmenu);
-    }
-    if (!flip_social_explore)
+    if (flip_social_explore)
     {
-        return;
+        free(flip_social_explore);
+        flip_social_explore = NULL;
     }
-    free(flip_social_explore);
-    flip_social_explore = NULL;
 }
 
 // for now we're just listing the current users
 // as the feed is upgraded, then we can port more to the explore view
 bool flip_social_get_explore(void)
 {
-    if (fhttp.state == INACTIVE)
+    if (!flipper_http_init(flipper_http_rx_callback, app_instance))
     {
-        FURI_LOG_E(TAG, "HTTP state is INACTIVE");
+        FURI_LOG_E(TAG, "Failed to initialize FlipperHTTP");
         return false;
     }
+    char directory[128];
+    snprintf(directory, sizeof(directory), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_social/explore");
+
+    // Create the directory
+    Storage *storage = furi_record_open(RECORD_STORAGE);
+    storage_common_mkdir(storage, directory);
     char *keyword = !app_instance->explore_logged_in || strlen(app_instance->explore_logged_in) == 0 ? "a" : app_instance->explore_logged_in;
     snprintf(
         fhttp.file_path,
         sizeof(fhttp.file_path),
-        STORAGE_EXT_PATH_PREFIX "/apps_data/flip_social/%s.json",
+        STORAGE_EXT_PATH_PREFIX "/apps_data/flip_social/explore/%s.json",
         keyword);
 
     fhttp.save_received_data = true;
@@ -88,30 +58,33 @@ bool flip_social_get_explore(void)
 }
 bool flip_social_get_explore_2(void)
 {
-    if (fhttp.state == INACTIVE)
+    if (!app_instance)
+    {
+        return false;
+    }
+    if (!flipper_http_init(flipper_http_rx_callback, app_instance))
     {
-        FURI_LOG_E(TAG, "HTTP state is INACTIVE");
+        FURI_LOG_E(TAG, "Failed to initialize FlipperHTTP");
         return false;
     }
+    char directory[128];
+    snprintf(directory, sizeof(directory), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_social/explore");
+
+    // Create the directory
+    Storage *storage = furi_record_open(RECORD_STORAGE);
+    storage_common_mkdir(storage, directory);
     char *keyword = !app_instance->message_users_logged_in || strlen(app_instance->message_users_logged_in) == 0 ? "a" : app_instance->message_users_logged_in;
     snprintf(
         fhttp.file_path,
         sizeof(fhttp.file_path),
-        STORAGE_EXT_PATH_PREFIX "/apps_data/flip_social/%s.json",
+        STORAGE_EXT_PATH_PREFIX "/apps_data/flip_social/explore/%s.json",
         keyword);
 
     fhttp.save_received_data = true;
     auth_headers_alloc();
     char url[256];
     snprintf(url, sizeof(url), "https://www.flipsocial.net/api/user/explore/%s/%d/", keyword, MAX_EXPLORE_USERS);
-    if (!flipper_http_get_request_with_headers(url, auth_headers))
-    {
-        FURI_LOG_E(TAG, "Failed to send HTTP request for explore");
-        fhttp.state = ISSUE;
-        return false;
-    }
-    fhttp.state = RECEIVING;
-    return true;
+    return flipper_http_get_request_with_headers(url, auth_headers);
 }
 
 bool flip_social_parse_json_explore()
@@ -121,15 +94,11 @@ bool flip_social_parse_json_explore()
     if (user_data == NULL)
     {
         FURI_LOG_E(TAG, "Failed to load received data from file.");
+        flipper_http_deinit();
         return false;
     }
-    char *data_cstr = (char *)furi_string_get_cstr(user_data);
-    if (data_cstr == NULL)
-    {
-        FURI_LOG_E(TAG, "Failed to get C-string from FuriString.");
-        furi_string_free(user_data);
-        return false;
-    }
+
+    flipper_http_deinit();
 
     // Allocate memory for each username only if not already allocated
     flip_social_explore = flip_social_explore_alloc();
@@ -137,7 +106,6 @@ bool flip_social_parse_json_explore()
     {
         FURI_LOG_E(TAG, "Failed to allocate memory for explore usernames.");
         furi_string_free(user_data);
-        free(data_cstr);
         return false;
     }
 
@@ -145,22 +113,21 @@ bool flip_social_parse_json_explore()
     flip_social_explore->count = 0;
 
     // set submenu
-    submenu_reset(app_instance->submenu_explore);
-    submenu_set_header(app_instance->submenu_explore, "Explore");
+    submenu_reset(app_instance->submenu);
+    submenu_set_header(app_instance->submenu, "Explore");
     // Parse the JSON array of usernames
     for (size_t i = 0; i < MAX_EXPLORE_USERS; i++)
     {
-        char *username = get_json_array_value("users", i, data_cstr, 64); // currently its 53 tokens (with max explore users at 50)
+        FuriString *username = get_json_array_value_furi("users", i, user_data); // currently its 53 tokens (with max explore users at 50)
         if (username == NULL)
         {
             break;
         }
-        snprintf(flip_social_explore->usernames[i], MAX_USER_LENGTH, "%s", username);
-        submenu_add_item(app_instance->submenu_explore, flip_social_explore->usernames[i], FlipSocialSubmenuExploreIndexStartIndex + i, flip_social_callback_submenu_choices, app_instance);
+        snprintf(flip_social_explore->usernames[i], MAX_USER_LENGTH, "%s", furi_string_get_cstr(username));
+        submenu_add_item(app_instance->submenu, flip_social_explore->usernames[i], FlipSocialSubmenuExploreIndexStartIndex + i, flip_social_callback_submenu_choices, app_instance);
         flip_social_explore->count++;
-        free(username);
+        furi_string_free(username);
     }
-    free(data_cstr);
     furi_string_free(user_data);
     return flip_social_explore->count > 0;
 }

+ 82 - 112
flip_social/feed/flip_social_feed.c

@@ -1,15 +1,15 @@
 #include "flip_social_feed.h"
 
-bool flip_social_get_feed()
+bool flip_social_get_feed(bool alloc_http, int series_index)
 {
     if (!app_instance)
     {
         FURI_LOG_E(TAG, "FlipSocialApp is NULL");
         return false;
     }
-    if (fhttp.state == INACTIVE)
+    if (alloc_http && !flipper_http_init(flipper_http_rx_callback, app_instance))
     {
-        FURI_LOG_E(TAG, "HTTP state is INACTIVE");
+        FURI_LOG_E(TAG, "Failed to initialize FlipperHTTP");
         return false;
     }
     // Get the feed from the server
@@ -26,7 +26,14 @@ bool flip_social_get_feed()
     fhttp.save_received_data = true;
     auth_headers_alloc();
     char command[96];
-    snprintf(command, 96, "https://www.flipsocial.net/api/feed/50/%s/extended/", app_instance->login_username_logged_out);
+    if (strstr(flip_social_feed_type[flip_social_feed_type_index], "Global"))
+    {
+        snprintf(command, 96, "https://www.flipsocial.net/api/feed/%d/%s/%d/max/series/", MAX_FEED_ITEMS, app_instance->login_username_logged_out, series_index);
+    }
+    else
+    {
+        snprintf(command, 96, "https://www.flipsocial.net/api/feed/%d/%s/%d/max/friends/series/", MAX_FEED_ITEMS, app_instance->login_username_logged_out, series_index);
+    }
     if (!flipper_http_get_request_with_headers(command, auth_headers))
     {
         FURI_LOG_E(TAG, "Failed to send HTTP request for feed");
@@ -44,15 +51,10 @@ FlipSocialFeedMini *flip_social_parse_json_feed()
     if (feed_data == NULL)
     {
         FURI_LOG_E(TAG, "Failed to load received data from file.");
+        flipper_http_deinit();
         return NULL;
     }
-    char *data_cstr = (char *)furi_string_get_cstr(feed_data);
-    if (data_cstr == NULL)
-    {
-        FURI_LOG_E(TAG, "Failed to get C-string from FuriString.");
-        furi_string_free(feed_data);
-        return NULL;
-    }
+    flipper_http_deinit();
 
     FlipSocialFeedMini *feed_info = (FlipSocialFeedMini *)malloc(sizeof(FlipSocialFeedMini));
     if (!feed_info)
@@ -61,65 +63,40 @@ FlipSocialFeedMini *flip_social_parse_json_feed()
         return NULL;
     }
 
-    // Remove newlines
-    char *pos = data_cstr;
-    while ((pos = strchr(pos, '\n')) != NULL)
-    {
-        *pos = ' ';
-    }
-
     int feed_count = 0;
 
     // Iterate through the feed array
     for (int i = 0; i < MAX_FEED_ITEMS; i++)
     {
         // Parse each item in the array
-        char *item = get_json_array_value("feed", i, data_cstr, MAX_TOKENS);
+        FuriString *item = get_json_array_value_furi("feed", i, feed_data);
         if (item == NULL)
         {
             break;
         }
 
         // Extract individual fields from the JSON object
-        char *username = get_json_value("username", item, 40);
-        char *message = get_json_value("message", item, 40);
-        char *flipped = get_json_value("flipped", item, 40);
-        char *flips = get_json_value("flip_count", item, 40);
-        char *id = get_json_value("id", item, 40);
-
-        if (username == NULL || message == NULL || flipped == NULL || id == NULL)
+        FuriString *id = get_json_value_furi("id", item);
+        if (id == NULL)
         {
             FURI_LOG_E(TAG, "Failed to parse item fields.");
-            free(item);
-            free(username);
-            free(message);
-            free(flipped);
-            free(flips);
-            free(id);
+            furi_string_free(item);
+            furi_string_free(id);
             continue;
         }
-
-        if (!flip_social_save_post(id, item))
+        if (!flip_social_save_post(furi_string_get_cstr(id), furi_string_get_cstr(item)))
         {
             FURI_LOG_E(TAG, "Failed to save post.");
-            free(item);
-            free(username);
-            free(message);
-            free(flipped);
-            free(flips);
-            free(id);
+            furi_string_free(item);
+            furi_string_free(id);
             continue;
         }
         feed_count++;
-        feed_info->ids[i] = atoi(id);
+        feed_info->ids[i] = atoi(furi_string_get_cstr(id));
 
-        // Free allocated memory
-        free(item);
-        free(username);
-        free(message);
-        free(flipped);
-        free(flips);
-        free(id);
+        // Furi_string_free allocated memory
+        furi_string_free(item);
+        furi_string_free(id);
     }
 
     // Store the number of feed items
@@ -127,14 +104,13 @@ FlipSocialFeedMini *flip_social_parse_json_feed()
     feed_info->index = 0;
 
     furi_string_free(feed_data);
-    free(data_cstr);
     return feed_info;
 }
 
 bool flip_social_load_feed_post(int post_id)
 {
     char file_path[128];
-    snprintf(file_path, sizeof(file_path), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_social/feed_post_%d.json", post_id);
+    snprintf(file_path, sizeof(file_path), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_social/feed/feed_post_%d.json", post_id);
 
     // load the received data from the saved file
     FuriString *feed_data = flipper_http_load_from_file(file_path);
@@ -143,85 +119,82 @@ bool flip_social_load_feed_post(int post_id)
         FURI_LOG_E(TAG, "Failed to load received data from file.");
         return false;
     }
-    char *data_cstr = (char *)furi_string_get_cstr(feed_data);
-    if (data_cstr == NULL)
+
+    // Parse the feed post
+    if (flip_feed_item)
+    {
+        free(flip_feed_item);
+    }
+    else
     {
-        FURI_LOG_E(TAG, "Failed to get C-string from FuriString.");
-        furi_string_free(feed_data);
-        return false;
+        // first time
+        save_char("series_index", "1");
     }
 
-    // Parse the feed post
-    if (!flip_feed_item)
+    flip_feed_item = (FlipSocialFeedItem *)malloc(sizeof(FlipSocialFeedItem));
+    if (flip_feed_item == NULL)
     {
-        flip_feed_item = (FlipSocialFeedItem *)malloc(sizeof(FlipSocialFeedItem));
-        if (flip_feed_item == NULL)
-        {
-            FURI_LOG_E(TAG, "Failed to allocate memory for feed post.");
-            furi_string_free(feed_data);
-            free(data_cstr);
-            return false;
-        }
-        flip_feed_item->username = malloc(MAX_USER_LENGTH);
-        flip_feed_item->message = malloc(MAX_MESSAGE_LENGTH);
+        FURI_LOG_E(TAG, "Failed to allocate memory for feed post.");
+        furi_string_free(feed_data);
+        return false;
     }
 
     // Extract individual fields from the JSON object
-    char *username = get_json_value("username", data_cstr, 16);
-    char *message = get_json_value("message", data_cstr, 16);
-    char *flipped = get_json_value("flipped", data_cstr, 16);
-    char *flips = get_json_value("flip_count", data_cstr, 16);
-    char *id = get_json_value("id", data_cstr, 16);
-
-    if (username == NULL || message == NULL || flipped == NULL || id == NULL)
+    FuriString *username = get_json_value_furi("username", feed_data);
+    FuriString *message = get_json_value_furi("message", feed_data);
+    FuriString *flipped = get_json_value_furi("flipped", feed_data);
+    FuriString *flips = get_json_value_furi("flip_count", feed_data);
+    FuriString *date_created = get_json_value_furi("date_created", feed_data);
+    if (username == NULL || message == NULL || flipped == NULL || flips == NULL || date_created == NULL)
     {
         FURI_LOG_E(TAG, "Failed to parse item fields.");
-        free(username);
-        free(message);
-        free(flipped);
-        free(flips);
-        free(id);
-        free(data_cstr);
+        furi_string_free(username);
+        furi_string_free(message);
+        furi_string_free(flipped);
+        furi_string_free(flips);
+        furi_string_free(date_created);
         furi_string_free(feed_data);
         return false;
     }
 
     // Safely copy strings with bounds checking
-    snprintf(flip_feed_item->username, MAX_USER_LENGTH, "%s", username);
-    snprintf(flip_feed_item->message, MAX_MESSAGE_LENGTH, "%s", message);
+    snprintf(flip_feed_item->username, MAX_USER_LENGTH, "%s", furi_string_get_cstr(username));
+    snprintf(flip_feed_item->message, MAX_MESSAGE_LENGTH, "%s", furi_string_get_cstr(message));
+    snprintf(flip_feed_item->date_created, MAX_LINE_LENGTH, "%s", furi_string_get_cstr(date_created));
 
     // Store boolean and integer values
-    flip_feed_item->is_flipped = strstr(flipped, "true") != NULL;
-    flip_feed_item->id = atoi(id);
-    flip_feed_item->flips = atoi(flips);
+    flip_feed_item->is_flipped = strstr(furi_string_get_cstr(flipped), "true") ? true : false;
+    flip_feed_item->id = post_id;
+    flip_feed_item->flips = atoi(furi_string_get_cstr(flips));
 
     // Free allocated memory
-    free(username);
-    free(message);
-    free(flipped);
-    free(flips);
-    free(id);
-
+    furi_string_free(username);
+    furi_string_free(message);
+    furi_string_free(flipped);
+    furi_string_free(flips);
+    furi_string_free(date_created);
     furi_string_free(feed_data);
-    free(data_cstr);
-
     return true;
 }
 
-bool flip_social_load_initial_feed()
+bool flip_social_load_initial_feed(bool alloc_http, int series_index)
 {
     if (fhttp.state == INACTIVE)
     {
         FURI_LOG_E(TAG, "HTTP state is INACTIVE");
         return false;
     }
-    if (!easy_flipper_set_loading(&app_instance->loading, FlipSocialViewLoading, flip_social_callback_to_submenu_logged_in, &app_instance->view_dispatcher))
+    Loading *loading;
+    int32_t loading_view_id = 987654321; // Random ID
+    loading = loading_alloc();
+    if (!loading)
     {
         FURI_LOG_E(TAG, "Failed to set loading screen");
         return false;
     }
-    view_dispatcher_switch_to_view(app_instance->view_dispatcher, FlipSocialViewLoading);
-    if (flip_social_get_feed()) // start the async request
+    view_dispatcher_add_view(app_instance->view_dispatcher, loading_view_id, loading_get_view(loading));
+    view_dispatcher_switch_to_view(app_instance->view_dispatcher, loading_view_id);
+    if (flip_social_get_feed(alloc_http, series_index)) // start the async request
     {
         furi_timer_start(fhttp.get_timeout_timer, TIMEOUT_DURATION_TICKS);
         fhttp.state = RECEIVING;
@@ -231,8 +204,8 @@ bool flip_social_load_initial_feed()
         FURI_LOG_E(HTTP_TAG, "Failed to send request");
         fhttp.state = ISSUE;
         view_dispatcher_switch_to_view(app_instance->view_dispatcher, FlipSocialViewLoggedInSubmenu);
-        view_dispatcher_remove_view(app_instance->view_dispatcher, FlipSocialViewLoading);
-        loading_free(app_instance->loading);
+        view_dispatcher_remove_view(app_instance->view_dispatcher, loading_view_id);
+        loading_free(loading);
         return false;
     }
     while (fhttp.state == RECEIVING && furi_timer_is_running(fhttp.get_timeout_timer) > 0)
@@ -247,10 +220,9 @@ bool flip_social_load_initial_feed()
     if (!flip_feed_info || flip_feed_info->count < 1)
     {
         FURI_LOG_E(TAG, "Failed to parse JSON feed");
-        fhttp.state = ISSUE;
         view_dispatcher_switch_to_view(app_instance->view_dispatcher, FlipSocialViewLoggedInSubmenu);
-        view_dispatcher_remove_view(app_instance->view_dispatcher, FlipSocialViewLoading);
-        loading_free(app_instance->loading);
+        view_dispatcher_remove_view(app_instance->view_dispatcher, loading_view_id);
+        loading_free(loading);
         return false;
     }
 
@@ -258,24 +230,22 @@ bool flip_social_load_initial_feed()
     if (!flip_social_load_feed_post(flip_feed_info->ids[flip_feed_info->index]))
     {
         FURI_LOG_E(TAG, "Failed to load feed post");
-        fhttp.state = ISSUE;
         view_dispatcher_switch_to_view(app_instance->view_dispatcher, FlipSocialViewLoggedInSubmenu);
-        view_dispatcher_remove_view(app_instance->view_dispatcher, FlipSocialViewLoading);
-        loading_free(app_instance->loading);
+        view_dispatcher_remove_view(app_instance->view_dispatcher, loading_view_id);
+        loading_free(loading);
         return false;
     }
-    if (!feed_dialog_alloc())
+    if (!feed_view_alloc())
     {
         FURI_LOG_E(TAG, "Failed to allocate feed dialog");
-        fhttp.state = ISSUE;
         view_dispatcher_switch_to_view(app_instance->view_dispatcher, FlipSocialViewLoggedInSubmenu);
-        view_dispatcher_remove_view(app_instance->view_dispatcher, FlipSocialViewLoading);
-        loading_free(app_instance->loading);
+        view_dispatcher_remove_view(app_instance->view_dispatcher, loading_view_id);
+        loading_free(loading);
         return false;
     }
-    view_dispatcher_switch_to_view(app_instance->view_dispatcher, FlipSocialViewFeedDialog);
-    view_dispatcher_remove_view(app_instance->view_dispatcher, FlipSocialViewLoading);
-    loading_free(app_instance->loading);
+    view_dispatcher_switch_to_view(app_instance->view_dispatcher, FlipSocialViewLoggedInFeed);
+    view_dispatcher_remove_view(app_instance->view_dispatcher, loading_view_id);
+    loading_free(loading);
 
     return true;
 }

+ 2 - 2
flip_social/feed/flip_social_feed.h

@@ -4,8 +4,8 @@
 #include <callback/flip_social_callback.h>
 #include <flip_storage/flip_social_storage.h>
 
-bool flip_social_get_feed();
+bool flip_social_get_feed(bool alloc_http, int series_index);
 bool flip_social_load_feed_post(int post_id);
-bool flip_social_load_initial_feed();
+bool flip_social_load_initial_feed(bool alloc_http, int series_index);
 FlipSocialFeedMini *flip_social_parse_json_feed();
 #endif

+ 60 - 257
flip_social/flip_social.c

@@ -1,12 +1,14 @@
 #include "flip_social.h"
 
-FlipSocialModel *flip_social_friends = NULL;        // Store the friends
-FlipSocialModel2 *flip_social_message_users = NULL; // Store the users that have sent messages to the logged in user
-FlipSocialModel *flip_social_explore = NULL;        // Store the users to explore
-FlipSocialMessage *flip_social_messages = NULL;     // Store the messages between the logged in user and the selected user
-FlipSocialFeedMini *flip_feed_info = NULL;          // Store the feed info
-FlipSocialFeedItem *flip_feed_item = NULL;          // Store a feed item
-FlipSocialApp *app_instance = NULL;
+FlipSocialModel* flip_social_friends = NULL; // Store the friends
+FlipSocialModel2* flip_social_message_users =
+    NULL; // Store the users that have sent messages to the logged in user
+FlipSocialModel* flip_social_explore = NULL; // Store the users to explore
+FlipSocialMessage* flip_social_messages =
+    NULL; // Store the messages between the logged in user and the selected user
+FlipSocialFeedMini* flip_feed_info = NULL; // Store the feed info
+FlipSocialFeedItem* flip_feed_item = NULL; // Store a feed item
+FlipSocialApp* app_instance = NULL;
 
 bool flip_social_sent_login_request = false;
 bool flip_social_sent_register_request = false;
@@ -15,11 +17,15 @@ bool flip_social_register_success = false;
 bool flip_social_dialog_shown = false;
 bool flip_social_dialog_stop = false;
 bool flip_social_send_message = false;
-char *selected_message = NULL;
+char* selected_message = NULL;
 
 char auth_headers[256] = {0};
+char* flip_social_feed_type[] = {"Global", "Friends"};
+uint8_t flip_social_feed_type_index = 0;
+char* flip_social_notification_type[] = {"OFF", "ON"};
+uint8_t flip_social_notification_type_index = 0;
 
-void flip_social_loader_free_model(View *view);
+void flip_social_loader_free_model(View* view);
 
 /**
  * @brief Function to free the resources used by FlipSocialApp.
@@ -27,292 +33,89 @@ void flip_social_loader_free_model(View *view);
  * @param app The FlipSocialApp object to free.
  * @return void
  */
-void flip_social_app_free(FlipSocialApp *app)
-{
-    if (!app)
-    {
+void flip_social_app_free(FlipSocialApp* app) {
+    if(!app) {
         FURI_LOG_E(TAG, "FlipSocialApp is NULL");
         return;
     }
-    if (!app->view_dispatcher)
-    {
+    if(!app->view_dispatcher) {
         FURI_LOG_E(TAG, "ViewDispatcher is NULL");
         return;
     }
 
     // Free Submenu(s)
-    if (app->submenu_logged_out)
-    {
+    if(app->submenu_logged_out) {
         view_dispatcher_remove_view(app->view_dispatcher, FlipSocialViewLoggedOutSubmenu);
         submenu_free(app->submenu_logged_out);
     }
-    if (app->submenu_logged_in)
-    {
+    if(app->submenu_logged_in) {
         view_dispatcher_remove_view(app->view_dispatcher, FlipSocialViewLoggedInSubmenu);
         submenu_free(app->submenu_logged_in);
     }
     //
-    if (app->submenu_messages_user_choices)
-    {
-        view_dispatcher_remove_view(app->view_dispatcher, FlipSocialViewLoggedInMessagesUserChoices);
-        submenu_free(app->submenu_messages_user_choices);
-    }
-
-    // Free Variable Item List(s)
-    if (app->variable_item_list_logged_out_wifi_settings)
-    {
-        view_dispatcher_remove_view(app->view_dispatcher, FlipSocialViewLoggedOutWifiSettings);
-        variable_item_list_free(app->variable_item_list_logged_out_wifi_settings);
-    }
-    if (app->variable_item_list_logged_out_login)
-    {
-        view_dispatcher_remove_view(app->view_dispatcher, FlipSocialViewLoggedOutLogin);
-        variable_item_list_free(app->variable_item_list_logged_out_login);
-    }
-    if (app->variable_item_list_logged_out_register)
-    {
-        view_dispatcher_remove_view(app->view_dispatcher, FlipSocialViewLoggedOutRegister);
-        variable_item_list_free(app->variable_item_list_logged_out_register);
-    }
-    if (app->variable_item_list_logged_in_profile)
-    {
-        view_dispatcher_remove_view(app->view_dispatcher, FlipSocialViewLoggedInProfile);
-        variable_item_list_free(app->variable_item_list_logged_in_profile);
-    }
-    if (app->variable_item_list_logged_in_settings)
-    {
-        view_dispatcher_remove_view(app->view_dispatcher, FlipSocialViewLoggedInSettings);
-        variable_item_list_free(app->variable_item_list_logged_in_settings);
-    }
-    if (app->variable_item_list_logged_in_settings_wifi)
-    {
-        view_dispatcher_remove_view(app->view_dispatcher, FlipSocialViewLoggedInSettingsWifi);
-        variable_item_list_free(app->variable_item_list_logged_in_settings_wifi);
-    }
-
-    // Free Text Input(s)
-    if (app->text_input_logged_out_wifi_settings_ssid)
-    {
-        view_dispatcher_remove_view(app->view_dispatcher, FlipSocialViewLoggedOutWifiSettingsSSIDInput);
-        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);
-        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);
-        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);
-        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);
-        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);
-        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);
-        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);
-        text_input_free(app->text_input_logged_in_change_password);
-    }
-    if (app->text_input_logged_in_change_bio)
-    {
-        view_dispatcher_remove_view(app->view_dispatcher, FlipSocialViewLoggedInChangeBioInput);
-        text_input_free(app->text_input_logged_in_change_bio);
-    }
-    if (app->text_input_logged_in_compose_pre_save_input)
-    {
-        view_dispatcher_remove_view(app->view_dispatcher, FlipSocialViewLoggedInComposeAddPreSaveInput);
-        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);
-        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);
-        text_input_free(app->text_input_logged_in_wifi_settings_password);
-    }
-    if (app->text_input_logged_in_messages_new_message)
-    {
-        view_dispatcher_remove_view(app->view_dispatcher, FlipSocialViewLoggedInMessagesNewMessageInput);
-        text_input_free(app->text_input_logged_in_messages_new_message);
-    }
-    if (app->text_input_logged_in_messages_new_message_user_choices)
-    {
-        view_dispatcher_remove_view(app->view_dispatcher, FlipSocialViewLoggedInMessagesNewMessageUserChoicesInput);
-        text_input_free(app->text_input_logged_in_messages_new_message_user_choices);
-    }
-    if (app->text_input_logged_in_explore)
-    {
-        view_dispatcher_remove_view(app->view_dispatcher, FlipSocialViewLoggedInExploreInput);
-        text_input_free(app->text_input_logged_in_explore);
-    }
-    if (app->text_input_logged_in_message_users)
-    {
-        view_dispatcher_remove_view(app->view_dispatcher, FlipSocialViewLoggedInMessageUsersInput);
-        text_input_free(app->text_input_logged_in_message_users);
-    }
 
     // Free Widget(s)
-    if (app->widget_result)
-    {
+    if(app->widget_result) {
         view_dispatcher_remove_view(app->view_dispatcher, FlipSocialViewWidgetResult);
         widget_free(app->widget_result);
     }
 
     // Free View(s)
-    if (app->view_loader)
-    {
+    if(app->view_loader) {
         view_dispatcher_remove_view(app->view_dispatcher, FlipSocialViewLoader);
         flip_social_loader_free_model(app->view_loader);
         view_free(app->view_loader);
     }
 
-    if (app->view_dispatcher)
-        view_dispatcher_free(app->view_dispatcher);
+    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)
+    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)
+    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)
+    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)
+    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)
+    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)
+    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->change_bio_logged_in)
-        free(app->change_bio_logged_in);
-    if (app->compose_pre_save_logged_in)
-        free(app->compose_pre_save_logged_in);
-    if (app->compose_pre_save_logged_in_temp_buffer)
+    if(app->change_bio_logged_in) free(app->change_bio_logged_in);
+    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->explore_logged_in)
-        free(app->explore_logged_in);
-    if (app->explore_logged_in_temp_buffer)
-        free(app->explore_logged_in_temp_buffer);
-    if (app->message_users_logged_in)
-        free(app->message_users_logged_in);
-    if (app->message_users_logged_in_temp_buffer)
-        free(app->message_users_logged_in_temp_buffer);
-    if (app->wifi_ssid_logged_in)
-        free(app->wifi_ssid_logged_in);
-    if (app->wifi_ssid_logged_in_temp_buffer)
-        free(app->wifi_ssid_logged_in_temp_buffer);
-    if (app->wifi_password_logged_in)
-        free(app->wifi_password_logged_in);
-    if (app->wifi_password_logged_in_temp_buffer)
-        free(app->wifi_password_logged_in_temp_buffer);
-    if (app->is_logged_in)
-        free(app->is_logged_in);
-    if (app->login_username_logged_in)
-        free(app->login_username_logged_in);
-    if (app->login_username_logged_in_temp_buffer)
-        free(app->login_username_logged_in_temp_buffer);
-    if (app->messages_new_message_logged_in)
-        free(app->messages_new_message_logged_in);
-    if (app->messages_new_message_logged_in_temp_buffer)
+    if(app->explore_logged_in) free(app->explore_logged_in);
+    if(app->explore_logged_in_temp_buffer) free(app->explore_logged_in_temp_buffer);
+    if(app->message_users_logged_in) free(app->message_users_logged_in);
+    if(app->message_users_logged_in_temp_buffer) free(app->message_users_logged_in_temp_buffer);
+    if(app->wifi_ssid_logged_in) free(app->wifi_ssid_logged_in);
+    if(app->wifi_ssid_logged_in_temp_buffer) free(app->wifi_ssid_logged_in_temp_buffer);
+    if(app->wifi_password_logged_in) free(app->wifi_password_logged_in);
+    if(app->wifi_password_logged_in_temp_buffer) free(app->wifi_password_logged_in_temp_buffer);
+    if(app->is_logged_in) free(app->is_logged_in);
+    if(app->login_username_logged_in) free(app->login_username_logged_in);
+    if(app->login_username_logged_in_temp_buffer) free(app->login_username_logged_in_temp_buffer);
+    if(app->messages_new_message_logged_in) free(app->messages_new_message_logged_in);
+    if(app->messages_new_message_logged_in_temp_buffer)
         free(app->messages_new_message_logged_in_temp_buffer);
-    if (app->message_user_choice_logged_in)
-        free(app->message_user_choice_logged_in);
-    if (app->message_user_choice_logged_in_temp_buffer)
+    if(app->message_user_choice_logged_in) free(app->message_user_choice_logged_in);
+    if(app->message_user_choice_logged_in_temp_buffer)
         free(app->message_user_choice_logged_in_temp_buffer);
-    if (selected_message)
-        free(selected_message);
-    if (app->explore_user_bio)
-        free(app->explore_user_bio);
-
-    // DeInit UART
-    flipper_http_deinit();
+    if(selected_message) free(selected_message);
+    if(app->explore_user_bio) free(app->explore_user_bio);
 
     // Free the app structure
-    if (app_instance)
-        free(app_instance);
-}
-
-void auth_headers_alloc(void)
-{
-    if (!app_instance)
-    {
-        snprintf(auth_headers, sizeof(auth_headers), "{\"Content-Type\":\"application/json\"}");
-        return;
-    }
-
-    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)
-    {
-        snprintf(auth_headers, sizeof(auth_headers), "{\"Content-Type\":\"application/json\",\"username\":\"%s\",\"password\":\"%s\"}", app_instance->login_username_logged_out, app_instance->login_password_logged_out);
-    }
-    else if (app_instance->login_username_logged_in && app_instance->change_password_logged_in && strlen(app_instance->login_username_logged_in) > 0 && strlen(app_instance->change_password_logged_in) > 0)
-    {
-        snprintf(auth_headers, sizeof(auth_headers), "{\"Content-Type\":\"application/json\",\"username\":\"%s\",\"password\":\"%s\"}", app_instance->login_username_logged_in, app_instance->change_password_logged_in);
-    }
-    else
-    {
-        snprintf(auth_headers, sizeof(auth_headers), "{\"Content-Type\":\"application/json\"}");
-    }
-}
-
-FlipSocialFeedMini *flip_feed_info_alloc(void)
-{
-    FlipSocialFeedMini *feed_info = (FlipSocialFeedMini *)malloc(sizeof(FlipSocialFeedMini));
-    if (!feed_info)
-    {
-        FURI_LOG_E(TAG, "Failed to allocate memory for feed_info");
-        return NULL;
-    }
-    feed_info->count = 0;
-    feed_info->index = 0;
-    return feed_info;
-}
-
-void flip_feed_info_free(void)
-{
-    if (!flip_feed_info)
-    {
-        return;
-    }
-    free(flip_feed_info);
-    flip_feed_info = NULL;
+    if(app_instance) free(app_instance);
 }

+ 223 - 205
flip_social/flip_social.h

@@ -7,42 +7,50 @@
 #include <flipper_http/flipper_http.h>
 #include <input/input.h>
 #include <flip_social_icons.h>
-#define TAG "FlipSocial"
+#include <font/font.h>
+
+#define TAG         "FlipSocial"
+#define VERSION_TAG TAG " v1.0.2"
 
 #define MAX_PRE_SAVED_MESSAGES 20 // Maximum number of pre-saved messages
-#define MAX_MESSAGE_LENGTH 100    // Maximum length of a message in the feed
-#define MAX_EXPLORE_USERS 50      // Maximum number of users to explore
-#define MAX_USER_LENGTH 32        // Maximum length of a username
-#define MAX_FRIENDS 50            // Maximum number of friends
-#define MAX_TOKENS 576            // Adjust based on expected JSON tokens
-#define MAX_FEED_ITEMS 50         // Maximum number of feed items
-#define MAX_LINE_LENGTH 30
-#define MAX_MESSAGE_USERS 40 // Maximum number of users to display in the submenu
-#define MAX_MESSAGES 20      // Maximum number of meesages between each user
+#define MAX_MESSAGE_LENGTH     100 // Maximum length of a message in the feed
+#define MAX_EXPLORE_USERS      50 // Maximum number of users to explore
+#define MAX_USER_LENGTH        32 // Maximum length of a username
+#define MAX_FRIENDS            50 // Maximum number of friends
+#define MAX_FEED_ITEMS         20 // Maximum number of feed items
+#define MAX_LINE_LENGTH        27
+#define MAX_MESSAGE_USERS      40 // Maximum number of users to display in the submenu
+#define MAX_MESSAGES           20 // Maximum number of meesages between each user
 
 #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"
+#define PRE_SAVED_MESSAGES_PATH \
+    STORAGE_EXT_PATH_PREFIX "/apps_data/flip_social/pre_saved_messages.txt"
 
 // 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
+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
+    FlipSocialSubmenuLoggedInIndexUserSettings, // click to go to the user settings screen
     //
-    FlipSocialSubmenuLoggedInIndexProfile,  // click to go to the profile screen
-    FlipSocialSubmenuExploreIndex,          // click to go to the explore
-    FlipSocialSubmenuLoggedInIndexFeed,     // click to go to the feed screen
+    FlipSocialSubmenuLoggedInIndexAbout, // click to go to the about screen
+    FlipSocialSubmenuLoggedInIndexWifiSettings, // click to go to the wifi settings screen
+    //
+    FlipSocialSubmenuLoggedInIndexProfile, // click to go to the profile screen
+    FlipSocialSubmenuExploreIndex, // click to go to the explore
+    FlipSocialSubmenuLoggedInIndexFeed, // click to go to the feed screen
     FlipSocialSubmenuLoggedInIndexMessages, // click to go to the messages screen
-    FlipSocialSubmenuLoggedInIndexCompose,  // click to go to the compose screen
+    FlipSocialSubmenuLoggedInIndexCompose, // click to go to the compose screen
     FlipSocialSubmenuLoggedInIndexSettings, // click to go to the settings screen
     FlipSocialSubmenuLoggedInSignOutButton, // click to sign out
     //
     FlipSocialSubmenuLoggedInIndexMessagesNewMessage, // click to add a new message
     //
-    FlipSocialSubmenuComposeIndexAddPreSave,       // click to add a pre-saved message
-    FlipSocialSubemnuComposeIndexStartIndex = 100, // starting index for the first pre saved message
+    FlipSocialSubmenuComposeIndexAddPreSave, // click to add a pre-saved message
+    FlipSocialSubemnuComposeIndexStartIndex =
+        100, // starting index for the first pre saved message
     //
     FlipSocialSubmenuExploreIndexStartIndex = 200, // starting index for the users to explore
     //
@@ -50,274 +58,282 @@ typedef enum
     //
     FlipSocialSubmenuLoggedInIndexMessagesUsersStart = 600, // starting index for the messages
     //
-    FlipSocialSubmenuLoggedInIndexMessagesUserChoicesIndexStart = 800, // click to select a user to message
+    FlipSocialSubmenuLoggedInIndexMessagesUserChoicesIndexStart =
+        800, // click to select a user to message
 } FlipSocialSubmenuIndex;
 
 // Define the ScriptPlaylist structure
-typedef struct
-{
-    char *messages[MAX_PRE_SAVED_MESSAGES];
+typedef struct {
+    char messages[MAX_PRE_SAVED_MESSAGES][MAX_MESSAGE_LENGTH];
     size_t count;
     size_t index;
 } PreSavedPlaylist;
 
 // Define a FlipSocialFeed individual item
-typedef struct
-{
-    char *username;
-    char *message;
+typedef struct {
+    char username[MAX_USER_LENGTH];
+    char message[MAX_MESSAGE_LENGTH];
+    char date_created[MAX_LINE_LENGTH];
     bool is_flipped;
     int id;
     int flips;
 } FlipSocialFeedItem;
 
-typedef struct
-{
+typedef struct {
     int ids[MAX_FEED_ITEMS];
     size_t count;
     size_t index;
+    int series_index;
 } FlipSocialFeedMini;
 
-typedef struct
-{
-    char *usernames[MAX_EXPLORE_USERS];
+typedef struct {
+    char usernames[MAX_EXPLORE_USERS][MAX_USER_LENGTH];
     int count;
     int index;
 } FlipSocialModel;
 
-typedef struct
-{
-    char *usernames[MAX_MESSAGE_USERS];
+typedef struct {
+    char usernames[MAX_MESSAGE_USERS][MAX_USER_LENGTH];
     int count;
     int index;
 } FlipSocialModel2;
 
-typedef struct
-{
-    char *usernames[MAX_MESSAGES];
-    char *messages[MAX_MESSAGES];
+typedef struct {
+    char usernames[MAX_MESSAGES][MAX_USER_LENGTH];
+    char messages[MAX_MESSAGES][MAX_MESSAGE_LENGTH];
     int count;
     int index;
 } FlipSocialMessage;
 
 // Define views for our Hello World application
-typedef enum
-{
-    FlipSocialViewLoggedOutSubmenu,      // The menu if the user is not logged in
-    FlipSocialViewLoggedOutLogin,        // The login screen
-    FlipSocialViewLoggedOutRegister,     // The register screen
-    FlipSocialViewLoggedOutAbout,        // The about screen
+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
+    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
+    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
+    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
     //
-    FlipSocialViewLoggedInChangeBioInput,         // Text input screen for bio input on profile screen
-    FlipSocialViewLoggedInChangePasswordInput,    // Text input screen for password input on change password screen
+    FlipSocialViewLoggedInChangeBioInput, // Text input screen for bio input on profile 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
+    FlipSocialViewLoggedInMessagesNewMessageInput, // Text input screen for new message input on messages screen
     FlipSocialViewLoggedInMessagesNewMessageUserChoicesInput, // Text input screen for new message input on messages screen
-    FlipSocialViewLoggedInMessagesUserChoices,                // the view after clicking [New Message] - select a user to message, then direct to input view
-    FlipSocialViewLoggedInExploreInput,                       // Text input screen for explore input on explore screen
+    FlipSocialViewLoggedInMessagesUserChoices, // the view after clicking [New Message] - select a user to message, then direct to input view
+    FlipSocialViewLoggedInExploreInput, // Text input screen for explore input on explore screen
     FlipSocialViewLoggedInMessageUsersInput,
     //
-    FlipSocialViewLoggedInSettingsAbout,             // The about screen
-    FlipSocialViewLoggedInSettingsWifi,              // The wifi settings screen
-    FlipSocialViewLoggedInWifiSettingsSSIDInput,     // Text input screen for SSID input on wifi screen
+    FlipSocialViewLoggedInSettingsAbout, // The about screen
+    FlipSocialViewLoggedInSettingsWifi, // The wifi settings screen
+    FlipSocialViewLoggedInSettingsUser, // The user settings screen
+    FlipSocialViewLoggedInWifiSettingsSSIDInput, // Text input screen for SSID input on wifi screen
     FlipSocialViewLoggedInWifiSettingsPasswordInput, // Text input screen for Password input on wifi screen
     //
     FlipSocialViewLoggedInSignOut, // The view after clicking the sign out button
     //
-    FlipSocialViewLoggedInExploreSubmenu,  // The view after clicking the explore button
-    FlipSocialViewLoggedInFriendsSubmenu,  // The view after clicking the friends button on the profile screen
+    FlipSocialViewLoggedInExploreSubmenu, // The view after clicking the explore button
+    FlipSocialViewLoggedInFriendsSubmenu, // The view after clicking the friends button on the profile screen
     FlipSocialViewLoggedInMessagesSubmenu, // The view after clicking the messages button on the profile screen
     //
     FlipSocialViewLoading, // The loading screen
     //
     FlipSocialViewWidgetResult, // The text box that displays the random fact
-    FlipSocialViewLoader,       // The loader screen retrieves data from the internet
+    FlipSocialViewLoader, // The loader screen retrieves data from the internet
     //
-    FlipSocialViewExploreDialog,  // The dialog for the explore screen
-    FlipSocialViewFriendsDialog,  // The dialog for the friends screen
+    FlipSocialViewExploreDialog, // The dialog for the explore screen
+    FlipSocialViewFriendsDialog, // The dialog for the friends screen
     FlipSocialViewMessagesDialog, // The dialog for the messages screen
-    FlipSocialViewComposeDialog,  // The dialog for the compose screen
-    FlipSocialViewFeedDialog,     // The dialog for the feed screen
+    FlipSocialViewComposeDialog, // The dialog for the compose screen
+    //
+    FlipSocialViewTextInput, // The text input screen
+    FlipSocialViewVariableItemList,
+    //
+    FlipSocialViewSubmenu,
 } FlipSocialView;
 
 // Define the application structure
-typedef struct
-{
-    View *view_loader;
-    Widget *widget_result;
-    //
-    ViewDispatcher *view_dispatcher;        // Switches between our views
-    Submenu *submenu_logged_out;            // The application submenu (logged out)
-    Submenu *submenu_logged_in;             // The application submenu (logged in)
-    Submenu *submenu_compose;               // The application submenu (compose)
-    Submenu *submenu_explore;               // The application submenu (explore)
-    Submenu *submenu_friends;               // The application submenu (friends)
-    Submenu *submenu_messages;              // The application submenu (messages)
-    Submenu *submenu_messages_user_choices; // The application submenu (messages user choices)
-    Widget *widget_logged_out_about;        // The about screen (logged out)
-    Widget *widget_logged_in_about;         // The about screen (logged in)
-
-    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
-
-    TextInput *text_input_logged_out_wifi_settings_ssid;     // Text input for ssid input on wifi settings screen
-    TextInput *text_input_logged_out_wifi_settings_password; // Text input for password input on wifi settings screen
-    TextInput *text_input_logged_out_login_username;         // Text input for username input on login screen
-    TextInput *text_input_logged_out_login_password;         // Text input for password input on login screen
-    TextInput *text_input_logged_out_register_username;      // Text input for username input on register screen
-    TextInput *text_input_logged_out_register_password;      // Text input for password input on register screen
-    TextInput *text_input_logged_out_register_password_2;    // Text input for password 2 input on register screen
-    //
-    TextInput *text_input_logged_in_change_password;        // Text input for password input on change password screen
-    TextInput *text_input_logged_in_change_bio;             // Text input for bio input on profile screen
-    TextInput *text_input_logged_in_compose_pre_save_input; // Text input for pre save input on compose screen
-    TextInput *text_input_logged_in_wifi_settings_ssid;     // Text input for ssid input on wifi settings screen
-    TextInput *text_input_logged_in_wifi_settings_password; // Text input for password input on wifi settings screen
-    //
-    TextInput *text_input_logged_in_messages_new_message;              // Text input for new message input on messages screen
-    TextInput *text_input_logged_in_messages_new_message_user_choices; //
-    //
-    TextInput *text_input_logged_in_explore; // Text input for explore input on explore screen
-    TextInput *text_input_logged_in_message_users;
-
-    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_profile_change_bio;      // Reference to the change bio configuration item
-    //
-    VariableItem *variable_item_logged_in_settings_about;         // Reference to the about configuration item
-    VariableItem *variable_item_logged_in_settings_wifi;          // Reference to the wifi settings configuration item
-    VariableItem *variable_item_logged_in_wifi_settings_ssid;     // Reference to the ssid configuration item
-    VariableItem *variable_item_logged_in_wifi_settings_password; // Reference to the password configuration item
-    //
-    VariableItem *variable_item_logged_in_profile_friends; // Reference to the friends configuration item
+typedef struct {
+    View* view_loader;
+    Widget* widget_result;
+    //
+    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;
+    //
+    Widget* widget_logged_out_about; // The about screen (logged out)
+    Widget* widget_logged_in_about; // The about screen (logged in)
+
+    VariableItemList* variable_item_list; // The main menu
+
+    TextInput* text_input; // The text input
+
+    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_profile_change_bio; // Reference to the change bio 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_settings_user; // Reference to the user settings configuration item
+    VariableItem*
+        variable_item_logged_in_user_settings_feed_type; // Reference to the feed type configuration item
+    VariableItem*
+        variable_item_logged_in_user_settings_notifications; // Reference to the notifications configuration item
+    VariableItem*
+        variable_item_logged_in_wifi_settings_ssid; // Reference to the ssid configuration item
+    VariableItem*
+        variable_item_logged_in_wifi_settings_password; // Reference to the password configuration item
+    //
+    VariableItem*
+        variable_item_logged_in_profile_friends; // Reference to the friends configuration item
     //
     PreSavedPlaylist pre_saved_messages; // Pre-saved messages for the feed screen
 
-    char *is_logged_in;         // Store the login status
+    char* is_logged_in; // Store the login status
     uint32_t is_logged_in_size; // Size of the login status buffer
 
-    char *login_username_logged_in;                     // Store the entered login username
-    char *login_username_logged_in_temp_buffer;         // Temporary buffer for login username text input
-    uint32_t login_username_logged_in_temp_buffer_size; // Size of the login username temporary buffer
+    char* login_username_logged_in; // Store the entered login username
+    char* login_username_logged_in_temp_buffer; // Temporary buffer for login username text input
+    uint32_t
+        login_username_logged_in_temp_buffer_size; // Size of the login username temporary buffer
 
-    char *change_bio_logged_in;                     // Store the entered bio
-    char *change_bio_logged_in_temp_buffer;         // Temporary buffer for bio text input
+    char* change_bio_logged_in; // Store the entered bio
+    char* change_bio_logged_in_temp_buffer; // Temporary buffer for bio text input
     uint32_t change_bio_logged_in_temp_buffer_size; // Size of the bio temporary buffer
     //
-    char *wifi_ssid_logged_out;                     // Store the entered wifi ssid
-    char *wifi_ssid_logged_out_temp_buffer;         // Temporary buffer for wifi ssid text input
+    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
+    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_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* 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_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_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* 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* 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
+    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
+    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
+    char* wifi_password_logged_in; // Store the entered wifi password
+    char* wifi_password_logged_in_temp_buffer; // Temporary buffer for wifi_password text input
     uint32_t wifi_password_logged_in_temp_buffer_size; // Size of the wifi_password temporary buffer
 
     //
-    char *messages_new_message_logged_in;                     // Store the entered new message
-    char *messages_new_message_logged_in_temp_buffer;         // Temporary buffer for new message text input
-    uint32_t messages_new_message_logged_in_temp_buffer_size; // Size of the new message temporary buffer
-
-    char *message_user_choice_logged_in;                     // Store the entered message to send to the selected user
-    char *message_user_choice_logged_in_temp_buffer;         // Temporary buffer for message to send to the selected user
-    uint32_t message_user_choice_logged_in_temp_buffer_size; // Size of the message to send to the selected user temporary buffer
+    char* messages_new_message_logged_in; // Store the entered new message
+    char* messages_new_message_logged_in_temp_buffer; // Temporary buffer for new message text input
+    uint32_t
+        messages_new_message_logged_in_temp_buffer_size; // Size of the new message temporary buffer
+
+    char* message_user_choice_logged_in; // Store the entered message to send to the selected user
+    char* message_user_choice_logged_in_temp_buffer; // Temporary buffer for message to send to the selected user
+    uint32_t
+        message_user_choice_logged_in_temp_buffer_size; // Size of the message to send to the selected user temporary buffer
     //
-    char *explore_logged_in;                     // Store the entered explore
-    char *explore_logged_in_temp_buffer;         // Temporary buffer for explore text input
+    char* explore_logged_in; // Store the entered explore
+    char* explore_logged_in_temp_buffer; // Temporary buffer for explore text input
     uint32_t explore_logged_in_temp_buffer_size; // Size of the explore temporary buffer
 
-    char *message_users_logged_in;                     // Store the entered message users
-    char *message_users_logged_in_temp_buffer;         // Temporary buffer for message users text input
+    char* message_users_logged_in; // Store the entered message users
+    char* message_users_logged_in_temp_buffer; // Temporary buffer for message users text input
     uint32_t message_users_logged_in_temp_buffer_size; // Size of the message users temporary buffer
 
-    Loading *loading; // The loading screen
-    DialogEx *dialog_explore;
-    DialogEx *dialog_friends;
-    DialogEx *dialog_messages;
-    DialogEx *dialog_compose;
-    DialogEx *dialog_feed;
+    DialogEx* dialog_explore;
+    DialogEx* dialog_friends;
+    DialogEx* dialog_messages;
+    DialogEx* dialog_compose;
+
+    View* view_feed;
 
-    char *explore_user_bio; // Store the bio of the selected user
+    char* explore_user_bio; // Store the bio of the selected user
 } FlipSocialApp;
 
-void flip_social_app_free(FlipSocialApp *app);
+void flip_social_app_free(FlipSocialApp* app);
 
-extern FlipSocialModel *flip_social_friends;        // Store the friends
-extern FlipSocialModel2 *flip_social_message_users; // Store the users that have sent messages to the logged in user
-extern FlipSocialModel *flip_social_explore;        // Store the users to explore
-extern FlipSocialMessage *flip_social_messages;     // Store the messages between the logged in user and the selected user
-extern FlipSocialFeedMini *flip_feed_info;          // Store the feed info
-extern FlipSocialFeedItem *flip_feed_item;          // Store a feed item
-extern FlipSocialApp *app_instance;
+extern FlipSocialModel* flip_social_friends; // Store the friends
+extern FlipSocialModel2*
+    flip_social_message_users; // Store the users that have sent messages to the logged in user
+extern FlipSocialModel* flip_social_explore; // Store the users to explore
+extern FlipSocialMessage*
+    flip_social_messages; // Store the messages between the logged in user and the selected user
+extern FlipSocialFeedMini* flip_feed_info; // Store the feed info
+extern FlipSocialFeedItem* flip_feed_item; // Store a feed item
+extern FlipSocialApp* app_instance;
 
 extern bool flip_social_sent_login_request;
 extern bool flip_social_sent_register_request;
@@ -326,10 +342,12 @@ extern bool flip_social_register_success;
 extern bool flip_social_dialog_shown;
 extern bool flip_social_dialog_stop;
 extern bool flip_social_send_message;
-extern char *selected_message;
+extern char* selected_message;
 extern char auth_headers[256];
-
-void auth_headers_alloc(void);
-FlipSocialFeedMini *flip_feed_info_alloc(void);
-void flip_feed_info_free(void);
-#endif
+//
+extern char* flip_social_feed_type[];
+extern uint8_t flip_social_feed_type_index;
+//
+extern char* flip_social_notification_type[];
+extern uint8_t flip_social_notification_type_index;
+#endif

+ 127 - 45
flip_social/flip_storage/flip_social_storage.c

@@ -8,6 +8,7 @@ void save_playlist(const PreSavedPlaylist *playlist)
         FURI_LOG_E(TAG, "Playlist is NULL");
         return;
     }
+
     // Create the directory for saving settings
     char directory_path[128];
     snprintf(directory_path, sizeof(directory_path), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_social");
@@ -29,7 +30,7 @@ void save_playlist(const PreSavedPlaylist *playlist)
     // Write each playlist message on a separate line
     for (size_t i = 0; i < playlist->count; ++i)
     {
-        // Add a newline character after each message
+        // Write the 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);
@@ -42,34 +43,23 @@ void save_playlist(const PreSavedPlaylist *playlist)
         }
     }
 
+    // Close the file and storage
     storage_file_close(file);
     storage_file_free(file);
     furi_record_close(RECORD_STORAGE);
 }
 
-// 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
-            }
-        }
-    }
+    // Clear existing data in the playlist
+    memset(playlist->messages, 0, sizeof(playlist->messages));
+    playlist->count = 0;
 
     // Open the storage
     Storage *storage = furi_record_open(RECORD_STORAGE);
@@ -80,29 +70,24 @@ bool load_playlist(PreSavedPlaylist *playlist)
         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
+        return false;
     }
 
-    // Initialize the playlist count
-    playlist->count = 0;
-
-    // Read the file byte by byte to simulate reading lines
+    // Read the file line by line
+    char line[MAX_MESSAGE_LENGTH] = {0};
+    size_t line_pos = 0;
     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
+    while (storage_file_read(file, &ch, 1) == 1)
     {
-        message_started = true;
-
-        if (ch == '\n' || message_pos >= (MAX_MESSAGE_LENGTH - 1)) // End of line or message is too long
+        if (ch == '\n' || line_pos >= (MAX_MESSAGE_LENGTH - 1)) // End of line or max length reached
         {
-            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;
+            line[line_pos] = '\0'; // Null-terminate the line
+            strncpy(playlist->messages[playlist->count], line, MAX_MESSAGE_LENGTH);
+            playlist->count++;
+            line_pos = 0;
 
-            // Ensure the playlist count does not exceed the maximum
+            // Ensure playlist count does not exceed maximum allowed
             if (playlist->count >= MAX_PRE_SAVED_MESSAGES)
             {
                 FURI_LOG_W(TAG, "Reached maximum playlist messages");
@@ -111,21 +96,16 @@ bool load_playlist(PreSavedPlaylist *playlist)
         }
         else
         {
-            playlist->messages[playlist->count][message_pos++] = ch; // Add character to current message
+            line[line_pos++] = ch;
         }
     }
 
-    // Handle the case where the last message does not end with a newline
-    if (message_started && message_pos > 0)
+    // Handle the last line if it does not end with a newline
+    if (line_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");
-        }
+        line[line_pos] = '\0'; // Null-terminate the last line
+        strncpy(playlist->messages[playlist->count], line, MAX_MESSAGE_LENGTH);
+        playlist->count++;
     }
 
     // Close the file and storage
@@ -135,6 +115,7 @@ bool load_playlist(PreSavedPlaylist *playlist)
 
     return true;
 }
+
 void save_settings(
     const char *ssid,
     const char *password,
@@ -396,13 +377,23 @@ bool load_settings(
     return true;
 }
 
-bool flip_social_save_post(char *post_id, char *json_feed_data)
+bool flip_social_save_post(const char *post_id, const char *json_feed_data)
 {
+    if (!post_id || !json_feed_data)
+    {
+        FURI_LOG_E(TAG, "Post ID or JSON feed data is NULL");
+        return false;
+    }
     Storage *storage = furi_record_open(RECORD_STORAGE);
     File *file = storage_file_alloc(storage);
 
+    // Create the directory for saving the feed
+    char directory_path[128];
+    snprintf(directory_path, sizeof(directory_path), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_social/feed");
+    storage_common_mkdir(storage, directory_path);
+
     char file_path[128];
-    snprintf(file_path, sizeof(file_path), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_social/feed_post_%s.json", post_id);
+    snprintf(file_path, sizeof(file_path), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_social/feed/feed_post_%s.json", post_id);
 
     if (!storage_file_open(file, file_path, FSAM_WRITE, FSOM_CREATE_ALWAYS))
     {
@@ -425,4 +416,95 @@ bool flip_social_save_post(char *post_id, char *json_feed_data)
     storage_file_free(file);
     furi_record_close(RECORD_STORAGE);
     return true;
+}
+//
+bool save_char(
+    const char *path_name, const char *value)
+{
+    if (!value)
+    {
+        return false;
+    }
+    // Create the directory for saving settings
+    char directory_path[256];
+    snprintf(directory_path, sizeof(directory_path), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_social/data");
+
+    // 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);
+    char file_path[256];
+    snprintf(file_path, sizeof(file_path), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_social/data/%s.txt", path_name);
+
+    // Open the file in write mode
+    if (!storage_file_open(file, file_path, FSAM_WRITE, FSOM_CREATE_ALWAYS))
+    {
+        FURI_LOG_E(HTTP_TAG, "Failed to open file for writing: %s", file_path);
+        storage_file_free(file);
+        furi_record_close(RECORD_STORAGE);
+        return false;
+    }
+
+    // Write the data to the file
+    size_t data_size = strlen(value) + 1; // Include null terminator
+    if (storage_file_write(file, value, data_size) != data_size)
+    {
+        FURI_LOG_E(HTTP_TAG, "Failed to append data to file");
+        storage_file_close(file);
+        storage_file_free(file);
+        furi_record_close(RECORD_STORAGE);
+        return false;
+    }
+
+    storage_file_close(file);
+    storage_file_free(file);
+    furi_record_close(RECORD_STORAGE);
+
+    return true;
+}
+
+bool load_char(
+    const char *path_name,
+    char *value,
+    size_t value_size)
+{
+    if (!value)
+    {
+        return false;
+    }
+    Storage *storage = furi_record_open(RECORD_STORAGE);
+    File *file = storage_file_alloc(storage);
+
+    char file_path[256];
+    snprintf(file_path, sizeof(file_path), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_social/data/%s.txt", path_name);
+
+    // Open the file for reading
+    if (!storage_file_open(file, file_path, FSAM_READ, FSOM_OPEN_EXISTING))
+    {
+        storage_file_free(file);
+        furi_record_close(RECORD_STORAGE);
+        return NULL; // Return false if the file does not exist
+    }
+
+    // Read data into the buffer
+    size_t read_count = storage_file_read(file, value, value_size);
+    if (storage_file_get_error(file) != FSE_OK)
+    {
+        FURI_LOG_E(HTTP_TAG, "Error reading from file.");
+        storage_file_close(file);
+        storage_file_free(file);
+        furi_record_close(RECORD_STORAGE);
+        return false;
+    }
+
+    // Ensure null-termination
+    value[read_count - 1] = '\0';
+
+    storage_file_close(file);
+    storage_file_free(file);
+    furi_record_close(RECORD_STORAGE);
+
+    return strlen(value) > 0;
 }

+ 9 - 1
flip_social/flip_storage/flip_social_storage.h

@@ -37,5 +37,13 @@ bool load_settings(
     char *is_logged_in,
     size_t is_logged_in_size);
 
-bool flip_social_save_post(char *post_id, char *json_feed_data);
+bool flip_social_save_post(const char *post_id, const char *json_feed_data);
+//
+
+bool save_char(
+    const char *path_name, const char *value);
+bool load_char(
+    const char *path_name,
+    char *value,
+    size_t value_size);
 #endif

+ 29 - 31
flip_social/flipper_http/flipper_http.c

@@ -1,8 +1,5 @@
 #include <flipper_http/flipper_http.h> // change this to where flipper_http.h is located
-FlipperHTTP fhttp;
-char rx_line_buffer[RX_LINE_BUFFER_SIZE];
-uint8_t file_buffer[FILE_BUFFER_SIZE];
-size_t file_buffer_len = 0;
+FlipperHTTP fhttp = {0};
 // Function to append received data to file
 // make sure to initialize the file path before calling this function
 bool flipper_http_append_to_file(
@@ -187,19 +184,19 @@ int32_t flipper_http_worker(void *context)
                 if (fhttp.save_bytes)
                 {
                     // Add byte to the buffer
-                    file_buffer[file_buffer_len++] = c;
+                    fhttp.file_buffer[fhttp.file_buffer_len++] = c;
                     // Write to file if buffer is full
-                    if (file_buffer_len >= FILE_BUFFER_SIZE)
+                    if (fhttp.file_buffer_len >= FILE_BUFFER_SIZE)
                     {
                         if (!flipper_http_append_to_file(
-                                file_buffer,
-                                file_buffer_len,
+                                fhttp.file_buffer,
+                                fhttp.file_buffer_len,
                                 fhttp.just_started_bytes,
                                 fhttp.file_path))
                         {
                             FURI_LOG_E(HTTP_TAG, "Failed to append data to file");
                         }
-                        file_buffer_len = 0;
+                        fhttp.file_buffer_len = 0;
                         fhttp.just_started_bytes = false;
                     }
                 }
@@ -210,17 +207,17 @@ int32_t flipper_http_worker(void *context)
                     // Handle line buffering
                     if (c == '\n' || rx_line_pos >= RX_LINE_BUFFER_SIZE - 1)
                     {
-                        rx_line_buffer[rx_line_pos] = '\0'; // Null-terminate the line
+                        fhttp.rx_line_buffer[rx_line_pos] = '\0'; // Null-terminate the line
 
                         // Invoke the callback with the complete line
-                        fhttp.handle_rx_line_cb(rx_line_buffer, fhttp.callback_context);
+                        fhttp.handle_rx_line_cb(fhttp.rx_line_buffer, fhttp.callback_context);
 
                         // Reset the line buffer position
                         rx_line_pos = 0;
                     }
                     else
                     {
-                        rx_line_buffer[rx_line_pos++] = c; // Add character to the line buffer
+                        fhttp.rx_line_buffer[rx_line_pos++] = c; // Add character to the line buffer
                     }
                 }
             }
@@ -382,6 +379,7 @@ bool flipper_http_init(FlipperHTTP_Callback callback, void *context)
     }
 
     // FURI_LOG_I(HTTP_TAG, "UART initialized successfully.");
+    fhttp.state = IDLE; // set idle for easy use
     return true;
 }
 
@@ -1114,27 +1112,27 @@ void flipper_http_rx_callback(const char *line, void *context)
                 const char marker[] = "[GET/END]";
                 const size_t marker_len = sizeof(marker) - 1; // Exclude null terminator
 
-                for (size_t i = 0; i <= file_buffer_len - marker_len; i++)
+                for (size_t i = 0; i <= fhttp.file_buffer_len - marker_len; i++)
                 {
                     // Check if the marker is found
-                    if (memcmp(&file_buffer[i], marker, marker_len) == 0)
+                    if (memcmp(&fhttp.file_buffer[i], marker, marker_len) == 0)
                     {
                         // Remove the marker by shifting the remaining data left
-                        size_t remaining_len = file_buffer_len - (i + marker_len);
-                        memmove(&file_buffer[i], &file_buffer[i + marker_len], remaining_len);
-                        file_buffer_len -= marker_len;
+                        size_t remaining_len = fhttp.file_buffer_len - (i + marker_len);
+                        memmove(&fhttp.file_buffer[i], &fhttp.file_buffer[i + marker_len], remaining_len);
+                        fhttp.file_buffer_len -= marker_len;
                         break;
                     }
                 }
 
                 // If there is data left in the buffer, append it to the file
-                if (file_buffer_len > 0)
+                if (fhttp.file_buffer_len > 0)
                 {
-                    if (!flipper_http_append_to_file(file_buffer, file_buffer_len, false, fhttp.file_path))
+                    if (!flipper_http_append_to_file(fhttp.file_buffer, fhttp.file_buffer_len, false, fhttp.file_path))
                     {
                         FURI_LOG_E(HTTP_TAG, "Failed to append data to file.");
                     }
-                    file_buffer_len = 0;
+                    fhttp.file_buffer_len = 0;
                 }
             }
 
@@ -1184,27 +1182,27 @@ void flipper_http_rx_callback(const char *line, void *context)
                 const char marker[] = "[POST/END]";
                 const size_t marker_len = sizeof(marker) - 1; // Exclude null terminator
 
-                for (size_t i = 0; i <= file_buffer_len - marker_len; i++)
+                for (size_t i = 0; i <= fhttp.file_buffer_len - marker_len; i++)
                 {
                     // Check if the marker is found
-                    if (memcmp(&file_buffer[i], marker, marker_len) == 0)
+                    if (memcmp(&fhttp.file_buffer[i], marker, marker_len) == 0)
                     {
                         // Remove the marker by shifting the remaining data left
-                        size_t remaining_len = file_buffer_len - (i + marker_len);
-                        memmove(&file_buffer[i], &file_buffer[i + marker_len], remaining_len);
-                        file_buffer_len -= marker_len;
+                        size_t remaining_len = fhttp.file_buffer_len - (i + marker_len);
+                        memmove(&fhttp.file_buffer[i], &fhttp.file_buffer[i + marker_len], remaining_len);
+                        fhttp.file_buffer_len -= marker_len;
                         break;
                     }
                 }
 
                 // If there is data left in the buffer, append it to the file
-                if (file_buffer_len > 0)
+                if (fhttp.file_buffer_len > 0)
                 {
-                    if (!flipper_http_append_to_file(file_buffer, file_buffer_len, false, fhttp.file_path))
+                    if (!flipper_http_append_to_file(fhttp.file_buffer, fhttp.file_buffer_len, false, fhttp.file_path))
                     {
                         FURI_LOG_E(HTTP_TAG, "Failed to append data to file.");
                     }
-                    file_buffer_len = 0;
+                    fhttp.file_buffer_len = 0;
                 }
             }
 
@@ -1332,7 +1330,7 @@ void flipper_http_rx_callback(const char *line, void *context)
         // for GET request, save data only if it's a bytes request
         fhttp.save_bytes = fhttp.is_bytes_request;
         fhttp.just_started_bytes = true;
-        file_buffer_len = 0;
+        fhttp.file_buffer_len = 0;
         return;
     }
     else if (strstr(line, "[POST/SUCCESS]") != NULL)
@@ -1344,7 +1342,7 @@ void flipper_http_rx_callback(const char *line, void *context)
         // for POST request, save data only if it's a bytes request
         fhttp.save_bytes = fhttp.is_bytes_request;
         fhttp.just_started_bytes = true;
-        file_buffer_len = 0;
+        fhttp.file_buffer_len = 0;
         return;
     }
     else if (strstr(line, "[PUT/SUCCESS]") != NULL)
@@ -1593,5 +1591,5 @@ void flipper_http_loading_task_2(bool (*http_request)(void),
     // Switch to the success view
     view_dispatcher_switch_to_view(*view_dispatcher, success_view_id);
     view_dispatcher_remove_view(*view_dispatcher, loading_view_id);
-    loading_free(loading);
+    // loading_free(loading);
 }

+ 5 - 5
flip_social/flipper_http/flipper_http.h

@@ -20,7 +20,7 @@
 #define UART_CH (momentum_settings.uart_esp_channel)    // UART channel
 #define TIMEOUT_DURATION_TICKS (8 * 1000) // 8 seconds (increased for Pico W)
 #define BAUDRATE (115200)                 // UART baudrate
-#define RX_BUF_SIZE 128                   // UART RX buffer size
+#define RX_BUF_SIZE 2048                  // UART RX buffer size
 #define RX_LINE_BUFFER_SIZE 7000          // UART RX line buffer size (increase for large responses)
 #define MAX_FILE_SHOW 7000                // Maximum data from file to show
 #define FILE_BUFFER_SIZE 512              // File buffer size
@@ -83,13 +83,13 @@ typedef struct
     bool save_received_data;   // Flag to save the received data to a file
 
     bool just_started_bytes; // Indicates if bytes data reception has just started
+
+    char rx_line_buffer[RX_LINE_BUFFER_SIZE];
+    uint8_t file_buffer[FILE_BUFFER_SIZE];
+    size_t file_buffer_len;
 } FlipperHTTP;
 
 extern FlipperHTTP fhttp;
-// Global static array for the line buffer
-extern char rx_line_buffer[RX_LINE_BUFFER_SIZE];
-extern uint8_t file_buffer[FILE_BUFFER_SIZE];
-extern size_t file_buffer_len;
 
 // fhttp.last_response holds the last received data from the UART
 

+ 316 - 0
flip_social/font/font.c

@@ -0,0 +1,316 @@
+#include <font/font.h>
+
+static const uint8_t u8g2_font_4x6_tf[];
+static const uint8_t u8g2_font_6x10_tf[];
+static const uint8_t u8g2_font_5x8_tf[];
+static const uint8_t u8g2_font_9x15_tf[];
+
+bool canvas_set_font_custom(Canvas *canvas, FontSize font_size)
+{
+    if (!canvas)
+    {
+        return false;
+    }
+    switch (font_size)
+    {
+    case FONT_SIZE_SMALL:
+        canvas_set_custom_u8g2_font(canvas, u8g2_font_4x6_tf);
+        break;
+    case FONT_SIZE_MEDIUM:
+        canvas_set_custom_u8g2_font(canvas, u8g2_font_5x8_tf);
+        break;
+    case FONT_SIZE_LARGE:
+        canvas_set_custom_u8g2_font(canvas, u8g2_font_6x10_tf);
+        break;
+    case FONT_SIZE_XLARGE:
+        canvas_set_custom_u8g2_font(canvas, u8g2_font_9x15_tf);
+        break;
+    default:
+        return false;
+    }
+    return true;
+}
+
+void canvas_draw_str_multi(Canvas *canvas, uint8_t x, uint8_t y, const char *str)
+{
+    if (!canvas || !str)
+    {
+        return;
+    }
+    elements_multiline_text(canvas, x, y, str);
+}
+
+/*
+  Fontname: -Misc-Fixed-Medium-R-Normal--6-60-75-75-C-40-ISO10646-1
+  Copyright: Public domain font.  Share and enjoy.
+  Glyphs: 191/919
+  BBX Build Mode: 0
+*/
+static const uint8_t u8g2_font_4x6_tf[] =
+    "\277\0\2\2\3\3\2\4\4\4\6\0\377\5\377\5\377\0\351\1\323\5\216 \5\200\315\0!\6\351\310"
+    "\254\0\42\6\223\313$\25#\12\254\310\244\64T\32*\1$\11\263\307\245\241GJ\0%\10\253\310d"
+    "\324F\1&\11\254\310\305\24\253\230\2'\5\321\313\10(\7\362\307\251f\0)\10\262\307\304T)\0"
+    "*\7\253\310\244j\65+\10\253\310\305\264b\2,\6\222\307)\0-\5\213\312\14.\5\311\310\4/"
+    "\7\253\310Ve\4\60\10\253\310UCU\0\61\7\253\310%Y\15\62\7\253\310\65S\32\63\10\253\310"
+    "\314\224\27\0\64\10\253\310$\65b\1\65\10\253\310\214\250\27\0\66\7\253\310M\325\2\67\10\253\310\314"
+    "TF\0\70\7\253\310\255\326\2\71\7\253\310\265\344\2:\6\341\310\304\0;\7\252\307e\250\0<\7"
+    "\253\310\246\272\0=\6\233\311\354\1>\7\253\310\344\252\4\77\10\253\310\350\224a\2@\6\253\310-["
+    "A\10\253\310UC\251\0B\10\253\310\250\264\322\2C\10\253\310U\62U\0D\10\253\310\250d-\0"
+    "E\10\253\310\214\250\342\0F\10\253\310\214\250b\4G\10\253\310\315\244\222\0H\10\253\310$\65\224\12"
+    "I\7\253\310\254X\15J\7\253\310\226\252\2K\10\253\310$\265\222\12L\7\253\310\304\346\0M\10\253"
+    "\310\244\61\224\12N\10\253\310\252\241$\0O\7\253\310UV\5P\10\253\310\250\264b\4Q\7\263\307"
+    "UV\35R\10\253\310\250\264\222\12S\7\253\310\355\274\0T\7\253\310\254\330\2U\7\253\310$\327\10"
+    "V\10\253\310$k\244\4W\10\253\310$\65\206\12X\10\253\310$\325R\1Y\10\253\310$UV\0"
+    "Z\7\253\310\314T\16[\6\352\310\254J\134\7\253\310\304\134\6]\6\252\310\250j^\5\223\313\65_"
+    "\5\213\307\14`\6\322\313\304\0a\7\243\310-\225\4b\10\253\310D\225\324\2c\6\243\310\315,d"
+    "\10\253\310\246\245\222\0e\6\243\310USf\10\253\310\246\264b\2g\10\253\307\255$\27\0h\10\253"
+    "\310D\225\254\0i\10\253\310e$\323\0j\10\263\307fX.\0k\10\253\310\304\264\222\12l\7\253"
+    "\310\310\326\0m\10\243\310\244\241T\0n\7\243\310\250d\5o\7\243\310U\252\2p\10\253\307\250\264"
+    "b\4q\10\253\307-\225d\0r\10\243\310\244\25#\0s\7\243\310\215\274\0t\10\253\310\245\25s"
+    "\0u\7\243\310$+\11v\7\243\310$\253\2w\10\243\310$\65T\0x\7\243\310\244\62\25y\10"
+    "\253\307$\225\344\2z\7\243\310\314\224\6{\10\263\307\246$\353\0|\6\351\310\14\1}\11\263\307\344"
+    "\250b\212\0~\7\224\313%\225\0\240\5\200\315\0\241\6\351\310\244\1\242\10\253\310\245\21W\2\243\7"
+    "\253\310\246\250\32\244\10\244\310\304$U\14\245\10\253\310\244j\305\4\246\6\351\310(\1\247\10\263\307\215"
+    "T\311\5\250\6\213\314\244\0\251\11\264\307\251\270\226L\12\252\7\253\310\255\244\7\253\10\234\311%\25S"
+    "\0\254\6\223\311\314\0\255\5\213\312\14\256\10\244\311\251\261\222\2\257\5\213\314\14\260\6\233\312u\1\261"
+    "\10\253\310\245\225\321\0\262\6\242\311(\3\263\7\252\310(\251\0\264\6\322\313)\0\265\10\253\307$k"
+    "E\0\266\7\254\310\15\265z\267\5\311\312\4\270\6\322\310)\0\271\6\242\311\255\2\272\7\253\310u\243"
+    "\1\273\10\234\311\244\230T\2\274\10\264\307\344\32U;\275\10\264\307\344J\307,\276\11\264\307\350\230Q"
+    "\262\3\277\10\253\310e\230\262\0\300\10\253\310\344\224\206\12\301\10\253\310\246j\250\0\302\10\253\310\310\224"
+    "\206\12\303\10\253\310\215\224\206\12\304\10\253\310\244\326P\1\305\10\253\310\305\224\206\12\306\11\254\310\215\224"
+    "\206\252\4\307\10\263\307U\62\65\1\310\10\253\310\304\241\342\0\311\7\253\310\216\25\7\312\10\253\310\215\221"
+    "\342\0\313\10\253\310\244\261\342\0\314\10\253\310\304\25\323\0\315\10\253\310\216\24\323\0\316\10\253\310\245\25"
+    "\323\0\317\7\253\310\244\262\32\320\11\254\310\314\264\252\221\0\321\10\254\310%\225\256\12\322\10\253\310\344\224"
+    "T\5\323\10\253\310\246JU\0\324\10\253\310\305\224T\5\325\10\254\310\215\325\31\1\326\10\253\310\244\226"
+    "\252\0\327\6\233\311\244\16\330\7\253\310\35j\1\331\10\253\310\344\224\324\10\332\10\253\310\246J\215\0\333"
+    "\10\253\310e\224\324\10\334\10\253\310\244\234\324\10\335\10\253\310\346T&\0\336\10\253\310D\225V\4\337"
+    "\10\263\307U+\15\11\340\10\253\310\344\270\222\0\341\10\253\310\246\270\222\0\342\10\253\310i\264\222\0\343"
+    "\11\254\310%\25U\251\0\344\10\253\310\244\214V\22\345\10\253\310e\270\222\0\346\10\244\310\215\264\342\0"
+    "\347\10\253\307UZ%\0\350\10\253\310\344\224\246\0\351\7\253\310\246j\12\352\10\253\310\310\224\246\0\353"
+    "\7\253\310\244\326\24\354\7\253\310\344X\15\355\6\253\310\316j\356\7\253\310u\246\1\357\10\253\310\244,"
+    "\323\0\360\10\253\310\244rU\0\361\11\254\310%\225dj\1\362\10\253\310\344\230Z\0\363\10\253\310\246"
+    "\230Z\0\364\10\253\310e\230Z\0\365\7\253\310l\324\5\366\10\253\310\244\214\272\0\367\10\253\310e\264"
+    "Q\2\370\7\243\310-\265\0\371\10\253\310\344\224T\22\372\10\253\310\246J%\1\373\10\253\310e\224T"
+    "\22\374\10\253\310\244\234T\22\375\10\263\307\246j\304\5\376\11\263\307\304\250\322\212\0\377\11\263\307\244\234"
+    "F\134\0\0\0\0\4\377\377\0";
+/*
+  Fontname: -Misc-Fixed-Medium-R-Normal--8-80-75-75-C-50-ISO10646-1
+  Copyright: Public domain font.  Share and enjoy.
+  Glyphs: 191/1426
+  BBX Build Mode: 0
+*/
+static const uint8_t u8g2_font_5x8_tf[] =
+    "\277\0\2\2\3\4\3\4\4\5\10\0\377\6\377\6\0\1\32\2\61\6\226 \5\0~\3!\7\61c"
+    "\63R\0\42\7\233n\223\254\0#\15=bW\246\64T\65T\231\22\0$\12=b\233W\275S\332"
+    "\21%\10\253f\23Sg\0&\12<b\27S\263j\246\0'\5\31o\63(\7\262b\247\232\1)"
+    "\10\262b\23S\245\0*\12,b\23\223\32I\305\0+\12-b\233Q\34\62\243\10,\7\233^\247"
+    "J\0-\6\14j\63\2.\7\233^\227V\2/\10\64b_\266\63\0\60\10\263bW\271*\0\61"
+    "\7\263b\227dk\62\12\64b\247bN*\217\0\63\12\64b\63b\324H&\5\64\12\64b\33U"
+    "\65bN\0\65\12\64b\63\364F\62)\0\66\12\64b\247\362\212\62)\0\67\12\64b\63r\314\61"
+    "G\0\70\12\64b\247bRQ&\5\71\12\64b\247\242L;)\0:\7\252b\63\342\10;\10\263"
+    "^g#U\2<\7\263b\233\312\134=\10\34f\63\62\32\1>\10\263b\223\313T\2\77\11\263b"
+    "\327L\31&\0@\14E^+\243\134I%YC\5A\11\64b\247\242\34S\6B\12\64b\263\342"
+    "HQ\216\4C\11\64b\247\242.\223\2D\11\64b\263\242s$\0E\11\64b\63\364\312y\4F"
+    "\11\64b\63\364\312\65\0G\12\64b\247\242N\63)\0H\11\64b\23\345\230f\0I\7\263b\263"
+    "bkJ\11\64b\67sUF\0K\11\64b\23U\222\251\63L\10\64b\223\273G\0M\11\64b"
+    "\23\307\21\315\0N\11\64b\23\327Xg\0O\11\64b\247\242\63)\0P\12\64b\263\242\34)g"
+    "\0Q\11<^\247\242\134n\24R\12\64b\263\242\34)\312\0S\12\64b\247b\312\250L\12T\10"
+    "\263b\263b\27\0U\10\64b\23=\223\2V\11\64b\23\235I*\0W\11\64b\23\315q\304\0"
+    "X\12\64b\23e\222*\312\0Y\13\65b\223u\252\63\312(\2Z\11\64b\63rl\217\0[\7"
+    "\263b\63bs\134\12\64b\223\63\312(\243\34]\7\263b\63\233#^\6\223r\327\0_\6\14^"
+    "\63\2`\6\222r\23\3a\10$b\67\242L\3b\12\64b\223\363\212r$\0c\7\243b\67\263"
+    "\0d\11\64b_\215(\323\0e\10$b\247\322\310\12f\11\64b[\225\63G\0g\11,^\247"
+    "b\332I\1h\11\64b\223\363\212f\0i\10\263b\227\221\254\6j\11\273^\233a\251*\0k\11"
+    "\64b\223\313\221\242\14l\7\263b#\273\6m\11%b\243Z*\251\2n\7$b\263\242\31o\10"
+    "$b\247\242L\12p\11,^\263\342H\71\3q\10,^\67b\332\5r\10$b\223\222\235\1s"
+    "\7\243b\67\362\2t\12\64b\227\343\314)&\0u\7$b\23\315\64v\7\243b\223\254\12w\11"
+    "%b\223UR]\0x\10$b\23\223T\61y\12,^\23e\32\61)\0z\10$b\63b\71"
+    "\2{\13<b\253\62J\32\305\214\4|\5\61cs}\14<b\243Q\314He\224$\0~\7\24"
+    "r\227T\2\240\5\0~\3\241\7\61c\223F\0\242\11\64^\33Gj\316\4\243\12\64b[\215\230"
+    "\223J\0\244\12-b\223\323Lq\345\0\245\13\65b\223S\65d\34\62\2\246\6\71c\263\6\247\12"
+    "<b\67\362\212i\217\4\250\6\213v\223\2\251\12\65b\267\252\71U\265\0\252\7\253j\267\222\36\253"
+    "\10\34f\227TL\1\254\6\233b\63\13\255\5\213j\63\256\11\65b\367\241\226Z\0\257\5\213v\63"
+    "\260\6\233n\327\5\261\10\253b\227VF\3\262\7\253j\327Li\263\7\253j\243/\0\264\6\222r"
+    "\247\0\265\11,^\23\315\221\62\0\266\14\65b\67F\32)\251\230b\12\267\5\11k\23\270\6\222^"
+    "\247\0\271\7\253j\227d\65\272\7\253j\327\215\6\273\10\34f\223bR\11\274\12<b\223[Q\215"
+    "\230\0\275\12<b\223\253\244r\214\3\276\14<b\223Q\314HU#&\0\277\11\263b\227a\212\251"
+    "\2\300\12<b\227QTqL\31\301\11<b[\253\70\246\14\302\12<b\247bRqL\31\303\12"
+    "<b\227TTqL\31\304\12<b\23\63TqL\31\305\12<b\247bRqL\31\306\11\64b"
+    "\67Rk\250J\307\12<^\247\242.\223\214\0\310\12<b\227Q\32z\345\21\311\11<b[\16\275"
+    "\362\10\312\12<b\247\342\330+\217\0\313\12<b\23\63\32z\345\21\314\11\273b\223\323\212\325\0\315"
+    "\11\273b\233\322\212\325\0\316\11\273bW\215\24\253\1\317\11\273b\223\362\212\325\0\320\13\65b\67\343"
+    "He\212i\1\321\12<b\227T\271\324\224\1\322\12<b\227QT\321L\12\323\11<b[\253h"
+    "&\5\324\12<b\247bR\321L\12\325\12<b\227TT\321L\12\326\12<b\23\63T\321L\12"
+    "\327\6\233b\223:\330\11\64b\67\322\221\216\4\331\11<b\227Q\351L\12\332\10<b\333t&\5"
+    "\333\11<b\247\242gR\0\334\12<b\23\63\212\316\244\0\335\13=b_\346Tg\224Q\4\336\12"
+    "\64b\223W\224#e\0\337\11\64b\247\242\352T\11\340\12<b\227Q\306#\312\64\341\11<b["
+    "S#\312\64\342\12<b[e\70\242L\3\343\12<b\227T\306#\312\64\344\11\64bW\303\21e"
+    "\32\345\12<b\247b\222#\312\64\346\11%b\63\242\62G\0\347\10\253^\67\263J\0\350\13<b"
+    "\227Q\306*\215\254\0\351\12<b[S*\215\254\0\352\13<b\247b\206*\215\254\0\353\12\64b"
+    "WC\225FV\0\354\11\273b\223\63\222\325\0\355\11\273b\233\62\222\325\0\356\10\273b\327\226\325\0"
+    "\357\10\263b\223\262\254\6\360\14<b\223b\225Q\32\61)\0\361\12<b\227T\206+\232\1\362\13"
+    "<b\227Q\306*\312\244\0\363\12<b[S*\312\244\0\364\13<b\247b\206*\312\244\0\365\13"
+    "<b\227T\306*\312\244\0\366\12\64b\23\63TQ&\5\367\10\253b\227\321F\11\370\11$b\67"
+    "\322H#\1\371\12<b\227Q\206\321L\3\372\11<b[\343h\246\1\373\12<b\247bF\321L"
+    "\3\374\11\64b\23\63\212f\32\375\13D^[\343(\323\210I\1\376\12<^\223\363\212#\345\14\377"
+    "\14<^\23\63\212\62\215\230\24\0\0\0\0\4\377\377\0";
+/*
+  Fontname: -Misc-Fixed-Medium-R-Normal--10-100-75-75-C-60-ISO10646-1
+  Copyright: Public domain terminal emulator font.  Share and enjoy.
+  Glyphs: 191/1597
+  BBX Build Mode: 0
+*/
+static const uint8_t u8g2_font_6x10_tf[] =
+    "\277\0\2\2\3\4\3\5\4\6\12\0\376\7\376\7\0\1B\2\222\7\263 \5\0b\7!\7\71C"
+    "g\250\0\42\7\233R'Y\1#\15=B\257Li\250j\250\62%\0$\13=B\67\257z\247\264"
+    "#\0%\13=B/\252\356\252%\23\0&\14=B/\247\230r\225dT\1'\5\31Sg(\10"
+    "\273B\67\225u\1)\10\273B'\227U\11*\12-F'\247j\250v\0+\12-F\67\243\70d"
+    "F\21,\7\233>O\225\0-\6\15Ng\10.\7\233>/\255\4/\13=B\37e\224\273QF"
+    "\0\60\12=B\67\247\332Nu\4\61\14=B\67\313\224QF\31\305!\62\14=Bo\345\214\242\314"
+    "\31\15\1\63\14=Bgh\224\263\206:-\0\64\14=B\77\313T\246\241\63J\0\65\13=B\347"
+    "F\311\314H\247\5\66\13=BW\346\214\222\251\323\2\67\14=Bgh\224\63\312\65\312\0\70\13="
+    "Boe\235V\326i\1\71\14=Boe\251TF\71J\0:\12\273>/\255\14\323J\0;\11"
+    "\273>/\255\14U\11<\12\274B\77\266QF\31\5=\10\35Jgh\70\4>\13\274B'\243\214"
+    "\62\212m\0\77\12=Bo\345\66\312t\4@\13=Boe\271\222\225\341\2A\13=B\67\247Z"
+    "\217\221u\0B\14=Bg\304*\246Y\305\241\0C\14=Boe\215\62\312(\247\5D\15=B"
+    "g\304*\246\230b\212C\1E\14=B\347F\31\215\224QFCF\15=B\347F\31\215\224QF"
+    "\31\1G\14=Boe\215\62\212;-\0H\11=B'\333cd;I\10\273Bg\305\256\1J"
+    "\14=Bwg\224QFe\224\0K\13=B'\313T\352\24\253\34L\16=B'\243\214\62\312("
+    "\243\214\206\0M\12=B'\353\265\222\266\3N\12=B'\353\251\222\334:O\11=Boe\357\264"
+    "\0P\15=Bg\244\254\207\312(\243\214\0Q\12E>oe\257j\303\0R\13=Bg\244\254\207"
+    "*\253\34S\13=Boe\15\67\324i\1T\16=Bg\310\214\62\312(\243\214\42\0U\10=B"
+    "'\373N\13V\13=B'\333\251L\61\345\10W\12=B'\273\222Jw\0X\12=B'\353T"
+    "W\265\16Y\14=B'\353Tg\224QF\21Z\12=Bgh\224\273\321\20[\10\273Bg\304\316"
+    "\1\134\15=B'\243\14\63\314\60\303\214\2]\10\273Bgv\216\0^\7\35R\67\247:_\6\15"
+    ">g\10`\6\22['\6a\12-Bo\303\64t\32\1b\14=B'\243\214\222\251\247R\0c"
+    "\12-Boe\215rZ\0d\13=B\37e\224\314-\225\12e\12-Bo\345\61\62\134\0f\14"
+    "=BWVy\304\214\62\312\0g\14=:oh\235FF:-\0h\13=B'\243\214\222\251\355"
+    "\0i\10\273B/#\331\32j\13\314:\77c]K\231\24\0k\14=B'\243\214\262L\263\312\1"
+    "l\7\273BG\366\32m\12-BG\265TRI\7n\10-B'\231\332\16o\11-Boe;"
+    "-\0p\14=:'\231z*\225QF\0q\13=:\317\334R\251\214\62\12r\13-B'\231\32"
+    "e\224\21\0s\12-Boe\270\341P\0t\15=B/\243<bF\31\305\250\0u\10-B'"
+    ";\225\12v\12-B'\353T\246\34\1w\11-B'[Iu\1x\11-B'\247\272\252\3y"
+    "\13=:'\233Je\244\323\2z\10-Bg\350\366\20{\13\274BWe\224\64\212\31\11|\6\71"
+    "C\347\10}\14\274BG\243\230\221\312(I\0~\11\35R/\252$\23\0\240\5\0b\7\241\7\71"
+    "C'\15\1\242\14=>\67\17\25SLy\304\10\243\13=BWVyg\24\225\2\244\12-B'"
+    "\247\231\342\312\1\245\15E>'\353T\307!\63\312(\2\246\6\71Cg\15\247\13E>oe\64;"
+    "\67J\13\250\6\213^'\5\251\14=Boe\225\246J:-\0\252\12\264FoD\245j\64\2\253"
+    "\13.B\267\212)\346\230c\0\254\7\224Jg\344\0\255\6\214Ng\4\256\13=Bo\345\221\346\324"
+    "i\1\257\6\15^g\10\260\6\233R\257\13\261\13\65B\67\243\70dFq\10\262\10\254NO\305\346"
+    "\10\263\12\254Ng\243\244\321H\0\264\6\22[O\1\265\12\65>'\333S\251\214\0\266\16=Bo"
+    "\214\64RR\61\305\24S\0\267\5\11O'\270\6\22;O\1\271\7\253N/\311j\272\12\264FO"
+    "E\231\64\34\1\273\14.B'\346\230c\212)F\0\274\20N>/#\15\63\314hf\244S\34\31"
+    "\6\275\20N>/#\15\63\314heT\303\214\62\32\276\16M>G\303\234a\224Y\246\64\62\12\277"
+    "\12=B\67\323\31\345\326\2\300\14EB/\303\274\262\36#\353\0\301\13EB\277^Y\217\221u\0"
+    "\302\14EB\67\247\270\262\36#\353\0\303\14EB/*\271\262\36#\353\0\304\13EB\257\246V\326"
+    "cd\35\305\14EB\67\247\270\262\36#\353\0\306\13>Bw\244\262\71Fl\16\307\15M:oe"
+    "\215\62\312(\247]\3\310\16EB/\217\215\62\32)\243\214\206\0\311\16EB\77\215\215\62\32)\243"
+    "\214\206\0\312\16EB\67\216\215\62\32)\243\214\206\0\313\16EB\257\32\33e\64RF\31\15\1\314"
+    "\11\303B'\247\25[\3\315\11\303B\67\245\25[\3\316\11\303B\257\32)\266\6\317\11\303B'\345"
+    "\25[\3\320\15=Bg\304*\216T\246\70\24\0\321\13EB\67uO\225\344\326\1\322\13EB/"
+    "\303\274\262;-\0\323\12EB\277^\331\235\26\0\324\13EB\67\247\270\262;-\0\325\12EB\67"
+    "\65Wv\247\5\326\12EB\257\246Vv\247\5\327\11-B'\247\272\252\3\330\13=Bo\305+\315"
+    "\231\26\0\331\12EB/\303\332;-\0\332\11EB\277\314\336i\1\333\13EB\67\247\214\263;-"
+    "\0\334\12EB\257\306\331;-\0\335\14EB\277\314:\325\31e\24\1\336\16=B'\243\221\362P"
+    "\31e\224\21\0\337\13=Boe\231\312*+\5\340\14EB/\303Ln\230\206N#\341\13EB"
+    "\277&\67LC\247\21\342\14EB\67\247Lm\230\206N#\343\14EB\67\265\251\15\323\320i\4\344"
+    "\13=B\257\246\66LC\247\21\345\14EB\67\247\234\67LC\247\21\346\13.BodT\215\231\207"
+    "\0\347\13=:oe\215r\332\65\0\350\14EB/\303L\256<F\206\13\351\13EB\277&W\36"
+    "#\303\5\352\14EB\67\247L\255<F\206\13\353\13=B\257\246V\36#\303\5\354\11\303B'g"
+    "$[\3\355\10\303B\257\206\262\65\356\10\303B\257-[\3\357\10\273B'e\331\32\360\13=BG"
+    "C\271\262\235\26\0\361\12EB\67\265q\62\265\35\362\13EB/\303L\256l\247\5\363\12EB\277"
+    "&W\266\323\2\364\13EB\67\247L\255l\247\5\365\13EB\67\265\251\225\355\264\0\366\12=B\257"
+    "\246V\266\323\2\367\11-F\67SCS\21\370\12-Bo\310\225\346P\0\371\13EB/\303Le"
+    "\247R\1\372\12EB\277\246\262S\251\0\373\13EB\67\247\214\263S\251\0\374\12=B\257\306\331\251"
+    "T\0\375\14M:\277\314\246R\31\351\264\0\376\15E:'\243\221\262=TF\31\1\377\15M:\257"
+    "\306\331T*#\235\26\0\0\0\0\4\377\377\0";
+
+/*
+  Fontname: -Misc-Fixed-Medium-R-Normal--15-140-75-75-C-90-ISO10646-1
+  Copyright: Public domain font.  Share and enjoy.
+  Glyphs: 191/4777
+  BBX Build Mode: 0
+*/
+static const uint8_t u8g2_font_9x15_tf[] =
+    "\277\0\3\2\4\4\4\5\5\11\17\0\375\12\375\13\377\1\223\3*\12\21 \5\0\310\63!\10\261\14"
+    "\63\16\221\0\42\10\64{\63\42S\0#\16\206\31s\242\226a\211Z\206%j\1$\24\267\371\362\302"
+    "A\211\42)L\322\65\11#\251\62\210\31\0%\21\247\11sB%JJ\255q\32\265\224\22\61\1&"
+    "\22\247\11s\304(\213\262(T\65)\251E\225H\13'\6\61|\63\6(\14\303\373\262\222(\211z"
+    "\213\262\0)\14\303\373\62\262(\213z\211\222\10*\15w\71\363J\225\266-i\252e\0+\13w\31"
+    "\363\342\332\60dq\15,\11R\334\62\206$Q\0-\7\27I\63\16\1.\7\42\14\63\206\0/\14"
+    "\247\11\263\323\70-\247\345\64\6\60\15\247\11\263\266J\352k\222e\23\0\61\15\247\11\363R\61\311\242"
+    "\270\267a\10\62\14\247\11s\6%U\373y\30\2\63\16\247\11\63\16qZ\335\343XM\6\5\64\21"
+    "\247\11sS\61\311\242Z\22&\303\220\306\25\0\65\17\247\11\63\16reH\304\270\254&\203\2\66\20"
+    "\247\11\263\206(\215+C\42\252\326dP\0\67\16\247\11\63\16q\32\247q\32\247q\10\70\21\247\11"
+    "\263\266J\232d\331VI\325$\313&\0\71\17\247\11s\6%uT\206$\256FC\4:\10r\14"
+    "\63\206x\10;\12\242\334\62\206xH\22\5<\11\245\12\63\263\216i\7=\12G)\63\16\71>\14"
+    "\1>\12\245\12\63\322\216YG\0\77\16\247\11s\6%U\343\264\71\207\63\0@\21\247\11s\6%"
+    "\65\15J\246D\223\42\347\203\2A\15\247\11\363\322$\253\244\326\341j\15B\22\247\11\63\6)LR"
+    "\61\31\244\60I\215\311 \1C\15\247\11s\6%\225\373\232\14\12\0D\15\247\11\63\6)LR\77"
+    "&\203\4E\15\247\11\63\16ry\220\342\346a\10F\14\247\11\63\16ry\220\342\316\0G\17\247\11"
+    "s\6%\225\333\206\324\232\14\12\0H\12\247\11\63R\327\341\352\65I\12\245\12\63\6)\354\247AJ"
+    "\15\250\11\363\6\65\357S\230\15\31\0K\21\247\11\63R\61\311\242\332\230\204QV\12\223\64L\12\247"
+    "\11\63\342\376<\14\1M\20\247\11\63Ru[*JE\212\244H\265\6N\17\247\11\63RuT\62"
+    ")\322\22q\265\6O\14\247\11s\6%\365\327dP\0P\15\247\11\63.\251u\30\222\270\63\0Q"
+    "\16\307\351r\6%\365K&U\6\65\27R\20\247\11\63.\251u\30\222(+\205I\252\6S\16\247"
+    "\11s\6%\265\357V\65\31\24\0T\12\247\11\63\16Y\334\337\0U\13\247\11\63R\377\232\14\12\0"
+    "V\21\247\11\63Rk\222EY\224U\302$L\322\14W\20\247\11\63RO\221\24I\221\24)\335\22"
+    "\0X\20\247\11\63R\65\311*i\234&Y%U\3Y\15\247\11\63R\65\311*i\334\33\0Z\14"
+    "\247\11\63\16q\332\347x\30\2[\12\304\373\62\6\255\177\33\2\134\13\247\11\63\362\70/\347\345<]"
+    "\12\304\372\62\206\254\177\33\4^\12Gi\363\322$\253\244\1_\7\30\370\62\16\2`\7\63\213\63\262"
+    "\2a\16w\11s\6=N\206!\25\225!\11b\17\247\11\63\342\226!\21U\353\250\14\11\0c\14"
+    "w\11s\6%\225[\223A\1d\15\247\11\263[\206D\134\35\225!\11e\15w\11s\6%U\207"
+    "s>(\0f\16\247\11\363\266R\26\305\341 \306\215\0g\23\247\331r\206DL\302$\214\206(\37"
+    "\224TM\6\5h\14\247\11\63\342\226!\21U\257\1i\12\245\12stt\354i\20j\15\326\331\62"
+    "u\312\332U\64&C\2k\17\247\11\63\342VMI\64\65\321\62%\15l\11\245\12\63\306\376\64\10"
+    "m\20w\11\63\26%\212\244H\212\244H\212\324\0n\13w\11\63\222!\21U\257\1o\14w\11s"
+    "\6%\365\232\14\12\0p\17\247\331\62\222!\21U\353\250\14I\134\6q\15\247\331r\206D\134\35\225"
+    "!\211\33r\14w\11\63\242IK\302$n\5s\15w\11s\6%\325\7]M\6\5t\14\227\11"
+    "\263\342p\330\342n\331\2u\20w\11\63\302$L\302$L\302$\214\206$v\16w\11\63R\65\311"
+    "\242\254\22&i\6w\16w\11\63RS$ER\244tK\0x\15w\11\63\322$\253\244\225\254\222"
+    "\6y\15\246\331\62B\337\224%\25\223!\1z\12w\11\63\16i\257\303\20{\15\305\373\262\226\260\32"
+    "ij\26V\7|\6\301\374\62>}\16\305\371\62\326\260\226jR\32V&\0~\12\67ys\64)"
+    "\322\24\0\240\5\0\310\63\241\10\261\14\63\244a\10\242\21\206\11\63\243!\211\22\251\222%Q\62D!"
+    "\0\243\21\247\11\363\266R\34\16b\234\212I\226$\13\0\244\16g\71\63\322d\220\262(\213\6%\15"
+    "\245\20\247\11\63R\65\311*\331 \206\203\30\327\0\246\10\261\374\62\6e\20\247\16\264\372r\224HT"
+    "\42S\42J\211\2\250\7%\232\63\62-\251\25\230\30\263\206,L\42K\224(\241\22%\222\224\204\331"
+    "\20\1\252\14u\71s\244\322\22EC:\10\253\15\207\31\363\242~\213\302(\214\302(\254\7F)\63"
+    "\256\15\255\7\25J\63\6\1\256\25\230\30\263\206,L\222I\211\22eRJJ\224\24\263!\2\257\6"
+    "\26\231\63\16\260\12Dks\224HJ\24\0\261\16\227\31\363\342\332\60dq\35\33\206\0\262\13dI"
+    "s\224(K\224l\10\263\13dIs\224\250(%\12\0\264\10\63\213\263\222\22\0\265\14\227\351\62R"
+    "\257\333\262\310\61\0\266\26\247\11s\206!K\264DK\222!\11\223\60\11\223\60\11\223\0\267\7\42L"
+    "\63\206\0\270\10\64\332\262\246D\1\271\10cIs\22\251e\272\12eIs\226LK\346A\273\16\207"
+    "\31\63\242\60\12\243\60\312\242~\3\274\17\247\11sR\271q\210\304$\213\62%\25\275\16\247\11sR"
+    "\271I\31\242\70\24\343!\276\21\247\11s\304(\315\263\250\42\211I\26eJ*\277\15\247\11\363r\270"
+    "\332\234\252\311\240\0\300\16\307\11s\362:\272URu\270Z\3\301\15\307\11s\333\321\255\222\252\303\325"
+    "\32\302\17\307\11\363\322$\253c[%U\207\253\65\303\17\267\11s\64i\307\266J\252\16Wk\0\304"
+    "\17\267\11s\262(\313\261\255\222\252\303\325\32\305\17\267\11\263\266\332\230d\225T\35\256\326\0\306\25\247"
+    "\11s\224!\312\242,\312\242lX\242,\312\242,\32\2\307\20\327\331r\6%\225\373\232\14\242\26\205"
+    "\32\0\310\21\307\11s\362:\66\14I\34\17Y\134\35\206\0\311\20\307\11s\333\261aH\342x\310\342"
+    "\352\60\4\312\22\307\11\363\322$\253#\303\220\304\361\220\305\325a\10\313\22\267\11s\262(\313\221aH"
+    "\342x\310\342\352\60\4\314\14\305\12\63\322\372 \205=\15\2\315\14\305\12\63\263\372 \205=\15\2\316"
+    "\15\305\12\263\262\244\226\16R\330\323 \317\14\265\12\63\62-\35\244\260\247A\320\25\250\10s\6-\214"
+    "\322$\35\302$M\322$M\302h\220\0\321\22\267\11s\64iG\322Q\311\244H\212\264D\134\3\322"
+    "\16\307\11s\362:\70(\251\257\311\240\0\323\15\307\11s\333\301AI}M\6\5\324\20\307\11\363\322"
+    "$\253C\203\222\372\232\14\12\0\325\17\267\11s\64i\207\6%\365\65\31\24\0\326\17\267\11s\262("
+    "\313\241AI}M\6\5\327\15w\31\63\322$\253\244\225\254\222\6\330\26\307\371\262\223A\11\267DK"
+    "\244H\212\224L\311\306dPb\0\331\15\307\11s\362:\226\372\65\31\24\0\332\14\307\11s\333\261\324"
+    "\257\311\240\0\333\16\307\11\363\322$\253#\251_\223A\1\334\16\267\11s\262(\313\221\324\257\311\240\0"
+    "\335\16\307\11s\333\261TM\262J\32\267\1\336\16\247\11\63\342xXR\353\60$q\31\337\24\246\11"
+    "\263\246,\311\222(Q\262\250\226dI\226$\12\0\340\21\267\11\263\362:\66\350q\62\14\251\250\14I"
+    "\0\341\20\267\11s\333\301A\217\223aHEeH\2\342\22\267\11\363\322$\253C\203\36'\303\220\212"
+    "\312\220\4\343\22\247\11\263\244$\322\241A\217\223aHEeH\2\344\22\247\11s\262(\313\241A\217"
+    "\223aHEeH\2\345\22\267\11\363\304(\324\261A\217\223aHEeH\2\346\17w\11s,Q"
+    "-J\6%\312\242\212\62\347\17\247\331r\6%\225[\223A\324\242P\3\350\17\267\11s\362:\70("
+    "\251:\234\363A\1\351\17\267\11s\333\301AI\325\341\234\17\12\0\352\21\267\11\363\322$\253C\203\222"
+    "\252\303\71\37\24\0\353\21\247\11s\262(\313\241AI\325\341\234\17\12\0\354\12\265\12\63\322\372\330\323"
+    " \355\12\265\12\363\332\221\261\247A\356\15\266\11\263\302$\312rd\355m\20\357\14\245\12\63\242$\212"
+    "\307\236\6\1\360\20\267\11s\242PL\362lPR\257\311\240\0\361\16\247\11s\64iG\222!\21U"
+    "\257\1\362\16\267\11s\362:\70(\251\327dP\0\363\15\267\11s\333\301AI\275&\203\2\364\17\267"
+    "\11\363\322$\253C\203\222zM\6\5\365\17\247\11s\64i\207\6%\365\232\14\12\0\366\17\247\11s"
+    "\262(\313\241AI\275&\203\2\367\16\227\11\363\322\65\307\206!\307\322\65\3\370\23\227\371\262\223A\311"
+    "\22-\221\42%S\262dPb\0\371\23\267\11s\362:\26&a\22&a\22&a\64$\1\372\22"
+    "\267\11s\333\261\60\11\223\60\11\223\60\11\243!\11\373\24\267\11\363\322$\253#a\22&a\22&a"
+    "\22FC\22\374\24\247\11s\242,\312\241\60\11\223\60\11\223\60\11\243!\11\375\17\346\331\62\333\241\320"
+    "\67eI\305dH\0\376\20\307\331\62\342\226!\21UuT\206$.\3\377\17\326\331r\242\366\320\67"
+    "eI\305dH\0\0\0\0\4\377\377\0";

+ 13 - 0
flip_social/font/font.h

@@ -0,0 +1,13 @@
+#pragma once
+#include <stdint.h>
+#include <gui/view.h>
+#include <gui/elements.h>
+typedef enum
+{
+    FONT_SIZE_SMALL = 1,
+    FONT_SIZE_MEDIUM = 2,
+    FONT_SIZE_LARGE = 3,
+    FONT_SIZE_XLARGE = 4
+} FontSize;
+extern bool canvas_set_font_custom(Canvas *canvas, FontSize font_size);
+extern void canvas_draw_str_multi(Canvas *canvas, uint8_t x, uint8_t y, const char *str);

+ 23 - 82
flip_social/friends/flip_social_friends.c

@@ -4,17 +4,10 @@ FlipSocialModel *flip_social_friends_alloc()
 {
     // Allocate memory for each username only if not already allocated
     FlipSocialModel *friends = malloc(sizeof(FlipSocialModel));
-    for (size_t i = 0; i < MAX_FRIENDS; i++)
+    if (friends == NULL)
     {
-        if (friends->usernames[i] == NULL)
-        {
-            friends->usernames[i] = malloc(MAX_USER_LENGTH);
-            if (friends->usernames[i] == NULL)
-            {
-                FURI_LOG_E(TAG, "Failed to allocate memory for username %zu", i);
-                return NULL; // Return false on memory allocation failure
-            }
-        }
+        FURI_LOG_E(TAG, "Failed to allocate memory for friends usernames.");
+        return NULL;
     }
     return friends;
 }
@@ -28,11 +21,6 @@ bool flip_social_get_friends()
         FURI_LOG_E(TAG, "App instance is NULL");
         return false;
     }
-    if (fhttp.state == INACTIVE)
-    {
-        FURI_LOG_E(TAG, "HTTP state is INACTIVE");
-        return false;
-    }
     // will return true unless the devboard is not connected
     char url[100];
     snprintf(
@@ -55,7 +43,7 @@ bool flip_social_get_friends()
 
 bool flip_social_update_friends()
 {
-    if (!app_instance->submenu_friends)
+    if (!app_instance->submenu)
     {
         FURI_LOG_E(TAG, "Friends submenu is NULL");
         return false;
@@ -66,11 +54,11 @@ bool flip_social_update_friends()
         return false;
     }
     // Add submenu items for the users
-    submenu_reset(app_instance->submenu_friends);
-    submenu_set_header(app_instance->submenu_friends, "Friends");
+    submenu_reset(app_instance->submenu);
+    submenu_set_header(app_instance->submenu, "Friends");
     for (int i = 0; i < flip_social_friends->count; i++)
     {
-        submenu_add_item(app_instance->submenu_friends, flip_social_friends->usernames[i], FlipSocialSubmenuLoggedInIndexFriendsStart + i, flip_social_callback_submenu_choices, app_instance);
+        submenu_add_item(app_instance->submenu, flip_social_friends->usernames[i], FlipSocialSubmenuLoggedInIndexFriendsStart + i, flip_social_callback_submenu_choices, app_instance);
     }
     return true;
 }
@@ -82,89 +70,42 @@ bool flip_social_parse_json_friends()
     if (friend_data == NULL)
     {
         FURI_LOG_E(TAG, "Failed to load received data from file.");
-        return false;
-    }
-    char *data_cstr = (char *)furi_string_get_cstr(friend_data);
-    if (data_cstr == NULL)
-    {
-        FURI_LOG_E(TAG, "Failed to get C-string from FuriString.");
-        furi_string_free(friend_data);
+        flipper_http_deinit();
         return false;
     }
 
-    // Allocate memory for each username only if not already allocated
+    //  Allocate memory for each username only if not already allocated
     flip_social_friends = flip_social_friends_alloc();
     if (flip_social_friends == NULL)
     {
         FURI_LOG_E(TAG, "Failed to allocate memory for friends usernames.");
         furi_string_free(friend_data);
-        free(data_cstr);
         return false;
     }
 
-    // Remove newlines
-    char *pos = data_cstr;
-    while ((pos = strchr(pos, '\n')) != NULL)
-    {
-        *pos = ' ';
-    }
-
     // Initialize friends count
     flip_social_friends->count = 0;
 
     // Reset the friends submenu
-    submenu_reset(app_instance->submenu_friends);
-    submenu_set_header(app_instance->submenu_friends, "Friends");
+    submenu_reset(app_instance->submenu);
+    submenu_set_header(app_instance->submenu, "Friends");
 
     // Extract the users array from the JSON
-    char *json_users = get_json_value("friends", data_cstr, 128);
-    if (json_users == NULL)
+    for (int i = 0; i < MAX_FRIENDS; i++)
     {
-        FURI_LOG_E(TAG, "Failed to parse friends array.");
-        furi_string_free(friend_data);
-        free(data_cstr);
-        return false;
-    }
-
-    // Manual tokenization for comma-separated values
-    char *start = json_users + 1; // Skip the opening bracket
-    char *end;
-    while ((end = strchr(start, ',')) != NULL && flip_social_friends->count < MAX_FRIENDS)
-    {
-        *end = '\0'; // Null-terminate the current token
-
-        // Remove quotes
-        if (*start == '"')
-            start++;
-        if (*(end - 1) == '"')
-            *(end - 1) = '\0';
-
-        // Copy username to pre-allocated memory
-        snprintf(flip_social_friends->usernames[flip_social_friends->count], MAX_USER_LENGTH, "%s", start);
-        submenu_add_item(app_instance->submenu_friends, flip_social_friends->usernames[flip_social_friends->count], FlipSocialSubmenuLoggedInIndexFriendsStart + flip_social_friends->count, flip_social_callback_submenu_choices, app_instance);
-        flip_social_friends->count++;
-        start = end + 1;
-    }
-
-    // Handle the last token
-    if (*start != '\0' && flip_social_friends->count < MAX_FRIENDS)
-    {
-        if (*start == '"')
-            start++;
-        if (*(start + strlen(start) - 1) == ']')
-            *(start + strlen(start) - 1) = '\0';
-        if (*(start + strlen(start) - 1) == '"')
-            *(start + strlen(start) - 1) = '\0';
-
-        snprintf(flip_social_friends->usernames[flip_social_friends->count], MAX_USER_LENGTH, "%s", start);
-        submenu_add_item(app_instance->submenu_friends, flip_social_friends->usernames[flip_social_friends->count], FlipSocialSubmenuLoggedInIndexFriendsStart + flip_social_friends->count, flip_social_callback_submenu_choices, app_instance);
+        FuriString *friend = get_json_array_value_furi("friends", i, friend_data);
+        if (friend == NULL)
+        {
+            FURI_LOG_E(TAG, "Failed to parse friend %d.", i);
+            furi_string_free(friend_data);
+            break;
+        }
+        snprintf(flip_social_friends->usernames[i], MAX_USER_LENGTH, "%s", furi_string_get_cstr(friend));
+        submenu_add_item(app_instance->submenu, flip_social_friends->usernames[i], FlipSocialSubmenuLoggedInIndexFriendsStart + i, flip_social_callback_submenu_choices, app_instance);
         flip_social_friends->count++;
+        furi_string_free(friend);
     }
-
     furi_string_free(friend_data);
-    free(data_cstr);
-    free(json_users);
-    free(start);
-    free(end);
+    // flipper_http_deinit();
     return true;
 }

+ 86 - 47
flip_social/jsmn/jsmn.c

@@ -7,8 +7,6 @@
  */
 
 #include <jsmn/jsmn.h>
-#include <stdlib.h>
-#include <string.h>
 
 /**
  * Allocates a fresh unused token from the token pool.
@@ -424,7 +422,7 @@ int jsmn_parse(jsmn_parser *parser, const char *js, const size_t len,
 }
 
 // Helper function to create a JSON object
-char *jsmn(const char *key, const char *value)
+char *get_json(const char *key, const char *value)
 {
     int length = strlen(key) + strlen(value) + 8;         // Calculate required length
     char *result = (char *)malloc(length * sizeof(char)); // Allocate memory
@@ -448,14 +446,14 @@ int jsoneq(const char *json, jsmntok_t *tok, const char *s)
 }
 
 // Return the value of the key in the JSON data
-char *get_json_value(char *key, char *json_data, uint32_t max_tokens)
+char *get_json_value(char *key, const char *json_data)
 {
     // Parse the JSON feed
     if (json_data != NULL)
     {
         jsmn_parser parser;
         jsmn_init(&parser);
-
+        uint32_t max_tokens = json_token_count(json_data);
         // Allocate tokens array on the heap
         jsmntok_t *tokens = malloc(sizeof(jsmntok_t) * max_tokens);
         if (tokens == NULL)
@@ -510,26 +508,73 @@ char *get_json_value(char *key, char *json_data, uint32_t max_tokens)
     {
         FURI_LOG_E("JSMM.H", "JSON data is NULL");
     }
-    FURI_LOG_E("JSMM.H", "Failed to find the key in the JSON.");
+    char warning[128];
+    snprintf(warning, sizeof(warning), "Failed to find the key \"%s\" in the JSON.", key);
+    FURI_LOG_E("JSMM.H", warning);
     return NULL; // Return NULL if something goes wrong
 }
 
-// Revised get_json_array_value function
-char *get_json_array_value(char *key, uint32_t index, char *json_data, uint32_t max_tokens)
+// Helper function to skip a token and all its descendants.
+// Returns the index of the next token after skipping this one.
+// On error or out of bounds, returns -1.
+static int skip_token(const jsmntok_t *tokens, int start, int total)
 {
-    // Retrieve the array string for the given key
-    char *array_str = get_json_value(key, json_data, max_tokens);
+    if (start < 0 || start >= total)
+        return -1;
+
+    int i = start;
+    if (tokens[i].type == JSMN_OBJECT)
+    {
+        // For an object: size is number of key-value pairs
+        int pairs = tokens[i].size;
+        i++; // move to first key-value pair
+        for (int p = 0; p < pairs; p++)
+        {
+            // skip key (primitive/string)
+            i++;
+            if (i >= total)
+                return -1;
+            // skip value (which could be object/array and must be skipped recursively)
+            i = skip_token(tokens, i, total);
+            if (i == -1)
+                return -1;
+        }
+        return i; // i is now just past the object
+    }
+    else if (tokens[i].type == JSMN_ARRAY)
+    {
+        // For an array: size is number of elements
+        int elems = tokens[i].size;
+        i++; // move to first element
+        for (int e = 0; e < elems; e++)
+        {
+            i = skip_token(tokens, i, total);
+            if (i == -1)
+                return -1;
+        }
+        return i; // i is now just past the array
+    }
+    else
+    {
+        // Primitive or string token, just skip it
+        return i + 1;
+    }
+}
+
+// Revised get_json_array_value
+char *get_json_array_value(char *key, uint32_t index, const char *json_data)
+{
+    // Always extract the full array each time from the original json_data
+    char *array_str = get_json_value(key, json_data);
     if (array_str == NULL)
     {
         FURI_LOG_E("JSMM.H", "Failed to get array for key: %s", key);
         return NULL;
     }
+    uint32_t max_tokens = json_token_count(array_str);
 
-    // Initialize the JSON parser
     jsmn_parser parser;
     jsmn_init(&parser);
-
-    // Allocate memory for JSON tokens
     jsmntok_t *tokens = malloc(sizeof(jsmntok_t) * max_tokens);
     if (tokens == NULL)
     {
@@ -538,7 +583,6 @@ char *get_json_array_value(char *key, uint32_t index, char *json_data, uint32_t
         return NULL;
     }
 
-    // Parse the JSON array
     int ret = jsmn_parse(&parser, array_str, strlen(array_str), tokens, max_tokens);
     if (ret < 0)
     {
@@ -548,7 +592,6 @@ char *get_json_array_value(char *key, uint32_t index, char *json_data, uint32_t
         return NULL;
     }
 
-    // Ensure the root element is an array
     if (ret < 1 || tokens[0].type != JSMN_ARRAY)
     {
         FURI_LOG_E("JSMM.H", "Value for key '%s' is not an array.", key);
@@ -557,50 +600,33 @@ char *get_json_array_value(char *key, uint32_t index, char *json_data, uint32_t
         return NULL;
     }
 
-    // Check if the index is within bounds
     if (index >= (uint32_t)tokens[0].size)
     {
-        FURI_LOG_E("JSMM.H", "Index %lu out of bounds for array with size %d.", (unsigned long)index, tokens[0].size);
+        FURI_LOG_E("JSMM.H", "Index %lu out of bounds for array with size %u.", index, tokens[0].size);
         free(tokens);
         free(array_str);
         return NULL;
     }
 
-    // Locate the token corresponding to the desired array element
-    int current_token = 1; // Start after the array token
+    // Find the index-th element: start from token[1], which is the first element
+    int elem_token = 1;
     for (uint32_t i = 0; i < index; i++)
     {
-        if (tokens[current_token].type == JSMN_OBJECT)
-        {
-            // For objects, skip all key-value pairs
-            current_token += 1 + 2 * tokens[current_token].size;
-        }
-        else if (tokens[current_token].type == JSMN_ARRAY)
-        {
-            // For nested arrays, skip all elements
-            current_token += 1 + tokens[current_token].size;
-        }
-        else
+        elem_token = skip_token(tokens, elem_token, ret);
+        if (elem_token == -1 || elem_token >= ret)
         {
-            // For primitive types, simply move to the next token
-            current_token += 1;
-        }
-
-        // Safety check to prevent out-of-bounds
-        if (current_token >= ret)
-        {
-            FURI_LOG_E("JSMM.H", "Unexpected end of tokens while traversing array.");
+            FURI_LOG_E("JSMM.H", "Error skipping tokens to reach element %lu.", i);
             free(tokens);
             free(array_str);
             return NULL;
         }
     }
 
-    // Extract the array element
-    jsmntok_t element = tokens[current_token];
+    // Now elem_token should point to the token of the requested element
+    jsmntok_t element = tokens[elem_token];
     int length = element.end - element.start;
     char *value = malloc(length + 1);
-    if (value == NULL)
+    if (!value)
     {
         FURI_LOG_E("JSMM.H", "Failed to allocate memory for array element.");
         free(tokens);
@@ -608,11 +634,9 @@ char *get_json_array_value(char *key, uint32_t index, char *json_data, uint32_t
         return NULL;
     }
 
-    // Copy the element value to a new string
     strncpy(value, array_str + element.start, length);
-    value[length] = '\0'; // Null-terminate the string
+    value[length] = '\0';
 
-    // Clean up
     free(tokens);
     free(array_str);
 
@@ -620,16 +644,16 @@ char *get_json_array_value(char *key, uint32_t index, char *json_data, uint32_t
 }
 
 // Revised get_json_array_values function with correct token skipping
-char **get_json_array_values(char *key, char *json_data, uint32_t max_tokens, int *num_values)
+char **get_json_array_values(char *key, char *json_data, int *num_values)
 {
     // Retrieve the array string for the given key
-    char *array_str = get_json_value(key, json_data, max_tokens);
+    char *array_str = get_json_value(key, json_data);
     if (array_str == NULL)
     {
         FURI_LOG_E("JSMM.H", "Failed to get array for key: %s", key);
         return NULL;
     }
-
+    uint32_t max_tokens = json_token_count(array_str);
     // Initialize the JSON parser
     jsmn_parser parser;
     jsmn_init(&parser);
@@ -745,3 +769,18 @@ char **get_json_array_values(char *key, char *json_data, uint32_t max_tokens, in
     free(array_str);
     return values;
 }
+
+int json_token_count(const char *json)
+{
+    if (json == NULL)
+    {
+        return JSMN_ERROR_INVAL;
+    }
+
+    jsmn_parser parser;
+    jsmn_init(&parser);
+
+    // Pass NULL for tokens and 0 for num_tokens to get the token count only
+    int ret = jsmn_parse(&parser, json, strlen(json), NULL, 0);
+    return ret; // If ret >= 0, it represents the number of tokens needed.
+}

+ 7 - 65
flip_social/jsmn/jsmn.h

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

+ 719 - 0
flip_social/jsmn/jsmn_furi.c

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

+ 74 - 0
flip_social/jsmn/jsmn_furi.h

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

+ 14 - 0
flip_social/jsmn/jsmn_h.c

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

+ 53 - 0
flip_social/jsmn/jsmn_h.h

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

+ 73 - 184
flip_social/messages/flip_social_messages.c

@@ -2,13 +2,6 @@
 
 FlipSocialModel2 *flip_social_messages_alloc()
 {
-    if (!app_instance->submenu_messages)
-    {
-        if (!easy_flipper_set_submenu(&app_instance->submenu_messages, FlipSocialViewLoggedInMessagesSubmenu, "Messages", flip_social_callback_to_submenu_logged_in, &app_instance->view_dispatcher))
-        {
-            return NULL;
-        }
-    }
     // Allocate memory for each username only if not already allocated
     FlipSocialModel2 *users = malloc(sizeof(FlipSocialModel2));
     if (users == NULL)
@@ -16,18 +9,6 @@ FlipSocialModel2 *flip_social_messages_alloc()
         FURI_LOG_E(TAG, "Failed to allocate memory for message users");
         return NULL;
     }
-    for (size_t i = 0; i < MAX_MESSAGE_USERS; i++)
-    {
-        if (users->usernames[i] == NULL)
-        {
-            users->usernames[i] = malloc(MAX_USER_LENGTH);
-            if (users->usernames[i] == NULL)
-            {
-                FURI_LOG_E(TAG, "Failed to allocate memory for username %zu", i);
-                return NULL; // Return false on memory allocation failure
-            }
-        }
-    }
     return users;
 }
 
@@ -40,27 +21,6 @@ FlipSocialMessage *flip_social_user_messages_alloc()
         FURI_LOG_E(TAG, "Failed to allocate memory for messages");
         return NULL;
     }
-    for (size_t i = 0; i < MAX_MESSAGES; i++)
-    {
-        if (messages->usernames[i] == NULL)
-        {
-            messages->usernames[i] = malloc(MAX_USER_LENGTH);
-            if (messages->usernames[i] == NULL)
-            {
-                FURI_LOG_E(TAG, "Failed to allocate memory for username %zu", i);
-                return NULL; // Return false on memory allocation failure
-            }
-        }
-        if (messages->messages[i] == NULL)
-        {
-            messages->messages[i] = malloc(MAX_MESSAGE_LENGTH);
-            if (messages->messages[i] == NULL)
-            {
-                FURI_LOG_E(TAG, "Failed to allocate memory for message %zu", i);
-                return NULL; // Return false on memory allocation failure
-            }
-        }
-    }
     return messages;
 }
 
@@ -76,12 +36,6 @@ void flip_social_free_message_users()
 
 void flip_social_free_messages()
 {
-    if (app_instance->submenu_messages)
-    {
-        submenu_free(app_instance->submenu_messages);
-        app_instance->submenu_messages = NULL;
-        view_dispatcher_remove_view(app_instance->view_dispatcher, FlipSocialViewLoggedInMessagesSubmenu);
-    }
     if (flip_social_messages == NULL)
     {
         return;
@@ -92,7 +46,12 @@ void flip_social_free_messages()
 
 bool flip_social_update_messages_submenu()
 {
-    if (app_instance->submenu_messages == NULL)
+    if (!app_instance)
+    {
+        FURI_LOG_E(TAG, "App instance is NULL");
+        return false;
+    }
+    if (app_instance->submenu == NULL)
     {
         FURI_LOG_E(TAG, "Submenu is NULL");
         return false;
@@ -102,19 +61,24 @@ bool flip_social_update_messages_submenu()
         FURI_LOG_E(TAG, "Message users model is NULL");
         return false;
     }
-    submenu_reset(app_instance->submenu_messages);
-    submenu_set_header(app_instance->submenu_messages, "Messages");
-    submenu_add_item(app_instance->submenu_messages, "[New Message]", FlipSocialSubmenuLoggedInIndexMessagesNewMessage, flip_social_callback_submenu_choices, app_instance);
+    submenu_reset(app_instance->submenu);
+    submenu_set_header(app_instance->submenu, "Messages");
+    submenu_add_item(app_instance->submenu, "[New Message]", FlipSocialSubmenuLoggedInIndexMessagesNewMessage, flip_social_callback_submenu_choices, app_instance);
     for (int i = 0; i < flip_social_message_users->count; i++)
     {
-        submenu_add_item(app_instance->submenu_messages, flip_social_message_users->usernames[i], FlipSocialSubmenuLoggedInIndexMessagesUsersStart + i, flip_social_callback_submenu_choices, app_instance);
+        submenu_add_item(app_instance->submenu, flip_social_message_users->usernames[i], FlipSocialSubmenuLoggedInIndexMessagesUsersStart + i, flip_social_callback_submenu_choices, app_instance);
     }
     return true;
 }
 
 bool flip_social_update_submenu_user_choices()
 {
-    if (app_instance->submenu_messages_user_choices == NULL)
+    if (app_instance == NULL)
+    {
+        FURI_LOG_E(TAG, "App instance is NULL");
+        return false;
+    }
+    if (app_instance->submenu == NULL)
     {
         FURI_LOG_E(TAG, "Submenu is NULL");
         return false;
@@ -124,11 +88,11 @@ bool flip_social_update_submenu_user_choices()
         FURI_LOG_E(TAG, "Explore model is NULL");
         return false;
     }
-    submenu_reset(app_instance->submenu_messages_user_choices);
-    submenu_set_header(app_instance->submenu_messages_user_choices, "Users");
+    submenu_reset(app_instance->submenu);
+    submenu_set_header(app_instance->submenu, "Users");
     for (int i = 0; i < flip_social_explore->count; i++)
     {
-        submenu_add_item(app_instance->submenu_messages_user_choices, flip_social_explore->usernames[i], FlipSocialSubmenuLoggedInIndexMessagesUserChoicesIndexStart + i, flip_social_callback_submenu_choices, app_instance);
+        submenu_add_item(app_instance->submenu, flip_social_explore->usernames[i], FlipSocialSubmenuLoggedInIndexMessagesUserChoicesIndexStart + i, flip_social_callback_submenu_choices, app_instance);
     }
     return true;
 }
@@ -141,16 +105,22 @@ bool flip_social_get_message_users()
         FURI_LOG_E(TAG, "Username is NULL");
         return false;
     }
-    if (fhttp.state == INACTIVE)
+    if (!flipper_http_init(flipper_http_rx_callback, app_instance))
     {
-        FURI_LOG_E(TAG, "HTTP state is INACTIVE");
+        FURI_LOG_E(TAG, "Failed to initialize FlipperHTTP");
         return false;
     }
+    char directory[128];
+    snprintf(directory, sizeof(directory), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_social/messages");
+
+    // Create the directory
+    Storage *storage = furi_record_open(RECORD_STORAGE);
+    storage_common_mkdir(storage, directory);
     char command[128];
     snprintf(
         fhttp.file_path,
         sizeof(fhttp.file_path),
-        STORAGE_EXT_PATH_PREFIX "/apps_data/flip_social/message_users.json");
+        STORAGE_EXT_PATH_PREFIX "/apps_data/flip_social/messages/message_users.json");
 
     fhttp.save_received_data = true;
     auth_headers_alloc();
@@ -159,6 +129,7 @@ bool flip_social_get_message_users()
     {
         FURI_LOG_E(TAG, "Failed to send HTTP request for messages");
         fhttp.state = ISSUE;
+        flipper_http_deinit();
         return false;
     }
     fhttp.state = RECEIVING;
@@ -168,9 +139,9 @@ bool flip_social_get_message_users()
 // Get all the messages between the logged in user and the selected user
 bool flip_social_get_messages_with_user()
 {
-    if (fhttp.state == INACTIVE)
+    if (!flipper_http_init(flipper_http_rx_callback, app_instance))
     {
-        FURI_LOG_E(TAG, "HTTP state is INACTIVE");
+        FURI_LOG_E(TAG, "Failed to initialize FlipperHTTP");
         return false;
     }
     if (app_instance->login_username_logged_out == NULL)
@@ -178,16 +149,23 @@ bool flip_social_get_messages_with_user()
         FURI_LOG_E(TAG, "Username is NULL");
         return false;
     }
-    if (!flip_social_message_users->usernames[flip_social_message_users->index] || strlen(flip_social_message_users->usernames[flip_social_message_users->index]) == 0)
+    if (strlen(flip_social_message_users->usernames[flip_social_message_users->index]) == 0)
     {
         FURI_LOG_E(TAG, "Username is NULL");
         return false;
     }
+    char directory[128];
+    snprintf(directory, sizeof(directory), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_social/messages");
+
+    // Create the directory
+    Storage *storage = furi_record_open(RECORD_STORAGE);
+    storage_common_mkdir(storage, directory);
+
     char command[256];
     snprintf(
         fhttp.file_path,
         sizeof(fhttp.file_path),
-        STORAGE_EXT_PATH_PREFIX "/apps_data/flip_social/%s_messages.json",
+        STORAGE_EXT_PATH_PREFIX "/apps_data/flip_social/messages/%s_messages.json",
         flip_social_message_users->usernames[flip_social_message_users->index]);
 
     fhttp.save_received_data = true;
@@ -211,15 +189,10 @@ bool flip_social_parse_json_message_users()
     if (message_data == NULL)
     {
         FURI_LOG_E(TAG, "Failed to load received data from file.");
+        flipper_http_deinit();
         return false;
     }
-    char *data_cstr = (char *)furi_string_get_cstr(message_data);
-    if (data_cstr == NULL)
-    {
-        FURI_LOG_E(TAG, "Failed to get C-string from FuriString.");
-        furi_string_free(message_data);
-        return false;
-    }
+    flipper_http_deinit();
 
     // Allocate memory for each username only if not already allocated
     flip_social_message_users = flip_social_messages_alloc();
@@ -227,65 +200,29 @@ bool flip_social_parse_json_message_users()
     {
         FURI_LOG_E(TAG, "Failed to allocate memory for message users.");
         furi_string_free(message_data);
-        free(data_cstr);
         return false;
     }
 
     // Initialize message users count
     flip_social_message_users->count = 0;
 
-    // Extract the users array from the JSON
-    char *json_users = get_json_value("users", data_cstr, 64);
-    if (json_users == NULL)
-    {
-        FURI_LOG_E(TAG, "Failed to parse users array.");
-        furi_string_free(message_data);
-        free(data_cstr);
-        return false;
-    }
-
-    // Manual tokenization for comma-separated values
-    char *start = json_users + 1; // Skip the opening bracket
-    char *end;
-    while ((end = strchr(start, ',')) != NULL && flip_social_message_users->count < MAX_MESSAGE_USERS)
+    for (int i = 0; i < MAX_MESSAGE_USERS; i++)
     {
-        *end = '\0'; // Null-terminate the current token
-
-        // Remove quotes
-        if (*start == '"')
-            start++;
-        if (*(end - 1) == '"')
-            *(end - 1) = '\0';
-
-        // Copy username to pre-allocated memory
-        snprintf(flip_social_message_users->usernames[flip_social_message_users->count], MAX_USER_LENGTH, "%s", start);
-        flip_social_message_users->count++;
-        start = end + 1;
-    }
-
-    // Handle the last token
-    if (*start != '\0' && flip_social_message_users->count < MAX_MESSAGE_USERS)
-    {
-        if (*start == '"')
-            start++;
-        if (*(start + strlen(start) - 1) == ']')
-            *(start + strlen(start) - 1) = '\0';
-        if (*(start + strlen(start) - 1) == '"')
-            *(start + strlen(start) - 1) = '\0';
-
-        snprintf(flip_social_message_users->usernames[flip_social_message_users->count], MAX_USER_LENGTH, "%s", start);
+        FuriString *user = get_json_array_value_furi("users", i, message_data);
+        if (user == NULL)
+        {
+            break;
+        }
+        snprintf(flip_social_message_users->usernames[i], MAX_USER_LENGTH, "%s", furi_string_get_cstr(user));
         flip_social_message_users->count++;
+        furi_string_free(user);
     }
 
     // Add submenu items for the users
     flip_social_update_messages_submenu();
 
     // Free the JSON data
-    free(json_users);
-    free(start);
-    free(end);
     furi_string_free(message_data);
-    free(data_cstr);
     return true;
 }
 
@@ -297,82 +234,42 @@ bool flip_social_parse_json_message_user_choices()
     if (user_data == NULL)
     {
         FURI_LOG_E(TAG, "Failed to load received data from file.");
-        return false;
-    }
-    char *data_cstr = (char *)furi_string_get_cstr(user_data);
-    if (data_cstr == NULL)
-    {
-        FURI_LOG_E(TAG, "Failed to get C-string from FuriString.");
-        furi_string_free(user_data);
+        flipper_http_deinit();
         return false;
     }
 
+    flipper_http_deinit();
+
     // Allocate memory for each username only if not already allocated
     flip_social_explore = flip_social_explore_alloc();
     if (flip_social_explore == NULL)
     {
         FURI_LOG_E(TAG, "Failed to allocate memory for explore usernames.");
         furi_string_free(user_data);
-        free(data_cstr);
         return false;
     }
 
     // Initialize explore count
     flip_social_explore->count = 0;
 
-    // Extract the users array from the JSON
-    char *json_users = get_json_value("users", data_cstr, 64);
-    if (json_users == NULL)
+    for (int i = 0; i < MAX_MESSAGE_USERS; i++)
     {
-        FURI_LOG_E(TAG, "Failed to parse users array.");
-        furi_string_free(user_data);
-        free(data_cstr);
-        return false;
-    }
-
-    // Manual tokenization for comma-separated values
-    char *start = json_users + 1; // Skip the opening bracket
-    char *end;
-    while ((end = strchr(start, ',')) != NULL && flip_social_explore->count < MAX_EXPLORE_USERS)
-    {
-        *end = '\0'; // Null-terminate the current token
-
-        // Remove quotes
-        if (*start == '"')
-            start++;
-        if (*(end - 1) == '"')
-            *(end - 1) = '\0';
-
-        // Copy username to pre-allocated memory
-        snprintf(flip_social_explore->usernames[flip_social_explore->count], MAX_USER_LENGTH, "%s", start);
-        flip_social_explore->count++;
-        start = end + 1;
-    }
-
-    // Handle the last token
-    if (*start != '\0' && flip_social_explore->count < MAX_EXPLORE_USERS)
-    {
-        if (*start == '"')
-            start++;
-        if (*(start + strlen(start) - 1) == ']')
-            *(start + strlen(start) - 1) = '\0';
-        if (*(start + strlen(start) - 1) == '"')
-            *(start + strlen(start) - 1) = '\0';
-
-        snprintf(flip_social_explore->usernames[flip_social_explore->count], MAX_USER_LENGTH, "%s", start);
+        FuriString *user = get_json_array_value_furi("users", i, user_data);
+        if (user == NULL)
+        {
+            break;
+        }
+        snprintf(flip_social_explore->usernames[i], MAX_USER_LENGTH, "%s", furi_string_get_cstr(user));
         flip_social_explore->count++;
+        furi_string_free(user);
     }
 
     // Add submenu items for the users
     flip_social_update_submenu_user_choices();
 
     // Free the JSON data
-    free(json_users);
-    free(start);
-    free(end);
     furi_string_free(user_data);
-    free(data_cstr);
-    return true;
+    return flip_social_explore->count > 0;
 }
 
 // parse messages between the logged in user and the selected user
@@ -383,15 +280,10 @@ bool flip_social_parse_json_messages()
     if (message_data == NULL)
     {
         FURI_LOG_E(TAG, "Failed to load received data from file.");
+        flipper_http_deinit();
         return false;
     }
-    char *data_cstr = (char *)furi_string_get_cstr(message_data);
-    if (data_cstr == NULL)
-    {
-        FURI_LOG_E(TAG, "Failed to get C-string from FuriString.");
-        furi_string_free(message_data);
-        return false;
-    }
+    flipper_http_deinit();
 
     // Allocate memory for each message only if not already allocated
     flip_social_messages = flip_social_user_messages_alloc();
@@ -399,7 +291,6 @@ bool flip_social_parse_json_messages()
     {
         FURI_LOG_E(TAG, "Failed to allocate memory for messages.");
         furi_string_free(message_data);
-        free(data_cstr);
         return false;
     }
 
@@ -410,40 +301,38 @@ bool flip_social_parse_json_messages()
     for (int i = 0; i < MAX_MESSAGES; i++)
     {
         // Parse each item in the array
-        char *item = get_json_array_value("conversations", i, data_cstr, 64);
+        FuriString *item = get_json_array_value_furi("conversations", i, message_data);
         if (item == NULL)
         {
             break;
         }
 
         // Extract individual fields from the JSON object
-        char *sender = get_json_value("sender", item, 8);
-        char *content = get_json_value("content", item, 8);
+        FuriString *sender = get_json_value_furi("sender", item);
+        FuriString *content = get_json_value_furi("content", item);
 
         if (sender == NULL || content == NULL)
         {
             FURI_LOG_E(TAG, "Failed to parse item fields.");
-            free(item);
+            furi_string_free(item);
             continue;
         }
 
         // Store parsed values in pre-allocated memory
-        snprintf(flip_social_messages->usernames[i], MAX_USER_LENGTH, "%s", sender);
-        snprintf(flip_social_messages->messages[i], MAX_MESSAGE_LENGTH, "%s", content);
+        snprintf(flip_social_messages->usernames[i], MAX_USER_LENGTH, "%s", furi_string_get_cstr(sender));
+        snprintf(flip_social_messages->messages[i], MAX_MESSAGE_LENGTH, "%s", furi_string_get_cstr(content));
         flip_social_messages->count++;
 
-        free(item);
-        free(sender);
-        free(content);
+        furi_string_free(item);
+        furi_string_free(sender);
+        furi_string_free(content);
     }
     if (!messages_dialog_alloc(true))
     {
         FURI_LOG_E(TAG, "Failed to allocate and set messages dialog.");
         furi_string_free(message_data);
-        free(data_cstr);
         return false;
     }
     furi_string_free(message_data);
-    free(data_cstr);
     return true;
 }

この差分においてかなりの量のファイルが変更されているため、一部のファイルを表示していません