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

Merge pull request #18 from jblanked/dev_0.8

FlipSocial - v0.8
JBlanked 1 год назад
Родитель
Сommit
448ff17035

+ 2 - 2
README.md

@@ -6,7 +6,7 @@ The highlight of this app is customizable pre-saves, which, as explained below,
 FlipSocial uses the FlipperHTTP flash for the WiFi Devboard, first introduced in the WebCrawler app: https://github.com/jblanked/WebCrawler-FlipperZero/tree/main/assets/FlipperHTTP
 
 ## Requirements
-- WiFi Developer Board or Raspberry Pi Pico W with FlipperHTTP Flash: https://github.com/jblanked/FlipperHTTP
+- WiFi Developer Board, Raspberry Pi, or ESP32 Device with FlipperHTTP Flash: https://github.com/jblanked/FlipperHTTP
 - WiFi Access Point
 
 
@@ -60,7 +60,7 @@ FlipSocial uses the FlipperHTTP flash for the WiFi Devboard, first introduced in
 - Loading screens.
 
 **v0.8**
-- Improve User Profile (Bio, friend count, block)
+- Improve User Profile
 - Improve Explore Page
 
 **v1.0**

+ 49 - 5
alloc/flip_social_alloc.c

@@ -41,6 +41,7 @@ FlipSocialApp *flip_social_app_alloc()
     app->register_password_logged_out_temp_buffer_size = MAX_USER_LENGTH;
     app->register_password_2_logged_out_temp_buffer_size = MAX_USER_LENGTH;
     app->change_password_logged_in_temp_buffer_size = MAX_USER_LENGTH;
+    app->change_bio_logged_in_temp_buffer_size = MAX_MESSAGE_LENGTH;
     app->compose_pre_save_logged_in_temp_buffer_size = MAX_MESSAGE_LENGTH;
     app->wifi_ssid_logged_in_temp_buffer_size = MAX_USER_LENGTH;
     app->wifi_password_logged_in_temp_buffer_size = MAX_USER_LENGTH;
@@ -48,6 +49,8 @@ FlipSocialApp *flip_social_app_alloc()
     app->login_username_logged_in_temp_buffer_size = MAX_USER_LENGTH;
     app->messages_new_message_logged_in_temp_buffer_size = MAX_MESSAGE_LENGTH;
     app->message_user_choice_logged_in_temp_buffer_size = MAX_MESSAGE_LENGTH;
+    app->explore_logged_in_temp_buffer_size = MAX_USER_LENGTH;
+    app->message_users_logged_in_temp_buffer_size = MAX_USER_LENGTH;
     if (!easy_flipper_set_buffer(&app->wifi_ssid_logged_out_temp_buffer, app->wifi_ssid_logged_out_temp_buffer_size))
     {
         return NULL;
@@ -80,6 +83,10 @@ FlipSocialApp *flip_social_app_alloc()
     {
         return NULL;
     }
+    if (!easy_flipper_set_buffer(&app->change_bio_logged_in_temp_buffer, app->change_bio_logged_in_temp_buffer_size))
+    {
+        return NULL;
+    }
     if (!easy_flipper_set_buffer(&app->compose_pre_save_logged_in_temp_buffer, app->compose_pre_save_logged_in_temp_buffer_size))
     {
         return NULL;
@@ -133,6 +140,10 @@ FlipSocialApp *flip_social_app_alloc()
     {
         return NULL;
     }
+    if (!easy_flipper_set_buffer(&app->change_bio_logged_in, app->change_bio_logged_in_temp_buffer_size))
+    {
+        return NULL;
+    }
     if (!easy_flipper_set_buffer(&app->compose_pre_save_logged_in, app->compose_pre_save_logged_in_temp_buffer_size))
     {
         return NULL;
@@ -170,17 +181,29 @@ FlipSocialApp *flip_social_app_alloc()
     {
         return NULL;
     }
-    if (!easy_flipper_set_buffer(&last_explore_response, app->message_user_choice_logged_in_temp_buffer_size))
+    if (!easy_flipper_set_buffer(&app->explore_logged_in, app->explore_logged_in_temp_buffer_size))
+    {
+        return NULL;
+    }
+    if (!easy_flipper_set_buffer(&app->explore_logged_in_temp_buffer, app->explore_logged_in_temp_buffer_size))
+    {
+        return NULL;
+    }
+    if (!easy_flipper_set_buffer(&app->message_users_logged_in, app->message_users_logged_in_temp_buffer_size))
+    {
+        return NULL;
+    }
+    if (!easy_flipper_set_buffer(&app->message_users_logged_in_temp_buffer, app->message_users_logged_in_temp_buffer_size))
     {
         return NULL;
     }
 
     // Allocate Submenu(s)
-    if (!easy_flipper_set_submenu(&app->submenu_logged_out, FlipSocialViewLoggedOutSubmenu, "FlipSocial v0.7", flip_social_callback_exit_app, &app->view_dispatcher))
+    if (!easy_flipper_set_submenu(&app->submenu_logged_out, FlipSocialViewLoggedOutSubmenu, "FlipSocial v0.8", flip_social_callback_exit_app, &app->view_dispatcher))
     {
         return NULL;
     }
-    if (!easy_flipper_set_submenu(&app->submenu_logged_in, FlipSocialViewLoggedInSubmenu, "FlipSocial v0.7", flip_social_callback_exit_app, &app->view_dispatcher))
+    if (!easy_flipper_set_submenu(&app->submenu_logged_in, FlipSocialViewLoggedInSubmenu, "FlipSocial v0.8", flip_social_callback_exit_app, &app->view_dispatcher))
     {
         return NULL;
     }
@@ -242,7 +265,8 @@ FlipSocialApp *flip_social_app_alloc()
     app->variable_item_logged_out_register_button = variable_item_list_add(app->variable_item_list_logged_out_register, "Register", 0, NULL, app);
     //
     app->variable_item_logged_in_profile_username = variable_item_list_add(app->variable_item_list_logged_in_profile, "Username", 1, NULL, app);
-    app->variable_item_logged_in_profile_change_password = variable_item_list_add(app->variable_item_list_logged_in_profile, "Change Password", 1, NULL, app);
+    app->variable_item_logged_in_profile_change_password = variable_item_list_add(app->variable_item_list_logged_in_profile, "Password", 1, NULL, app);
+    app->variable_item_logged_in_profile_change_bio = variable_item_list_add(app->variable_item_list_logged_in_profile, "Bio", 1, NULL, app);
     app->variable_item_logged_in_profile_friends = variable_item_list_add(app->variable_item_list_logged_in_profile, "Friends", 0, NULL, app);
     //
     app->variable_item_logged_in_settings_about = variable_item_list_add(app->variable_item_list_logged_in_settings, "About", 0, NULL, app);
@@ -281,7 +305,11 @@ FlipSocialApp *flip_social_app_alloc()
         return NULL;
     }
     //
-    if (!easy_flipper_set_uart_text_input(&app->text_input_logged_in_change_password, FlipSocialViewLoggedInChangePasswordInput, "Enter New Password", app->change_password_logged_in_temp_buffer, app->change_password_logged_in_temp_buffer_size, flip_social_logged_in_profile_change_password_updated, flip_social_callback_to_profile_logged_in, &app->view_dispatcher, app))
+    if (!easy_flipper_set_uart_text_input(&app->text_input_logged_in_change_password, FlipSocialViewLoggedInChangePasswordInput, "Password", app->change_password_logged_in_temp_buffer, app->change_password_logged_in_temp_buffer_size, flip_social_logged_in_profile_change_password_updated, flip_social_callback_to_profile_logged_in, &app->view_dispatcher, app))
+    {
+        return NULL;
+    }
+    if (!easy_flipper_set_uart_text_input(&app->text_input_logged_in_change_bio, FlipSocialViewLoggedInChangeBioInput, "Bio", app->change_bio_logged_in_temp_buffer, app->change_bio_logged_in_temp_buffer_size, flip_social_logged_in_profile_change_bio_updated, flip_social_callback_to_profile_logged_in, &app->view_dispatcher, app))
     {
         return NULL;
     }
@@ -306,6 +334,14 @@ FlipSocialApp *flip_social_app_alloc()
     {
         return NULL;
     }
+    if (!easy_flipper_set_uart_text_input(&app->text_input_logged_in_explore, FlipSocialViewLoggedInExploreInput, "Enter Username or Keyword", app->explore_logged_in_temp_buffer, app->explore_logged_in_temp_buffer_size, flip_social_logged_in_explore_updated, flip_social_callback_to_submenu_logged_in, &app->view_dispatcher, app))
+    {
+        return NULL;
+    }
+    if (!easy_flipper_set_uart_text_input(&app->text_input_logged_in_message_users, FlipSocialViewLoggedInMessageUsersInput, "Enter Username or Keyword", app->message_users_logged_in_temp_buffer, app->message_users_logged_in_temp_buffer_size, flip_social_logged_in_message_users_updated, flip_social_callback_to_submenu_logged_in, &app->view_dispatcher, app))
+    {
+        return NULL;
+    }
 
     // Load the settings
     if (!load_settings(app->wifi_ssid_logged_out,
@@ -320,6 +356,8 @@ FlipSocialApp *flip_social_app_alloc()
                        app->login_password_logged_out_temp_buffer_size,
                        app->change_password_logged_in,
                        app->change_password_logged_in_temp_buffer_size,
+                       app->change_bio_logged_in,
+                       app->change_bio_logged_in_temp_buffer_size,
                        app->is_logged_in,
                        app->is_logged_in_size))
 
@@ -377,6 +415,11 @@ FlipSocialApp *flip_social_app_alloc()
             strncpy(app->change_password_logged_in_temp_buffer, app->change_password_logged_in, app->change_password_logged_in_temp_buffer_size - 1);
             app->change_password_logged_in_temp_buffer[app->change_password_logged_in_temp_buffer_size - 1] = '\0';
         }
+        if (app->change_bio_logged_in && app->change_bio_logged_in_temp_buffer)
+        {
+            strncpy(app->change_bio_logged_in_temp_buffer, app->change_bio_logged_in, app->change_bio_logged_in_temp_buffer_size - 1);
+            app->change_bio_logged_in_temp_buffer[app->change_bio_logged_in_temp_buffer_size - 1] = '\0';
+        }
         if (app->compose_pre_save_logged_in && app->compose_pre_save_logged_in_temp_buffer)
         {
             strncpy(app->compose_pre_save_logged_in_temp_buffer, app->compose_pre_save_logged_in, app->compose_pre_save_logged_in_temp_buffer_size - 1);
@@ -470,6 +513,7 @@ FlipSocialApp *flip_social_app_alloc()
         variable_item_set_current_value_text(app->variable_item_logged_out_wifi_settings_ssid, app->wifi_ssid_logged_out);
         variable_item_set_current_value_text(app->variable_item_logged_out_login_username, app->login_username_logged_out);
         variable_item_set_current_value_text(app->variable_item_logged_in_profile_username, app->login_username_logged_in);
+        variable_item_set_current_value_text(app->variable_item_logged_in_profile_change_bio, app->change_bio_logged_in);
         //
 
         if (app->is_logged_in != NULL && strcmp(app->is_logged_in, "true") == 0)

+ 1 - 1
application.fam

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

+ 6 - 0
assets/CHANGELOG.md

@@ -1,3 +1,9 @@
+## 0.8 - New Features
+- Added support for RPC_KEYBOARD (thanks to Derek Jamison).
+- Introduced a bio feature in the Profile section: View and update your bio.
+- Enhanced the Explore view: Clicking on a user now displays their bio and friend count. You can also search for users.
+- Improved the Messages view: Users can now search for a contact to send messages.
+
 ## 0.7
 - Improved memory allocation
 - Increased the max explore users from 50 to 100

+ 2 - 2
assets/README.md

@@ -6,7 +6,7 @@ The highlight of this app is customizable pre-saves, which, as explained below,
 FlipSocial uses the FlipperHTTP flash for the WiFi Devboard, first introduced in the WebCrawler app: https://github.com/jblanked/WebCrawler-FlipperZero/tree/main/assets/FlipperHTTP
 
 ## Requirements
-- WiFi Developer Board or Raspberry Pi Pico W with FlipperHTTP Flash: https://github.com/jblanked/FlipperHTTP
+- WiFi Developer Board, Raspberry Pi, or ESP32 Device with FlipperHTTP Flash: https://github.com/jblanked/FlipperHTTP
 - WiFi Access Point
 
 
@@ -60,7 +60,7 @@ FlipSocial uses the FlipperHTTP flash for the WiFi Devboard, first introduced in
 - Loading screens.
 
 **v0.8**
-- Improve User Profile (Bio, friend count, block)
+- Improve User Profile
 - Improve Explore Page
 
 **v1.0**

+ 219 - 46
callback/flip_social_callback.c

@@ -106,7 +106,7 @@ static void flip_social_free_friends(void)
     }
 }
 
-void flip_social_request_error_draw(Canvas *canvas)
+static void flip_social_request_error_draw(Canvas *canvas)
 {
     if (canvas == NULL)
     {
@@ -200,7 +200,7 @@ static char *flip_social_login_parse(DataLoaderModel *model)
             strcpy(app_instance->change_password_logged_in, app_instance->login_password_logged_out);
         }
 
-        save_settings(app_instance->wifi_ssid_logged_out, app_instance->wifi_password_logged_out, app_instance->login_username_logged_out, app_instance->login_username_logged_in, app_instance->login_password_logged_out, app_instance->change_password_logged_in, app_instance->is_logged_in);
+        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->change_bio_logged_in, app_instance->is_logged_in);
 
         // send user to the logged in submenu
         view_dispatcher_switch_to_view(app_instance->view_dispatcher, FlipSocialViewLoggedInSubmenu);
@@ -281,7 +281,7 @@ static char *flip_social_register_parse(DataLoaderModel *model)
         auth_headers_alloc();
 
         // 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);
+        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->change_bio_logged_in, app_instance->is_logged_in);
 
         // send user to the logged in submenu
         view_dispatcher_switch_to_view(app_instance->view_dispatcher, FlipSocialViewLoggedInSubmenu);
@@ -481,7 +481,6 @@ uint32_t flip_social_callback_to_explore_logged_in(void *context)
 {
     UNUSED(context);
     flip_social_dialog_stop = false;
-    last_explore_response = "";
     flip_social_dialog_shown = false;
     if (flip_social_explore)
     {
@@ -499,7 +498,6 @@ uint32_t flip_social_callback_to_friends_logged_in(void *context)
 {
     UNUSED(context);
     flip_social_dialog_stop = false;
-    last_explore_response = "";
     flip_social_dialog_shown = false;
     flip_social_friends->index = 0;
     return FlipSocialViewLoggedInFriendsSubmenu;
@@ -980,6 +978,48 @@ bool feed_dialog_alloc()
     }
     return false;
 }
+static bool flip_social_get_user_info()
+{
+    char url[256];
+    snprintf(url, sizeof(url), "https://www.flipsocial.net/api/user/users/%s/extended/", flip_social_explore->usernames[flip_social_explore->index]);
+    if (!flipper_http_get_request_with_headers(url, auth_headers))
+    {
+        FURI_LOG_E(TAG, "Failed to send HTTP request for user info");
+        fhttp.state = ISSUE;
+        return false;
+    }
+    fhttp.state = RECEIVING;
+    return true;
+}
+static bool flip_social_parse_user_info()
+{
+    if (fhttp.last_response == NULL)
+    {
+        FURI_LOG_E(TAG, "Response is NULL");
+        return false;
+    }
+    if (!app_instance->explore_user_bio)
+    {
+        FURI_LOG_E(TAG, "App instance is NULL");
+        return false;
+    }
+    char *bio = get_json_value("bio", fhttp.last_response, 32);
+    char *friends = get_json_value("friends", fhttp.last_response, 32);
+    if (bio && friends)
+    {
+        if (strlen(bio) != 0)
+        {
+            snprintf(app_instance->explore_user_bio, MAX_MESSAGE_LENGTH, "%s (%s friends)", bio, friends);
+        }
+        else
+        {
+            snprintf(app_instance->explore_user_bio, MAX_MESSAGE_LENGTH, "%s friends", friends);
+        }
+        free(bio);
+        return true;
+    }
+    return false;
+}
 
 /**
  * @brief Handle ALL submenu item selections.
@@ -1030,12 +1070,7 @@ void flip_social_callback_submenu_choices(void *context, uint32_t index)
             &app->view_dispatcher);                // view dispatcher
         break;
     case FlipSocialSubmenuLoggedInIndexMessagesNewMessage:
-        flipper_http_loading_task(
-            flip_social_get_explore,                     // get the explore users
-            flip_social_parse_json_message_user_choices, // parse the explore users
-            FlipSocialViewLoggedInMessagesUserChoices,   // switch to the user choices if successful
-            FlipSocialViewLoggedInSubmenu,               // switch back to the main submenu if failed
-            &app->view_dispatcher);                      // view dispatcher
+        view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedInMessageUsersInput);
         break;
     case FlipSocialSubmenuLoggedInIndexFeed:
         free_flip_social_group();
@@ -1048,12 +1083,7 @@ void flip_social_callback_submenu_choices(void *context, uint32_t index)
         break;
     case FlipSocialSubmenuExploreIndex:
         free_flip_social_group();
-        flipper_http_loading_task(
-            flip_social_get_explore,              // get the explore users
-            flip_social_parse_json_explore,       // parse the explore users
-            FlipSocialViewLoggedInExploreSubmenu, // switch to the explore submenu if successful
-            FlipSocialViewLoggedInSubmenu,        // switch back to the main submenu if failed
-            &app->view_dispatcher);               // view dispatcher
+        view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedInExploreInput);
         break;
     case FlipSocialSubmenuLoggedInIndexCompose:
         free_pre_saved_messages();
@@ -1071,7 +1101,7 @@ void flip_social_callback_submenu_choices(void *context, uint32_t index)
         free_flip_social_group();
         app->is_logged_in = "false";
 
-        save_settings(app->wifi_ssid_logged_out, app->wifi_password_logged_out, app->login_username_logged_out, app->login_username_logged_in, app->login_password_logged_out, app->change_password_logged_in, app->is_logged_in);
+        save_settings(app->wifi_ssid_logged_out, app->wifi_password_logged_out, app->login_username_logged_out, app->login_username_logged_in, app->login_password_logged_out, app->change_password_logged_in, app->change_bio_logged_in, app->is_logged_in);
 
         view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedOutSubmenu);
         break;
@@ -1124,30 +1154,70 @@ void flip_social_callback_submenu_choices(void *context, uint32_t index)
                 return;
             }
             flip_social_explore->index = index - FlipSocialSubmenuExploreIndexStartIndex;
-            flip_social_free_explore_dialog();
-            if (!app->dialog_explore)
+            // loading task to get the user info
+            if (app->explore_user_bio)
             {
-                if (!easy_flipper_set_dialog_ex(
-                        &app->dialog_explore,
-                        FlipSocialViewExploreDialog,
-                        "User Options",
-                        0,
-                        0,
-                        flip_social_explore->usernames[flip_social_explore->index],
-                        0,
-                        10,
-                        "Remove",
-                        "Add",
-                        NULL,
-                        explore_dialog_callback,
-                        flip_social_callback_to_explore_logged_in,
-                        &app->view_dispatcher,
-                        app))
+                free(app->explore_user_bio);
+                app->explore_user_bio = NULL;
+            }
+            if (!easy_flipper_set_buffer(&app->explore_user_bio, MAX_MESSAGE_LENGTH))
+            {
+                return;
+            }
+            if (flipper_http_process_response_async(flip_social_get_user_info, flip_social_parse_user_info))
+            {
+                flip_social_free_explore_dialog();
+                if (!app->dialog_explore)
                 {
-                    return;
+                    if (!easy_flipper_set_dialog_ex(
+                            &app->dialog_explore,
+                            FlipSocialViewExploreDialog,
+                            flip_social_explore->usernames[flip_social_explore->index],
+                            0,
+                            0,
+                            app->explore_user_bio,
+                            0,
+                            10,
+                            "Remove", // remove if user is a friend (future update)
+                            "Add",    // add if user is not a friend (future update)
+                            NULL,
+                            explore_dialog_callback,
+                            flip_social_callback_to_explore_logged_in,
+                            &app->view_dispatcher,
+                            app))
+                    {
+                        return;
+                    }
                 }
+                view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewExploreDialog);
+            }
+            else
+            {
+                flip_social_free_explore_dialog();
+                if (!app->dialog_explore)
+                {
+                    if (!easy_flipper_set_dialog_ex(
+                            &app->dialog_explore,
+                            FlipSocialViewExploreDialog,
+                            flip_social_explore->usernames[flip_social_explore->index],
+                            0,
+                            0,
+                            "",
+                            0,
+                            10,
+                            "Remove", // remove if user is a friend (future update)
+                            "Add",    // add if user is not a friend (future update)
+                            NULL,
+                            explore_dialog_callback,
+                            flip_social_callback_to_explore_logged_in,
+                            &app->view_dispatcher,
+                            app))
+                    {
+                        return;
+                    }
+                }
+                view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewExploreDialog);
             }
-            view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewExploreDialog);
         }
 
         // handle the friends selection
@@ -1263,7 +1333,7 @@ void flip_social_logged_out_wifi_settings_ssid_updated(void *context)
     }
 
     // Save the settings
-    save_settings(app_instance->wifi_ssid_logged_out, app_instance->wifi_password_logged_out, app_instance->login_username_logged_out, app_instance->login_username_logged_in, app_instance->login_password_logged_out, app_instance->change_password_logged_in, app_instance->is_logged_in);
+    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->change_bio_logged_in, app_instance->is_logged_in);
 
     view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedOutWifiSettings);
 }
@@ -1308,7 +1378,7 @@ void flip_social_logged_out_wifi_settings_password_updated(void *context)
     }
 
     // Save the settings
-    save_settings(app_instance->wifi_ssid_logged_out, app_instance->wifi_password_logged_out, app_instance->login_username_logged_out, app_instance->login_username_logged_in, app_instance->login_password_logged_out, app_instance->change_password_logged_in, app_instance->is_logged_in);
+    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->change_bio_logged_in, app_instance->is_logged_in);
 
     view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedOutWifiSettings);
 }
@@ -1371,7 +1441,7 @@ void flip_social_logged_out_login_username_updated(void *context)
     }
 
     // Save the settings
-    save_settings(app_instance->wifi_ssid_logged_out, app_instance->wifi_password_logged_out, app_instance->login_username_logged_out, app_instance->login_username_logged_in, app_instance->login_password_logged_out, app_instance->change_password_logged_in, app_instance->is_logged_in);
+    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->change_bio_logged_in, app_instance->is_logged_in);
 
     view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedOutLogin);
 }
@@ -1408,7 +1478,7 @@ void flip_social_logged_out_login_password_updated(void *context)
     }
 
     // Save the settings
-    save_settings(app_instance->wifi_ssid_logged_out, app_instance->wifi_password_logged_out, app_instance->login_username_logged_out, app_instance->login_username_logged_in, app_instance->login_password_logged_out, app_instance->change_password_logged_in, app_instance->is_logged_in);
+    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->change_bio_logged_in, app_instance->is_logged_in);
 
     view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedOutLogin);
 }
@@ -1592,7 +1662,7 @@ void flip_social_logged_in_wifi_settings_ssid_updated(void *context)
     }
 
     // Save the settings
-    save_settings(app_instance->wifi_ssid_logged_in, app_instance->wifi_password_logged_in, app_instance->login_username_logged_out, app_instance->login_username_logged_in, app_instance->login_password_logged_out, app_instance->change_password_logged_in, app_instance->is_logged_in);
+    save_settings(app_instance->wifi_ssid_logged_in, app_instance->wifi_password_logged_in, app_instance->login_username_logged_out, app_instance->login_username_logged_in, app_instance->login_password_logged_out, app_instance->change_password_logged_in, app_instance->change_bio_logged_in, app_instance->is_logged_in);
 
     // update the wifi settings
     if (strlen(app->wifi_ssid_logged_in) > 0 && strlen(app->wifi_password_logged_in) > 0)
@@ -1638,7 +1708,7 @@ void flip_social_logged_in_wifi_settings_password_updated(void *context)
     }
 
     // Save the settings
-    save_settings(app_instance->wifi_ssid_logged_in, app_instance->wifi_password_logged_in, app_instance->login_username_logged_out, app_instance->login_username_logged_in, app_instance->login_password_logged_out, app_instance->change_password_logged_in, app_instance->is_logged_in);
+    save_settings(app_instance->wifi_ssid_logged_in, app_instance->wifi_password_logged_in, app_instance->login_username_logged_out, app_instance->login_username_logged_in, app_instance->login_password_logged_out, app_instance->change_password_logged_in, app_instance->change_bio_logged_in, app_instance->is_logged_in);
 
     // update the wifi settings
     if (strlen(app->wifi_ssid_logged_in) > 0 && strlen(app->wifi_password_logged_in) > 0)
@@ -1772,7 +1842,44 @@ void flip_social_logged_in_profile_change_password_updated(void *context)
         return;
     }
     // Save the settings
-    save_settings(app_instance->wifi_ssid_logged_out, app_instance->wifi_password_logged_out, app_instance->login_username_logged_out, app_instance->login_username_logged_in, app_instance->login_password_logged_out, app_instance->change_password_logged_in, app_instance->is_logged_in);
+    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->change_bio_logged_in, app_instance->is_logged_in);
+
+    view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedInProfile);
+}
+
+void flip_social_logged_in_profile_change_bio_updated(void *context)
+{
+    FlipSocialApp *app = (FlipSocialApp *)context;
+    if (!app)
+    {
+        FURI_LOG_E(TAG, "FlipSocialApp is NULL");
+        return;
+    }
+
+    // Store the entered message
+    strncpy(app->change_bio_logged_in, app->change_bio_logged_in_temp_buffer, app->change_bio_logged_in_temp_buffer_size);
+
+    // Ensure null-termination
+    app->change_bio_logged_in[app->change_bio_logged_in_temp_buffer_size - 1] = '\0';
+
+    // Update the message item text
+    if (app->variable_item_logged_in_profile_change_bio)
+    {
+        variable_item_set_current_value_text(app->variable_item_logged_in_profile_change_bio, app->change_bio_logged_in);
+    }
+
+    // send post request to change bio
+    auth_headers_alloc();
+    char payload[256];
+    snprintf(payload, sizeof(payload), "{\"username\":\"%s\",\"bio\":\"%s\"}", app->login_username_logged_out, app->change_bio_logged_in);
+    if (!flipper_http_post_request_with_headers("https://www.flipsocial.net/api/user/change-bio/", auth_headers, payload))
+    {
+        FURI_LOG_E(TAG, "Failed to send post request to change bio");
+        FURI_LOG_E(TAG, "Make sure the Flipper is connected to the Wifi Dev Board");
+        return;
+    }
+    // Save the settings
+    save_settings(app_instance->wifi_ssid_logged_out, app_instance->wifi_password_logged_out, app_instance->login_username_logged_out, app_instance->login_username_logged_in, app_instance->login_password_logged_out, app_instance->change_password_logged_in, app_instance->change_bio_logged_in, app_instance->is_logged_in);
 
     view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedInProfile);
 }
@@ -1799,7 +1906,10 @@ void flip_social_text_input_logged_in_profile_item_selected(void *context, uint3
     case 1: // Change Password
         view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedInChangePasswordInput);
         break;
-    case 2: // Friends
+    case 2: // Change Bio
+        view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedInChangeBioInput);
+        break;
+    case 3: // Friends
         flip_social_free_friends();
         free_flip_social_group();
         if (!app->submenu_friends)
@@ -1968,6 +2078,69 @@ void flip_social_logged_in_messages_new_message_updated(void *context)
     view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedInMessagesSubmenu);
 }
 
+void flip_social_logged_in_explore_updated(void *context)
+{
+    FlipSocialApp *app = (FlipSocialApp *)context;
+    if (!app)
+    {
+        FURI_LOG_E(TAG, "FlipSocialApp is NULL");
+        return;
+    }
+
+    // check if the message is empty
+    if (app->explore_logged_in_temp_buffer_size == 0)
+    {
+        FURI_LOG_E(TAG, "Message is empty");
+        strncpy(app->explore_logged_in, "a", 2);
+    }
+    else
+    {
+        // Store the entered message
+        strncpy(app->explore_logged_in, app->explore_logged_in_temp_buffer, app->explore_logged_in_temp_buffer_size);
+    }
+
+    // Ensure null-termination
+    app->explore_logged_in[app->explore_logged_in_temp_buffer_size - 1] = '\0';
+
+    flipper_http_loading_task(
+        flip_social_get_explore,              // get the explore users
+        flip_social_parse_json_explore,       // parse the explore users
+        FlipSocialViewLoggedInExploreSubmenu, // switch to the explore submenu if successful
+        FlipSocialViewLoggedInSubmenu,        // switch back to the main submenu if failed
+        &app->view_dispatcher);               // view dispatcher
+}
+void flip_social_logged_in_message_users_updated(void *context)
+{
+    FlipSocialApp *app = (FlipSocialApp *)context;
+    if (!app)
+    {
+        FURI_LOG_E(TAG, "FlipSocialApp is NULL");
+        return;
+    }
+
+    // check if the message is empty
+    if (app->message_users_logged_in_temp_buffer_size == 0)
+    {
+        FURI_LOG_E(TAG, "Message is empty");
+        strncpy(app->message_users_logged_in, "a", 2);
+    }
+    else
+    {
+        // Store the entered message
+        strncpy(app->message_users_logged_in, app->message_users_logged_in_temp_buffer, app->message_users_logged_in_temp_buffer_size);
+    }
+
+    // Ensure null-termination
+    app->message_users_logged_in[app->message_users_logged_in_temp_buffer_size - 1] = '\0';
+
+    flipper_http_loading_task(
+        flip_social_get_explore_2,                   // get the explore users
+        flip_social_parse_json_message_user_choices, // parse the explore users
+        FlipSocialViewLoggedInMessagesUserChoices,   // switch to the user choices if successful
+        FlipSocialViewLoggedInSubmenu,               // switch back to the main submenu if failed
+        &app->view_dispatcher);                      // view dispatcher
+}
+
 static void flip_social_widget_set_text(char *message, Widget **widget)
 {
     if (widget == NULL)

+ 3 - 2
callback/flip_social_callback.h

@@ -8,8 +8,6 @@
 #include <feed/flip_social_feed.h>
 #include <flip_storage/flip_social_storage.h>
 
-void flip_social_request_error_draw(Canvas *canvas);
-
 /**
  * @brief Navigation callback to go back to the submenu Logged out.
  * @param context The context - unused
@@ -223,6 +221,7 @@ void flip_social_logged_in_profile_change_password_updated(void *context);
  * @param index The index of the selected item.
  * @return void
  */
+void flip_social_logged_in_profile_change_bio_updated(void *context);
 void flip_social_text_input_logged_in_profile_item_selected(void *context, uint32_t index);
 
 /**
@@ -253,6 +252,8 @@ void flip_social_logged_in_messages_new_message_updated(void *context);
  * @return void
  */
 void flip_social_text_input_logged_out_register_item_selected(void *context, uint32_t index);
+void flip_social_logged_in_explore_updated(void *context);
+void flip_social_logged_in_message_users_updated(void *context);
 
 // Add edits by Derek Jamison
 typedef enum DataState DataState;

+ 0 - 461
draw/flip_social_draw.c

@@ -1,461 +0,0 @@
-#include "flip_social_draw.h"
-Action action = ActionNone;
-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 on_input(const void *event, void *ctx)
-{
-    UNUSED(ctx);
-
-    InputKey key = ((InputEvent *)event)->key;
-    InputType type = ((InputEvent *)event)->type;
-
-    if (type != InputTypeRelease)
-    {
-        return;
-    }
-
-    switch (key)
-    {
-    case InputKeyOk:
-        action = ActionFlip;
-        break;
-    case InputKeyBack:
-        action = ActionBack;
-        break;
-    case InputKeyRight:
-        action = ActionNext;
-        break;
-    case InputKeyLeft:
-        action = ActionPrev;
-        break;
-    case InputKeyUp:
-        action = ActionPrev;
-        break;
-    case InputKeyDown:
-        action = ActionNext;
-        break;
-    default:
-        action = ActionNone;
-        break;
-    }
-}
-
-// Function to draw the message on the canvas with word wrapping
-void draw_user_message(Canvas *canvas, const char *user_message, int x, int y)
-{
-    if (user_message == NULL)
-    {
-        FURI_LOG_E(TAG, "User message is NULL.");
-        return;
-    }
-
-    size_t msg_length = strlen(user_message);
-    size_t start = 0;
-    int line_num = 0;
-    char line[MAX_LINE_LENGTH + 1]; // Buffer for the current line (+1 for null terminator)
-
-    while (start < msg_length && line_num < 4)
-    {
-        size_t remaining = msg_length - start;
-        size_t len = (remaining > MAX_LINE_LENGTH) ? MAX_LINE_LENGTH : remaining;
-
-        if (remaining > MAX_LINE_LENGTH)
-        {
-            // Find the last space within the first 'len' characters
-            size_t last_space = len;
-            while (last_space > 0 && user_message[start + last_space - 1] != ' ')
-            {
-                last_space--;
-            }
-
-            if (last_space > 0)
-            {
-                len = last_space; // Adjust len to the position of the last space
-            }
-        }
-
-        // Copy the substring to 'line' and null-terminate it
-        memcpy(line, user_message + start, len);
-        line[len] = '\0'; // Ensure the string is null-terminated
-
-        // Draw the string on the canvas
-        // Adjust the y-coordinate based on the line number
-        canvas_draw_str_aligned(canvas, x, y + line_num * 10, AlignLeft, AlignTop, line);
-
-        // Update the start position for the next line
-        start += len;
-
-        // Skip any spaces to avoid leading spaces on the next line
-        while (start < msg_length && user_message[start] == ' ')
-        {
-            start++;
-        }
-
-        // Increment the line number
-        line_num++;
-    }
-}
-
-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;
-    }
-    if (!selected_message)
-    {
-        FURI_LOG_E(TAG, "Selected message is NULL");
-        return;
-    }
-
-    if (strlen(selected_message) > MAX_MESSAGE_LENGTH)
-    {
-        FURI_LOG_E(TAG, "Message is too long");
-        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);
-        auth_headers_alloc();
-    }
-
-    draw_user_message(canvas, selected_message, 0, 2);
-
-    canvas_draw_icon(canvas, 0, 53, &I_ButtonLeft_4x7);
-    canvas_draw_str_aligned(canvas, 7, 54, AlignLeft, AlignTop, "Delete");
-    canvas_draw_icon(canvas, 52, 53, &I_ButtonBACK_10x8);
-    canvas_draw_str_aligned(canvas, 64, 54, AlignLeft, AlignTop, "Back");
-    canvas_draw_icon(canvas, 100, 53, &I_ButtonRight_4x7);
-    canvas_draw_str_aligned(canvas, 107, 54, AlignLeft, AlignTop, "Post");
-
-    // handle action
-    switch (action)
-    {
-    case ActionNone:
-        break;
-    case ActionBack:
-        flip_social_dialog_stop = true;
-        break;
-    case ActionNext:
-        // send selected_message
-        if (selected_message && app_instance->login_username_logged_in)
-        {
-            if (strlen(selected_message) > MAX_MESSAGE_LENGTH)
-            {
-                FURI_LOG_E(TAG, "Message is too long");
-                return;
-            }
-            // Send the selected_message
-            char command[256];
-            snprintf(command, sizeof(command), "{\"username\":\"%s\",\"content\":\"%s\"}",
-                     app_instance->login_username_logged_in, selected_message);
-            if (!flipper_http_post_request_with_headers(
-                    "https://www.flipsocial.net/api/feed/post/",
-                    auth_headers,
-                    command))
-            {
-                FURI_LOG_E(TAG, "Failed to send HTTP request for feed");
-                fhttp.state = ISSUE;
-                return;
-            }
-
-            fhttp.state = RECEIVING;
-            furi_timer_start(fhttp.get_timeout_timer, TIMEOUT_DURATION_TICKS);
-        }
-        else
-        {
-            FURI_LOG_E(TAG, "Message or username is NULL");
-            return;
-        }
-        while (fhttp.state == RECEIVING && furi_timer_is_running(fhttp.get_timeout_timer) > 0)
-        {
-            // Wait for the feed to be received
-            furi_delay_ms(100);
-
-            // Draw the resulting string on the canvas
-            canvas_draw_str(canvas, 0, 30, "Receiving..");
-        }
-        flip_social_dialog_stop = true;
-        furi_timer_stop(fhttp.get_timeout_timer);
-        break;
-    case ActionPrev:
-        // delete message
-        app_instance->pre_saved_messages.messages[app_instance->pre_saved_messages.index] = NULL;
-
-        for (uint32_t i = app_instance->pre_saved_messages.index; i < app_instance->pre_saved_messages.count - 1; i++)
-        {
-            app_instance->pre_saved_messages.messages[i] = app_instance->pre_saved_messages.messages[i + 1];
-        }
-        app_instance->pre_saved_messages.count--;
-
-        // add the item to the submenu
-        submenu_reset(app_instance->submenu_compose);
-
-        submenu_add_item(app_instance->submenu_compose, "Add Pre-Save", FlipSocialSubmenuComposeIndexAddPreSave, flip_social_callback_submenu_choices, app_instance);
-
-        for (uint32_t i = 0; i < app_instance->pre_saved_messages.count; i++)
-        {
-            submenu_add_item(app_instance->submenu_compose, app_instance->pre_saved_messages.messages[i], FlipSocialSubemnuComposeIndexStartIndex + i, flip_social_callback_submenu_choices, app_instance);
-        }
-
-        // save playlist
-        save_playlist(&app_instance->pre_saved_messages);
-
-        flip_social_dialog_stop = true;
-        break;
-    default:
-        action = ActionNone;
-        break;
-    }
-
-    if (flip_social_dialog_stop)
-    {
-        furi_pubsub_unsubscribe(app_instance->input_event_queue, app_instance->input_event);
-        flip_social_dialog_shown = false;
-        flip_social_dialog_stop = false;
-        if (action == ActionNext)
-        {
-            canvas_clear(canvas);
-            canvas_draw_str(canvas, 0, 10, "Sent successfully!");
-            canvas_draw_str(canvas, 0, 50, "Loading feed :D");
-            canvas_draw_str(canvas, 0, 60, "Please wait...");
-            action = ActionNone;
-            if (!flip_social_load_initial_feed())
-            {
-                FURI_LOG_E(TAG, "Failed to load initial feed");
-                return;
-            }
-        }
-        else if (action == ActionBack)
-        {
-            action = ActionNone;
-            view_dispatcher_switch_to_view(app_instance->view_dispatcher, FlipSocialViewLoggedInSubmenu);
-        }
-        else
-        {
-            action = ActionNone;
-            view_dispatcher_switch_to_view(app_instance->view_dispatcher, FlipSocialViewLoggedInCompose);
-        }
-    }
-}
-// function to draw the dialog canvas
-void flip_social_canvas_draw_message(Canvas *canvas, char *user_username, char *user_message, bool is_flipped, bool show_prev, bool show_next, int flip_count)
-{
-    canvas_set_color(canvas, ColorBlack);
-    canvas_set_font(canvas, FontPrimary);
-    canvas_draw_str_aligned(canvas, 64, 5, AlignCenter, AlignCenter, user_username);
-    canvas_set_font(canvas, FontSecondary);
-
-    char flip_count_str[12];
-    if (flip_count == 1)
-    {
-        snprintf(flip_count_str, sizeof(flip_count_str), "%d Flip", flip_count);
-        canvas_draw_str_aligned(canvas, 106, 54, AlignLeft, AlignTop, flip_count_str);
-    }
-    else
-    {
-        snprintf(flip_count_str, sizeof(flip_count_str), "%d Flips", flip_count);
-
-        if (flip_count < 10)
-        {
-            canvas_draw_str_aligned(canvas, 100, 54, AlignLeft, AlignTop, flip_count_str);
-        }
-        else if (flip_count < 100)
-        {
-            canvas_draw_str_aligned(canvas, 94, 54, AlignLeft, AlignTop, flip_count_str);
-        }
-        else
-        {
-            canvas_draw_str_aligned(canvas, 88, 54, AlignLeft, AlignTop, flip_count_str);
-        }
-    }
-
-    draw_user_message(canvas, user_message, 0, 12);
-
-    // combine and shift icons/labels around if not show_prev or show_next
-    if (show_prev && show_next && !is_flipped)
-    {
-        canvas_draw_icon(canvas, 0, 54, &I_ButtonLeft_4x7);
-        canvas_draw_str_aligned(canvas, 6, 54, AlignLeft, AlignTop, "Prev");
-        canvas_draw_icon(canvas, 30, 54, &I_ButtonRight_4x7);
-        canvas_draw_str_aligned(canvas, 36, 54, AlignLeft, AlignTop, "Next");
-        canvas_draw_icon(canvas, 58, 54, &I_ButtonOK_7x7);
-        canvas_draw_str_aligned(canvas, 67, 54, AlignLeft, AlignTop, "Flip");
-    }
-    else if (show_prev && !show_next && !is_flipped)
-    {
-        canvas_draw_icon(canvas, 0, 54, &I_ButtonLeft_4x7);
-        canvas_draw_str_aligned(canvas, 6, 54, AlignLeft, AlignTop, "Prev");
-        canvas_draw_icon(canvas, 28, 54, &I_ButtonOK_7x7);
-        canvas_draw_str_aligned(canvas, 37, 54, AlignLeft, AlignTop, "Flip");
-    }
-    else if (!show_prev && show_next && !is_flipped)
-    {
-        canvas_draw_icon(canvas, 0, 54, &I_ButtonRight_4x7);
-        canvas_draw_str_aligned(canvas, 6, 54, AlignLeft, AlignTop, "Next");
-        canvas_draw_icon(canvas, 28, 54, &I_ButtonOK_7x7);
-        canvas_draw_str_aligned(canvas, 37, 54, AlignLeft, AlignTop, "Flip");
-    }
-    else if (show_prev && show_next && is_flipped)
-    {
-        canvas_draw_icon(canvas, 0, 54, &I_ButtonLeft_4x7);
-        canvas_draw_str_aligned(canvas, 6, 54, AlignLeft, AlignTop, "Prev");
-        canvas_draw_icon(canvas, 28, 54, &I_ButtonRight_4x7);
-        canvas_draw_str_aligned(canvas, 34, 54, AlignLeft, AlignTop, "Next");
-        canvas_draw_icon(canvas, 54, 54, &I_ButtonOK_7x7);
-        canvas_draw_str_aligned(canvas, 63, 54, AlignLeft, AlignTop, "UnFlip");
-    }
-    else if (show_prev && !show_next && is_flipped)
-    {
-        canvas_draw_icon(canvas, 0, 54, &I_ButtonLeft_4x7);
-        canvas_draw_str_aligned(canvas, 6, 54, AlignLeft, AlignTop, "Prev");
-        canvas_draw_icon(canvas, 28, 54, &I_ButtonOK_7x7);
-        canvas_draw_str_aligned(canvas, 37, 54, AlignLeft, AlignTop, "UnFlip");
-    }
-    else if (!show_prev && show_next && is_flipped)
-    {
-        canvas_draw_icon(canvas, 0, 54, &I_ButtonRight_4x7);
-        canvas_draw_str_aligned(canvas, 6, 54, AlignLeft, AlignTop, "Next");
-        canvas_draw_icon(canvas, 28, 54, &I_ButtonOK_7x7);
-        canvas_draw_str_aligned(canvas, 37, 54, AlignLeft, AlignTop, "UnFlip");
-    }
-    else if (!show_prev && !show_next && is_flipped)
-    {
-        canvas_draw_icon(canvas, 0, 54, &I_ButtonOK_7x7);
-        canvas_draw_str_aligned(canvas, 9, 54, AlignLeft, AlignTop, "UnFlip");
-    }
-    else
-    {
-        canvas_draw_icon(canvas, 0, 54, &I_ButtonOK_7x7);
-        canvas_draw_str_aligned(canvas, 9, 54, AlignLeft, AlignTop, "Flip");
-    }
-}
-// Callback function to handle the feed dialog
-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);
-        auth_headers_alloc();
-    }
-
-    // handle action
-    switch (action)
-    {
-    case ActionNone:
-        flip_social_canvas_draw_message(canvas, flip_feed_item->username, flip_feed_item->message, flip_feed_item->is_flipped, flip_feed_info->index > 0, flip_feed_info->index < flip_feed_info->count - 1, flip_feed_item->flips);
-        break;
-    case ActionNext:
-        canvas_clear(canvas);
-        if (flip_feed_info->index < flip_feed_info->count - 1)
-        {
-            flip_feed_info->index++;
-        }
-        // load the next feed item
-        if (!flip_social_load_feed_post(flip_feed_info->ids[flip_feed_info->index]))
-        {
-            FURI_LOG_E(TAG, "Failed to load nexy feed post");
-            fhttp.state = ISSUE;
-            return;
-        }
-        flip_social_canvas_draw_message(canvas, flip_feed_item->username, flip_feed_item->message, flip_feed_item->is_flipped, flip_feed_info->index > 0, flip_feed_info->index < flip_feed_info->count - 1, flip_feed_item->flips);
-        action = ActionNone;
-        break;
-    case ActionPrev:
-        canvas_clear(canvas);
-        if (flip_feed_info->index > 0)
-        {
-            flip_feed_info->index--;
-        }
-        // load the previous feed item
-        if (!flip_social_load_feed_post(flip_feed_info->ids[flip_feed_info->index]))
-        {
-            FURI_LOG_E(TAG, "Failed to load nexy feed post");
-            fhttp.state = ISSUE;
-            return;
-        }
-        flip_social_canvas_draw_message(canvas, flip_feed_item->username, flip_feed_item->message, flip_feed_item->is_flipped, flip_feed_info->index > 0, flip_feed_info->index < flip_feed_info->count - 1, flip_feed_item->flips);
-        action = ActionNone;
-        break;
-    case ActionFlip:
-        canvas_clear(canvas);
-        // Moved to above the is_flipped check
-        if (!flip_feed_item->is_flipped)
-        {
-            // increase the flip count
-            flip_feed_item->flips++;
-        }
-        else
-        {
-            // decrease the flip count
-            flip_feed_item->flips--;
-        }
-        // change the flip status
-        flip_feed_item->is_flipped = !flip_feed_item->is_flipped;
-        // send post request to flip the message
-        if (app_instance->login_username_logged_in == NULL)
-        {
-            FURI_LOG_E(TAG, "Username is NULL");
-            return;
-        }
-        char payload[256];
-        snprintf(payload, sizeof(payload), "{\"username\":\"%s\",\"post_id\":\"%u\"}", app_instance->login_username_logged_in, flip_feed_item->id);
-        flipper_http_post_request_with_headers("https://www.flipsocial.net/api/feed/flip/", auth_headers, payload);
-        flip_social_canvas_draw_message(canvas, flip_feed_item->username, flip_feed_item->message, flip_feed_item->is_flipped, flip_feed_info->index > 0, flip_feed_info->index < flip_feed_info->count - 1, flip_feed_item->flips);
-        action = ActionNone;
-        break;
-    case ActionBack:
-        canvas_clear(canvas);
-        flip_social_dialog_stop = true;
-        flip_feed_info->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;
-    }
-}

+ 0 - 33
draw/flip_social_draw.h

@@ -1,33 +0,0 @@
-#ifndef FLIP_SOCIAL_DRAW_H
-#define FLIP_SOCIAL_DRAW_H
-#include <flip_social.h>
-#include <flip_storage/flip_social_storage.h>
-#include <callback/flip_social_callback.h>
-#include <friends/flip_social_friends.h>
-
-typedef enum
-{
-    ActionNone,
-    ActionBack,
-    ActionNext,
-    ActionPrev,
-    ActionFlip,
-} Action;
-
-extern Action action;
-
-bool flip_social_board_is_active(Canvas *canvas);
-
-void flip_social_handle_error(Canvas *canvas);
-
-void on_input(const void *event, void *ctx);
-// Function to draw the message on the canvas with word wrapping
-void draw_user_message(Canvas *canvas, const char *user_message, int x, int y);
-
-void flip_social_callback_draw_compose(Canvas *canvas, void *model);
-// function to draw the dialog canvas
-void flip_social_canvas_draw_message(Canvas *canvas, char *user_username, char *user_message, bool is_flipped, bool show_prev, bool show_next, int flip_count);
-// Callback function to handle the feed dialog
-void flip_social_callback_draw_feed(Canvas *canvas, void *model);
-
-#endif

+ 34 - 4
explore/flip_social_explore.c

@@ -66,14 +66,45 @@ bool flip_social_get_explore(void)
         FURI_LOG_E(TAG, "HTTP state is INACTIVE");
         return false;
     }
+    char *keyword = !app_instance->explore_logged_in || strlen(app_instance->explore_logged_in) == 0 ? "a" : app_instance->explore_logged_in;
     snprintf(
         fhttp.file_path,
         sizeof(fhttp.file_path),
-        STORAGE_EXT_PATH_PREFIX "/apps_data/flip_social/users.json");
+        STORAGE_EXT_PATH_PREFIX "/apps_data/flip_social/%s.json",
+        keyword);
 
     fhttp.save_received_data = true;
     auth_headers_alloc();
-    if (!flipper_http_get_request_with_headers("https://www.flipsocial.net/api/user/users/", auth_headers))
+    char url[256];
+    snprintf(url, sizeof(url), "https://www.flipsocial.net/api/user/explore/%s/%d/", keyword, MAX_EXPLORE_USERS);
+    if (!flipper_http_get_request_with_headers(url, auth_headers))
+    {
+        FURI_LOG_E(TAG, "Failed to send HTTP request for explore");
+        fhttp.state = ISSUE;
+        return false;
+    }
+    fhttp.state = RECEIVING;
+    return true;
+}
+bool flip_social_get_explore_2(void)
+{
+    if (fhttp.state == INACTIVE)
+    {
+        FURI_LOG_E(TAG, "HTTP state is INACTIVE");
+        return false;
+    }
+    char *keyword = !app_instance->message_users_logged_in || strlen(app_instance->message_users_logged_in) == 0 ? "a" : app_instance->message_users_logged_in;
+    snprintf(
+        fhttp.file_path,
+        sizeof(fhttp.file_path),
+        STORAGE_EXT_PATH_PREFIX "/apps_data/flip_social/%s.json",
+        keyword);
+
+    fhttp.save_received_data = true;
+    auth_headers_alloc();
+    char url[256];
+    snprintf(url, sizeof(url), "https://www.flipsocial.net/api/user/explore/%s/%d/", keyword, MAX_EXPLORE_USERS);
+    if (!flipper_http_get_request_with_headers(url, auth_headers))
     {
         FURI_LOG_E(TAG, "Failed to send HTTP request for explore");
         fhttp.state = ISSUE;
@@ -116,11 +147,10 @@ bool flip_social_parse_json_explore()
     // set submenu
     submenu_reset(app_instance->submenu_explore);
     submenu_set_header(app_instance->submenu_explore, "Explore");
-
     // Parse the JSON array of usernames
     for (size_t i = 0; i < MAX_EXPLORE_USERS; i++)
     {
-        char *username = get_json_array_value("users", i, data_cstr, MAX_TOKENS); // currently its 330 tokens
+        char *username = get_json_array_value("users", i, data_cstr, 64); // currently its 53 tokens (with max explore users at 50)
         if (username == NULL)
         {
             break;

+ 1 - 0
explore/flip_social_explore.h

@@ -5,5 +5,6 @@
 FlipSocialModel *flip_social_explore_alloc();
 void flip_social_free_explore();
 bool flip_social_get_explore();
+bool flip_social_get_explore_2(void);
 bool flip_social_parse_json_explore();
 #endif

+ 5 - 5
feed/flip_social_feed.c

@@ -167,11 +167,11 @@ bool flip_social_load_feed_post(int post_id)
     }
 
     // Extract individual fields from the JSON object
-    char *username = get_json_value("username", data_cstr, 40);
-    char *message = get_json_value("message", data_cstr, 40);
-    char *flipped = get_json_value("flipped", data_cstr, 40);
-    char *flips = get_json_value("flip_count", data_cstr, 40);
-    char *id = get_json_value("id", data_cstr, 40);
+    char *username = get_json_value("username", data_cstr, 16);
+    char *message = get_json_value("message", data_cstr, 16);
+    char *flipped = get_json_value("flipped", data_cstr, 16);
+    char *flips = get_json_value("flip_count", data_cstr, 16);
+    char *id = get_json_value("id", data_cstr, 16);
 
     if (username == NULL || message == NULL || flipped == NULL || id == NULL)
     {

+ 27 - 6
flip_social.c

@@ -15,7 +15,6 @@ bool flip_social_register_success = false;
 bool flip_social_dialog_shown = false;
 bool flip_social_dialog_stop = false;
 bool flip_social_send_message = false;
-char *last_explore_response = NULL;
 char *selected_message = NULL;
 
 char auth_headers[256] = {0};
@@ -132,6 +131,11 @@ void flip_social_app_free(FlipSocialApp *app)
         view_dispatcher_remove_view(app->view_dispatcher, FlipSocialViewLoggedInChangePasswordInput);
         uart_text_input_free(app->text_input_logged_in_change_password);
     }
+    if (app->text_input_logged_in_change_bio)
+    {
+        view_dispatcher_remove_view(app->view_dispatcher, FlipSocialViewLoggedInChangeBioInput);
+        uart_text_input_free(app->text_input_logged_in_change_bio);
+    }
     if (app->text_input_logged_in_compose_pre_save_input)
     {
         view_dispatcher_remove_view(app->view_dispatcher, FlipSocialViewLoggedInComposeAddPreSaveInput);
@@ -157,6 +161,16 @@ void flip_social_app_free(FlipSocialApp *app)
         view_dispatcher_remove_view(app->view_dispatcher, FlipSocialViewLoggedInMessagesNewMessageUserChoicesInput);
         uart_text_input_free(app->text_input_logged_in_messages_new_message_user_choices);
     }
+    if (app->text_input_logged_in_explore)
+    {
+        view_dispatcher_remove_view(app->view_dispatcher, FlipSocialViewLoggedInExploreInput);
+        uart_text_input_free(app->text_input_logged_in_explore);
+    }
+    if (app->text_input_logged_in_message_users)
+    {
+        view_dispatcher_remove_view(app->view_dispatcher, FlipSocialViewLoggedInMessageUsersInput);
+        uart_text_input_free(app->text_input_logged_in_message_users);
+    }
 
     // Free Widget(s)
     if (app->widget_result)
@@ -209,10 +223,20 @@ void flip_social_app_free(FlipSocialApp *app)
         free(app->change_password_logged_in);
     if (app->change_password_logged_in_temp_buffer)
         free(app->change_password_logged_in_temp_buffer);
+    if (app->change_bio_logged_in)
+        free(app->change_bio_logged_in);
     if (app->compose_pre_save_logged_in)
         free(app->compose_pre_save_logged_in);
     if (app->compose_pre_save_logged_in_temp_buffer)
         free(app->compose_pre_save_logged_in_temp_buffer);
+    if (app->explore_logged_in)
+        free(app->explore_logged_in);
+    if (app->explore_logged_in_temp_buffer)
+        free(app->explore_logged_in_temp_buffer);
+    if (app->message_users_logged_in)
+        free(app->message_users_logged_in);
+    if (app->message_users_logged_in_temp_buffer)
+        free(app->message_users_logged_in_temp_buffer);
     if (app->wifi_ssid_logged_in)
         free(app->wifi_ssid_logged_in);
     if (app->wifi_ssid_logged_in_temp_buffer)
@@ -235,13 +259,10 @@ void flip_social_app_free(FlipSocialApp *app)
         free(app->message_user_choice_logged_in);
     if (app->message_user_choice_logged_in_temp_buffer)
         free(app->message_user_choice_logged_in_temp_buffer);
-    if (last_explore_response)
-        free(last_explore_response);
     if (selected_message)
         free(selected_message);
-
-    if (app->input_event && app->input_event_queue)
-        furi_pubsub_unsubscribe(app->input_event_queue, app->input_event);
+    if (app->explore_user_bio)
+        free(app->explore_user_bio);
 
     // DeInit UART
     flipper_http_deinit();

+ 30 - 11
flip_social.h

@@ -11,13 +11,13 @@
 
 #define MAX_PRE_SAVED_MESSAGES 20 // Maximum number of pre-saved messages
 #define MAX_MESSAGE_LENGTH 100    // Maximum length of a message in the feed
-#define MAX_EXPLORE_USERS 100     // Maximum number of users to explore
+#define MAX_EXPLORE_USERS 50      // Maximum number of users to explore
 #define MAX_USER_LENGTH 32        // Maximum length of a username
 #define MAX_FRIENDS 50            // Maximum number of friends
-#define MAX_TOKENS 640            // Adjust based on expected JSON tokens
+#define MAX_TOKENS 576            // Adjust based on expected JSON tokens
 #define MAX_FEED_ITEMS 50         // Maximum number of feed items
 #define MAX_LINE_LENGTH 30
-#define MAX_MESSAGE_USERS 20 // Maximum number of users to display in the submenu
+#define MAX_MESSAGE_USERS 40 // Maximum number of users to display in the submenu
 #define MAX_MESSAGES 20      // Maximum number of meesages between each user
 
 #define SETTINGS_PATH STORAGE_EXT_PATH_PREFIX "/apps_data/flip_social/settings.bin"
@@ -125,12 +125,15 @@ typedef enum
     FlipSocialViewLoggedInCompose,  // The compose screen
     FlipSocialViewLoggedInSettings, // The settings screen
     //
+    FlipSocialViewLoggedInChangeBioInput,         // Text input screen for bio input on profile screen
     FlipSocialViewLoggedInChangePasswordInput,    // Text input screen for password input on change password screen
     FlipSocialViewLoggedInComposeAddPreSaveInput, // Text input screen for add text input on compose screen
     //
     FlipSocialViewLoggedInMessagesNewMessageInput,            // Text input screen for new message input on messages screen
     FlipSocialViewLoggedInMessagesNewMessageUserChoicesInput, // Text input screen for new message input on messages screen
     FlipSocialViewLoggedInMessagesUserChoices,                // the view after clicking [New Message] - select a user to message, then direct to input view
+    FlipSocialViewLoggedInExploreInput,                       // Text input screen for explore input on explore screen
+    FlipSocialViewLoggedInMessageUsersInput,
     //
     FlipSocialViewLoggedInSettingsAbout,             // The about screen
     FlipSocialViewLoggedInSettingsWifi,              // The wifi settings screen
@@ -189,12 +192,16 @@ typedef struct
     UART_TextInput *text_input_logged_out_register_password_2;    // Text input for password 2 input on register screen
     //
     UART_TextInput *text_input_logged_in_change_password;        // Text input for password input on change password screen
+    UART_TextInput *text_input_logged_in_change_bio;             // Text input for bio input on profile screen
     UART_TextInput *text_input_logged_in_compose_pre_save_input; // Text input for pre save input on compose screen
     UART_TextInput *text_input_logged_in_wifi_settings_ssid;     // Text input for ssid input on wifi settings screen
     UART_TextInput *text_input_logged_in_wifi_settings_password; // Text input for password input on wifi settings screen
     //
     UART_TextInput *text_input_logged_in_messages_new_message;              // Text input for new message input on messages screen
     UART_TextInput *text_input_logged_in_messages_new_message_user_choices; //
+    //
+    UART_TextInput *text_input_logged_in_explore; // Text input for explore input on explore screen
+    UART_TextInput *text_input_logged_in_message_users;
 
     VariableItem *variable_item_logged_out_wifi_settings_ssid;     // Reference to the ssid configuration item
     VariableItem *variable_item_logged_out_wifi_settings_password; // Reference to the password configuration item
@@ -208,16 +215,15 @@ typedef struct
     //
     VariableItem *variable_item_logged_in_profile_username;        // Reference to the username configuration item
     VariableItem *variable_item_logged_in_profile_change_password; // Reference to the change password configuration item
-    VariableItem *variable_item_logged_in_settings_about;          // Reference to the about configuration item
-    VariableItem *variable_item_logged_in_settings_wifi;           // Reference to the wifi settings configuration item
-    VariableItem *variable_item_logged_in_wifi_settings_ssid;      // Reference to the ssid configuration item
-    VariableItem *variable_item_logged_in_wifi_settings_password;  // Reference to the password configuration item
+    VariableItem *variable_item_logged_in_profile_change_bio;      // Reference to the change bio configuration item
+    //
+    VariableItem *variable_item_logged_in_settings_about;         // Reference to the about configuration item
+    VariableItem *variable_item_logged_in_settings_wifi;          // Reference to the wifi settings configuration item
+    VariableItem *variable_item_logged_in_wifi_settings_ssid;     // Reference to the ssid configuration item
+    VariableItem *variable_item_logged_in_wifi_settings_password; // Reference to the password configuration item
     //
     VariableItem *variable_item_logged_in_profile_friends; // Reference to the friends configuration item
     //
-    FuriPubSub *input_event_queue;
-    FuriPubSubSubscription *input_event;
-
     PreSavedPlaylist pre_saved_messages; // Pre-saved messages for the feed screen
 
     char *is_logged_in;         // Store the login status
@@ -227,6 +233,10 @@ typedef struct
     char *login_username_logged_in_temp_buffer;         // Temporary buffer for login username text input
     uint32_t login_username_logged_in_temp_buffer_size; // Size of the login username temporary buffer
 
+    char *change_bio_logged_in;                     // Store the entered bio
+    char *change_bio_logged_in_temp_buffer;         // Temporary buffer for bio text input
+    uint32_t change_bio_logged_in_temp_buffer_size; // Size of the bio temporary buffer
+    //
     char *wifi_ssid_logged_out;                     // Store the entered wifi ssid
     char *wifi_ssid_logged_out_temp_buffer;         // Temporary buffer for wifi ssid text input
     uint32_t wifi_ssid_logged_out_temp_buffer_size; // Size of the wifi ssid temporary buffer
@@ -280,6 +290,14 @@ typedef struct
     char *message_user_choice_logged_in;                     // Store the entered message to send to the selected user
     char *message_user_choice_logged_in_temp_buffer;         // Temporary buffer for message to send to the selected user
     uint32_t message_user_choice_logged_in_temp_buffer_size; // Size of the message to send to the selected user temporary buffer
+    //
+    char *explore_logged_in;                     // Store the entered explore
+    char *explore_logged_in_temp_buffer;         // Temporary buffer for explore text input
+    uint32_t explore_logged_in_temp_buffer_size; // Size of the explore temporary buffer
+
+    char *message_users_logged_in;                     // Store the entered message users
+    char *message_users_logged_in_temp_buffer;         // Temporary buffer for message users text input
+    uint32_t message_users_logged_in_temp_buffer_size; // Size of the message users temporary buffer
 
     Loading *loading; // The loading screen
     DialogEx *dialog_explore;
@@ -287,6 +305,8 @@ typedef struct
     DialogEx *dialog_messages;
     DialogEx *dialog_compose;
     DialogEx *dialog_feed;
+
+    char *explore_user_bio; // Store the bio of the selected user
 } FlipSocialApp;
 
 void flip_social_app_free(FlipSocialApp *app);
@@ -306,7 +326,6 @@ extern bool flip_social_register_success;
 extern bool flip_social_dialog_shown;
 extern bool flip_social_dialog_stop;
 extern bool flip_social_send_message;
-extern char *last_explore_response;
 extern char *selected_message;
 extern char auth_headers[256];
 

+ 27 - 1
flip_storage/flip_social_storage.c

@@ -47,7 +47,6 @@ void save_playlist(const PreSavedPlaylist *playlist)
     furi_record_close(RECORD_STORAGE);
 }
 
-// Function to load the playlist
 // Function to load the playlist
 bool load_playlist(PreSavedPlaylist *playlist)
 {
@@ -143,6 +142,7 @@ void save_settings(
     const char *login_username_logged_in,
     const char *login_password_logged_out,
     const char *change_password_logged_in,
+    const char *change_bio_logged_in,
     const char *is_logged_in)
 {
     // Create the directory for saving settings
@@ -219,6 +219,14 @@ void save_settings(
         FURI_LOG_E(TAG, "Failed to write is_logged_in");
     }
 
+    // Save the change_bio_logged_in length and data
+    size_t change_bio_length = strlen(change_bio_logged_in) + 1; // Include null terminator
+    if (storage_file_write(file, &change_bio_length, sizeof(size_t)) != sizeof(size_t) ||
+        storage_file_write(file, change_bio_logged_in, change_bio_length) != change_bio_length)
+    {
+        FURI_LOG_E(TAG, "Failed to write change_bio_logged_in");
+    }
+
     storage_file_close(file);
     storage_file_free(file);
     furi_record_close(RECORD_STORAGE);
@@ -237,6 +245,8 @@ bool load_settings(
     size_t password_out_size,
     char *change_password_logged_in,
     size_t change_password_size,
+    char *change_bio_logged_in,
+    size_t change_bio_size,
     char *is_logged_in,
     size_t is_logged_in_size)
 {
@@ -363,6 +373,22 @@ bool load_settings(
         is_logged_in[is_logged_in_length - 1] = '\0'; // Ensure null-termination
     }
 
+    // Load the change_bio_logged_in
+    size_t change_bio_length;
+    if (storage_file_read(file, &change_bio_length, sizeof(size_t)) != sizeof(size_t) || change_bio_length > change_bio_size ||
+        storage_file_read(file, change_bio_logged_in, change_bio_length) != change_bio_length)
+    {
+        FURI_LOG_E(TAG, "Failed to read change_bio_logged_in");
+        // storage_file_close(file);
+        // storage_file_free(file);
+        // furi_record_close(RECORD_STORAGE);
+        //  return false;
+    }
+    else
+    {
+        change_bio_logged_in[change_bio_length - 1] = '\0'; // Ensure null-termination
+    }
+
     storage_file_close(file);
     storage_file_free(file);
     furi_record_close(RECORD_STORAGE);

+ 3 - 0
flip_storage/flip_social_storage.h

@@ -16,6 +16,7 @@ void save_settings(
     const char *login_username_logged_in,
     const char *login_password_logged_out,
     const char *change_password_logged_in,
+    const char *change_bio_logged_in,
     const char *is_logged_in);
 
 bool load_settings(
@@ -31,6 +32,8 @@ bool load_settings(
     size_t password_out_size,
     char *change_password_logged_in,
     size_t change_password_size,
+    char *change_bio_logged_in,
+    size_t change_bio_size,
     char *is_logged_in,
     size_t is_logged_in_size);
 

+ 1 - 1
friends/flip_social_friends.c

@@ -117,7 +117,7 @@ bool flip_social_parse_json_friends()
     submenu_set_header(app_instance->submenu_friends, "Friends");
 
     // Extract the users array from the JSON
-    char *json_users = get_json_value("friends", data_cstr, MAX_TOKENS);
+    char *json_users = get_json_value("friends", data_cstr, 128);
     if (json_users == NULL)
     {
         FURI_LOG_E(TAG, "Failed to parse friends array.");

+ 9 - 30
messages/flip_social_messages.c

@@ -40,7 +40,7 @@ FlipSocialMessage *flip_social_user_messages_alloc()
         FURI_LOG_E(TAG, "Failed to allocate memory for messages");
         return NULL;
     }
-    for (size_t i = 0; i < MAX_MESSAGE_USERS; i++)
+    for (size_t i = 0; i < MAX_MESSAGES; i++)
     {
         if (messages->usernames[i] == NULL)
         {
@@ -154,7 +154,7 @@ bool flip_social_get_message_users()
 
     fhttp.save_received_data = true;
     auth_headers_alloc();
-    snprintf(command, 128, "https://www.flipsocial.net/api/messages/%s/get/list/", app_instance->login_username_logged_out);
+    snprintf(command, 128, "https://www.flipsocial.net/api/messages/%s/get/list/%d/", app_instance->login_username_logged_out, MAX_MESSAGE_USERS);
     if (!flipper_http_get_request_with_headers(command, auth_headers))
     {
         FURI_LOG_E(TAG, "Failed to send HTTP request for messages");
@@ -183,7 +183,7 @@ bool flip_social_get_messages_with_user()
         FURI_LOG_E(TAG, "Username is NULL");
         return false;
     }
-    char command[128];
+    char command[256];
     snprintf(
         fhttp.file_path,
         sizeof(fhttp.file_path),
@@ -192,7 +192,7 @@ bool flip_social_get_messages_with_user()
 
     fhttp.save_received_data = true;
     auth_headers_alloc();
-    snprintf(command, 128, "https://www.flipsocial.net/api/messages/%s/get/%s/", app_instance->login_username_logged_out, flip_social_message_users->usernames[flip_social_message_users->index]);
+    snprintf(command, sizeof(command), "https://www.flipsocial.net/api/messages/%s/get/%s/%d/", app_instance->login_username_logged_out, flip_social_message_users->usernames[flip_social_message_users->index], MAX_MESSAGES);
     if (!flipper_http_get_request_with_headers(command, auth_headers))
     {
         FURI_LOG_E(TAG, "Failed to send HTTP request for messages");
@@ -231,18 +231,11 @@ bool flip_social_parse_json_message_users()
         return false;
     }
 
-    // Remove newlines
-    char *pos = data_cstr;
-    while ((pos = strchr(pos, '\n')) != NULL)
-    {
-        *pos = ' ';
-    }
-
     // Initialize message users count
     flip_social_message_users->count = 0;
 
     // Extract the users array from the JSON
-    char *json_users = get_json_value("users", data_cstr, MAX_TOKENS);
+    char *json_users = get_json_value("users", data_cstr, 64);
     if (json_users == NULL)
     {
         FURI_LOG_E(TAG, "Failed to parse users array.");
@@ -324,18 +317,11 @@ bool flip_social_parse_json_message_user_choices()
         return false;
     }
 
-    // Remove newlines
-    char *pos = data_cstr;
-    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", data_cstr, MAX_TOKENS);
+    char *json_users = get_json_value("users", data_cstr, 64);
     if (json_users == NULL)
     {
         FURI_LOG_E(TAG, "Failed to parse users array.");
@@ -417,13 +403,6 @@ bool flip_social_parse_json_messages()
         return false;
     }
 
-    // Remove newlines
-    char *pos = data_cstr;
-    while ((pos = strchr(pos, '\n')) != NULL)
-    {
-        *pos = ' ';
-    }
-
     // Initialize messages count
     flip_social_messages->count = 0;
 
@@ -431,15 +410,15 @@ bool flip_social_parse_json_messages()
     for (int i = 0; i < MAX_MESSAGES; i++)
     {
         // Parse each item in the array
-        char *item = get_json_array_value("conversations", i, data_cstr, MAX_TOKENS);
+        char *item = get_json_array_value("conversations", i, data_cstr, 64);
         if (item == NULL)
         {
             break;
         }
 
         // Extract individual fields from the JSON object
-        char *sender = get_json_value("sender", item, 64);
-        char *content = get_json_value("content", item, 64);
+        char *sender = get_json_value("sender", item, 8);
+        char *content = get_json_value("content", item, 8);
 
         if (sender == NULL || content == NULL)
         {

+ 162 - 0
text_input/rpc_keyboard.h

@@ -0,0 +1,162 @@
+#pragma once
+
+#include <core/common_defines.h>
+#include <core/mutex.h>
+#include <core/pubsub.h>
+
+#define RECORD_RPC_KEYBOARD "rpckeyboard"
+
+#define RPC_KEYBOARD_KEY_RIGHT '\x13'
+#define RPC_KEYBOARD_KEY_LEFT '\x14'
+#define RPC_KEYBOARD_KEY_ENTER '\x0D'
+#define RPC_KEYBOARD_KEY_BACKSPACE '\x08'
+
+typedef enum
+{
+    // Unknown error occurred
+    RpcKeyboardChatpadStatusError,
+    // The chatpad worker is stopped
+    RpcKeyboardChatpadStatusStopped,
+    // The chatpad worker is started, but not ready
+    RpcKeyboardChatpadStatusStarted,
+    // The chatpad worker is ready and got response from chatpad
+    RpcKeyboardChatpadStatusReady,
+} RpcKeyboardChatpadStatus;
+
+typedef struct RpcKeyboard RpcKeyboard;
+
+typedef enum
+{
+    // Replacement text was provided by the user
+    RpcKeyboardEventTypeTextEntered,
+    // A single character was provided by the user
+    RpcKeyboardEventTypeCharEntered,
+    // A macro was entered by the user
+    RpcKeyboardEventTypeMacroEntered,
+} RpcKeyboardEventType;
+
+typedef struct
+{
+    // The mutex to protect the data, call furi_mutex_acquire/furi_mutex_release.
+    FuriMutex *mutex;
+    // The text message, macro or character.
+    char message[256];
+    // The length of the message.
+    uint16_t length;
+    // The newline enabled flag, allow newline to submit text.
+    bool newline_enabled;
+} RpcKeyboardEventData;
+
+typedef struct
+{
+    RpcKeyboardEventType type;
+    RpcKeyboardEventData data;
+} RpcKeyboardEvent;
+
+typedef FuriPubSub *(*RpcKeyboardGetPubsub)(RpcKeyboard *rpc_keyboard);
+typedef void (*RpcKeyboardNewlineEnable)(RpcKeyboard *rpc_keyboard, bool enable);
+typedef void (*RpcKeyboardPublishCharFn)(RpcKeyboard *keyboard, char character);
+typedef void (*RpcKeyboardPublishMacroFn)(RpcKeyboard *rpc_keyboard, char macro);
+typedef char *(*RpcKeyboardGetMacroFn)(RpcKeyboard *rpc_keyboard, char macro);
+typedef void (*RpcKeyboardSetMacroFn)(RpcKeyboard *rpc_keyboard, char macro, char *value);
+typedef void (*RpcKeyboardChatpadStartFn)(RpcKeyboard *rpc_keyboard);
+typedef void (*RpcKeyboardChatpadStopFn)(RpcKeyboard *rpc_keyboard);
+typedef RpcKeyboardChatpadStatus (*RpcKeyboardChatpadStatusFn)(RpcKeyboard *rpc_keyboard);
+
+typedef struct RpcKeyboardFunctions RpcKeyboardFunctions;
+struct RpcKeyboardFunctions
+{
+    uint16_t major;
+    uint16_t minor;
+    RpcKeyboardGetPubsub fn_get_pubsub;
+    RpcKeyboardNewlineEnable fn_newline_enable;
+    RpcKeyboardPublishCharFn fn_publish_char;
+    RpcKeyboardPublishMacroFn fn_publish_macro;
+    RpcKeyboardGetMacroFn fn_get_macro;
+    RpcKeyboardSetMacroFn fn_set_macro;
+    RpcKeyboardChatpadStartFn fn_chatpad_start;
+    RpcKeyboardChatpadStopFn fn_chatpad_stop;
+    RpcKeyboardChatpadStatusFn fn_chatpad_status;
+};
+
+/**
+ * @brief STARTUP - Register the remote keyboard.
+ */
+void rpc_keyboard_register(void);
+
+/**
+ * @brief UNUSED - Unregister the remote keyboard.
+ */
+void rpc_keyboard_release(void);
+
+/**
+ * @brief Get the pubsub object for the remote keyboard.
+ * @details This function returns the pubsub object, use to subscribe to keyboard events.
+ * @param[in] rpc_keyboard pointer to the RECORD_RPC_KEYBOARD.
+ * @return FuriPubSub* pointer to the pubsub object.
+ */
+FuriPubSub *rpc_keyboard_get_pubsub(RpcKeyboard *rpc_keyboard);
+
+/**
+ * @brief Enable or disable newline character submitting the text.
+ * @param[in] rpc_keyboard pointer to the RECORD_RPC_KEYBOARD.
+ * @param[in] enable true to enable, false to disable.
+ */
+void rpc_keyboard_newline_enable(RpcKeyboard *rpc_keyboard, bool enable);
+
+/**
+ * @brief Publish the replacement text to the remote keyboard.
+ * @param[in] rpc_keyboard pointer to the RECORD_RPC_KEYBOARD.
+ * @param[in] bytes pointer to the text buffer.
+ * @param[in] buffer_size size of the text buffer.
+ */
+void rpc_keyboard_publish_text(RpcKeyboard *rpc_keyboard, uint8_t *bytes, uint32_t buffer_size);
+
+/**
+ * @brief Publish a single key pressed on the remote keyboard.
+ * @param[in] rpc_keyboard pointer to the RECORD_RPC_KEYBOARD.
+ * @param[in] character the character that was pressed.
+ */
+void rpc_keyboard_publish_char(RpcKeyboard *rpc_keyboard, char character);
+
+/**
+ * @brief Publish a macro key pressed on the remote keyboard.
+ * @param[in] rpc_keyboard pointer to the RECORD_RPC_KEYBOARD.
+ * @param[in] character the macro key that was pressed.
+ */
+void rpc_keyboard_publish_macro(RpcKeyboard *rpc_keyboard, char macro);
+
+/**
+ * @brief Get the macro text associated with a macro key.
+ * @param[in] rpc_keyboard pointer to the RECORD_RPC_KEYBOARD.
+ * @param[in] macro the macro key.
+ * @return char* pointer to the macro text. NULL if the macro key is not set. User must free the memory.
+ */
+char *rpc_keyboard_get_macro(RpcKeyboard *rpc_keyboard, char macro);
+
+/**
+ * @brief Set the macro text associated with a macro key.
+ * @param[in] rpc_keyboard pointer to the RECORD_RPC_KEYBOARD.
+ * @param[in] macro the macro key.
+ * @param[in] value the macro text.
+ */
+void rpc_keyboard_set_macro(RpcKeyboard *rpc_keyboard, char macro, char *value);
+
+/**
+ * @brief Initializes the chatpad and starts listening for keypresses.
+ * @param[in] rpc_keyboard pointer to the RECORD_RPC_KEYBOARD.
+ */
+void rpc_keyboard_chatpad_start(RpcKeyboard *rpc_keyboard);
+
+/**
+ * @brief Stops the chatpad & frees resources.
+ * @param[in] rpc_keyboard pointer to the RECORD_RPC_KEYBOARD.
+ */
+void rpc_keyboard_chatpad_stop(RpcKeyboard *rpc_keyboard);
+
+/**
+ * @brief Get the status of the chatpad.
+ * @param[in] rpc_keyboard pointer to the RECORD_RPC_KEYBOARD.
+ * @return RpcKeyboardChatpadStatus the status of the chatpad.
+ */
+RpcKeyboardChatpadStatus rpc_keyboard_chatpad_status(RpcKeyboard *rpc_keyboard);

+ 150 - 0
text_input/rpc_keyboard_stub.c

@@ -0,0 +1,150 @@
+#include "rpc_keyboard.h"
+
+#include <furi.h>
+
+static bool rpc_keyboard_functions_check_version(RpcKeyboardFunctions *stub)
+{
+    furi_check(stub);
+    if (stub->major == 1 && stub->minor > 2)
+    {
+        return true;
+    }
+    FURI_LOG_D("RpcKeyboard", "Unsupported version %d.%d", stub->major, stub->minor);
+    return false;
+}
+
+/**
+ * @brief Get the pubsub object for the remote keyboard.
+ * @details This function returns the pubsub object, use to subscribe to keyboard events.
+ * @param[in] rpc_keyboard pointer to the RECORD_RPC_KEYBOARD.
+ * @return FuriPubSub* pointer to the pubsub object.
+ */
+FuriPubSub *rpc_keyboard_get_pubsub(RpcKeyboard *rpc_keyboard)
+{
+    RpcKeyboardFunctions *stub = (RpcKeyboardFunctions *)rpc_keyboard;
+    if (!rpc_keyboard_functions_check_version(stub))
+    {
+        return NULL;
+    }
+    return stub->fn_get_pubsub((RpcKeyboard *)rpc_keyboard);
+}
+
+/**
+ * @brief Enable or disable newline character submitting the text.
+ * @param[in] rpc_keyboard pointer to the RECORD_RPC_KEYBOARD.
+ * @param[in] enable true to enable, false to disable.
+ */
+void rpc_keyboard_newline_enable(RpcKeyboard *rpc_keyboard, bool enable)
+{
+    RpcKeyboardFunctions *stub = (RpcKeyboardFunctions *)rpc_keyboard;
+    if (!rpc_keyboard_functions_check_version(stub))
+    {
+        return;
+    }
+    stub->fn_newline_enable((RpcKeyboard *)rpc_keyboard, enable);
+}
+
+/**
+ * @brief Publish a single key pressed on the remote keyboard.
+ * @param[in] rpc_keyboard pointer to the RECORD_RPC_KEYBOARD.
+ * @param[in] character the character that was pressed.
+ */
+void rpc_keyboard_publish_char(RpcKeyboard *rpc_keyboard, char character)
+{
+    RpcKeyboardFunctions *stub = (RpcKeyboardFunctions *)rpc_keyboard;
+    if (!rpc_keyboard_functions_check_version(stub))
+    {
+        return;
+    }
+    stub->fn_publish_char((RpcKeyboard *)rpc_keyboard, character);
+}
+
+/**
+ * @brief Publish a macro key pressed on the remote keyboard.
+ * @param[in] rpc_keyboard pointer to the RECORD_RPC_KEYBOARD.
+ * @param[in] character the macro key that was pressed.
+ */
+void rpc_keyboard_publish_macro(RpcKeyboard *rpc_keyboard, char macro)
+{
+    RpcKeyboardFunctions *stub = (RpcKeyboardFunctions *)rpc_keyboard;
+    if (!rpc_keyboard_functions_check_version(stub))
+    {
+        return;
+    }
+    stub->fn_publish_macro((RpcKeyboard *)rpc_keyboard, macro);
+}
+
+/**
+ * @brief Get the macro text associated with a macro key.
+ * @param[in] rpc_keyboard pointer to the RECORD_RPC_KEYBOARD.
+ * @param[in] macro the macro key.
+ * @return char* pointer to the macro text. NULL if the macro key is not set. User must free the memory.
+ */
+char *rpc_keyboard_get_macro(RpcKeyboard *rpc_keyboard, char macro)
+{
+    RpcKeyboardFunctions *stub = (RpcKeyboardFunctions *)rpc_keyboard;
+    if (!rpc_keyboard_functions_check_version(stub))
+    {
+        return NULL;
+    }
+    return stub->fn_get_macro((RpcKeyboard *)rpc_keyboard, macro);
+}
+
+/**
+ * @brief Set the macro text associated with a macro key.
+ * @param[in] rpc_keyboard pointer to the RECORD_RPC_KEYBOARD.
+ * @param[in] macro the macro key.
+ * @param[in] value the macro text.
+ */
+void rpc_keyboard_set_macro(RpcKeyboard *rpc_keyboard, char macro, char *value)
+{
+    RpcKeyboardFunctions *stub = (RpcKeyboardFunctions *)rpc_keyboard;
+    if (!rpc_keyboard_functions_check_version(stub))
+    {
+        return;
+    }
+    stub->fn_set_macro((RpcKeyboard *)rpc_keyboard, macro, value);
+}
+
+/**
+ * @brief Initializes the chatpad and starts listening for keypresses.
+ * @param[in] rpc_keyboard pointer to the RECORD_RPC_KEYBOARD.
+ */
+void rpc_keyboard_chatpad_start(RpcKeyboard *rpc_keyboard)
+{
+    RpcKeyboardFunctions *stub = (RpcKeyboardFunctions *)rpc_keyboard;
+    if (!rpc_keyboard_functions_check_version(stub))
+    {
+        return;
+    }
+    stub->fn_chatpad_start((RpcKeyboard *)rpc_keyboard);
+}
+
+/**
+ * @brief Stops the chatpad & frees resources.
+ * @param[in] rpc_keyboard pointer to the RECORD_RPC_KEYBOARD.
+ */
+void rpc_keyboard_chatpad_stop(RpcKeyboard *rpc_keyboard)
+{
+    RpcKeyboardFunctions *stub = (RpcKeyboardFunctions *)rpc_keyboard;
+    if (!rpc_keyboard_functions_check_version(stub))
+    {
+        return;
+    }
+    stub->fn_chatpad_stop((RpcKeyboard *)rpc_keyboard);
+}
+
+/**
+ * @brief Get the status of the chatpad.
+ * @param[in] rpc_keyboard pointer to the RECORD_RPC_KEYBOARD.
+ * @return RpcKeyboardChatpadStatus the status of the chatpad.
+ */
+RpcKeyboardChatpadStatus rpc_keyboard_chatpad_status(RpcKeyboard *rpc_keyboard)
+{
+    RpcKeyboardFunctions *stub = (RpcKeyboardFunctions *)rpc_keyboard;
+    if (!rpc_keyboard_functions_check_version(stub))
+    {
+        return RpcKeyboardChatpadStatusError;
+    }
+    return stub->fn_chatpad_status((RpcKeyboard *)rpc_keyboard);
+}

+ 209 - 1
text_input/uart_text_input.c

@@ -4,6 +4,7 @@
 #include <gui/elements.h>
 #include "flip_social_icons.h"
 #include <furi.h>
+#include "rpc_keyboard.h"
 
 struct UART_TextInput
 {
@@ -25,6 +26,9 @@ typedef struct
     size_t text_buffer_size;
     bool clear_default_text;
 
+    FuriPubSubSubscription *keyboard_subscription;
+    bool invoke_callback;
+
     UART_TextInputCallback callback;
     void *callback_context;
 
@@ -281,6 +285,21 @@ static void uart_text_input_view_draw_callback(Canvas *canvas, void *_model)
     uint8_t needed_string_width = canvas_width(canvas) - 8;
     uint8_t start_pos = 4;
 
+    if (model->invoke_callback)
+    {
+        model->invoke_callback = false;
+        if (model->validator_callback && (!model->validator_callback(model->text_buffer, model->validator_text, model->validator_callback_context)))
+        {
+            model->valadator_message_visible = true;
+        }
+        else if (model->callback != 0)
+        {
+            // We hijack the current thread to invoke the callback (we aren't doing a draw).
+            // model->callback(model->callback_context);
+            return;
+        }
+    }
+
     const char *text = model->text_buffer;
 
     canvas_clear(canvas);
@@ -640,6 +659,193 @@ void uart_text_input_timer_callback(void *context)
         true);
 }
 
+static void text_input_keyboard_callback_line(UART_TextInput *text_input, const RpcKeyboardEvent *event)
+{
+    with_view_model(
+        text_input->view,
+        UART_TextInputModel * model,
+        {
+            if (model->text_buffer != NULL && model->text_buffer_size > 0)
+            {
+                if (event->data.length > 0)
+                {
+                    furi_mutex_acquire(event->data.mutex, FuriWaitForever);
+                    size_t len = event->data.length;
+                    if (len >= model->text_buffer_size)
+                    {
+                        len = model->text_buffer_size - 1;
+                    }
+
+                    bool newline = false;
+                    bool substitutions = false;
+                    size_t copy_index = 0;
+                    for (size_t i = 0; i < len; i++)
+                    {
+                        char ch = event->data.message[i];
+                        if ((ch >= 0x20 && ch <= 0x7E) || ch == '\n' || ch == '\r')
+                        {
+                            model->text_buffer[copy_index++] = ch;
+                            if (ch == '\n' || ch == '\r')
+                            {
+                                newline = event->data.newline_enabled && !substitutions; // TODO: No min-length check?
+                                break;
+                            }
+                        }
+                    }
+                    model->text_buffer[copy_index] = '\0';
+                    furi_mutex_release(event->data.mutex);
+                    FURI_LOG_D("text_input", "copy: %d, %d, %s", len, copy_index, model->text_buffer);
+
+                    // Set focus on Save
+                    model->selected_row = 3;
+                    model->selected_column = 8;
+
+                    // Hijack the next draw to invoke the callback if newline is true.
+                    model->invoke_callback = newline;
+                }
+            }
+        },
+        true);
+}
+
+static void text_input_keyboard_type_key(UART_TextInput *text_input, char selected)
+{
+    with_view_model(
+        text_input->view,
+        UART_TextInputModel * model,
+        {
+            size_t text_length = strlen(model->text_buffer);
+            char search_key = isupper(selected) ? tolower(selected) : selected == ' ' ? '_'
+                                                                                      : selected;
+            bool found = false;
+            for (int row = 0; row < keyboard_row_count; row++)
+            {
+                const UART_TextInputKey *keys = get_row(row);
+                for (int column = 0; column < get_row_size(row); column++)
+                {
+                    if (keys[column].text == search_key)
+                    {
+                        model->selected_row = row;
+                        model->selected_column = column;
+                        found = true;
+                    }
+                }
+            }
+            if (!found)
+            {
+                // Set focus on Backspace
+                model->selected_row = 2;
+                model->selected_column = 9;
+            }
+
+            if (selected == ENTER_KEY)
+            {
+                if (model->validator_callback && (!model->validator_callback(model->text_buffer, model->validator_text, model->validator_callback_context)))
+                {
+                    model->valadator_message_visible = true;
+                    furi_timer_start(text_input->timer, furi_kernel_get_tick_frequency() * 4);
+                }
+                else if (model->callback != 0)
+                { // TODO: no min-length check
+                    model->callback(model->callback_context);
+                }
+            }
+            else if (selected == BACKSPACE_KEY)
+            {
+                uart_text_input_backspace_cb(model);
+            }
+            else
+            {
+                if (model->clear_default_text)
+                {
+                    text_length = 0;
+                }
+                if (selected == RPC_KEYBOARD_KEY_LEFT || selected == RPC_KEYBOARD_KEY_RIGHT)
+                {
+                    // ignore these keys for now
+                }
+                else if (text_length < (model->text_buffer_size - 1))
+                {
+                    model->text_buffer[text_length] = selected;
+                    model->text_buffer[text_length + 1] = 0;
+                }
+            }
+            model->clear_default_text = false;
+        },
+        true);
+}
+
+static void text_input_keyboard_callback(const void *message, void *context)
+{
+    UART_TextInput *text_input = context;
+    const RpcKeyboardEvent *event = message;
+
+    if (event == NULL)
+    {
+        return;
+    }
+
+    switch (event->type)
+    {
+    case RpcKeyboardEventTypeTextEntered:
+        text_input_keyboard_callback_line(text_input, event);
+        break;
+    case RpcKeyboardEventTypeCharEntered:
+        char ch = event->data.message[0];
+        FURI_LOG_I("text_input", "char: %c", ch);
+        text_input_keyboard_type_key(text_input, ch);
+        break;
+    case RpcKeyboardEventTypeMacroEntered:
+        furi_mutex_acquire(event->data.mutex, FuriWaitForever);
+        FURI_LOG_I("text_input", "macro: %s", event->data.message);
+        for (size_t i = 0; i < event->data.length; i++)
+        {
+            text_input_keyboard_type_key(text_input, event->data.message[i]);
+        }
+        furi_mutex_release(event->data.mutex);
+        break;
+    }
+}
+
+static void text_input_view_enter_callback(void *context)
+{
+    furi_assert(context);
+    UART_TextInput *text_input = context;
+    if (furi_record_exists(RECORD_RPC_KEYBOARD))
+    {
+        RpcKeyboard *rpc_keyboard = furi_record_open(RECORD_RPC_KEYBOARD);
+        FuriPubSub *rpc_keyboard_pubsub = rpc_keyboard_get_pubsub(rpc_keyboard);
+        if (rpc_keyboard_pubsub != NULL)
+        {
+            with_view_model(text_input->view, UART_TextInputModel * model, { model->keyboard_subscription = furi_pubsub_subscribe(rpc_keyboard_pubsub, text_input_keyboard_callback, text_input); }, false);
+        }
+        furi_record_close(RECORD_RPC_KEYBOARD);
+    }
+}
+
+static void text_input_view_exit_callback(void *context)
+{
+    furi_assert(context);
+    UART_TextInput *text_input = context;
+    if (furi_record_exists(RECORD_RPC_KEYBOARD))
+    {
+        RpcKeyboard *rpc_keyboard = furi_record_open(RECORD_RPC_KEYBOARD);
+        FuriPubSub *rpc_keyboard_pubsub = rpc_keyboard_get_pubsub(rpc_keyboard);
+        if (rpc_keyboard_pubsub != NULL)
+        {
+            with_view_model(
+                text_input->view,
+                UART_TextInputModel * model,
+                {
+                    furi_pubsub_unsubscribe(rpc_keyboard_pubsub, model->keyboard_subscription);
+                    model->keyboard_subscription = NULL;
+                },
+                false);
+        }
+        furi_record_close(RECORD_RPC_KEYBOARD);
+    }
+}
+
 UART_TextInput *uart_text_input_alloc()
 {
     UART_TextInput *uart_text_input = malloc(sizeof(UART_TextInput));
@@ -648,6 +854,8 @@ UART_TextInput *uart_text_input_alloc()
     view_allocate_model(uart_text_input->view, ViewModelTypeLocking, sizeof(UART_TextInputModel));
     view_set_draw_callback(uart_text_input->view, uart_text_input_view_draw_callback);
     view_set_input_callback(uart_text_input->view, uart_text_input_view_input_callback);
+    view_set_enter_callback(uart_text_input->view, text_input_view_enter_callback);
+    view_set_exit_callback(uart_text_input->view, text_input_view_exit_callback);
 
     uart_text_input->timer =
         furi_timer_alloc(uart_text_input_timer_callback, FuriTimerTypeOnce, uart_text_input);
@@ -732,7 +940,7 @@ void uart_text_input_set_result_callback(
             if (text_buffer && text_buffer[0] != '\0')
             {
                 // Set focus on Save
-                model->selected_row = 2;
+                model->selected_row = 3;
                 model->selected_column = 8;
             }
         },