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

FlipSocial - v0.3

- Added an Explore page: Discover other users and add them as friends.
- Added a Friends page: View and remove friends.
- Improved error handling.
jblanked 1 год назад
Родитель
Сommit
9e22fe272b
17 измененных файлов с 1468 добавлено и 712 удалено
  1. BIN
      .DS_Store
  2. 32 19
      README.md
  3. 8 4
      app.c
  4. 1 1
      application.fam
  5. 10 2
      assets/CHANGELOG.md
  6. 32 19
      assets/README.md
  7. 2 0
      easy_flipper.h
  8. 89 588
      flip_social_callback.h
  9. 809 0
      flip_social_draw.h
  10. 57 2
      flip_social_e.h
  11. 124 0
      flip_social_explore.h
  12. 0 74
      flip_social_feed.h
  13. 22 0
      flip_social_free.h
  14. 132 0
      flip_social_friends.h
  15. 21 2
      flip_social_i.h
  16. 1 1
      flipper_http.h
  17. 128 0
      jsmn.h

+ 32 - 19
README.md

@@ -11,19 +11,23 @@ The first social media app for Flipper Zero. Connect with other users directly o
 - Feed
 - Profile
 - Customizable Pre-Saves
+- Explore (NEW)
+- Friends (NEW)
 - Direct Messaging (coming soon)
 
-**Login/Logout**
-Log in to your account to view and post on the Feed. You can also change your password and log out when needed.
+**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.
+**Registration:** Create an account with just a username and password—no email or personal information required or collected.
+
+**Feed:** View up to 128 of the latest posts, create your own posts, and "Flip" a post—FlipSocial’s version of liking or favoriting a post.
+
+**Customizable Pre-Saves**: The biggest challenge with a social media app on the Flipper Zero is using only the directional pad for input. As a solution, I implemented a pre-saved text system. The pre-saves are stored in a pre_saved_messages.txt file on your SD card. You can edit the pre-saves by opening qFlipper, downloading the file from the /apps_data/flip_social/ folder, adding your pre-saves (separated by new lines), and then copying it back to your SD card. You can also create the pre-saves directly within the app.
+
+**Explore:** Discover other users and add them as friends.
+
+**Friends:** View and remove friends.
 
-**Feed**
-View up to 128 of the latest posts, create your own posts, and "Flip" a post—FlipSocial’s version of liking or favoriting a post.
 
-**Customizable Pre-Saves**
-The biggest challenge with a social media app on the Flipper Zero is using only the directional pad for input. As a solution, I implemented a pre-saved text system. The pre-saves are stored in a pre_saved_messages.txt file on your SD card. You can edit the pre-saves by opening qFlipper, downloading the file from the /apps_data/flip_social/ folder, adding your pre-saves (separated by new lines), and then copying it back to your SD card. You can also create the pre-saves directly within the app.
 
 ## Roadmap
 **v0.2**
@@ -37,24 +41,33 @@ The biggest challenge with a social media app on the Flipper Zero is using only
 - Direct Messaging
 - Privacy Settings
 
+**v0.5**
+- Improved Explore Page
+
+**v0.6**
+- Improved Feed Page
+
 ## Contribution
 This is a big task, and I welcome all contributors, especially developers who are into animations and graphics. Fork the repository, create a pull request, and I will review your edits.
 
 ## Known Bugs
-1. When clicking any button other than the BACK button in the Feed view or the post creation view, the app doesn't respond to inputs.
-   - Solution: Restart your Flipper device.
+1. When clicking any button other than the BACK button in the Feed view, post creation view, or 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.
+- 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.
+- 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.
+- 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 after visiting the feed and post views back-t-back.
+- Solution: Restart your Flipper device.

+ 8 - 4
app.c

@@ -1,8 +1,12 @@
 // app.c
-#include <jsmn.h>                 // Include cJSON
-#include <uart_text_input.h>      // Include the text input widget
-#include <flip_social_e.h>        // Include the FlipSocialApp structure
-#include <flip_social_storage.h>  // Include the storage functions
+#include <jsmn.h>                // Include cJSON
+#include <uart_text_input.h>     // Include the text input widget
+#include <flip_social_e.h>       // Include the FlipSocialApp structure
+#include <flip_social_storage.h> // Include the storage functions
+#include "flip_social_draw.h"
+#include "flip_social_feed.h"
+#include "flip_social_explore.h"
+#include "flip_social_friends.h"
 #include <flip_social_callback.h> // Include the callback functions
 #include <flip_social_i.h>        // Include the initialization functions
 #include <flip_social_free.h>     // Include the cleanup functions

+ 1 - 1
application.fam

@@ -9,6 +9,6 @@ App(
     fap_icon_assets="assets",
     fap_author="jblanked",
     fap_weburl="https://github.com/jblanked/FlipSocial",
-    fap_version="0.2",
+    fap_version="0.3",
     fap_description="Social media platform for the Flipper Zero.",
 )

+ 10 - 2
assets/CHANGELOG.md

@@ -1,2 +1,10 @@
-## v0.1
-- Initial version.
+## 0.3
+- Added an Explore page: Discover other users and add them as friends.
+- Added a Friends page: View and remove friends.
+- Improved error handling.
+
+## 0.2
+- Stability patch.
+
+## 0.1
+- Initial release.

+ 32 - 19
assets/README.md

@@ -11,19 +11,23 @@ The first social media app for Flipper Zero. Connect with other users directly o
 - Feed
 - Profile
 - Customizable Pre-Saves
+- Explore (NEW)
+- Friends (NEW)
 - Direct Messaging (coming soon)
 
-**Login/Logout**
-Log in to your account to view and post on the Feed. You can also change your password and log out when needed.
+**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.
+**Registration:** Create an account with just a username and password—no email or personal information required or collected.
+
+**Feed:** View up to 128 of the latest posts, create your own posts, and "Flip" a post—FlipSocial’s version of liking or favoriting a post.
+
+**Customizable Pre-Saves**: The biggest challenge with a social media app on the Flipper Zero is using only the directional pad for input. As a solution, I implemented a pre-saved text system. The pre-saves are stored in a pre_saved_messages.txt file on your SD card. You can edit the pre-saves by opening qFlipper, downloading the file from the /apps_data/flip_social/ folder, adding your pre-saves (separated by new lines), and then copying it back to your SD card. You can also create the pre-saves directly within the app.
+
+**Explore:** Discover other users and add them as friends.
+
+**Friends:** View and remove friends.
 
-**Feed**
-View up to 128 of the latest posts, create your own posts, and "Flip" a post—FlipSocial’s version of liking or favoriting a post.
 
-**Customizable Pre-Saves**
-The biggest challenge with a social media app on the Flipper Zero is using only the directional pad for input. As a solution, I implemented a pre-saved text system. The pre-saves are stored in a pre_saved_messages.txt file on your SD card. You can edit the pre-saves by opening qFlipper, downloading the file from the /apps_data/flip_social/ folder, adding your pre-saves (separated by new lines), and then copying it back to your SD card. You can also create the pre-saves directly within the app.
 
 ## Roadmap
 **v0.2**
@@ -37,24 +41,33 @@ The biggest challenge with a social media app on the Flipper Zero is using only
 - Direct Messaging
 - Privacy Settings
 
+**v0.5**
+- Improved Explore Page
+
+**v0.6**
+- Improved Feed Page
+
 ## Contribution
 This is a big task, and I welcome all contributors, especially developers who are into animations and graphics. Fork the repository, create a pull request, and I will review your edits.
 
 ## Known Bugs
-1. When clicking any button other than the BACK button in the Feed view or the post creation view, the app doesn't respond to inputs.
-   - Solution: Restart your Flipper device.
+1. When clicking any button other than the BACK button in the Feed view, post creation view, or 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.
+- 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.
+- 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.
+- 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 after visiting the feed and post views back-t-back.
+- Solution: Restart your Flipper device.

+ 2 - 0
easy_flipper.h

@@ -18,6 +18,8 @@
 #include <gui/modules/popup.h>
 #include <gui/modules/loading.h>
 #include <uart_text_input.h>
+#include <stdio.h>
+#include <string.h>
 
 #define EASY_TAG "EasyFlipper"
 

+ 89 - 588
flip_social_callback.h

@@ -1,18 +1,6 @@
 // flip_social_callback.h
 #ifndef FLIP_SOCIAL_CALLBACK_H
 #define FLIP_SOCIAL_CALLBACK_H
-#include "flip_social_feed.h"
-
-bool flip_social_sent_login_request = false;
-bool flip_social_sent_register_request = false;
-bool flip_social_login_success = false;
-bool flip_social_register_success = false;
-bool flip_social_dialog_shown = false;
-bool flip_social_dialog_stop = false;
-
-uint32_t flip_social_pre_saved_message_clicked_index = 0;
-static void flip_social_logged_in_compose_pre_save_updated(void *context);
-static void flip_social_callback_submenu_choices(void *context, uint32_t index);
 
 /**
  * @brief Navigation callback to go back to the submenu Logged out.
@@ -118,585 +106,37 @@ static uint32_t flip_social_callback_to_profile_logged_in(void *context)
     return FlipSocialViewLoggedInProfile;
 }
 
-static void on_input(const void *event, void *ctx)
-{
-    UNUSED(ctx);
-
-    InputKey key = ((InputEvent *)event)->key;
-    InputType type = ((InputEvent *)event)->type;
-
-    if (type != InputTypeRelease)
-    {
-        return;
-    }
-
-    switch (key)
-    {
-    case InputKeyOk:
-        action = ActionFlip;
-        break;
-    case InputKeyBack:
-        action = ActionBack;
-        break;
-    case InputKeyRight:
-        action = ActionNext;
-        break;
-    case InputKeyLeft:
-        action = ActionPrev;
-        break;
-    case InputKeyUp:
-        action = ActionPrev;
-        break;
-    case InputKeyDown:
-        action = ActionNext;
-        break;
-    default:
-        action = ActionNone;
-        break;
-    }
-}
-
-// Function to draw the message on the canvas with word wrapping
-void draw_user_message(Canvas *canvas, const char *user_message, int x)
-{
-    if (user_message == NULL)
-    {
-        FURI_LOG_E(TAG, "User message is NULL.");
-        return;
-    }
-
-    size_t msg_length = strlen(user_message);
-    size_t start = 0;
-    int line_num = 0;
-    char line[MAX_LINE_LENGTH + 1]; // Buffer for the current line (+1 for null terminator)
-
-    while (start < msg_length && line_num < MAX_FEED_ITEMS)
-    {
-        size_t remaining = msg_length - start;
-        size_t len = (remaining > MAX_LINE_LENGTH) ? MAX_LINE_LENGTH : remaining;
-
-        if (remaining > MAX_LINE_LENGTH)
-        {
-            // Find the last space within the first 'len' characters
-            size_t last_space = len;
-            while (last_space > 0 && user_message[start + last_space - 1] != ' ')
-            {
-                last_space--;
-            }
-
-            if (last_space > 0)
-            {
-                len = last_space; // Adjust len to the position of the last space
-            }
-        }
-
-        // Copy the substring to 'line' and null-terminate it
-        memcpy(line, user_message + start, len);
-        line[len] = '\0'; // Ensure the string is null-terminated
-
-        // Draw the string on the canvas
-        // Adjust the y-coordinate based on the line number
-        canvas_draw_str_aligned(canvas, 0, x + line_num * 10, AlignLeft, AlignTop, line);
-
-        // Update the start position for the next line
-        start += len;
-
-        // Skip any spaces to avoid leading spaces on the next line
-        while (start < msg_length && user_message[start] == ' ')
-        {
-            start++;
-        }
-
-        // Increment the line number
-        line_num++;
-    }
-}
-
-static void flip_social_callback_draw_compose(Canvas *canvas, void *model)
-{
-    UNUSED(model);
-    if (!canvas)
-    {
-        FURI_LOG_E(TAG, "Canvas is NULL");
-        return;
-    }
-    if (!app_instance)
-    {
-        FURI_LOG_E(TAG, "FlipSocialApp is NULL");
-        return;
-    }
-
-    char *message = app_instance->pre_saved_messages.messages[flip_social_pre_saved_message_clicked_index];
-
-    if (!flip_social_dialog_shown)
-    {
-        flip_social_dialog_shown = true;
-        app_instance->input_event_queue = furi_record_open(RECORD_INPUT_EVENTS);
-        app_instance->input_event = furi_pubsub_subscribe(app_instance->input_event_queue, on_input, NULL);
-    }
-
-    draw_user_message(canvas, message, 2);
-
-    canvas_draw_icon(canvas, 0, 53, &I_ButtonLeft_4x7);
-    canvas_draw_str_aligned(canvas, 7, 54, AlignLeft, AlignTop, "Delete");
-    canvas_draw_icon(canvas, 52, 53, &I_ButtonBACK_10x8);
-    canvas_draw_str_aligned(canvas, 64, 54, AlignLeft, AlignTop, "Back");
-    canvas_draw_icon(canvas, 100, 53, &I_ButtonRight_4x7);
-    canvas_draw_str_aligned(canvas, 107, 54, AlignLeft, AlignTop, "Post");
-
-    // handle action
-    switch (action)
-    {
-    case ActionNone:
-        break;
-    case ActionBack:
-        flip_social_dialog_stop = true;
-        break;
-    case ActionNext:
-        // send message
-        if (message && app_instance->login_username_logged_in)
-        {
-            // Send the message
-            char command[128];
-            snprintf(command, sizeof(command), "{\"username\":\"%s\",\"content\":\"%s\"}",
-                     app_instance->login_username_logged_in, message);
-
-            bool success = flipper_http_post_request_with_headers(
-                "https://www.flipsocial.net/api/feed/post/",
-                "{\"Content-Type\":\"application/json\"}",
-                command);
-
-            if (!success)
-            {
-                FURI_LOG_E(TAG, "Failed to send HTTP request for feed");
-                furi_check(success); // Log the error with furi_check
-                return;              // Exit early to avoid further errors
-            }
-
-            fhttp.state = RECEIVING;
-            furi_timer_start(fhttp.get_timeout_timer, TIMEOUT_DURATION_TICKS);
-        }
-        else
-        {
-            FURI_LOG_E(TAG, "Message or username is NULL");
-            furi_check(false); // Log as an error and return
-            return;
-        }
-
-        int i = 0;
-        while (fhttp.state == RECEIVING && furi_timer_is_running(fhttp.get_timeout_timer) > 0)
-        {
-            // Wait for the feed to be received
-            furi_delay_ms(100);
-
-            char dots_str[64] = "Receiving";
-
-            // Append dots to the string based on the value of i
-            int dot_count = i % 4;
-            int len = strlen(dots_str);
-            snprintf(dots_str + len, sizeof(dots_str) - len, "%.*s", dot_count, "....");
-
-            // Draw the resulting string on the canvas
-            canvas_draw_str(canvas, 0, 30, dots_str);
-
-            i++;
-        }
-        flip_social_dialog_stop = true;
-        furi_timer_stop(fhttp.get_timeout_timer);
-        break;
-    case ActionPrev:
-        // delete message
-        // remove the message from app_instance->pre_saved_messages
-        app_instance->pre_saved_messages.messages[flip_social_pre_saved_message_clicked_index] = NULL;
-
-        for (uint32_t i = flip_social_pre_saved_message_clicked_index; i < app_instance->pre_saved_messages.count - 1; i++)
-        {
-            app_instance->pre_saved_messages.messages[i] = app_instance->pre_saved_messages.messages[i + 1];
-        }
-        app_instance->pre_saved_messages.count--;
-
-        // add the item to the submenu
-        submenu_reset(app_instance->submenu_compose);
-
-        submenu_add_item(app_instance->submenu_compose, "Add Pre-Save", FlipSocialSubmenuComposeIndexAddPreSave, flip_social_callback_submenu_choices, app_instance);
-
-        for (uint32_t i = 0; i < app_instance->pre_saved_messages.count; i++)
-        {
-            submenu_add_item(app_instance->submenu_compose, app_instance->pre_saved_messages.messages[i], FlipSocialSubemnuComposeIndexStartIndex + i, flip_social_callback_submenu_choices, app_instance);
-        }
-
-        // save playlist
-        save_playlist(&app_instance->pre_saved_messages);
-
-        flip_social_dialog_stop = true;
-        break;
-    default:
-        action = ActionNone;
-        break;
-    }
-
-    if (flip_social_dialog_stop)
-    {
-        furi_pubsub_unsubscribe(app_instance->input_event_queue, app_instance->input_event);
-        flip_social_dialog_shown = false;
-        flip_social_dialog_stop = false;
-        if (action == ActionBack || action == ActionNext)
-        {
-            action = ActionNone;
-            view_dispatcher_switch_to_view(app_instance->view_dispatcher, FlipSocialViewLoggedInSubmenu);
-        }
-        else
-        {
-            action = ActionNone;
-            view_dispatcher_switch_to_view(app_instance->view_dispatcher, FlipSocialViewLoggedInCompose);
-        }
-    }
-}
-// function to draw the dialog canvas
-static void flip_social_canvas_draw_message(Canvas *canvas, char *user_username, char *user_message, bool is_flipped, bool show_prev, bool show_next)
-{
-    canvas_set_color(canvas, ColorBlack);
-    canvas_set_font(canvas, FontPrimary);
-    canvas_draw_str_aligned(canvas, 64, 5, AlignCenter, AlignCenter, user_username);
-    canvas_set_font(canvas, FontSecondary);
-
-    draw_user_message(canvas, user_message, 12);
-
-    canvas_set_font(canvas, FontSecondary);
-    if (show_prev)
-    {
-        canvas_draw_icon(canvas, 0, 53, &I_ButtonLeft_4x7);
-        canvas_draw_str_aligned(canvas, 9, 54, AlignLeft, AlignTop, "Prev");
-    }
-    if (!is_flipped)
-    {
-        canvas_draw_icon(canvas, 52, 53, &I_ButtonOK_7x7);
-        canvas_draw_str_aligned(canvas, 61, 54, AlignLeft, AlignTop, "Flip");
-    }
-    else
-    {
-        canvas_draw_icon(canvas, 47, 53, &I_ButtonOK_7x7);
-        canvas_draw_str_aligned(canvas, 56, 54, AlignLeft, AlignTop, "UnFlip");
-    }
-    if (show_next)
-    {
-        canvas_draw_icon(canvas, 98, 53, &I_ButtonRight_4x7);
-        canvas_draw_str_aligned(canvas, 107, 54, AlignLeft, AlignTop, "Next");
-    }
-}
-// Callback function to handle the feed dialog
-static void flip_social_callback_draw_feed(Canvas *canvas, void *model)
-{
-    UNUSED(model);
-    if (!canvas)
-    {
-        FURI_LOG_E(TAG, "Canvas is NULL");
-        return;
-    }
-    if (!app_instance)
-    {
-        FURI_LOG_E(TAG, "FlipSocialApp is NULL");
-        return;
-    }
-
-    if (!flip_social_dialog_shown)
-    {
-        flip_social_dialog_shown = true;
-        app_instance->input_event_queue = furi_record_open(RECORD_INPUT_EVENTS);
-        app_instance->input_event = furi_pubsub_subscribe(app_instance->input_event_queue, on_input, NULL);
-    }
-
-    // handle action
-    switch (action)
-    {
-    case ActionNone:
-        flip_social_canvas_draw_message(canvas, flip_social_feed.usernames[flip_social_feed.index], flip_social_feed.messages[flip_social_feed.index], flip_social_feed.is_flipped[flip_social_feed.index], flip_social_feed.index > 0, flip_social_feed.index < flip_social_feed.count - 1);
-        break;
-    case ActionNext:
-        canvas_clear(canvas);
-        if (flip_social_feed.index < flip_social_feed.count - 1)
-        {
-            flip_social_feed.index++;
-        }
-        flip_social_canvas_draw_message(canvas, flip_social_feed.usernames[flip_social_feed.index], flip_social_feed.messages[flip_social_feed.index], flip_social_feed.is_flipped[flip_social_feed.index], flip_social_feed.index > 0, flip_social_feed.index < flip_social_feed.count - 1);
-        action = ActionNone;
-        break;
-    case ActionPrev:
-        canvas_clear(canvas);
-        if (flip_social_feed.index > 0)
-        {
-            flip_social_feed.index--;
-        }
-        flip_social_canvas_draw_message(canvas, flip_social_feed.usernames[flip_social_feed.index], flip_social_feed.messages[flip_social_feed.index], flip_social_feed.is_flipped[flip_social_feed.index], flip_social_feed.index > 0, flip_social_feed.index < flip_social_feed.count - 1);
-        action = ActionNone;
-        break;
-    case ActionFlip:
-        canvas_clear(canvas);
-        flip_social_feed.is_flipped[flip_social_feed.index] = !flip_social_feed.is_flipped[flip_social_feed.index];
-        flip_social_canvas_draw_message(canvas, flip_social_feed.usernames[flip_social_feed.index], flip_social_feed.messages[flip_social_feed.index], flip_social_feed.is_flipped[flip_social_feed.index], flip_social_feed.index > 0, flip_social_feed.index < flip_social_feed.count - 1);
-        action = ActionNone;
-        // send post request to flip the message
-        if (app_instance->login_username_logged_in == NULL)
-        {
-            FURI_LOG_E(TAG, "Username is NULL");
-            return;
-        }
-        char payload[256];
-        snprintf(payload, sizeof(payload), "{\"username\":\"%s\",\"post_id\":\"%lu\"}", app_instance->login_username_logged_in, flip_social_feed.ids[flip_social_feed.index]);
-        flipper_http_post_request_with_headers("https://www.flipsocial.net/api/feed/flip/", "{\"Content-Type\":\"application/json\"}", payload);
-        break;
-    case ActionBack:
-        canvas_clear(canvas);
-        flip_social_dialog_stop = true;
-        flip_social_feed.index = 0;
-        action = ActionNone;
-        break;
-    default:
-        break;
-    }
-
-    if (flip_social_dialog_stop)
-    {
-        furi_pubsub_unsubscribe(app_instance->input_event_queue, app_instance->input_event);
-        flip_social_dialog_shown = false;
-        flip_social_dialog_stop = false;
-        action = ActionNone;
-    }
-}
 /**
- * @brief Navigation callback for asynchonously handling the login process.
- * @param canvas The canvas to draw on.
- * @param model The model - unused
- * @return void
+ * @brief Navigation callback to bring the user back to the Explore submenu
+ * @param context The context - unused
+ * @return next view id (FlipSocialViewLoggedInExploreSubmenu)
  */
-static void flip_social_callback_draw_login(Canvas *canvas, void *model)
+static uint32_t flip_social_callback_to_explore_logged_in(void *context)
 {
-    UNUSED(model);
-    if (!canvas)
-    {
-        FURI_LOG_E(TAG, "Canvas is NULL");
-        return;
-    }
-
-    canvas_set_font(canvas, FontSecondary);
-
-    if (!flip_social_board_is_active(canvas))
-    {
-        return;
-    }
-
-    canvas_draw_str(canvas, 0, 7, "Logging in...");
-
-    // Perform login request
-    if (!flip_social_sent_login_request)
-    {
-
-        if (!app_instance->login_username_logged_out || !app_instance->login_password_logged_out || strlen(app_instance->login_username_logged_out) == 0 || strlen(app_instance->login_password_logged_out) == 0)
-        {
-            canvas_clear(canvas);
-            canvas_draw_str(canvas, 0, 10, "Please enter your credentials.");
-            canvas_draw_str(canvas, 0, 60, "Press BACK to return.");
-            return;
-        }
-
-        flip_social_sent_login_request = true;
-
-        char buffer[256];
-        snprintf(buffer, sizeof(buffer), "{\"username\":\"%s\",\"password\":\"%s\"}", app_instance->login_username_logged_out, app_instance->login_password_logged_out);
-        flip_social_login_success = flipper_http_post_request_with_headers("https://www.flipsocial.net/api/login/", "{\"Content-Type\":\"application/json\"}", buffer);
-        if (flip_social_login_success)
-        {
-            fhttp.state = RECEIVING;
-            return;
-        }
-        else
-        {
-            fhttp.state = ISSUE;
-            return;
-        }
-    }
-    // handle response
-    if (flip_social_sent_login_request && flip_social_login_success)
-    {
-        canvas_set_font(canvas, FontSecondary);
-        canvas_draw_str(canvas, 0, 17, "Request Sent!");
-        canvas_draw_str(canvas, 0, 32, "Awaiting reponse...");
-
-        if (fhttp.state == IDLE && fhttp.received_data != NULL)
-        {
-            // read response
-            if (strstr(fhttp.received_data, "[SUCCESS]") != NULL || strstr(fhttp.received_data, "User found") != NULL)
-            {
-                canvas_draw_str(canvas, 0, 42, "Login successful!");
-                canvas_draw_str(canvas, 0, 62, "Welcome back!");
-
-                app_instance->is_logged_in = "true";
-
-                save_settings(app_instance->wifi_ssid_logged_out, app_instance->wifi_password_logged_out, app_instance->login_username_logged_out, app_instance->login_username_logged_in, app_instance->login_password_logged_out, app_instance->change_password_logged_in, app_instance->is_logged_in);
-
-                // send user to the logged in submenu
-                view_dispatcher_switch_to_view(app_instance->view_dispatcher, FlipSocialViewLoggedInSubmenu);
-            }
-            else if (strstr(fhttp.received_data, "User not found") != NULL)
-            {
-                canvas_clear(canvas);
-                canvas_draw_str(canvas, 0, 10, "Account not found...");
-                canvas_draw_str(canvas, 0, 60, "Press BACK to return.");
-            }
-            else
-            {
-                flip_social_handle_error(canvas);
-            }
-        }
-        else if ((fhttp.state == ISSUE || fhttp.state == INACTIVE) && fhttp.received_data != NULL)
-        {
-            flip_social_handle_error(canvas);
-        }
-        else if (fhttp.state == IDLE && fhttp.received_data == NULL)
-        {
-            flip_social_handle_error(canvas);
-        }
-    }
-    else if (flip_social_sent_login_request && !flip_social_login_success)
-    {
-        canvas_clear(canvas);
-        canvas_draw_str(canvas, 0, 10, "Failed sending request.");
-        canvas_draw_str(canvas, 0, 50, "Update your WiFi settings.");
-        canvas_draw_str(canvas, 0, 62, "Press BACK to return.");
-    }
+    UNUSED(context);
+    flip_social_dialog_stop = true;
+    last_explore_response = "";
+    flip_social_dialog_shown = false;
+    flip_social_explore.index = 0;
+    action = ActionNone;
+    return FlipSocialViewLoggedInExploreSubmenu;
 }
 
 /**
- * @brief Navigation callback for asynchonously handling the register process.
- * @param canvas The canvas to draw on.
- * @param model The model - unused
- * @return void
+ * @brief Navigation callback to bring the user back to the Friends submenu
+ * @param context The context - unused
+ * @return next view id (FlipSocialViewLoggedInFriendsSubmenu)
  */
-static void flip_social_callback_draw_register(Canvas *canvas, void *model)
+static uint32_t flip_social_callback_to_friends_logged_in(void *context)
 {
-    UNUSED(model);
-    if (!canvas)
-    {
-        FURI_LOG_E(TAG, "Canvas is NULL");
-        return;
-    }
-
-    canvas_set_font(canvas, FontSecondary);
-
-    if (!flip_social_board_is_active(canvas))
-    {
-        return;
-    }
-
-    canvas_draw_str(canvas, 0, 7, "Registering...");
-
-    // Perform login request
-    if (!flip_social_sent_register_request)
-    {
-        // check if the username and password are valid
-        if (!app_instance->register_username_logged_out || !app_instance->register_password_logged_out || strlen(app_instance->register_username_logged_out) == 0 || strlen(app_instance->register_password_logged_out) == 0)
-        {
-            canvas_clear(canvas);
-            canvas_draw_str(canvas, 0, 10, "Please enter your credentials.");
-            canvas_draw_str(canvas, 0, 60, "Press BACK to return.");
-            return;
-        }
-
-        // check if both passwords match
-        if (strcmp(app_instance->register_password_logged_out, app_instance->register_password_2_logged_out) != 0)
-        {
-            canvas_clear(canvas);
-            canvas_draw_str(canvas, 0, 10, "Passwords do not match.");
-            canvas_draw_str(canvas, 0, 60, "Press BACK to return.");
-            return;
-        }
-
-        char buffer[128];
-        snprintf(buffer, sizeof(buffer), "{\"username\":\"%s\",\"password\":\"%s\"}", app_instance->register_username_logged_out, app_instance->register_password_logged_out);
-        flip_social_register_success = flipper_http_post_request_with_headers("https://www.flipsocial.net/api/register/", "{\"Content-Type\":\"application/json\"}", buffer);
-
-        flip_social_sent_register_request = true;
-        if (flip_social_register_success)
-        {
-            // Set the state to RECEIVING to ensure we continue to see the receiving message
-            fhttp.state = RECEIVING;
-        }
-        else
-        {
-            fhttp.state = ISSUE;
-        }
-    }
-    // handle response
-    if (flip_social_sent_register_request && flip_social_register_success)
-    {
-        canvas_set_font(canvas, FontSecondary);
-        canvas_draw_str(canvas, 0, 17, "Request Sent!");
-        canvas_draw_str(canvas, 0, 32, "Awaiting reponse...");
-
-        if (fhttp.state == IDLE)
-        {
-            // read response
-            if (fhttp.received_data != NULL && (strstr(fhttp.received_data, "[SUCCESS]") != NULL || strstr(fhttp.received_data, "User created") != NULL))
-            {
-                canvas_draw_str(canvas, 0, 42, "Registeration successful!");
-                canvas_draw_str(canvas, 0, 62, "Welcome to FlipSocial!");
-
-                // set the login credentials
-                if (app_instance->login_username_logged_out)
-                {
-                    app_instance->login_username_logged_out = app_instance->register_username_logged_out;
-                }
-                if (app_instance->login_password_logged_out)
-                {
-                    app_instance->login_password_logged_out = app_instance->register_password_logged_out;
-                    app_instance->change_password_logged_in = app_instance->register_password_logged_out;
-                }
-                if (app_instance->login_username_logged_in)
-                {
-                    app_instance->login_username_logged_in = app_instance->register_username_logged_out;
-                }
-
-                app_instance->is_logged_in = "true";
-
-                // save the credentials
-                save_settings(app_instance->wifi_ssid_logged_out, app_instance->wifi_password_logged_out, app_instance->login_username_logged_out, app_instance->login_username_logged_in, app_instance->login_password_logged_out, app_instance->change_password_logged_in, app_instance->is_logged_in);
-
-                // send user to the logged in submenu
-                view_dispatcher_switch_to_view(app_instance->view_dispatcher, FlipSocialViewLoggedInSubmenu);
-            }
-            else if (strstr(fhttp.received_data, "Username or password not provided") != NULL)
-            {
-                canvas_clear(canvas);
-                canvas_draw_str(canvas, 0, 10, "Please enter your credentials.");
-                canvas_draw_str(canvas, 0, 60, "Press BACK to return.");
-            }
-            else if (strstr(fhttp.received_data, "User already exists") != NULL || strstr(fhttp.received_data, "Multiple users found") != NULL)
-            {
-                canvas_draw_str(canvas, 0, 42, "Registration failed...");
-                canvas_draw_str(canvas, 0, 52, "Username already exists.");
-                canvas_draw_str(canvas, 0, 62, "Press BACK to return.");
-            }
-            else
-            {
-                canvas_draw_str(canvas, 0, 42, "Registration failed...");
-                canvas_draw_str(canvas, 0, 52, "Update your credentials.");
-                canvas_draw_str(canvas, 0, 62, "Press BACK to return.");
-            }
-        }
-        else if (fhttp.state == ISSUE || fhttp.state == INACTIVE)
-        {
-            flip_social_handle_error(canvas);
-        }
-    }
-    else if (flip_social_sent_register_request && !flip_social_register_success)
-    {
-        canvas_clear(canvas);
-        canvas_draw_str(canvas, 0, 10, "Failed sending request.");
-        canvas_draw_str(canvas, 0, 50, "Update your WiFi settings.");
-        canvas_draw_str(canvas, 0, 62, "Press BACK to return.");
-    }
+    UNUSED(context);
+    flip_social_dialog_stop = true;
+    last_explore_response = "";
+    flip_social_dialog_shown = false;
+    flip_social_friends.index = 0;
+    action = ActionNone;
+    return FlipSocialViewLoggedInFriendsSubmenu;
 }
-
 /**
  * @brief Navigation callback for exiting the application
  * @param context The context - unused
@@ -767,6 +207,27 @@ static void flip_social_callback_submenu_choices(void *context, uint32_t index)
         }
         view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedInFeed);
         break;
+    case FlipSocialSubmenuExploreIndex:
+        // process explore page the same way as the feed page, then switch to the explore page
+        if (flip_social_get_explore()) // start the async explore request
+        {
+            furi_timer_start(fhttp.get_timeout_timer, TIMEOUT_DURATION_TICKS);
+        }
+        while (fhttp.state == RECEIVING && furi_timer_is_running(fhttp.get_timeout_timer) > 0)
+        {
+            // Wait for the explore to be received
+            furi_delay_ms(100);
+        }
+        furi_timer_stop(fhttp.get_timeout_timer);
+        if (!flip_social_parse_json_explore()) // parse the JSON before switching to the explore (synchonous)
+        {
+            FURI_LOG_E(TAG, "Failed to parse the JSON explore...");
+            return; // just return for now, no temporary explore yet
+            // show a popup message saving wifi is disconnected
+        }
+        furi_timer_stop(fhttp.get_timeout_timer);
+        view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedInExploreSubmenu);
+        break;
     case FlipSocialSubmenuLoggedInIndexCompose:
         view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedInCompose);
         break;
@@ -781,14 +242,33 @@ static void flip_social_callback_submenu_choices(void *context, uint32_t index)
         view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedOutSubmenu);
         break;
     case FlipSocialSubmenuComposeIndexAddPreSave:
-
         view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedInComposeAddPreSaveInput);
         break;
     default:
-        // handle FlipSocialSubemnuComposeIndexStartIndex + i
-        // set temp variable to hold the index
-        flip_social_pre_saved_message_clicked_index = index - FlipSocialSubemnuComposeIndexStartIndex;
-        view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedInProcessCompose);
+        // Handle the pre-saved message selection (has a max of 25 items)
+        if (index >= FlipSocialSubemnuComposeIndexStartIndex && index < FlipSocialSubemnuComposeIndexStartIndex + MAX_PRE_SAVED_MESSAGES)
+        {
+            flip_social_feed.index = index - FlipSocialSubemnuComposeIndexStartIndex;
+            view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedInProcessCompose);
+        }
+
+        // Handle the explore selection
+        else if (index >= FlipSocialSubmenuExploreIndexStartIndex && index < FlipSocialSubmenuExploreIndexStartIndex + MAX_EXPLORE_USERS)
+        {
+            flip_social_explore.index = index - FlipSocialSubmenuExploreIndexStartIndex;
+            view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedInExploreProccess);
+        }
+
+        // handle the friends selection
+        else if (index >= FlipSocialSubmenuLoggedInIndexFriendsStart && index < FlipSocialSubmenuLoggedInIndexFriendsStart + MAX_FRIENDS)
+        {
+            flip_social_friends.index = index - FlipSocialSubmenuLoggedInIndexFriendsStart;
+            view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedInFriendsProcess);
+        }
+        else
+        {
+            FURI_LOG_E(TAG, "Unknown submenu index");
+        }
         break;
     }
 }
@@ -1329,7 +809,7 @@ static void flip_social_logged_in_profile_change_password_updated(void *context)
     char payload[256];
     snprintf(payload, sizeof(payload), "{\"username\":\"%s\",\"old_password\":\"%s\",\"new_password\":\"%s\"}", app->login_username_logged_out, old_password, app->change_password_logged_in);
 
-    if (!flipper_http_post_request_with_headers("https://www.flipsocial.net/api/change-password/", "Content-Type: application/json", payload))
+    if (!flipper_http_post_request_with_headers("https://www.flipsocial.net/api/user/change-password/", "Content-Type: application/json", payload))
     {
         FURI_LOG_E(TAG, "Failed to send post request to change password");
         FURI_LOG_E(TAG, "Make sure the Flipper is connected to the Wifi Dev Board");
@@ -1364,6 +844,27 @@ static void flip_social_text_input_logged_in_profile_item_selected(void *context
     case 1: // Change Password
         view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedInChangePasswordInput);
         break;
+    case 2: // Friends
+        // get friends then switch to the friends screen
+        if (flip_social_get_friends()) // start the async friends request
+        {
+            furi_timer_start(fhttp.get_timeout_timer, TIMEOUT_DURATION_TICKS);
+        }
+        while (fhttp.state == RECEIVING && furi_timer_is_running(fhttp.get_timeout_timer) > 0)
+        {
+            // Wait for the friends to be received
+            furi_delay_ms(100);
+        }
+        furi_timer_stop(fhttp.get_timeout_timer);
+        if (!flip_social_parse_json_friends()) // parse the JSON before switching to the friends (synchonous)
+        {
+            FURI_LOG_E(TAG, "Failed to parse the JSON friends...");
+            return; // just return for now, no temporary friends yet
+            // show a popup message saving wifi is disconnected
+        }
+        furi_timer_stop(fhttp.get_timeout_timer);
+        view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedInFriendsSubmenu);
+        break;
     default:
         FURI_LOG_E(TAG, "Unknown configuration item index");
         break;

+ 809 - 0
flip_social_draw.h

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

+ 57 - 2
flip_social_e.h

@@ -11,6 +11,11 @@
 
 #define MAX_PRE_SAVED_MESSAGES 25 // Maximum number of pre-saved messages
 #define MAX_MESSAGE_LENGTH 100    // Maximum length of a message in the feed
+#define MAX_EXPLORE_USERS 50      // Maximum number of users to explore
+#define MAX_FRIENDS 50            // Maximum number of friends
+#define MAX_TOKENS 512            // Adjust based on expected JSON tokens
+#define MAX_FEED_ITEMS 128
+#define MAX_LINE_LENGTH 30
 
 // Define the submenu items for our Hello World application
 typedef enum
@@ -28,6 +33,11 @@ typedef enum
     //
     FlipSocialSubmenuComposeIndexAddPreSave,      // click to add a pre-saved message
     FlipSocialSubemnuComposeIndexStartIndex = 99, // starting index for the first pre saved message
+    //
+    FlipSocialSubmenuExploreIndex = 150,           // click to go to the explore
+    FlipSocialSubmenuExploreIndexStartIndex = 151, // starting index for the users to explore
+    //
+    FlipSocialSubmenuLoggedInIndexFriendsStart = 1000, // starting index for the friends
 } FlipSocialSubmenuIndex;
 
 typedef enum
@@ -83,6 +93,11 @@ typedef enum
     FlipSocialViewLoggedInProcessCompose,            // The dialog view to delete or send the clicked pre-saved text
     //
     FlipSocialViewLoggedInSignOut, // The view after clicking the sign out button
+    //
+    FlipSocialViewLoggedInExploreSubmenu,  // The view after clicking the explore button
+    FlipSocialViewLoggedInExploreProccess, // The view after clicking on a user in the explore screen
+    FlipSocialViewLoggedInFriendsSubmenu,  // The view after clicking the friends button on the profile screen
+    FlipSocialViewLoggedInFriendsProcess,  // The view after clicking on a friend in the friends screen
 } FlipSocialView;
 
 // Define the application structure
@@ -92,6 +107,8 @@ typedef struct
     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)
     Widget *widget_logged_out_about; // The about screen (logged out)
     Widget *widget_logged_in_about;  // The about screen (logged in)
 
@@ -99,6 +116,8 @@ typedef struct
     View *view_process_register; // The screen displayed after clicking register
     View *view_process_feed;     // Dialog for the feed screen
     View *view_process_compose;  // Dialog for the compose screen (delete or send)
+    View *view_process_explore;  // Dialog for the explore screen (view user profile - add or delete friend)
+    View *view_process_friends;  // Dialog for the friends screen (view user profile - add or delete friend)
 
     VariableItemList *variable_item_list_logged_out_wifi_settings; // The wifi settings menu
     VariableItemList *variable_item_list_logged_out_login;         // The login menu
@@ -138,9 +157,13 @@ typedef struct
     VariableItem *variable_item_logged_in_wifi_settings_ssid;      // Reference to the ssid configuration item
     VariableItem *variable_item_logged_in_wifi_settings_password;  // Reference to the password configuration item
     //
+    VariableItem *variable_item_logged_in_profile_friends; // Reference to the friends configuration item
+    //
     FuriPubSub *input_event_queue;
     FuriPubSubSubscription *input_event;
 
+    PreSavedPlaylist pre_saved_messages; // Pre-saved messages for the feed screen
+
     char *is_logged_in;         // Store the login status
     uint32_t is_logged_in_size; // Size of the login status buffer
 
@@ -148,8 +171,6 @@ typedef struct
     char *login_username_logged_in_temp_buffer;         // Temporary buffer for login username text input
     uint32_t login_username_logged_in_temp_buffer_size; // Size of the login username temporary buffer
 
-    PreSavedPlaylist pre_saved_messages; // Pre-saved messages for the feed screen
-
     char *wifi_ssid_logged_out;                     // Store the entered wifi ssid
     char *wifi_ssid_logged_out_temp_buffer;         // Temporary buffer for wifi ssid text input
     uint32_t wifi_ssid_logged_out_temp_buffer_size; // Size of the wifi ssid temporary buffer
@@ -213,4 +234,38 @@ char *strndup(const char *s, size_t n)
     result[len] = '\0';
     return (char *)memcpy(result, s, len);
 }
+
+static FlipSocialApp *app_instance = NULL;
+
+typedef struct
+{
+    char *usernames[MAX_FEED_ITEMS];
+    char *messages[MAX_FEED_ITEMS];
+    bool is_flipped[MAX_FEED_ITEMS];
+    uint32_t ids[MAX_FEED_ITEMS];
+    size_t count;
+    size_t index;
+} FlipSocialFeed;
+
+typedef struct
+{
+    char *usernames[MAX_EXPLORE_USERS];
+    size_t count;
+    size_t index;
+} FlipSocialModel;
+
+static FlipSocialModel flip_social_explore;
+static FlipSocialModel flip_social_friends;
+
+// temporary FlipSocialFeed object
+static FlipSocialFeed flip_social_feed = {
+    .usernames = {"JBlanked", "FlipperKing", "FlipperQueen"},
+    .messages = {"Welcome. This is a temp message. Either the feed didn't load or there was a server error.", "I am the Chosen Flipper.", "No one can flip like me."},
+    .is_flipped = {false, false, true},
+    .ids = {0, 1, 2},
+    .count = 3,
+    .index = 0};
+
+static void flip_social_logged_in_compose_pre_save_updated(void *context);
+static void flip_social_callback_submenu_choices(void *context, uint32_t index);
 #endif

+ 124 - 0
flip_social_explore.h

@@ -0,0 +1,124 @@
+#ifndef FLIP_SOCIAL_EXPLORE_H
+#define FLIP_SOCIAL_EXPLORE_H
+
+// 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()
+{
+    // will return true unless the devboard is not connected
+    bool success = flipper_http_get_request_with_headers("https://www.flipsocial.net/api/user/users/", "{\"Content-Type\":\"application/json\"}");
+    if (!success)
+    {
+        FURI_LOG_E(TAG, "Failed to send HTTP request for explore");
+        return false;
+    }
+    fhttp.state = RECEIVING;
+    return true;
+}
+
+bool flip_social_parse_json_explore()
+{
+    if (fhttp.received_data == NULL)
+    {
+        FURI_LOG_E(TAG, "No data received.");
+        return false;
+    }
+
+    // Remove newlines
+    char *pos = fhttp.received_data;
+    while ((pos = strchr(pos, '\n')) != NULL)
+    {
+        *pos = ' ';
+    }
+
+    // Initialize explore count
+    flip_social_explore.count = 0;
+
+    // Extract the users array from the JSON
+    char *json_users = get_json_value("users", fhttp.received_data, MAX_TOKENS);
+    if (json_users == NULL)
+    {
+        FURI_LOG_E(TAG, "Failed to parse users array.");
+        return false;
+    }
+
+    // Manual tokenization for comma-separated values
+    char *start = json_users + 1; // Skip the opening bracket
+    char *end;
+    while ((end = strchr(start, ',')) != NULL)
+    {
+        *end = '\0'; // Null-terminate the current token
+
+        // Remove the quotes
+        if (*start == '"')
+        {
+            start++;
+        }
+        if (*(end - 1) == '"')
+        {
+            *(end - 1) = '\0';
+        }
+
+        // Allocate memory for the username
+        size_t length = strlen(start) + 1;
+        flip_social_explore.usernames[flip_social_explore.count] = malloc(length);
+        if (flip_social_explore.usernames[flip_social_explore.count] == NULL)
+        {
+            FURI_LOG_E(TAG, "Memory allocation failed.");
+            return false;
+        }
+
+        // Copy the username to the allocated memory
+        strncpy(flip_social_explore.usernames[flip_social_explore.count], start, length);
+        flip_social_explore.count++;
+        start = end + 1; // Move to the next token
+    }
+
+    // Handle the last token
+    if (*start != '\0')
+    {
+        // Remove the quotes
+        if (*start == '"')
+        {
+            start++;
+        }
+        // Skip the closing bracket
+        if (*(start + strlen(start) - 1) == ']')
+        {
+            *(start + strlen(start) - 1) = '\0';
+        }
+        // Remove the quotes
+        if (*(start + strlen(start) - 1) == '"')
+        {
+            *(start + strlen(start) - 1) = '\0';
+        }
+
+        // Allocate memory for the username
+        size_t length = strlen(start) + 1;
+        flip_social_explore.usernames[flip_social_explore.count] = malloc(length);
+        if (flip_social_explore.usernames[flip_social_explore.count] == NULL)
+        {
+            FURI_LOG_E(TAG, "Memory allocation failed.");
+            return false;
+        }
+
+        // Copy the username to the allocated memory
+        strncpy(flip_social_explore.usernames[flip_social_explore.count], start, length);
+        flip_social_explore.count++;
+    }
+
+    // Add submenu items for the users
+    submenu_reset(app_instance->submenu_explore);
+    submenu_set_header(app_instance->submenu_explore, "Explore");
+    for (uint32_t i = 0; i < flip_social_explore.count; i++)
+    {
+        submenu_add_item(app_instance->submenu_explore, flip_social_explore.usernames[i], FlipSocialSubmenuExploreIndexStartIndex + i, flip_social_callback_submenu_choices, app_instance);
+    }
+
+    // Free the json_users
+    free(json_users);
+
+    return true;
+}
+
+#endif // FLIP_SOCIAL_EXPLORE_H

+ 0 - 74
flip_social_feed.h

@@ -1,32 +1,6 @@
 #ifndef FLIP_SOCIAL_FEED_H
 #define FLIP_SOCIAL_FEED_H
 
-static FlipSocialApp *app_instance = NULL;
-
-#define MAX_TOKENS 512 // Adjust based on expected JSON tokens
-
-typedef struct
-{
-    char *usernames[128];
-    char *messages[128];
-    bool is_flipped[128];
-    uint32_t ids[128];
-    size_t count;
-    size_t index;
-} FlipSocialFeed;
-
-#define MAX_FEED_ITEMS 128
-#define MAX_LINE_LENGTH 30
-
-// temporary FlipSocialFeed object
-static FlipSocialFeed flip_social_feed = {
-    .usernames = {"JBlanked", "FlipperKing", "FlipperQueen"},
-    .messages = {"Welcome. This is a temp message. Either the feed didn't load or there was a server error.", "I am the Chosen Flipper.", "No one can flip like me."},
-    .is_flipped = {false, false, true},
-    .ids = {0, 1, 2},
-    .count = 3,
-    .index = 0};
-
 bool flip_social_get_feed()
 {
     // Get the feed from the server
@@ -100,52 +74,4 @@ bool flip_social_parse_json_feed()
     return true;
 }
 
-bool flip_social_board_is_active(Canvas *canvas)
-{
-    if (fhttp.state == INACTIVE)
-    {
-        canvas_draw_str(canvas, 0, 7, "Wifi Dev Board disconnected.");
-        canvas_draw_str(canvas, 0, 17, "Please connect to the board.");
-        canvas_draw_str(canvas, 0, 32, "If your board is connected,");
-        canvas_draw_str(canvas, 0, 42, "make sure you have flashed");
-        canvas_draw_str(canvas, 0, 52, "your WiFi Devboard with the");
-        canvas_draw_str(canvas, 0, 62, "latest FlipperHTTP flash.");
-        return false;
-    }
-    return true;
-}
-
-void flip_social_handle_error(Canvas *canvas)
-{
-    if (fhttp.received_data != NULL)
-    {
-        if (strstr(fhttp.received_data, "[ERROR] Not connected to Wifi. Failed to reconnect.") != NULL)
-        {
-            canvas_clear(canvas);
-            canvas_draw_str(canvas, 0, 10, "[ERROR] Not connected to Wifi.");
-            canvas_draw_str(canvas, 0, 50, "Update your WiFi settings.");
-            canvas_draw_str(canvas, 0, 60, "Press BACK to return.");
-        }
-        else if (strstr(fhttp.received_data, "[ERROR] Failed to connect to Wifi.") != NULL)
-        {
-            canvas_clear(canvas);
-            canvas_draw_str(canvas, 0, 10, "[ERROR] Not connected to Wifi.");
-            canvas_draw_str(canvas, 0, 50, "Update your WiFi settings.");
-            canvas_draw_str(canvas, 0, 60, "Press BACK to return.");
-        }
-        else
-        {
-            canvas_draw_str(canvas, 0, 42, "Failed...");
-            canvas_draw_str(canvas, 0, 52, "Update your credentials.");
-            canvas_draw_str(canvas, 0, 62, "Press BACK to return.");
-        }
-    }
-    else
-    {
-        canvas_draw_str(canvas, 0, 42, "Failed...");
-        canvas_draw_str(canvas, 0, 52, "Update your credentials.");
-        canvas_draw_str(canvas, 0, 62, "Press BACK to return.");
-    }
-}
-
 #endif // FLIP_SOCIAL_FEED_H

+ 22 - 0
flip_social_free.h

@@ -43,6 +43,16 @@ static void flip_social_app_free(FlipSocialApp *app)
         view_dispatcher_remove_view(app->view_dispatcher, FlipSocialViewLoggedInCompose);
         submenu_free(app->submenu_compose);
     }
+    if (app->submenu_explore)
+    {
+        view_dispatcher_remove_view(app->view_dispatcher, FlipSocialViewLoggedInExploreSubmenu);
+        submenu_free(app->submenu_explore);
+    }
+    if (app->submenu_friends)
+    {
+        view_dispatcher_remove_view(app->view_dispatcher, FlipSocialViewLoggedInFriendsSubmenu);
+        submenu_free(app->submenu_friends);
+    }
 
     // Free Variable Item List(s)
     if (app->variable_item_list_logged_out_wifi_settings)
@@ -166,6 +176,16 @@ static void flip_social_app_free(FlipSocialApp *app)
         view_dispatcher_remove_view(app->view_dispatcher, FlipSocialViewLoggedInProcessCompose);
         view_free(app->view_process_compose);
     }
+    if (app->view_process_explore)
+    {
+        view_dispatcher_remove_view(app->view_dispatcher, FlipSocialViewLoggedInExploreProccess);
+        view_free(app->view_process_explore);
+    }
+    if (app->view_process_friends)
+    {
+        view_dispatcher_remove_view(app->view_dispatcher, FlipSocialViewLoggedInFriendsProcess);
+        view_free(app->view_process_friends);
+    }
 
     if (app->view_dispatcher)
         view_dispatcher_free(app->view_dispatcher);
@@ -229,6 +249,8 @@ static void flip_social_app_free(FlipSocialApp *app)
     if (fhttp.received_data)
         free(fhttp.received_data);
 
+    // free playlist and explore page
+
     // DeInit UART
     flipper_http_deinit();
 

+ 132 - 0
flip_social_friends.h

@@ -0,0 +1,132 @@
+#ifndef FLIP_SOCIAL_FRIENDS
+#define FLIP_SOCIAL_FRIENDS
+
+// for now we're just listing the current users
+// as the feed is upgraded, then we can port more to the friends view
+static bool flip_social_get_friends()
+{
+    // will return true unless the devboard is not connected
+    char url[100];
+    snprintf(url, 100, "https://www.flipsocial.net/api/user/friends/%s/", app_instance->login_username_logged_in);
+    bool success = flipper_http_get_request_with_headers(url, "{\"Content-Type\":\"application/json\"}");
+    if (!success)
+    {
+        FURI_LOG_E(TAG, "Failed to send HTTP request for friends");
+        return false;
+    }
+    fhttp.state = RECEIVING;
+    return true;
+}
+
+static void flip_social_update_friends()
+{
+    // Add submenu items for the users
+    submenu_reset(app_instance->submenu_friends);
+    submenu_set_header(app_instance->submenu_friends, "Friends");
+    for (uint32_t 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);
+    }
+}
+
+static bool flip_social_parse_json_friends()
+{
+    if (fhttp.received_data == NULL)
+    {
+        FURI_LOG_E(TAG, "No data received.");
+        return false;
+    }
+
+    // Remove newlines
+    char *pos = fhttp.received_data;
+    while ((pos = strchr(pos, '\n')) != NULL)
+    {
+        *pos = ' ';
+    }
+
+    // Initialize friends count
+    flip_social_friends.count = 0;
+
+    // Extract the users array from the JSON
+    char *json_users = get_json_value("friends", fhttp.received_data, MAX_TOKENS);
+    if (json_users == NULL)
+    {
+        FURI_LOG_E(TAG, "Failed to parse friends array.");
+        return false;
+    }
+
+    // Manual tokenization for comma-separated values
+    char *start = json_users + 1; // Skip the opening bracket
+    char *end;
+    while ((end = strchr(start, ',')) != NULL)
+    {
+        *end = '\0'; // Null-terminate the current token
+
+        // Remove the quotes
+        if (*start == '"')
+        {
+            start++;
+        }
+        if (*(end - 1) == '"')
+        {
+            *(end - 1) = '\0';
+        }
+
+        // Allocate memory for the username
+        size_t length = strlen(start) + 1;
+        flip_social_friends.usernames[flip_social_friends.count] = malloc(length);
+        if (flip_social_friends.usernames[flip_social_friends.count] == NULL)
+        {
+            FURI_LOG_E(TAG, "Memory allocation failed.");
+            return false;
+        }
+
+        // Copy the username to the allocated memory
+        strncpy(flip_social_friends.usernames[flip_social_friends.count], start, length);
+        flip_social_friends.count++;
+        start = end + 1; // Move to the next token
+    }
+
+    // Handle the last token
+    if (*start != '\0')
+    {
+        // Remove the quotes
+        if (*start == '"')
+        {
+            start++;
+        }
+        // Skip the closing bracket
+        if (*(start + strlen(start) - 1) == ']')
+        {
+            *(start + strlen(start) - 1) = '\0';
+        }
+        // Remove the quotes
+        if (*(start + strlen(start) - 1) == '"')
+        {
+            *(start + strlen(start) - 1) = '\0';
+        }
+
+        // Allocate memory for the username
+        size_t length = strlen(start) + 1;
+        flip_social_friends.usernames[flip_social_friends.count] = malloc(length);
+        if (flip_social_friends.usernames[flip_social_friends.count] == NULL)
+        {
+            FURI_LOG_E(TAG, "Memory allocation failed.");
+            return false;
+        }
+
+        // Copy the username to the allocated memory
+        strncpy(flip_social_friends.usernames[flip_social_friends.count], start, length);
+        flip_social_friends.count++;
+    }
+
+    // Add submenu items for the users
+    flip_social_update_friends();
+
+    // Free the json_users
+    free(json_users);
+
+    return true;
+}
+
+#endif // FLIP_SOCIAL_FRIENDS

+ 21 - 2
flip_social_i.h

@@ -146,11 +146,11 @@ static FlipSocialApp *flip_social_app_alloc()
     }
 
     // Allocate Submenu(s)
-    if (!easy_flipper_set_submenu(&app->submenu_logged_out, FlipSocialViewLoggedOutSubmenu, "FlipSocial v0.2", flip_social_callback_exit_app, &app->view_dispatcher))
+    if (!easy_flipper_set_submenu(&app->submenu_logged_out, FlipSocialViewLoggedOutSubmenu, "FlipSocial v0.3", flip_social_callback_exit_app, &app->view_dispatcher))
     {
         return NULL;
     }
-    if (!easy_flipper_set_submenu(&app->submenu_logged_in, FlipSocialViewLoggedInSubmenu, "FlipSocial v0.2", flip_social_callback_exit_app, &app->view_dispatcher))
+    if (!easy_flipper_set_submenu(&app->submenu_logged_in, FlipSocialViewLoggedInSubmenu, "FlipSocial v0.3", flip_social_callback_exit_app, &app->view_dispatcher))
     {
         return NULL;
     }
@@ -158,12 +158,21 @@ static FlipSocialApp *flip_social_app_alloc()
     {
         return NULL;
     }
+    if (!easy_flipper_set_submenu(&app->submenu_explore, FlipSocialViewLoggedInExploreSubmenu, "Explore", flip_social_callback_to_submenu_logged_in, &app->view_dispatcher))
+    {
+        return NULL;
+    }
+    if (!easy_flipper_set_submenu(&app->submenu_friends, FlipSocialViewLoggedInFriendsSubmenu, "Friends", flip_social_callback_to_profile_logged_in, &app->view_dispatcher))
+    {
+        return NULL;
+    }
 
     submenu_add_item(app->submenu_logged_out, "Login", FlipSocialSubmenuLoggedOutIndexLogin, flip_social_callback_submenu_choices, app);
     submenu_add_item(app->submenu_logged_out, "Register", FlipSocialSubmenuLoggedOutIndexRegister, flip_social_callback_submenu_choices, app);
     submenu_add_item(app->submenu_logged_out, "About", FlipSocialSubmenuLoggedOutIndexAbout, flip_social_callback_submenu_choices, app);
     submenu_add_item(app->submenu_logged_out, "Settings", FlipSocialSubmenuLoggedOutIndexWifiSettings, flip_social_callback_submenu_choices, app);
     //
+    submenu_add_item(app->submenu_logged_in, "Explore", FlipSocialSubmenuExploreIndex, flip_social_callback_submenu_choices, app);
     submenu_add_item(app->submenu_logged_in, "Feed", FlipSocialSubmenuLoggedInIndexFeed, flip_social_callback_submenu_choices, app);
     submenu_add_item(app->submenu_logged_in, "Post", FlipSocialSubmenuLoggedInIndexCompose, flip_social_callback_submenu_choices, app);
     submenu_add_item(app->submenu_logged_in, "Profile", FlipSocialSubmenuLoggedInIndexProfile, flip_social_callback_submenu_choices, app);
@@ -171,6 +180,7 @@ static FlipSocialApp *flip_social_app_alloc()
     submenu_add_item(app->submenu_logged_in, "Sign Out", FlipSocialSubmenuLoggedInSignOutButton, flip_social_callback_submenu_choices, app);
     //
     submenu_add_item(app->submenu_compose, "Add Pre-Save", FlipSocialSubmenuComposeIndexAddPreSave, flip_social_callback_submenu_choices, app);
+    //
 
     // Allocate View(s)
     if (!easy_flipper_set_view(&app->view_process_login, FlipSocialViewLoggedOutProcessLogin, flip_social_callback_draw_login, NULL, flip_social_callback_to_login_logged_out, &app->view_dispatcher, app))
@@ -189,6 +199,14 @@ static FlipSocialApp *flip_social_app_alloc()
     {
         return NULL;
     }
+    if (!easy_flipper_set_view(&app->view_process_explore, FlipSocialViewLoggedInExploreProccess, flip_social_callback_draw_explore, NULL, flip_social_callback_to_explore_logged_in, &app->view_dispatcher, app))
+    {
+        return NULL;
+    }
+    if (!easy_flipper_set_view(&app->view_process_friends, FlipSocialViewLoggedInFriendsProcess, flip_social_callback_draw_friends, NULL, flip_social_callback_to_friends_logged_in, &app->view_dispatcher, app))
+    {
+        return NULL;
+    }
 
     // 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))
@@ -230,6 +248,7 @@ static FlipSocialApp *flip_social_app_alloc()
     //
     app->variable_item_logged_in_profile_username = variable_item_list_add(app->variable_item_list_logged_in_profile, "Username", 0, NULL, NULL);
     app->variable_item_logged_in_profile_change_password = variable_item_list_add(app->variable_item_list_logged_in_profile, "Change Password", 0, NULL, NULL);
+    app->variable_item_logged_in_profile_friends = variable_item_list_add(app->variable_item_list_logged_in_profile, "Friends", 0, NULL, NULL);
     //
     app->variable_item_logged_in_settings_about = variable_item_list_add(app->variable_item_list_logged_in_settings, "About", 0, NULL, NULL);
     app->variable_item_logged_in_settings_wifi = variable_item_list_add(app->variable_item_list_logged_in_settings, "WiFi", 0, NULL, NULL);

+ 1 - 1
flipper_http.h

@@ -724,7 +724,7 @@ void flipper_http_rx_callback(const char *line, void *context)
     }
 
     // Uncomment below line to log the data received over UART
-    //(HTTP_TAG, "Received UART line: %s", line);
+    // FURI_LOG_I(HTTP_TAG, "Received UART line: %s", line);
 
     // Check if we've started receiving data from a GET request
     if (fhttp.started_receiving_get)

+ 128 - 0
jsmn.h

@@ -719,4 +719,132 @@ char *get_json_array_value(char *key, uint32_t index, char *json_data, uint32_t
 
   return value;
 }
+
+// Revised get_json_array_values function with correct token skipping
+char **get_json_array_values(char *key, char *json_data, uint32_t max_tokens, int *num_values)
+{
+  // Retrieve the array string for the given key
+  char *array_str = get_json_value(key, json_data, max_tokens);
+  if (array_str == NULL)
+  {
+    FURI_LOG_E("JSMM.H", "Failed to get array for key: %s", key);
+    return NULL;
+  }
+
+  // Initialize the JSON parser
+  jsmn_parser parser;
+  jsmn_init(&parser);
+
+  // Allocate memory for JSON tokens
+  jsmntok_t *tokens = malloc(sizeof(jsmntok_t) * max_tokens); // Allocate on the heap
+  if (tokens == NULL)
+  {
+    FURI_LOG_E("JSMM.H", "Failed to allocate memory for JSON tokens.");
+    free(array_str);
+    return NULL;
+  }
+
+  // Parse the JSON array
+  int ret = jsmn_parse(&parser, array_str, strlen(array_str), tokens, max_tokens);
+  if (ret < 0)
+  {
+    FURI_LOG_E("JSMM.H", "Failed to parse JSON array: %d", ret);
+    free(tokens);
+    free(array_str);
+    return NULL;
+  }
+
+  // Ensure the root element is an array
+  if (tokens[0].type != JSMN_ARRAY)
+  {
+    FURI_LOG_E("JSMM.H", "Value for key '%s' is not an array.", key);
+    free(tokens);
+    free(array_str);
+    return NULL;
+  }
+
+  // Allocate memory for the array of values (maximum possible)
+  int array_size = tokens[0].size;
+  char **values = malloc(array_size * sizeof(char *));
+  if (values == NULL)
+  {
+    FURI_LOG_E("JSMM.H", "Failed to allocate memory for array of values.");
+    free(tokens);
+    free(array_str);
+    return NULL;
+  }
+
+  int actual_num_values = 0;
+
+  // Traverse the array and extract all object values
+  int current_token = 1; // Start after the array token
+  for (int i = 0; i < array_size; i++)
+  {
+    if (current_token >= ret)
+    {
+      FURI_LOG_E("JSMM.H", "Unexpected end of tokens while traversing array.");
+      break;
+    }
+
+    jsmntok_t element = tokens[current_token];
+
+    if (element.type != JSMN_OBJECT)
+    {
+      FURI_LOG_E("JSMM.H", "Array element %d is not an object, skipping.", i);
+      // Skip this element
+      current_token += 1;
+      continue;
+    }
+
+    int length = element.end - element.start;
+
+    // Allocate a new string for the value and copy the data
+    char *value = malloc(length + 1);
+    if (value == NULL)
+    {
+      FURI_LOG_E("JSMM.H", "Failed to allocate memory for array element.");
+      for (int j = 0; j < actual_num_values; j++)
+      {
+        free(values[j]);
+      }
+      free(values);
+      free(tokens);
+      free(array_str);
+      return NULL;
+    }
+
+    strncpy(value, array_str + element.start, length);
+    value[length] = '\0'; // Null-terminate the string
+
+    values[actual_num_values] = value;
+    actual_num_values++;
+
+    // Skip all tokens related to this object to avoid misparsing
+    current_token += 1 + (2 * element.size); // Each key-value pair consumes two tokens
+  }
+
+  *num_values = actual_num_values;
+
+  // Reallocate the values array to actual_num_values if necessary
+  if (actual_num_values < array_size)
+  {
+    char **reduced_values = realloc(values, actual_num_values * sizeof(char *));
+    if (reduced_values != NULL)
+    {
+      values = reduced_values;
+    }
+
+    // Free the remaining values
+    for (int i = actual_num_values; i < array_size; i++)
+    {
+      free(values[i]);
+    }
+  }
+
+  // Clean up
+  free(tokens);
+  free(array_str);
+  return values;
+}
+
 #endif /* JB_JSMN_EDIT */