Browse Source

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 year ago
parent
commit
9e22fe272b
17 changed files with 1468 additions and 712 deletions
  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

BIN
.DS_Store


+ 32 - 19
README.md

@@ -11,19 +11,23 @@ The first social media app for Flipper Zero. Connect with other users directly o
 - Feed
 - Feed
 - Profile
 - Profile
 - Customizable Pre-Saves
 - Customizable Pre-Saves
+- Explore (NEW)
+- Friends (NEW)
 - Direct Messaging (coming soon)
 - 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
 ## Roadmap
 **v0.2**
 **v0.2**
@@ -37,24 +41,33 @@ The biggest challenge with a social media app on the Flipper Zero is using only
 - Direct Messaging
 - Direct Messaging
 - Privacy Settings
 - Privacy Settings
 
 
+**v0.5**
+- Improved Explore Page
+
+**v0.6**
+- Improved Feed Page
+
 ## Contribution
 ## 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.
 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
 ## 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.
 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."
 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.
 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
 // 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_callback.h> // Include the callback functions
 #include <flip_social_i.h>        // Include the initialization functions
 #include <flip_social_i.h>        // Include the initialization functions
 #include <flip_social_free.h>     // Include the cleanup functions
 #include <flip_social_free.h>     // Include the cleanup functions

+ 1 - 1
application.fam

@@ -9,6 +9,6 @@ App(
     fap_icon_assets="assets",
     fap_icon_assets="assets",
     fap_author="jblanked",
     fap_author="jblanked",
     fap_weburl="https://github.com/jblanked/FlipSocial",
     fap_weburl="https://github.com/jblanked/FlipSocial",
-    fap_version="0.2",
+    fap_version="0.3",
     fap_description="Social media platform for the Flipper Zero.",
     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
 - Feed
 - Profile
 - Profile
 - Customizable Pre-Saves
 - Customizable Pre-Saves
+- Explore (NEW)
+- Friends (NEW)
 - Direct Messaging (coming soon)
 - 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
 ## Roadmap
 **v0.2**
 **v0.2**
@@ -37,24 +41,33 @@ The biggest challenge with a social media app on the Flipper Zero is using only
 - Direct Messaging
 - Direct Messaging
 - Privacy Settings
 - Privacy Settings
 
 
+**v0.5**
+- Improved Explore Page
+
+**v0.6**
+- Improved Feed Page
+
 ## Contribution
 ## 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.
 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
 ## 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.
 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."
 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.
 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/popup.h>
 #include <gui/modules/loading.h>
 #include <gui/modules/loading.h>
 #include <uart_text_input.h>
 #include <uart_text_input.h>
+#include <stdio.h>
+#include <string.h>
 
 
 #define EASY_TAG "EasyFlipper"
 #define EASY_TAG "EasyFlipper"
 
 

+ 89 - 588
flip_social_callback.h

@@ -1,18 +1,6 @@
 // flip_social_callback.h
 // flip_social_callback.h
 #ifndef FLIP_SOCIAL_CALLBACK_H
 #ifndef FLIP_SOCIAL_CALLBACK_H
 #define 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.
  * @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;
     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
  * @brief Navigation callback for exiting the application
  * @param context The context - unused
  * @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);
         view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedInFeed);
         break;
         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:
     case FlipSocialSubmenuLoggedInIndexCompose:
         view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedInCompose);
         view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedInCompose);
         break;
         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);
         view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedOutSubmenu);
         break;
         break;
     case FlipSocialSubmenuComposeIndexAddPreSave:
     case FlipSocialSubmenuComposeIndexAddPreSave:
-
         view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedInComposeAddPreSaveInput);
         view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedInComposeAddPreSaveInput);
         break;
         break;
     default:
     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;
         break;
     }
     }
 }
 }
@@ -1329,7 +809,7 @@ static void flip_social_logged_in_profile_change_password_updated(void *context)
     char payload[256];
     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);
     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, "Failed to send post request to change password");
         FURI_LOG_E(TAG, "Make sure the Flipper is connected to the Wifi Dev Board");
         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
     case 1: // Change Password
         view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedInChangePasswordInput);
         view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedInChangePasswordInput);
         break;
         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:
     default:
         FURI_LOG_E(TAG, "Unknown configuration item index");
         FURI_LOG_E(TAG, "Unknown configuration item index");
         break;
         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_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_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
 // Define the submenu items for our Hello World application
 typedef enum
 typedef enum
@@ -28,6 +33,11 @@ typedef enum
     //
     //
     FlipSocialSubmenuComposeIndexAddPreSave,      // click to add a pre-saved message
     FlipSocialSubmenuComposeIndexAddPreSave,      // click to add a pre-saved message
     FlipSocialSubemnuComposeIndexStartIndex = 99, // starting index for the first 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;
 } FlipSocialSubmenuIndex;
 
 
 typedef enum
 typedef enum
@@ -83,6 +93,11 @@ typedef enum
     FlipSocialViewLoggedInProcessCompose,            // The dialog view to delete or send the clicked pre-saved text
     FlipSocialViewLoggedInProcessCompose,            // The dialog view to delete or send the clicked pre-saved text
     //
     //
     FlipSocialViewLoggedInSignOut, // The view after clicking the sign out button
     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;
 } FlipSocialView;
 
 
 // Define the application structure
 // Define the application structure
@@ -92,6 +107,8 @@ typedef struct
     Submenu *submenu_logged_out;     // The application submenu (logged out)
     Submenu *submenu_logged_out;     // The application submenu (logged out)
     Submenu *submenu_logged_in;      // The application submenu (logged in)
     Submenu *submenu_logged_in;      // The application submenu (logged in)
     Submenu *submenu_compose;        // The application submenu (compose)
     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_out_about; // The about screen (logged out)
     Widget *widget_logged_in_about;  // The about screen (logged in)
     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_register; // The screen displayed after clicking register
     View *view_process_feed;     // Dialog for the feed screen
     View *view_process_feed;     // Dialog for the feed screen
     View *view_process_compose;  // Dialog for the compose screen (delete or send)
     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_wifi_settings; // The wifi settings menu
     VariableItemList *variable_item_list_logged_out_login;         // The login 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_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_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;
     FuriPubSub *input_event_queue;
     FuriPubSubSubscription *input_event;
     FuriPubSubSubscription *input_event;
 
 
+    PreSavedPlaylist pre_saved_messages; // Pre-saved messages for the feed screen
+
     char *is_logged_in;         // Store the login status
     char *is_logged_in;         // Store the login status
     uint32_t is_logged_in_size; // Size of the login status buffer
     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
     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
     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;                     // Store the entered wifi ssid
     char *wifi_ssid_logged_out_temp_buffer;         // Temporary buffer for wifi ssid text input
     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
     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';
     result[len] = '\0';
     return (char *)memcpy(result, s, len);
     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
 #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
 #ifndef FLIP_SOCIAL_FEED_H
 #define 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()
 bool flip_social_get_feed()
 {
 {
     // Get the feed from the server
     // Get the feed from the server
@@ -100,52 +74,4 @@ bool flip_social_parse_json_feed()
     return true;
     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
 #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);
         view_dispatcher_remove_view(app->view_dispatcher, FlipSocialViewLoggedInCompose);
         submenu_free(app->submenu_compose);
         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)
     // Free Variable Item List(s)
     if (app->variable_item_list_logged_out_wifi_settings)
     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_dispatcher_remove_view(app->view_dispatcher, FlipSocialViewLoggedInProcessCompose);
         view_free(app->view_process_compose);
         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)
     if (app->view_dispatcher)
         view_dispatcher_free(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)
     if (fhttp.received_data)
         free(fhttp.received_data);
         free(fhttp.received_data);
 
 
+    // free playlist and explore page
+
     // DeInit UART
     // DeInit UART
     flipper_http_deinit();
     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)
     // 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;
         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;
         return NULL;
     }
     }
@@ -158,12 +158,21 @@ static FlipSocialApp *flip_social_app_alloc()
     {
     {
         return NULL;
         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, "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, "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, "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_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, "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, "Post", FlipSocialSubmenuLoggedInIndexCompose, flip_social_callback_submenu_choices, app);
     submenu_add_item(app->submenu_logged_in, "Profile", FlipSocialSubmenuLoggedInIndexProfile, flip_social_callback_submenu_choices, app);
     submenu_add_item(app->submenu_logged_in, "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_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);
     submenu_add_item(app->submenu_compose, "Add Pre-Save", FlipSocialSubmenuComposeIndexAddPreSave, flip_social_callback_submenu_choices, app);
+    //
 
 
     // Allocate View(s)
     // 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))
     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;
         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)
     // 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))
     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_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_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_about = variable_item_list_add(app->variable_item_list_logged_in_settings, "About", 0, NULL, NULL);
     app->variable_item_logged_in_settings_wifi = variable_item_list_add(app->variable_item_list_logged_in_settings, "WiFi", 0, NULL, NULL);
     app->variable_item_logged_in_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
     // 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
     // Check if we've started receiving data from a GET request
     if (fhttp.started_receiving_get)
     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;
   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 */
 #endif /* JB_JSMN_EDIT */