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

rename callback + import the new loader setup

jblanked 9 месяцев назад
Родитель
Сommit
f05e279cb5

+ 1 - 1
alloc/alloc.h

@@ -1,6 +1,6 @@
 #pragma once
 #include <flip_social.h>
-#include <callback/flip_social_callback.h>
+#include <callback/callback.h>
 #include <alloc/free.h>
 void auth_headers_alloc(void);
 FlipSocialFeedMini *flip_feed_info_alloc(void);

+ 0 - 11
alloc/flip_social_alloc.c

@@ -13,17 +13,6 @@ FlipSocialApp *flip_social_app_alloc()
     {
         return NULL;
     }
-    view_dispatcher_set_custom_event_callback(app->view_dispatcher, flip_social_custom_event_callback);
-    // Main view
-    if (!easy_flipper_set_view(&app->view_loader, FlipSocialViewLoader, flip_social_loader_draw_callback, NULL, flip_social_callback_to_submenu_logged_out, &app->view_dispatcher, app))
-    {
-        return NULL;
-    }
-    flip_social_loader_init(app->view_loader);
-    if (!easy_flipper_set_widget(&app->widget_result, FlipSocialViewWidgetResult, "", flip_social_callback_to_submenu_logged_out, &app->view_dispatcher))
-    {
-        return NULL;
-    }
 
     // Allocate the text input buffers
     app->wifi_ssid_logged_out_temp_buffer_size = MAX_USER_LENGTH;

+ 1 - 1
alloc/flip_social_alloc.h

@@ -1,7 +1,7 @@
 #ifndef FLIP_SOCIAL_ALLOC_H
 #define FLIP_SOCIAL_ALLOC_H
 #include <flip_social.h>
-#include <callback/flip_social_callback.h>
+#include <callback/callback.h>
 #include <flip_storage/flip_social_storage.h>
 
 /**

+ 7 - 1
alloc/free.c

@@ -1,6 +1,9 @@
 #include <alloc/free.h>
-void free_all(bool should_free_variable_item_list, bool should_free_submenu)
+#include <callback/loader.h>
+void free_all(bool should_free_variable_item_list, bool should_free_submenu, void *context)
 {
+    FlipSocialApp *app = (FlipSocialApp *)context;
+    furi_check(app, "FlipSocialApp is NULL");
 
     if (should_free_submenu)
     {
@@ -28,6 +31,9 @@ void free_all(bool should_free_variable_item_list, bool should_free_submenu)
         // flipper_http_deinit();
         went_to_friends = false;
     }
+
+    // free Derek's loader
+    loader_view_free(app);
 }
 void free_text_input()
 {

+ 2 - 2
alloc/free.h

@@ -1,7 +1,7 @@
 #pragma once
 #include <flip_social.h>
-#include <callback/flip_social_callback.h>
-void free_all(bool should_free_variable_item_list, bool should_free_submenu);
+#include <callback/callback.h>
+void free_all(bool should_free_variable_item_list, bool should_free_submenu, void *context);
 void free_text_input();
 void flip_social_free_explore_dialog();
 void flip_social_free_friends_dialog();

+ 36 - 574
callback/flip_social_callback.c → callback/callback.c

@@ -1,67 +1,5 @@
-#include <callback/flip_social_callback.h>
-
-// Below added by Derek Jamison
-// FURI_LOG_DEV will log only during app development. Be sure that Settings/System/Log Device is "LPUART"; so we dont use serial port.
-#ifdef DEVELOPMENT
-#define FURI_LOG_DEV(tag, format, ...) furi_log_print_format(FuriLogLevelInfo, tag, format, ##__VA_ARGS__)
-#define DEV_CRASH() furi_crash()
-#else
-#define FURI_LOG_DEV(tag, format, ...)
-#define DEV_CRASH()
-#endif
-
-static void flip_social_request_error_draw(Canvas *canvas, DataLoaderModel *model)
-{
-    if (canvas == NULL)
-    {
-        FURI_LOG_E(TAG, "error_draw - canvas is NULL");
-        DEV_CRASH();
-        return;
-    }
-    if (model->fhttp->last_response != NULL)
-    {
-        if (strstr(model->fhttp->last_response, "[ERROR] Not connected to Wifi. Failed to reconnect.") != NULL)
-        {
-            canvas_clear(canvas);
-            canvas_draw_str(canvas, 0, 10, "[ERROR] Not connected to Wifi.");
-            canvas_draw_str(canvas, 0, 50, "Update your WiFi settings.");
-            canvas_draw_str(canvas, 0, 60, "Press BACK to return.");
-        }
-        else if (strstr(model->fhttp->last_response, "[ERROR] Failed to connect to Wifi.") != NULL)
-        {
-            canvas_clear(canvas);
-            canvas_draw_str(canvas, 0, 10, "[ERROR] Not connected to Wifi.");
-            canvas_draw_str(canvas, 0, 50, "Update your WiFi settings.");
-            canvas_draw_str(canvas, 0, 60, "Press BACK to return.");
-        }
-        else if (strstr(model->fhttp->last_response, "[ERROR] GET request failed or returned empty data.") != NULL)
-        {
-            canvas_clear(canvas);
-            canvas_draw_str(canvas, 0, 10, "[ERROR] WiFi error.");
-            canvas_draw_str(canvas, 0, 50, "Update your WiFi settings.");
-            canvas_draw_str(canvas, 0, 60, "Press BACK to return.");
-        }
-        else if (strstr(model->fhttp->last_response, "[PONG]") != NULL)
-        {
-            canvas_clear(canvas);
-            canvas_draw_str(canvas, 0, 10, "[STATUS]Connecting to AP...");
-        }
-        else
-        {
-            canvas_clear(canvas);
-            FURI_LOG_E(TAG, "Received an error: %s", model->fhttp->last_response);
-            canvas_draw_str(canvas, 0, 10, "[ERROR] Unusual error...");
-            canvas_draw_str(canvas, 0, 60, "Press BACK and retry.");
-        }
-    }
-    else
-    {
-        canvas_clear(canvas);
-        canvas_draw_str(canvas, 0, 10, "[ERROR] Unknown error.");
-        canvas_draw_str(canvas, 0, 50, "Update your WiFi settings.");
-        canvas_draw_str(canvas, 0, 60, "Press BACK to return.");
-    }
-}
+#include <callback/callback.h>
+#include <callback/loader.h>
 
 static bool flip_social_login_fetch(DataLoaderModel *model)
 {
@@ -131,7 +69,12 @@ static char *flip_social_login_parse(DataLoaderModel *model)
 
 static void flip_social_login_switch_to_view(FlipSocialApp *app)
 {
-    flip_social_generic_switch_to_view(app, "Logging in...", flip_social_login_fetch, flip_social_login_parse, 1, flip_social_callback_to_login_logged_out, FlipSocialViewLoader);
+    if (!loader_view_alloc(app))
+    {
+        FURI_LOG_E(TAG, "Failed to allocate view loader");
+        return;
+    }
+    loader_switch_to_view(app, "Logging in...", flip_social_login_fetch, flip_social_login_parse, 1, flip_social_callback_to_login_logged_out, FlipSocialViewLoader);
 }
 
 static bool flip_social_register_fetch(DataLoaderModel *model)
@@ -225,7 +168,12 @@ static char *flip_social_register_parse(DataLoaderModel *model)
 
 static void flip_social_register_switch_to_view(FlipSocialApp *app)
 {
-    flip_social_generic_switch_to_view(app, "Registering...", flip_social_register_fetch, flip_social_register_parse, 1, flip_social_callback_to_register_logged_out, FlipSocialViewLoader);
+    if (!loader_view_alloc(app))
+    {
+        FURI_LOG_E(TAG, "Failed to allocate view loader");
+        return;
+    }
+    loader_switch_to_view(app, "Registering...", flip_social_register_fetch, flip_social_register_parse, 1, flip_social_callback_to_register_logged_out, FlipSocialViewLoader);
 }
 
 /**
@@ -409,8 +357,7 @@ uint32_t flip_social_callback_to_messages_user_choices(void *context)
 uint32_t flip_social_callback_exit_app(void *context)
 {
     // Exit the application
-    UNUSED(context);
-    free_all(true, true);
+    free_all(true, true, context);
     return VIEW_NONE;
 }
 
@@ -716,7 +663,7 @@ void flip_social_callback_submenu_choices(void *context, uint32_t index)
     case FlipSocialSubmenuLoggedOutIndexLogin:
         flip_social_sent_login_request = false;
         flip_social_login_success = false;
-        free_all(true, true);
+        free_all(true, true, context);
         if (!alloc_variable_item_list(FlipSocialViewLoggedOutLogin))
         {
             FURI_LOG_E(TAG, "Failed to allocate variable item list");
@@ -727,7 +674,7 @@ void flip_social_callback_submenu_choices(void *context, uint32_t index)
     case FlipSocialSubmenuLoggedOutIndexRegister:
         flip_social_sent_register_request = false;
         flip_social_register_success = false;
-        free_all(true, true);
+        free_all(true, true, context);
         if (!alloc_variable_item_list(FlipSocialViewLoggedOutRegister))
         {
             FURI_LOG_E(TAG, "Failed to allocate variable item list");
@@ -743,7 +690,7 @@ void flip_social_callback_submenu_choices(void *context, uint32_t index)
         view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedOutAbout);
         break;
     case FlipSocialSubmenuLoggedOutIndexWifiSettings:
-        free_all(false, false);
+        free_all(false, false, app);
         if (!alloc_variable_item_list(FlipSocialViewLoggedOutWifiSettings))
         {
             FURI_LOG_E(TAG, "Failed to allocate text input");
@@ -752,7 +699,7 @@ void flip_social_callback_submenu_choices(void *context, uint32_t index)
         view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewVariableItemList);
         break;
     case FlipSocialSubmenuLoggedInIndexProfile:
-        free_all(true, true);
+        free_all(true, true, context);
         if (!alloc_variable_item_list(FlipSocialViewLoggedInProfile))
         {
             FURI_LOG_E(TAG, "Failed to allocate variable item list");
@@ -761,7 +708,7 @@ void flip_social_callback_submenu_choices(void *context, uint32_t index)
         view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewVariableItemList);
         break;
     case FlipSocialSubmenuLoggedInIndexMessages:
-        free_all(true, true);
+        free_all(true, true, context);
         if (!alloc_submenu(FlipSocialViewLoggedInMessagesSubmenu))
         {
             FURI_LOG_E(TAG, "Failed to allocate submenu");
@@ -785,7 +732,7 @@ void flip_social_callback_submenu_choices(void *context, uint32_t index)
         view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewTextInput);
         break;
     case FlipSocialSubmenuLoggedInIndexFeed:
-        free_all(true, true);
+        free_all(true, true, context);
         FlipperHTTP *fhttp = flipper_http_alloc();
         if (!fhttp)
         {
@@ -800,7 +747,7 @@ void flip_social_callback_submenu_choices(void *context, uint32_t index)
         flipper_http_free(fhttp);
         break;
     case FlipSocialSubmenuExploreIndex:
-        free_all(true, true);
+        free_all(true, true, context);
         if (!alloc_text_input(FlipSocialViewLoggedInExploreInput))
         {
             FURI_LOG_E(TAG, "Failed to allocate text input");
@@ -809,7 +756,7 @@ void flip_social_callback_submenu_choices(void *context, uint32_t index)
         view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewTextInput);
         break;
     case FlipSocialSubmenuLoggedInIndexCompose:
-        free_all(true, true);
+        free_all(true, true, context);
         if (!alloc_submenu(FlipSocialViewLoggedInCompose))
         {
             FURI_LOG_E(TAG, "Failed to allocate submenu");
@@ -818,7 +765,7 @@ void flip_social_callback_submenu_choices(void *context, uint32_t index)
         view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewSubmenu);
         break;
     case FlipSocialSubmenuLoggedInIndexSettings:
-        free_all(true, true);
+        free_all(true, true, context);
         if (!alloc_submenu(FlipSocialViewLoggedInSettings))
         {
             FURI_LOG_E(TAG, "Failed to allocate submenu");
@@ -827,7 +774,7 @@ void flip_social_callback_submenu_choices(void *context, uint32_t index)
         view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewSubmenu);
         break;
     case FlipSocialSubmenuLoggedInIndexAbout:
-        free_all(true, false);
+        free_all(true, false, context);
         if (!about_widget_alloc(true))
         {
             FURI_LOG_E(TAG, "Failed to allocate about widget");
@@ -836,7 +783,7 @@ void flip_social_callback_submenu_choices(void *context, uint32_t index)
         view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedInSettingsAbout);
         break;
     case FlipSocialSubmenuLoggedInIndexWifiSettings:
-        free_all(true, false);
+        free_all(true, false, context);
         if (!alloc_variable_item_list(FlipSocialViewLoggedInSettingsWifi))
         {
             FURI_LOG_E(TAG, "Failed to allocate variable item list");
@@ -845,7 +792,7 @@ void flip_social_callback_submenu_choices(void *context, uint32_t index)
         view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewVariableItemList);
         break;
     case FlipSocialSubmenuLoggedInIndexUserSettings:
-        free_all(true, false);
+        free_all(true, false, context);
         if (!alloc_variable_item_list(FlipSocialViewLoggedInSettingsUser))
         {
             FURI_LOG_E(TAG, "Failed to allocate variable item list");
@@ -854,7 +801,7 @@ void flip_social_callback_submenu_choices(void *context, uint32_t index)
         view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewVariableItemList);
         break;
     case FlipSocialSubmenuLoggedInSignOutButton:
-        free_all(true, true);
+        free_all(true, true, context);
         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->change_bio_logged_in, app->is_logged_in);
@@ -1295,7 +1242,7 @@ void flip_social_text_input_logged_out_login_item_selected(void *context, uint32
     {
     case 0: // Input Username
         // view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedOutLoginUsernameInput);
-        free_all(false, true);
+        free_all(false, true, app);
         if (!alloc_text_input(FlipSocialViewLoggedOutLoginUsernameInput))
         {
             FURI_LOG_E(TAG, "Failed to allocate text input");
@@ -1305,7 +1252,7 @@ void flip_social_text_input_logged_out_login_item_selected(void *context, uint32
         break;
     case 1: // Input Password
         // view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedOutLoginPasswordInput);
-        free_all(false, true);
+        free_all(false, true, app);
         if (!alloc_text_input(FlipSocialViewLoggedOutLoginPasswordInput))
         {
             FURI_LOG_E(TAG, "Failed to allocate text input");
@@ -1428,7 +1375,7 @@ void flip_social_text_input_logged_out_register_item_selected(void *context, uin
     switch (index)
     {
     case 0: // Input Username
-        free_all(false, true);
+        free_all(false, true, app);
         if (!alloc_text_input(FlipSocialViewLoggedOutRegisterUsernameInput))
         {
             FURI_LOG_E(TAG, "Failed to allocate text input");
@@ -1437,7 +1384,7 @@ void flip_social_text_input_logged_out_register_item_selected(void *context, uin
         view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewTextInput);
         break;
     case 1: // Input Password
-        free_all(false, true);
+        free_all(false, true, app);
         if (!alloc_text_input(FlipSocialViewLoggedOutRegisterPasswordInput))
         {
             FURI_LOG_E(TAG, "Failed to allocate text input");
@@ -1446,7 +1393,7 @@ void flip_social_text_input_logged_out_register_item_selected(void *context, uin
         view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewTextInput);
         break;
     case 2: // Input Password 2
-        free_all(false, true);
+        free_all(false, true, app);
         if (!alloc_text_input(FlipSocialViewLoggedOutRegisterPassword2Input))
         {
             FURI_LOG_E(TAG, "Failed to allocate text input");
@@ -1589,7 +1536,7 @@ void flip_social_text_input_logged_in_wifi_settings_item_selected(void *context,
     {
     case 0: // Input SSID
         // view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedInWifiSettingsSSIDInput);
-        free_all(false, false);
+        free_all(false, false, app);
         if (!alloc_text_input(FlipSocialViewLoggedInWifiSettingsSSIDInput))
         {
             FURI_LOG_E(TAG, "Failed to allocate text input for SSID");
@@ -1599,7 +1546,7 @@ void flip_social_text_input_logged_in_wifi_settings_item_selected(void *context,
         break;
     case 1: // Input Password
         // view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedInWifiSettingsPasswordInput);
-        free_all(false, false);
+        free_all(false, false, app);
         if (!alloc_text_input(FlipSocialViewLoggedInWifiSettingsPasswordInput))
         {
             FURI_LOG_E(TAG, "Failed to allocate text input for Password");
@@ -1829,7 +1776,7 @@ void flip_social_text_input_logged_in_profile_item_selected(void *context, uint3
         view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewTextInput);
         break;
     case 3: // Friends
-        free_all(false, true);
+        free_all(false, true, app);
         if (!alloc_submenu(FlipSocialViewLoggedInFriendsSubmenu))
         {
             FURI_LOG_E(TAG, "Failed to allocate submenu for friends");
@@ -2083,491 +2030,6 @@ void flip_social_logged_in_message_users_updated(void *context)
     //     &app->view_dispatcher);                      // view dispatcher
 }
 
-static void flip_social_widget_set_text(char *message, Widget **widget)
-{
-    if (widget == NULL)
-    {
-        FURI_LOG_E(TAG, "flip_social_set_widget_text - widget is NULL");
-        DEV_CRASH();
-        return;
-    }
-    if (message == NULL)
-    {
-        FURI_LOG_E(TAG, "flip_social_set_widget_text - message is NULL");
-        DEV_CRASH();
-        return;
-    }
-    widget_reset(*widget);
-
-    uint32_t message_length = strlen(message); // Length of the message
-    uint32_t i = 0;                            // Index tracker
-    uint32_t formatted_index = 0;              // Tracker for where we are in the formatted message
-    char *formatted_message;                   // Buffer to hold the final formatted message
-
-    // Allocate buffer with double the message length plus one for safety
-    if (!easy_flipper_set_buffer(&formatted_message, message_length * 2 + 1))
-    {
-        return;
-    }
-
-    while (i < message_length)
-    {
-        uint32_t max_line_length = 31;                  // Maximum characters per line
-        uint32_t remaining_length = message_length - i; // Remaining characters
-        uint32_t line_length = (remaining_length < max_line_length) ? remaining_length : max_line_length;
-
-        // Check for newline character within the current segment
-        uint32_t newline_pos = i;
-        bool found_newline = false;
-        for (; newline_pos < i + line_length && newline_pos < message_length; newline_pos++)
-        {
-            if (message[newline_pos] == '\n')
-            {
-                found_newline = true;
-                break;
-            }
-        }
-
-        if (found_newline)
-        {
-            // If newline found, set line_length up to the newline
-            line_length = newline_pos - i;
-        }
-
-        // Temporary buffer to hold the current line
-        char line[32];
-        strncpy(line, message + i, line_length);
-        line[line_length] = '\0';
-
-        // If newline was found, skip it for the next iteration
-        if (found_newline)
-        {
-            i += line_length + 1; // +1 to skip the '\n' character
-        }
-        else
-        {
-            // Check if the line ends in the middle of a word and adjust accordingly
-            if (line_length == max_line_length && message[i + line_length] != '\0' && message[i + line_length] != ' ')
-            {
-                // Find the last space within the current line to avoid breaking a word
-                char *last_space = strrchr(line, ' ');
-                if (last_space != NULL)
-                {
-                    // Adjust the line_length to avoid cutting the word
-                    line_length = last_space - line;
-                    line[line_length] = '\0'; // Null-terminate at the space
-                }
-            }
-
-            // Move the index forward by the determined line_length
-            i += line_length;
-
-            // Skip any spaces at the beginning of the next line
-            while (i < message_length && message[i] == ' ')
-            {
-                i++;
-            }
-        }
-
-        // Manually copy the fixed line into the formatted_message buffer
-        for (uint32_t j = 0; j < line_length; j++)
-        {
-            formatted_message[formatted_index++] = line[j];
-        }
-
-        // Add a newline character for line spacing
-        formatted_message[formatted_index++] = '\n';
-    }
-
-    // Null-terminate the formatted_message
-    formatted_message[formatted_index] = '\0';
-
-    // Add the formatted message to the widget
-    widget_add_text_scroll_element(*widget, 0, 0, 128, 64, formatted_message);
-}
-
-void flip_social_loader_draw_callback(Canvas *canvas, void *model)
-{
-    if (!canvas || !model)
-    {
-        FURI_LOG_E(TAG, "flip_social_loader_draw_callback - canvas or model is NULL");
-        return;
-    }
-
-    DataLoaderModel *data_loader_model = (DataLoaderModel *)model;
-    HTTPState http_state = data_loader_model->fhttp->state;
-    DataState data_state = data_loader_model->data_state;
-    char *title = data_loader_model->title;
-
-    canvas_set_font(canvas, FontSecondary);
-
-    if (http_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;
-    }
-
-    if (data_state == DataStateError || data_state == DataStateParseError)
-    {
-        flip_social_request_error_draw(canvas, data_loader_model);
-        return;
-    }
-
-    canvas_draw_str(canvas, 0, 7, title);
-    canvas_draw_str(canvas, 0, 17, "Loading...");
-
-    if (data_state == DataStateInitial)
-    {
-        return;
-    }
-
-    if (http_state == SENDING)
-    {
-        canvas_draw_str(canvas, 0, 27, "Fetching...");
-        return;
-    }
-
-    if (http_state == RECEIVING || data_state == DataStateRequested)
-    {
-        canvas_draw_str(canvas, 0, 27, "Receiving...");
-        return;
-    }
-
-    if (http_state == IDLE && data_state == DataStateReceived)
-    {
-        canvas_draw_str(canvas, 0, 27, "Processing...");
-        return;
-    }
-
-    if (http_state == IDLE && data_state == DataStateParsed)
-    {
-        canvas_draw_str(canvas, 0, 27, "Processed...");
-        return;
-    }
-}
-
-static void flip_social_loader_process_callback(void *context)
-{
-    if (context == NULL)
-    {
-        FURI_LOG_E(TAG, "loader_process_callback - context is NULL");
-        DEV_CRASH();
-        return;
-    }
-
-    FlipSocialApp *app = (FlipSocialApp *)context;
-    View *view = app->view_loader;
-
-    DataState current_data_state;
-    DataLoaderModel *loader_model = NULL;
-    with_view_model(
-        view,
-        DataLoaderModel * model,
-        {
-            current_data_state = model->data_state;
-            loader_model = model;
-        },
-        false);
-    if (!loader_model || !loader_model->fhttp)
-    {
-        FURI_LOG_E(TAG, "Model or fhttp is NULL");
-        DEV_CRASH();
-        return;
-    }
-
-    if (current_data_state == DataStateInitial)
-    {
-        with_view_model(
-            view,
-            DataLoaderModel * model,
-            {
-                model->data_state = DataStateRequested;
-                DataLoaderFetch fetch = model->fetcher;
-                if (fetch == NULL)
-                {
-                    FURI_LOG_E(TAG, "Model doesn't have Fetch function assigned.");
-                    model->data_state = DataStateError;
-                    return;
-                }
-
-                // Clear any previous responses
-                strncpy(model->fhttp->last_response, "", 1);
-                bool request_status = fetch(model);
-                if (!request_status)
-                {
-                    model->data_state = DataStateError;
-                }
-            },
-            true);
-    }
-    else if (current_data_state == DataStateRequested || current_data_state == DataStateError)
-    {
-        if (loader_model->fhttp->state == IDLE && loader_model->fhttp->last_response != NULL)
-        {
-            if (strstr(loader_model->fhttp->last_response, "[PONG]") != NULL)
-            {
-                FURI_LOG_DEV(TAG, "PONG received.");
-            }
-            else if (strncmp(loader_model->fhttp->last_response, "[SUCCESS]", 9))
-            {
-                FURI_LOG_DEV(TAG, "SUCCESS received. %s", loader_model->fhttp->last_response ? loader_model->fhttp->last_response : "NULL");
-            }
-            else if (strncmp(loader_model->fhttp->last_response, "[ERROR]", 9))
-            {
-                FURI_LOG_DEV(TAG, "ERROR received. %s", loader_model->fhttp->last_response ? loader_model->fhttp->last_response : "NULL");
-            }
-            else if (strlen(loader_model->fhttp->last_response))
-            {
-                // Still waiting on response
-            }
-            else
-            {
-                with_view_model(view, DataLoaderModel * model, { model->data_state = DataStateReceived; }, true);
-            }
-        }
-        else if (loader_model->fhttp->state == SENDING || loader_model->fhttp->state == RECEIVING)
-        {
-            // continue waiting
-        }
-        else if (loader_model->fhttp->state == INACTIVE)
-        {
-            // inactive. try again
-        }
-        else if (loader_model->fhttp->state == ISSUE)
-        {
-            with_view_model(view, DataLoaderModel * model, { model->data_state = DataStateError; }, true);
-        }
-        else
-        {
-            FURI_LOG_DEV(TAG, "Unexpected state: %d lastresp: %s", loader_model->fhttp->state, loader_model->fhttp->last_response ? loader_model->fhttp->last_response : "NULL");
-            DEV_CRASH();
-        }
-    }
-    else if (current_data_state == DataStateReceived)
-    {
-        with_view_model(
-            view,
-            DataLoaderModel * model,
-            {
-                char *data_text;
-                if (model->parser == NULL)
-                {
-                    data_text = NULL;
-                    FURI_LOG_DEV(TAG, "Parser is NULL");
-                    DEV_CRASH();
-                }
-                else
-                {
-                    data_text = model->parser(model);
-                }
-                FURI_LOG_DEV(TAG, "Parsed data: %s\r\ntext: %s", model->fhttp->last_response ? model->fhttp->last_response : "NULL", data_text ? data_text : "NULL");
-                model->data_text = data_text;
-                if (data_text == NULL)
-                {
-                    model->data_state = DataStateParseError;
-                }
-                else
-                {
-                    model->data_state = DataStateParsed;
-                }
-            },
-            true);
-    }
-    else if (current_data_state == DataStateParsed)
-    {
-        with_view_model(
-            view,
-            DataLoaderModel * model,
-            {
-                if (++model->request_index < model->request_count)
-                {
-                    model->data_state = DataStateInitial;
-                }
-                else
-                {
-                    flip_social_widget_set_text(model->data_text != NULL ? model->data_text : "", &app->widget_result);
-                    if (model->data_text != NULL)
-                    {
-                        free(model->data_text);
-                        model->data_text = NULL;
-                    }
-                    view_set_previous_callback(widget_get_view(app->widget_result), model->back_callback);
-                    view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewWidgetResult);
-                }
-            },
-            true);
-    }
-}
-
-static void flip_social_loader_timer_callback(void *context)
-{
-    if (context == NULL)
-    {
-        FURI_LOG_E(TAG, "flip_social_loader_timer_callback - context is NULL");
-        DEV_CRASH();
-        return;
-    }
-    FlipSocialApp *app = (FlipSocialApp *)context;
-    view_dispatcher_send_custom_event(app->view_dispatcher, FlipSocialCustomEventProcess);
-}
-
-static void flip_social_loader_on_enter(void *context)
-{
-    if (context == NULL)
-    {
-        FURI_LOG_E(TAG, "flip_social_loader_on_enter - context is NULL");
-        DEV_CRASH();
-        return;
-    }
-    FlipSocialApp *app = (FlipSocialApp *)context;
-    View *view = app->view_loader;
-    with_view_model(
-        view,
-        DataLoaderModel * model,
-        {
-            view_set_previous_callback(view, model->back_callback);
-            if (model->timer == NULL)
-            {
-                model->timer = furi_timer_alloc(flip_social_loader_timer_callback, FuriTimerTypePeriodic, app);
-            }
-            furi_timer_start(model->timer, 250);
-        },
-        true);
-}
-
-static void flip_social_loader_on_exit(void *context)
-{
-    if (context == NULL)
-    {
-        FURI_LOG_E(TAG, "flip_social_loader_on_exit - context is NULL");
-        DEV_CRASH();
-        return;
-    }
-    FlipSocialApp *app = (FlipSocialApp *)context;
-    View *view = app->view_loader;
-    with_view_model(
-        view,
-        DataLoaderModel * model,
-        {
-            if (model->timer)
-            {
-                furi_timer_stop(model->timer);
-            }
-        },
-        false);
-}
-
-void flip_social_loader_init(View *view)
-{
-    if (view == NULL)
-    {
-        FURI_LOG_E(TAG, "flip_social_loader_init - view is NULL");
-        DEV_CRASH();
-        return;
-    }
-    view_allocate_model(view, ViewModelTypeLocking, sizeof(DataLoaderModel));
-    view_set_enter_callback(view, flip_social_loader_on_enter);
-    view_set_exit_callback(view, flip_social_loader_on_exit);
-}
-
-void flip_social_loader_free_model(View *view)
-{
-    if (view == NULL)
-    {
-        FURI_LOG_E(TAG, "loader_free_model - view is NULL");
-        DEV_CRASH();
-        return;
-    }
-    with_view_model(
-        view,
-        DataLoaderModel * model,
-        {
-            if (model->timer)
-            {
-                furi_timer_free(model->timer);
-                model->timer = NULL;
-            }
-            if (model->parser_context)
-            {
-                // do not free the context here, it is the app context
-                // free(model->parser_context);
-                // model->parser_context = NULL;
-            }
-            if (model->fhttp)
-            {
-                flipper_http_free(model->fhttp);
-                model->fhttp = NULL;
-            }
-        },
-        false);
-}
-
-bool flip_social_custom_event_callback(void *context, uint32_t index)
-{
-    if (context == NULL)
-    {
-        FURI_LOG_E(TAG, "flip_social_custom_event_callback - context is NULL");
-        DEV_CRASH();
-        return false;
-    }
-
-    switch (index)
-    {
-    case FlipSocialCustomEventProcess:
-        flip_social_loader_process_callback(context);
-        return true;
-    default:
-        FURI_LOG_DEV(TAG, "flip_social_custom_event_callback. Unknown index: %ld", index);
-        return false;
-    }
-}
-
-void flip_social_generic_switch_to_view(FlipSocialApp *app, char *title, DataLoaderFetch fetcher, DataLoaderParser parser, size_t request_count, ViewNavigationCallback back, uint32_t view_id)
-{
-    if (app == NULL)
-    {
-        FURI_LOG_E(TAG, "flip_social_generic_switch_to_view - app is NULL");
-        DEV_CRASH();
-        return;
-    }
-
-    View *view = app->view_loader;
-    if (view == NULL)
-    {
-        FURI_LOG_E(TAG, "flip_social_generic_switch_to_view - view is NULL");
-        DEV_CRASH();
-        return;
-    }
-
-    with_view_model(
-        view,
-        DataLoaderModel * model,
-        {
-            model->title = title;
-            model->fetcher = fetcher;
-            model->parser = parser;
-            model->request_index = 0;
-            model->request_count = request_count;
-            model->back_callback = back;
-            model->data_state = DataStateInitial;
-            model->data_text = NULL;
-            //
-            model->parser_context = app;
-            if (!model->fhttp)
-            {
-                model->fhttp = flipper_http_alloc();
-            }
-        },
-        true);
-
-    view_dispatcher_switch_to_view(app->view_dispatcher, view_id);
-}
-
 bool flip_social_get_home_notification(FlipperHTTP *fhttp)
 {
     if (!app_instance)

+ 0 - 36
callback/flip_social_callback.h → callback/callback.h

@@ -259,48 +259,12 @@ 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;
-enum DataState
-{
-    DataStateInitial,
-    DataStateRequested,
-    DataStateReceived,
-    DataStateParsed,
-    DataStateParseError,
-    DataStateError,
-};
-
 typedef enum FlipSocialCustomEvent FlipSocialCustomEvent;
 enum FlipSocialCustomEvent
 {
     FlipSocialCustomEventProcess,
 };
 
-typedef struct DataLoaderModel DataLoaderModel;
-typedef bool (*DataLoaderFetch)(DataLoaderModel *model);
-typedef char *(*DataLoaderParser)(DataLoaderModel *model);
-struct DataLoaderModel
-{
-    char *title;
-    char *data_text;
-    DataState data_state;
-    DataLoaderFetch fetcher;
-    DataLoaderParser parser;
-    void *parser_context;
-    size_t request_index;
-    size_t request_count;
-    ViewNavigationCallback back_callback;
-    FuriTimer *timer;
-    FlipperHTTP *fhttp;
-};
-void flip_social_generic_switch_to_view(FlipSocialApp *app, char *title, DataLoaderFetch fetcher, DataLoaderParser parser, size_t request_count, ViewNavigationCallback back, uint32_t view_id);
-
-void flip_social_loader_draw_callback(Canvas *canvas, void *model);
-
-void flip_social_loader_init(View *view);
-
-void flip_social_loader_free_model(View *view);
-bool flip_social_custom_event_callback(void *context, uint32_t index);
 void messages_dialog_callback(DialogExResult result, void *context);
 void feed_dialog_callback(DialogExResult result, void *context);
 //

+ 590 - 0
callback/loader.c

@@ -0,0 +1,590 @@
+#include <callback/loader.h>
+#include <callback/utils.h>
+#include <alloc/alloc.h>
+
+bool loader_view_alloc(void *context)
+{
+    FlipSocialApp *app = (FlipSocialApp *)context;
+    furi_check(app, "FlipSocialApp is NULL");
+    if (app->view_loader)
+    {
+        FURI_LOG_E(TAG, "View loader already allocated");
+        return false;
+    }
+    if (app->widget_result)
+    {
+        FURI_LOG_E(TAG, "Widget result already allocated");
+        return false;
+    }
+
+    view_dispatcher_set_custom_event_callback(app->view_dispatcher, loader_custom_event_callback);
+
+    if (!easy_flipper_set_view(&app->view_loader, FlipSocialViewLoader, loader_draw_callback, NULL, flip_social_callback_to_submenu_logged_in, &app->view_dispatcher, app))
+    {
+        return false;
+    }
+
+    loader_init(app->view_loader);
+
+    return easy_flipper_set_widget(&app->widget_result, FlipSocialViewWidgetResult, "", flip_social_callback_to_submenu_logged_in, &app->view_dispatcher);
+}
+
+void loader_view_free(void *context)
+{
+    FlipSocialApp *app = (FlipSocialApp *)context;
+    furi_check(app, "FlipSocialApp is NULL");
+    // Free Widget(s)
+    if (app->widget_result)
+    {
+        view_dispatcher_remove_view(app->view_dispatcher, FlipSocialViewWidgetResult);
+        widget_free(app->widget_result);
+        app->widget_result = NULL;
+    }
+
+    // Free View(s)
+    if (app->view_loader)
+    {
+        view_dispatcher_remove_view(app->view_dispatcher, FlipSocialViewLoader);
+        loader_free_model(app->view_loader);
+        view_free(app->view_loader);
+        app->view_loader = NULL;
+    }
+}
+
+static void loader_error_draw(Canvas *canvas, DataLoaderModel *model)
+{
+    if (canvas == NULL)
+    {
+        FURI_LOG_E(TAG, "error_draw - canvas is NULL");
+        DEV_CRASH();
+        return;
+    }
+    if (model->fhttp->last_response != NULL)
+    {
+        if (strstr(model->fhttp->last_response, "[ERROR] Not connected to Wifi. Failed to reconnect.") != NULL)
+        {
+            canvas_clear(canvas);
+            canvas_draw_str(canvas, 0, 10, "[ERROR] Not connected to Wifi.");
+            canvas_draw_str(canvas, 0, 50, "Update your WiFi settings.");
+            canvas_draw_str(canvas, 0, 60, "Press BACK to return.");
+        }
+        else if (strstr(model->fhttp->last_response, "[ERROR] Failed to connect to Wifi.") != NULL)
+        {
+            canvas_clear(canvas);
+            canvas_draw_str(canvas, 0, 10, "[ERROR] Not connected to Wifi.");
+            canvas_draw_str(canvas, 0, 50, "Update your WiFi settings.");
+            canvas_draw_str(canvas, 0, 60, "Press BACK to return.");
+        }
+        else if (strstr(model->fhttp->last_response, "[ERROR] GET request failed or returned empty data.") != NULL)
+        {
+            canvas_clear(canvas);
+            canvas_draw_str(canvas, 0, 10, "[ERROR] WiFi error.");
+            canvas_draw_str(canvas, 0, 50, "Update your WiFi settings.");
+            canvas_draw_str(canvas, 0, 60, "Press BACK to return.");
+        }
+        else if (strstr(model->fhttp->last_response, "[PONG]") != NULL)
+        {
+            canvas_clear(canvas);
+            canvas_draw_str(canvas, 0, 10, "[STATUS]Connecting to AP...");
+        }
+        else
+        {
+            canvas_clear(canvas);
+            FURI_LOG_E(TAG, "Received an error: %s", model->fhttp->last_response);
+            canvas_draw_str(canvas, 0, 10, "[ERROR] Unusual error...");
+            canvas_draw_str(canvas, 0, 60, "Press BACK and retry.");
+        }
+    }
+    else
+    {
+        canvas_clear(canvas);
+        canvas_draw_str(canvas, 0, 10, "[ERROR] Unknown error.");
+        canvas_draw_str(canvas, 0, 50, "Update your WiFi settings.");
+        canvas_draw_str(canvas, 0, 60, "Press BACK to return.");
+    }
+}
+
+static void loader_process_callback(void *context)
+{
+    if (context == NULL)
+    {
+        FURI_LOG_E(TAG, "loader_process_callback - context is NULL");
+        DEV_CRASH();
+        return;
+    }
+
+    FlipSocialApp *app = (FlipSocialApp *)context;
+    View *view = app->view_loader;
+
+    DataState current_data_state;
+    DataLoaderModel *loader_model = NULL;
+    with_view_model(
+        view,
+        DataLoaderModel * model,
+        {
+            current_data_state = model->data_state;
+            loader_model = model;
+        },
+        false);
+    if (!loader_model || !loader_model->fhttp)
+    {
+        FURI_LOG_E(TAG, "Model or fhttp is NULL");
+        DEV_CRASH();
+        return;
+    }
+
+    if (current_data_state == DataStateInitial)
+    {
+        with_view_model(
+            view,
+            DataLoaderModel * model,
+            {
+                model->data_state = DataStateRequested;
+                DataLoaderFetch fetch = model->fetcher;
+                if (fetch == NULL)
+                {
+                    FURI_LOG_E(TAG, "Model doesn't have Fetch function assigned.");
+                    model->data_state = DataStateError;
+                    return;
+                }
+
+                // Clear any previous responses
+                strncpy(model->fhttp->last_response, "", 1);
+                bool request_status = fetch(model);
+                if (!request_status)
+                {
+                    model->data_state = DataStateError;
+                }
+            },
+            true);
+    }
+    else if (current_data_state == DataStateRequested || current_data_state == DataStateError)
+    {
+        if (loader_model->fhttp->state == IDLE && loader_model->fhttp->last_response != NULL)
+        {
+            if (strstr(loader_model->fhttp->last_response, "[PONG]") != NULL)
+            {
+                FURI_LOG_DEV(TAG, "PONG received.");
+            }
+            else if (strncmp(loader_model->fhttp->last_response, "[SUCCESS]", 9))
+            {
+                FURI_LOG_DEV(TAG, "SUCCESS received. %s", loader_model->fhttp->last_response ? loader_model->fhttp->last_response : "NULL");
+            }
+            else if (strncmp(loader_model->fhttp->last_response, "[ERROR]", 9))
+            {
+                FURI_LOG_DEV(TAG, "ERROR received. %s", loader_model->fhttp->last_response ? loader_model->fhttp->last_response : "NULL");
+            }
+            else if (strlen(loader_model->fhttp->last_response))
+            {
+                // Still waiting on response
+            }
+            else
+            {
+                with_view_model(view, DataLoaderModel * model, { model->data_state = DataStateReceived; }, true);
+            }
+        }
+        else if (loader_model->fhttp->state == SENDING || loader_model->fhttp->state == RECEIVING)
+        {
+            // continue waiting
+        }
+        else if (loader_model->fhttp->state == INACTIVE)
+        {
+            // inactive. try again
+        }
+        else if (loader_model->fhttp->state == ISSUE)
+        {
+            with_view_model(view, DataLoaderModel * model, { model->data_state = DataStateError; }, true);
+        }
+        else
+        {
+            FURI_LOG_DEV(TAG, "Unexpected state: %d lastresp: %s", loader_model->fhttp->state, loader_model->fhttp->last_response ? loader_model->fhttp->last_response : "NULL");
+            DEV_CRASH();
+        }
+    }
+    else if (current_data_state == DataStateReceived)
+    {
+        with_view_model(
+            view,
+            DataLoaderModel * model,
+            {
+                char *data_text;
+                if (model->parser == NULL)
+                {
+                    data_text = NULL;
+                    FURI_LOG_DEV(TAG, "Parser is NULL");
+                    DEV_CRASH();
+                }
+                else
+                {
+                    data_text = model->parser(model);
+                }
+                FURI_LOG_DEV(TAG, "Parsed data: %s\r\ntext: %s", model->fhttp->last_response ? model->fhttp->last_response : "NULL", data_text ? data_text : "NULL");
+                model->data_text = data_text;
+                if (data_text == NULL)
+                {
+                    model->data_state = DataStateParseError;
+                }
+                else
+                {
+                    model->data_state = DataStateParsed;
+                }
+            },
+            true);
+    }
+    else if (current_data_state == DataStateParsed)
+    {
+        with_view_model(
+            view,
+            DataLoaderModel * model,
+            {
+                if (++model->request_index < model->request_count)
+                {
+                    model->data_state = DataStateInitial;
+                }
+                else
+                {
+                    loader_widget_set_text(model->data_text != NULL ? model->data_text : "", &app->widget_result);
+                    if (model->data_text != NULL)
+                    {
+                        free(model->data_text);
+                        model->data_text = NULL;
+                    }
+                    view_set_previous_callback(widget_get_view(app->widget_result), model->back_callback);
+                    view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewWidgetResult);
+                }
+            },
+            true);
+    }
+}
+
+bool loader_custom_event_callback(void *context, uint32_t index)
+{
+    if (context == NULL)
+    {
+        FURI_LOG_E(TAG, "custom_event_callback - context is NULL");
+        DEV_CRASH();
+        return false;
+    }
+
+    switch (index)
+    {
+    case FlipSocialCustomEventProcess:
+        loader_process_callback(context);
+        return true;
+    default:
+        FURI_LOG_DEV(TAG, "custom_event_callback. Unknown index: %ld", index);
+        return false;
+    }
+}
+
+void loader_draw_callback(Canvas *canvas, void *model)
+{
+    if (!canvas || !model)
+    {
+        FURI_LOG_E(TAG, "loader_draw_callback - canvas or model is NULL");
+        return;
+    }
+
+    DataLoaderModel *data_loader_model = (DataLoaderModel *)model;
+    HTTPState http_state = data_loader_model->fhttp->state;
+    DataState data_state = data_loader_model->data_state;
+    char *title = data_loader_model->title;
+
+    canvas_set_font(canvas, FontSecondary);
+
+    if (http_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;
+    }
+
+    if (data_state == DataStateError || data_state == DataStateParseError)
+    {
+        loader_error_draw(canvas, data_loader_model);
+        return;
+    }
+
+    canvas_draw_str(canvas, 0, 7, title);
+    canvas_draw_str(canvas, 0, 17, "Loading...");
+
+    if (data_state == DataStateInitial)
+    {
+        return;
+    }
+
+    if (http_state == SENDING)
+    {
+        canvas_draw_str(canvas, 0, 27, "Fetching...");
+        return;
+    }
+
+    if (http_state == RECEIVING || data_state == DataStateRequested)
+    {
+        canvas_draw_str(canvas, 0, 27, "Receiving...");
+        return;
+    }
+
+    if (http_state == IDLE && data_state == DataStateReceived)
+    {
+        canvas_draw_str(canvas, 0, 27, "Processing...");
+        return;
+    }
+
+    if (http_state == IDLE && data_state == DataStateParsed)
+    {
+        canvas_draw_str(canvas, 0, 27, "Processed...");
+        return;
+    }
+}
+
+static void loader_timer_callback(void *context)
+{
+    if (context == NULL)
+    {
+        FURI_LOG_E(TAG, "loader_timer_callback - context is NULL");
+        DEV_CRASH();
+        return;
+    }
+    FlipSocialApp *app = (FlipSocialApp *)context;
+    view_dispatcher_send_custom_event(app->view_dispatcher, FlipSocialCustomEventProcess);
+}
+
+static void loader_on_enter(void *context)
+{
+    if (context == NULL)
+    {
+        FURI_LOG_E(TAG, "loader_on_enter - context is NULL");
+        DEV_CRASH();
+        return;
+    }
+    FlipSocialApp *app = (FlipSocialApp *)context;
+    View *view = app->view_loader;
+    with_view_model(
+        view,
+        DataLoaderModel * model,
+        {
+            view_set_previous_callback(view, model->back_callback);
+            if (model->timer == NULL)
+            {
+                model->timer = furi_timer_alloc(loader_timer_callback, FuriTimerTypePeriodic, app);
+            }
+            furi_timer_start(model->timer, 250);
+        },
+        true);
+}
+
+static void loader_on_exit(void *context)
+{
+    if (context == NULL)
+    {
+        FURI_LOG_E(TAG, "loader_on_exit - context is NULL");
+        DEV_CRASH();
+        return;
+    }
+    FlipSocialApp *app = (FlipSocialApp *)context;
+    View *view = app->view_loader;
+    with_view_model(
+        view,
+        DataLoaderModel * model,
+        {
+            if (model->timer)
+            {
+                furi_timer_stop(model->timer);
+            }
+        },
+        false);
+}
+
+void loader_init(View *view)
+{
+    if (view == NULL)
+    {
+        FURI_LOG_E(TAG, "loader_init - view is NULL");
+        DEV_CRASH();
+        return;
+    }
+    view_allocate_model(view, ViewModelTypeLocking, sizeof(DataLoaderModel));
+    view_set_enter_callback(view, loader_on_enter);
+    view_set_exit_callback(view, loader_on_exit);
+}
+
+void loader_free_model(View *view)
+{
+    if (view == NULL)
+    {
+        FURI_LOG_E(TAG, "loader_free_model - view is NULL");
+        DEV_CRASH();
+        return;
+    }
+    with_view_model(
+        view,
+        DataLoaderModel * model,
+        {
+            if (model->timer)
+            {
+                furi_timer_free(model->timer);
+                model->timer = NULL;
+            }
+            if (model->parser_context)
+            {
+                // do not free the context here, it is the app context
+                // free(model->parser_context);
+                // model->parser_context = NULL;
+            }
+            if (model->fhttp)
+            {
+                flipper_http_free(model->fhttp);
+                model->fhttp = NULL;
+            }
+        },
+        false);
+}
+
+void loader_switch_to_view(FlipSocialApp *app, char *title, DataLoaderFetch fetcher, DataLoaderParser parser, size_t request_count, ViewNavigationCallback back, uint32_t view_id)
+{
+    if (app == NULL)
+    {
+        FURI_LOG_E(TAG, "loader_switch_to_view - app is NULL");
+        DEV_CRASH();
+        return;
+    }
+
+    View *view = app->view_loader;
+    if (view == NULL)
+    {
+        FURI_LOG_E(TAG, "loader_switch_to_view - view is NULL");
+        DEV_CRASH();
+        return;
+    }
+
+    with_view_model(
+        view,
+        DataLoaderModel * model,
+        {
+            model->title = title;
+            model->fetcher = fetcher;
+            model->parser = parser;
+            model->request_index = 0;
+            model->request_count = request_count;
+            model->back_callback = back;
+            model->data_state = DataStateInitial;
+            model->data_text = NULL;
+            //
+            model->parser_context = app;
+            if (!model->fhttp)
+            {
+                model->fhttp = flipper_http_alloc();
+            }
+        },
+        true);
+
+    view_dispatcher_switch_to_view(app->view_dispatcher, view_id);
+}
+
+void loader_widget_set_text(char *message, Widget **widget)
+{
+    if (widget == NULL)
+    {
+        FURI_LOG_E(TAG, "set_widget_text - widget is NULL");
+        DEV_CRASH();
+        return;
+    }
+    if (message == NULL)
+    {
+        FURI_LOG_E(TAG, "set_widget_text - message is NULL");
+        DEV_CRASH();
+        return;
+    }
+    widget_reset(*widget);
+
+    uint32_t message_length = strlen(message); // Length of the message
+    uint32_t i = 0;                            // Index tracker
+    uint32_t formatted_index = 0;              // Tracker for where we are in the formatted message
+    char *formatted_message;                   // Buffer to hold the final formatted message
+
+    // Allocate buffer with double the message length plus one for safety
+    if (!easy_flipper_set_buffer(&formatted_message, message_length * 2 + 1))
+    {
+        return;
+    }
+
+    while (i < message_length)
+    {
+        uint32_t max_line_length = 31;                  // Maximum characters per line
+        uint32_t remaining_length = message_length - i; // Remaining characters
+        uint32_t line_length = (remaining_length < max_line_length) ? remaining_length : max_line_length;
+
+        // Check for newline character within the current segment
+        uint32_t newline_pos = i;
+        bool found_newline = false;
+        for (; newline_pos < i + line_length && newline_pos < message_length; newline_pos++)
+        {
+            if (message[newline_pos] == '\n')
+            {
+                found_newline = true;
+                break;
+            }
+        }
+
+        if (found_newline)
+        {
+            // If newline found, set line_length up to the newline
+            line_length = newline_pos - i;
+        }
+
+        // Temporary buffer to hold the current line
+        char line[32];
+        strncpy(line, message + i, line_length);
+        line[line_length] = '\0';
+
+        // If newline was found, skip it for the next iteration
+        if (found_newline)
+        {
+            i += line_length + 1; // +1 to skip the '\n' character
+        }
+        else
+        {
+            // Check if the line ends in the middle of a word and adjust accordingly
+            if (line_length == max_line_length && message[i + line_length] != '\0' && message[i + line_length] != ' ')
+            {
+                // Find the last space within the current line to avoid breaking a word
+                char *last_space = strrchr(line, ' ');
+                if (last_space != NULL)
+                {
+                    // Adjust the line_length to avoid cutting the word
+                    line_length = last_space - line;
+                    line[line_length] = '\0'; // Null-terminate at the space
+                }
+            }
+
+            // Move the index forward by the determined line_length
+            i += line_length;
+
+            // Skip any spaces at the beginning of the next line
+            while (i < message_length && message[i] == ' ')
+            {
+                i++;
+            }
+        }
+
+        // Manually copy the fixed line into the formatted_message buffer
+        for (uint32_t j = 0; j < line_length; j++)
+        {
+            formatted_message[formatted_index++] = line[j];
+        }
+
+        // Add a newline character for line spacing
+        formatted_message[formatted_index++] = '\n';
+    }
+
+    // Null-terminate the formatted_message
+    formatted_message[formatted_index] = '\0';
+
+    // Add the formatted message to the widget
+    widget_add_text_scroll_element(*widget, 0, 0, 128, 64, formatted_message);
+}

+ 39 - 0
callback/loader.h

@@ -0,0 +1,39 @@
+#pragma once
+#include <flip_social.h>
+
+typedef enum DataState DataState;
+enum DataState
+{
+    DataStateInitial,
+    DataStateRequested,
+    DataStateReceived,
+    DataStateParsed,
+    DataStateParseError,
+    DataStateError,
+};
+
+typedef struct DataLoaderModel DataLoaderModel;
+typedef bool (*DataLoaderFetch)(DataLoaderModel *model);
+typedef char *(*DataLoaderParser)(DataLoaderModel *model);
+struct DataLoaderModel
+{
+    char *title;
+    char *data_text;
+    DataState data_state;
+    DataLoaderFetch fetcher;
+    DataLoaderParser parser;
+    void *parser_context;
+    size_t request_index;
+    size_t request_count;
+    ViewNavigationCallback back_callback;
+    FuriTimer *timer;
+    FlipperHTTP *fhttp;
+};
+bool loader_view_alloc(void *context);
+void loader_view_free(void *context);
+void loader_switch_to_view(FlipSocialApp *app, char *title, DataLoaderFetch fetcher, DataLoaderParser parser, size_t request_count, ViewNavigationCallback back, uint32_t view_id);
+void loader_draw_callback(Canvas *canvas, void *model);
+void loader_init(View *view);
+void loader_widget_set_text(char *message, Widget **widget);
+void loader_free_model(View *view);
+bool loader_custom_event_callback(void *context, uint32_t index);

+ 12 - 0
callback/utils.h

@@ -0,0 +1,12 @@
+#pragma once
+#include <flip_social.h>
+
+// Below added by Derek Jamison
+// FURI_LOG_DEV will log only during app development. Be sure that Settings/System/Log Device is "LPUART"; so we dont use serial port.
+#ifdef DEVELOPMENT
+#define FURI_LOG_DEV(tag, format, ...) furi_log_print_format(FuriLogLevelInfo, tag, format, ##__VA_ARGS__)
+#define DEV_CRASH() furi_crash()
+#else
+#define FURI_LOG_DEV(tag, format, ...)
+#define DEV_CRASH()
+#endif

+ 1 - 1
explore/flip_social_explore.h

@@ -1,7 +1,7 @@
 #ifndef FLIP_SOCIAL_EXPLORE_H
 #define FLIP_SOCIAL_EXPLORE_H
 #include "flip_social.h"
-#include <callback/flip_social_callback.h>
+#include <callback/callback.h>
 FlipSocialModel *flip_social_explore_alloc();
 void flip_social_free_explore();
 bool flip_social_get_explore(FlipperHTTP *fhttp);

+ 1 - 1
feed/flip_social_feed.h

@@ -1,7 +1,7 @@
 #ifndef FLIP_SOCIAL_FEED_H
 #define FLIP_SOCIAL_FEED_H
 #include "flip_social.h"
-#include <callback/flip_social_callback.h>
+#include <callback/callback.h>
 #include <flip_storage/flip_social_storage.h>
 
 bool flip_social_get_feed(FlipperHTTP *fhttp, int series_index);

+ 2 - 17
flip_social.c

@@ -23,8 +23,6 @@ uint8_t flip_social_feed_type_index = 0;
 char *flip_social_notification_type[] = {"OFF", "ON"};
 uint8_t flip_social_notification_type_index = 0;
 
-void flip_social_loader_free_model(View *view);
-
 /**
  * @brief Function to free the resources used by FlipSocialApp.
  * @details Cleans up all allocated resources before exiting the application.
@@ -55,22 +53,9 @@ void flip_social_app_free(FlipSocialApp *app)
         view_dispatcher_remove_view(app->view_dispatcher, FlipSocialViewLoggedInSubmenu);
         submenu_free(app->submenu_logged_in);
     }
-    //
-
-    // Free Widget(s)
-    if (app->widget_result)
-    {
-        view_dispatcher_remove_view(app->view_dispatcher, FlipSocialViewWidgetResult);
-        widget_free(app->widget_result);
-    }
 
-    // Free View(s)
-    if (app->view_loader)
-    {
-        view_dispatcher_remove_view(app->view_dispatcher, FlipSocialViewLoader);
-        flip_social_loader_free_model(app->view_loader);
-        view_free(app->view_loader);
-    }
+    if (app->fhttp)
+        flipper_http_free(app->fhttp);
 
     if (app->view_dispatcher)
         view_dispatcher_free(app->view_dispatcher);

+ 1 - 1
friends/flip_social_friends.h

@@ -1,7 +1,7 @@
 #ifndef FLIP_SOCIAL_FRIENDS_H
 #define FLIP_SOCIAL_FRIENDS_H
 #include "flip_social.h"
-#include <callback/flip_social_callback.h>
+#include <callback/callback.h>
 
 FlipSocialModel *flip_social_friends_alloc();
 bool flip_social_get_friends(FlipperHTTP *fhttp);

+ 1 - 1
messages/flip_social_messages.h

@@ -2,7 +2,7 @@
 #define FLIP_SOCIAL_MESSAGES_H
 
 #include "flip_social.h"
-#include <callback/flip_social_callback.h>
+#include <callback/callback.h>
 #include <explore/flip_social_explore.h>
 
 FlipSocialModel2 *flip_social_messages_alloc();