jblanked 1 год назад
Родитель
Сommit
a43fab1bf4

+ 5 - 0
CHANGELOG.md

@@ -1,3 +1,8 @@
+## v1.2
+Updates from Derek Jamison:
+- Improved progress display.
+- Added connectivity check on startup.
+
 ## 1.1
 - Improved memory allocation.
 - Updated WiFi configuration.

+ 8 - 13
alloc/flip_weather_alloc.c

@@ -39,19 +39,20 @@ FlipWeatherApp *flip_weather_app_alloc()
     {
         return NULL;
     }
-
+    view_dispatcher_set_custom_event_callback(app->view_dispatcher, flip_weather_custom_event_callback);
     // Main view
-    if (!easy_flipper_set_view(&app->view_weather, FlipWeatherViewWeather, flip_weather_view_draw_callback_weather, NULL, callback_to_submenu, &app->view_dispatcher, app))
+    if (!easy_flipper_set_view(&app->view_loader, FlipWeatherViewLoader, flip_weather_loader_draw_callback, NULL, callback_to_submenu, &app->view_dispatcher, app))
     {
         return NULL;
     }
-    if (!easy_flipper_set_view(&app->view_gps, FlipWeatherViewGPS, flip_weather_view_draw_callback_gps, NULL, callback_to_submenu, &app->view_dispatcher, app))
+    flip_weather_loader_init(app->view_loader);
+
+    // Widget
+    if (!easy_flipper_set_widget(&app->widget, FlipWeatherViewAbout, "FlipWeather v1.2\n-----\nUse WiFi to get GPS and \nWeather information.\n-----\nwww.github.com/jblanked", callback_to_submenu, &app->view_dispatcher))
     {
         return NULL;
     }
-
-    // Widget
-    if (!easy_flipper_set_widget(&app->widget, FlipWeatherViewAbout, "FlipWeather v1.1\n-----\nUse WiFi to get GPS and \nWeather information.\n-----\nwww.github.com/jblanked", callback_to_submenu, &app->view_dispatcher))
+    if (!easy_flipper_set_widget(&app->widget_result, FlipWeatherViewWidgetResult, "Error, try again.", callback_to_submenu, &app->view_dispatcher))
     {
         return NULL;
     }
@@ -77,7 +78,7 @@ FlipWeatherApp *flip_weather_app_alloc()
     variable_item_set_current_value_text(app->variable_item_password, "");
 
     // Submenu
-    if (!easy_flipper_set_submenu(&app->submenu, FlipWeatherViewSubmenu, "FlipWeather v1.1", callback_exit_app, &app->view_dispatcher))
+    if (!easy_flipper_set_submenu(&app->submenu, FlipWeatherViewSubmenu, "FlipWeather v1.2", callback_exit_app, &app->view_dispatcher))
     {
         return NULL;
     }
@@ -107,12 +108,6 @@ FlipWeatherApp *flip_weather_app_alloc()
         }
     }
 
-    // Popup
-    if (!easy_flipper_set_popup(&app->popup_error, FlipWeatherViewPopupError, "[ERROR]", 0, 0, "Wifi Dev Board disconnected.\nIf your board is connected,\nmake sure you have the\nlatest FlipperHTTP flash.", 0, 12, flip_weather_popup_callback, callback_to_submenu, &app->view_dispatcher, app))
-    {
-        return NULL;
-    }
-
     // Switch to the main view
     view_dispatcher_switch_to_view(app->view_dispatcher, FlipWeatherViewSubmenu);
 

+ 35 - 4
app.c

@@ -8,8 +8,8 @@ int32_t flip_weather_app(void *p)
     UNUSED(p);
 
     // Initialize the FlipWeather application
-    FlipWeatherApp *app = flip_weather_app_alloc();
-    if (!app)
+    app_instance = flip_weather_app_alloc();
+    if (!app_instance)
     {
         FURI_LOG_E(TAG, "Failed to allocate FlipWeatherApp");
         return -1;
@@ -21,11 +21,42 @@ int32_t flip_weather_app(void *p)
         return -1;
     }
 
+    // Thanks to Derek Jamison for the following edits
+    if (app_instance->uart_text_input_buffer_ssid != NULL &&
+        app_instance->uart_text_input_buffer_password != NULL)
+    {
+        // Try to wait for pong response.
+        uint8_t counter = 10;
+        while (fhttp.state == INACTIVE && --counter > 0)
+        {
+            FURI_LOG_D(TAG, "Waiting for PONG");
+            furi_delay_ms(100);
+        }
+
+        if (counter == 0)
+        {
+            DialogsApp *dialogs = furi_record_open(RECORD_DIALOGS);
+            DialogMessage *message = dialog_message_alloc();
+            dialog_message_set_header(
+                message, "[FlipperHTTP Error]", 64, 0, AlignCenter, AlignTop);
+            dialog_message_set_text(
+                message,
+                "Ensure your WiFi Developer\nBoard or Pico W is connected\nand the latest FlipperHTTP\nfirmware is installed.",
+                0,
+                63,
+                AlignLeft,
+                AlignBottom);
+            dialog_message_show(dialogs, message);
+            dialog_message_free(message);
+            furi_record_close(RECORD_DIALOGS);
+        }
+    }
+
     // Run the view dispatcher
-    view_dispatcher_run(app->view_dispatcher);
+    view_dispatcher_run(app_instance->view_dispatcher);
 
     // Free the resources used by the FlipWeather application
-    flip_weather_app_free(app);
+    flip_weather_app_free(app_instance);
 
     // Return 0 to indicate success
     return 0;

+ 1 - 1
application.fam

@@ -10,5 +10,5 @@ App(
     fap_description="Use WiFi to get GPS and Weather information on your Flipper Zero.",
     fap_author="JBlanked",
     fap_weburl="https://github.com/jblanked/FlipWeather",
-    fap_version = "1.1",
+    fap_version = "1.2",
 )

BIN
assets/01-home.png


+ 496 - 180
callback/flip_weather_callback.c

@@ -1,22 +1,27 @@
 #include "callback/flip_weather_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
+
 bool weather_request_success = false;
 bool sent_weather_request = false;
 bool got_weather_data = false;
 
-void flip_weather_popup_callback(void *context)
+static void flip_weather_request_error_draw(Canvas *canvas)
 {
-    FlipWeatherApp *app = (FlipWeatherApp *)context;
-    if (!app)
+    if (canvas == NULL)
     {
-        FURI_LOG_E(TAG, "FlipWeatherApp is NULL");
+        FURI_LOG_E(TAG, "flip_weather_request_error_draw - canvas is NULL");
+        DEV_CRASH();
         return;
     }
-    view_dispatcher_switch_to_view(app->view_dispatcher, FlipWeatherViewSubmenu);
-}
-
-void flip_weather_request_error(Canvas *canvas)
-{
     if (fhttp.last_response != NULL)
     {
         if (strstr(fhttp.last_response, "[ERROR] Not connected to Wifi. Failed to reconnect.") != NULL)
@@ -40,6 +45,11 @@ void flip_weather_request_error(Canvas *canvas)
             canvas_draw_str(canvas, 0, 50, "Update your WiFi settings.");
             canvas_draw_str(canvas, 0, 60, "Press BACK to return.");
         }
+        else if (strstr(fhttp.last_response, "[PONG]") != NULL)
+        {
+            canvas_clear(canvas);
+            canvas_draw_str(canvas, 0, 10, "[STATUS]Connecting to AP...");
+        }
         else
         {
             canvas_clear(canvas);
@@ -56,164 +66,14 @@ void flip_weather_request_error(Canvas *canvas)
         canvas_draw_str(canvas, 0, 60, "Press BACK to return.");
     }
 }
-
-void flip_weather_handle_gps_draw(Canvas *canvas, bool show_gps_data)
+static void flip_weather_gps_switch_to_view(FlipWeatherApp *app)
 {
-    if (sent_get_request)
-    {
-        if (fhttp.state == RECEIVING)
-        {
-            if (show_gps_data)
-            {
-                canvas_clear(canvas);
-                canvas_draw_str(canvas, 0, 10, "Loading GPS...");
-                canvas_draw_str(canvas, 0, 22, "Receiving...");
-            }
-        }
-        // check status
-        else if (fhttp.state == ISSUE || !get_request_success)
-        {
-            flip_weather_request_error(canvas);
-        }
-        else if (fhttp.state == IDLE)
-        {
-            // success, draw GPS
-            process_geo_location();
-
-            if (show_gps_data)
-            {
-                canvas_clear(canvas);
-                canvas_draw_str(canvas, 0, 10, city_data);
-                canvas_draw_str(canvas, 0, 20, region_data);
-                canvas_draw_str(canvas, 0, 30, country_data);
-                canvas_draw_str(canvas, 0, 40, lat_data);
-                canvas_draw_str(canvas, 0, 50, lon_data);
-                canvas_draw_str(canvas, 0, 60, ip_data);
-            }
-        }
-    }
-}
-
-// Callback for drawing the weather screen
-void flip_weather_view_draw_callback_weather(Canvas *canvas, void *model)
-{
-    if (!canvas)
-    {
-        return;
-    }
-    UNUSED(model);
-
-    canvas_set_font(canvas, FontSecondary);
-
-    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;
-    }
-
-    canvas_draw_str(canvas, 0, 10, "Loading location data...");
-    // handle geo location until it's processed and then handle weather
-
-    // start the process
-    if (!send_geo_location_request() || fhttp.state == ISSUE)
-    {
-        flip_weather_request_error(canvas);
-    }
-    // wait until geo location is processed
-    if (!sent_get_request || !get_request_success || fhttp.state == RECEIVING)
-    {
-        canvas_draw_str(canvas, 0, 22, "Receiving data...");
-        return;
-    }
-    // get/set geo lcoation once
-    if (!geo_information_processed)
-    {
-        flip_weather_handle_gps_draw(canvas, false);
-        canvas_draw_str(canvas, 0, 34, "Parsed location data.");
-    }
-    // start the weather process
-    if (!sent_weather_request && fhttp.state == IDLE)
-    {
-        canvas_clear(canvas);
-        canvas_draw_str(canvas, 0, 10, "Getting Weather...");
-        sent_weather_request = true;
-        char url[512];
-        char *lattitude = lat_data + 10;
-        char *longitude = lon_data + 11;
-        char *headers = jsmn("Content-Type", "application/json");
-        snprintf(url, 512, "https://api.open-meteo.com/v1/forecast?latitude=%s&longitude=%s&current=temperature_2m,precipitation,rain,showers,snowfall&temperature_unit=celsius&wind_speed_unit=mph&precipitation_unit=inch&forecast_days=1", lattitude, longitude);
-        weather_request_success = flipper_http_get_request_with_headers(url, headers);
-        free(headers);
-        if (!weather_request_success)
-        {
-            FURI_LOG_E(TAG, "Failed to send GET request");
-            fhttp.state = ISSUE;
-            flip_weather_request_error(canvas);
-        }
-        fhttp.state = RECEIVING;
-    }
-    else
-    {
-        if (fhttp.state == RECEIVING)
-        {
-            canvas_draw_str(canvas, 0, 10, "Loading Weather...");
-            canvas_draw_str(canvas, 0, 22, "Receiving...");
-            return;
-        }
-
-        // check status
-        if (fhttp.state == ISSUE || !weather_request_success)
-        {
-            flip_weather_request_error(canvas);
-            fhttp.state = ISSUE;
-        }
-        else
-        {
-            // success, draw weather
-            process_weather();
-            canvas_clear(canvas);
-            canvas_draw_str(canvas, 0, 10, temperature_data);
-            canvas_draw_str(canvas, 0, 20, precipitation_data);
-            canvas_draw_str(canvas, 0, 30, rain_data);
-            canvas_draw_str(canvas, 0, 40, showers_data);
-            canvas_draw_str(canvas, 0, 50, snowfall_data);
-            canvas_draw_str(canvas, 0, 60, time_data);
-        }
-    }
+    flip_weather_generic_switch_to_view(app, "Fetching GPS data..", send_geo_location_request, process_geo_location, 1, callback_to_submenu, FlipWeatherViewLoader);
 }
 
-// Callback for drawing the GPS screen
-void flip_weather_view_draw_callback_gps(Canvas *canvas, void *model)
+static void flip_weather_weather_switch_to_view(FlipWeatherApp *app)
 {
-    if (!canvas)
-    {
-        return;
-    }
-    UNUSED(model);
-
-    if (fhttp.state == INACTIVE)
-    {
-        canvas_set_font(canvas, FontSecondary);
-        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 (!send_geo_location_request() || fhttp.state == ISSUE)
-    {
-        flip_weather_request_error(canvas);
-    }
-
-    flip_weather_handle_gps_draw(canvas, true);
+    flip_weather_generic_switch_to_view(app, "Fetching Weather data..", send_geo_weather_request, process_weather, 1, callback_to_submenu, FlipWeatherViewLoader);
 }
 
 void callback_submenu_choices(void *context, uint32_t index)
@@ -227,24 +87,11 @@ void callback_submenu_choices(void *context, uint32_t index)
     switch (index)
     {
     case FlipWeatherSubmenuIndexWeather:
-        if (!flip_weather_handle_ip_address())
-        {
-            view_dispatcher_switch_to_view(app->view_dispatcher, FlipWeatherViewPopupError);
-        }
-        else
-        {
-            view_dispatcher_switch_to_view(app->view_dispatcher, FlipWeatherViewWeather);
-        }
+        flipper_http_loading_task(send_geo_location_request, process_geo_location_2, FlipWeatherViewSubmenu, FlipWeatherViewSubmenu, &app->view_dispatcher);
+        flip_weather_weather_switch_to_view(app);
         break;
     case FlipWeatherSubmenuIndexGPS:
-        if (!flip_weather_handle_ip_address())
-        {
-            view_dispatcher_switch_to_view(app->view_dispatcher, FlipWeatherViewPopupError);
-        }
-        else
-        {
-            view_dispatcher_switch_to_view(app->view_dispatcher, FlipWeatherViewGPS);
-        }
+        flip_weather_gps_switch_to_view(app);
         break;
     case FlipWeatherSubmenuIndexAbout:
         view_dispatcher_switch_to_view(app->view_dispatcher, FlipWeatherViewAbout);
@@ -347,6 +194,16 @@ uint32_t callback_to_submenu(void *context)
     weather_information_processed = false;
     sent_weather_request = false;
     weather_request_success = false;
+    if (weather_data != NULL)
+    {
+        free(weather_data);
+        weather_data = NULL;
+    }
+    if (total_data != NULL)
+    {
+        free(total_data);
+        total_data = NULL;
+    }
     return FlipWeatherViewSubmenu;
 }
 
@@ -398,4 +255,463 @@ uint32_t callback_to_wifi_settings(void *context)
     }
     UNUSED(context);
     return FlipWeatherViewSettings;
-}
+}
+
+static void flip_weather_widget_set_text(char *message, Widget **widget)
+{
+    if (widget == NULL)
+    {
+        FURI_LOG_E(TAG, "flip_weather_set_widget_text - widget is NULL");
+        DEV_CRASH();
+        return;
+    }
+    if (message == NULL)
+    {
+        FURI_LOG_E(TAG, "flip_weather_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_weather_loader_draw_callback(Canvas *canvas, void *model)
+{
+    if (!canvas || !model)
+    {
+        FURI_LOG_E(TAG, "flip_weather_loader_draw_callback - canvas or model is NULL");
+        return;
+    }
+
+    SerialState http_state = fhttp.state;
+    DataLoaderModel *data_loader_model = (DataLoaderModel *)model;
+    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_weather_request_error_draw(canvas);
+        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, "Sending...");
+        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_weather_loader_process_callback(void *context)
+{
+    if (context == NULL)
+    {
+        FURI_LOG_E(TAG, "flip_weather_loader_process_callback - context is NULL");
+        DEV_CRASH();
+        return;
+    }
+
+    FlipWeatherApp *app = (FlipWeatherApp *)context;
+    View *view = app->view_loader;
+
+    DataState current_data_state;
+    with_view_model(view, DataLoaderModel * model, { current_data_state = model->data_state; }, false);
+
+    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(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 (fhttp.state == IDLE && fhttp.last_response != NULL)
+        {
+            if (strstr(fhttp.last_response, "[PONG]") != NULL)
+            {
+                FURI_LOG_DEV(TAG, "PONG received.");
+            }
+            else if (strncmp(fhttp.last_response, "[SUCCESS]", 9) == 0)
+            {
+                FURI_LOG_DEV(TAG, "SUCCESS received. %s", fhttp.last_response ? fhttp.last_response : "NULL");
+            }
+            else if (strncmp(fhttp.last_response, "[ERROR]", 9) == 0)
+            {
+                FURI_LOG_DEV(TAG, "ERROR received. %s", fhttp.last_response ? fhttp.last_response : "NULL");
+            }
+            else if (strlen(fhttp.last_response) == 0)
+            {
+                // Still waiting on response
+            }
+            else
+            {
+                with_view_model(view, DataLoaderModel * model, { model->data_state = DataStateReceived; }, true);
+            }
+        }
+        else if (fhttp.state == SENDING || fhttp.state == RECEIVING)
+        {
+            // continue waiting
+        }
+        else if (fhttp.state == INACTIVE)
+        {
+            // inactive. try again
+        }
+        else if (fhttp.state == ISSUE)
+        {
+            with_view_model(view, DataLoaderModel * model, { model->data_state = DataStateError; }, true);
+        }
+        else
+        {
+            FURI_LOG_DEV(TAG, "Unexpected state: %d lastresp: %s", fhttp.state, fhttp.last_response ? 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", fhttp.last_response ? 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_weather_widget_set_text(model->data_text != NULL ? model->data_text : "Empty result", &app_instance->widget_result);
+                    if (model->data_text != NULL)
+                    {
+                        free(model->data_text);
+                        model->data_text = NULL;
+                    }
+                    view_set_previous_callback(widget_get_view(app_instance->widget_result), model->back_callback);
+                    view_dispatcher_switch_to_view(app_instance->view_dispatcher, FlipWeatherViewWidgetResult);
+                }
+            },
+            true);
+    }
+}
+
+static void flip_weather_loader_timer_callback(void *context)
+{
+    if (context == NULL)
+    {
+        FURI_LOG_E(TAG, "flip_weather_loader_timer_callback - context is NULL");
+        DEV_CRASH();
+        return;
+    }
+    FlipWeatherApp *app = (FlipWeatherApp *)context;
+    view_dispatcher_send_custom_event(app->view_dispatcher, FlipWeatherCustomEventProcess);
+}
+
+static void flip_weather_loader_on_enter(void *context)
+{
+    if (context == NULL)
+    {
+        FURI_LOG_E(TAG, "flip_weather_loader_on_enter - context is NULL");
+        DEV_CRASH();
+        return;
+    }
+    FlipWeatherApp *app = (FlipWeatherApp *)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_weather_loader_timer_callback, FuriTimerTypePeriodic, app);
+            }
+            furi_timer_start(model->timer, 250);
+        },
+        true);
+}
+
+static void flip_weather_loader_on_exit(void *context)
+{
+    if (context == NULL)
+    {
+        FURI_LOG_E(TAG, "flip_weather_loader_on_exit - context is NULL");
+        DEV_CRASH();
+        return;
+    }
+    FlipWeatherApp *app = (FlipWeatherApp *)context;
+    View *view = app->view_loader;
+    with_view_model(
+        view,
+        DataLoaderModel * model,
+        {
+            if (model->timer)
+            {
+                furi_timer_stop(model->timer);
+            }
+        },
+        false);
+}
+
+void flip_weather_loader_init(View *view)
+{
+    if (view == NULL)
+    {
+        FURI_LOG_E(TAG, "flip_weather_loader_init - view is NULL");
+        DEV_CRASH();
+        return;
+    }
+    view_allocate_model(view, ViewModelTypeLocking, sizeof(DataLoaderModel));
+    view_set_enter_callback(view, flip_weather_loader_on_enter);
+    view_set_exit_callback(view, flip_weather_loader_on_exit);
+}
+
+void flip_weather_loader_free_model(View *view)
+{
+    if (view == NULL)
+    {
+        FURI_LOG_E(TAG, "flip_weather_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)
+            {
+                free(model->parser_context);
+                model->parser_context = NULL;
+            }
+        },
+        false);
+}
+
+bool flip_weather_custom_event_callback(void *context, uint32_t index)
+{
+    if (context == NULL)
+    {
+        FURI_LOG_E(TAG, "flip_weather_custom_event_callback - context is NULL");
+        DEV_CRASH();
+        return false;
+    }
+
+    switch (index)
+    {
+    case FlipWeatherCustomEventProcess:
+        flip_weather_loader_process_callback(context);
+        return true;
+    default:
+        FURI_LOG_DEV(TAG, "flip_weather_custom_event_callback. Unknown index: %ld", index);
+        return false;
+    }
+}
+
+void flip_weather_generic_switch_to_view(FlipWeatherApp *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_weather_generic_switch_to_view - app is NULL");
+        DEV_CRASH();
+        return;
+    }
+
+    View *view = app->view_loader;
+    if (view == NULL)
+    {
+        FURI_LOG_E(TAG, "flip_weather_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;
+        },
+        true);
+
+    view_dispatcher_switch_to_view(app->view_dispatcher, view_id);
+}

+ 11 - 3
callback/flip_weather_callback.h

@@ -9,9 +9,6 @@ extern bool weather_request_success;
 extern bool sent_weather_request;
 extern bool got_weather_data;
 
-void flip_weather_popup_callback(void *context);
-void flip_weather_request_error(Canvas *canvas);
-void flip_weather_handle_gps_draw(Canvas *canvas, bool show_gps_data);
 void flip_weather_view_draw_callback_weather(Canvas *canvas, void *model);
 void flip_weather_view_draw_callback_gps(Canvas *canvas, void *model);
 void callback_submenu_choices(void *context, uint32_t index);
@@ -27,4 +24,15 @@ void settings_item_selected(void *context, uint32_t index);
  */
 uint32_t callback_exit_app(void *context);
 uint32_t callback_to_wifi_settings(void *context);
+
+// Add edits by Derek Jamison
+void flip_weather_generic_switch_to_view(FlipWeatherApp *app, char *title, DataLoaderFetch fetcher, DataLoaderParser parser, size_t request_count, ViewNavigationCallback back, uint32_t view_id);
+
+void flip_weather_loader_draw_callback(Canvas *canvas, void *model);
+
+void flip_weather_loader_init(View *view);
+
+void flip_weather_loader_free_model(View *view);
+
+bool flip_weather_custom_event_callback(void *context, uint32_t index);
 #endif

+ 2 - 0
easy_flipper/easy_flipper.h

@@ -15,6 +15,8 @@
 #include <gui/modules/text_box.h>
 #include <gui/modules/variable_item_list.h>
 #include <gui/modules/dialog_ex.h>
+#include <notification/notification.h>
+#include <dialogs/dialogs.h>
 #include <gui/modules/popup.h>
 #include <gui/modules/loading.h>
 #include <text_input/uart_text_input.h>

+ 18 - 26
flip_weather.c

@@ -1,18 +1,13 @@
 #include "flip_weather.h"
 
-char city_data[48];
-char region_data[48];
-char country_data[48];
 char lat_data[32];
 char lon_data[32];
-char ip_data[32];
-char temperature_data[32];
-char precipitation_data[32];
-char rain_data[32];
-char showers_data[32];
-char snowfall_data[32];
-char time_data[32];
-char ip_address[16];
+
+char *total_data = NULL;
+char *weather_data = NULL;
+
+FlipWeatherApp *app_instance = NULL;
+void flip_weather_loader_free_model(View *view);
 
 // Function to free the resources used by FlipWeatherApp
 void flip_weather_app_free(FlipWeatherApp *app)
@@ -24,15 +19,11 @@ void flip_weather_app_free(FlipWeatherApp *app)
     }
 
     // Free View(s)
-    if (app->view_weather)
+    if (app->view_loader)
     {
-        view_dispatcher_remove_view(app->view_dispatcher, FlipWeatherViewWeather);
-        view_free(app->view_weather);
-    }
-    if (app->view_gps)
-    {
-        view_dispatcher_remove_view(app->view_dispatcher, FlipWeatherViewGPS);
-        view_free(app->view_gps);
+        view_dispatcher_remove_view(app->view_dispatcher, FlipWeatherViewLoader);
+        flip_weather_loader_free_model(app->view_loader);
+        view_free(app->view_loader);
     }
 
     // Free Submenu(s)
@@ -48,6 +39,11 @@ void flip_weather_app_free(FlipWeatherApp *app)
         view_dispatcher_remove_view(app->view_dispatcher, FlipWeatherViewAbout);
         widget_free(app->widget);
     }
+    if (app->widget_result)
+    {
+        view_dispatcher_remove_view(app->view_dispatcher, FlipWeatherViewWidgetResult);
+        widget_free(app->widget_result);
+    }
 
     // Free Variable Item List(s)
     if (app->variable_item_list)
@@ -68,13 +64,6 @@ void flip_weather_app_free(FlipWeatherApp *app)
         uart_text_input_free(app->uart_text_input_password);
     }
 
-    // Free Popup(s)
-    if (app->popup_error)
-    {
-        view_dispatcher_remove_view(app->view_dispatcher, FlipWeatherViewPopupError);
-        popup_free(app->popup_error);
-    }
-
     // Free the text input buffer
     if (app->uart_text_input_buffer_ssid)
         free(app->uart_text_input_buffer_ssid);
@@ -95,6 +84,9 @@ void flip_weather_app_free(FlipWeatherApp *app)
     // close the gui
     furi_record_close(RECORD_GUI);
 
+    if (total_data)
+        free(total_data);
+
     // free the app
     if (app)
         free(app);

+ 9 - 16
flip_weather.h

@@ -20,25 +20,25 @@ typedef enum
 // Define a single view for our FlipWeather application
 typedef enum
 {
-    FlipWeatherViewWeather,           // The weather screen
-    FlipWeatherViewGPS,               // The GPS screen
     FlipWeatherViewSubmenu,           // The main submenu
     FlipWeatherViewAbout,             // The about screen
     FlipWeatherViewSettings,          // The wifi settings screen
     FlipWeatherViewTextInputSSID,     // The text input screen for SSID
     FlipWeatherViewTextInputPassword, // The text input screen for password
     //
-    FlipWeatherViewPopupError, // The error popup screen
+    FlipWeatherViewPopupError,   // The error popup screen
+    FlipWeatherViewWidgetResult, // The text box that displays the random fact
+    FlipWeatherViewLoader,       // The loader screen retrieves data from the internet
 } FlipWeatherView;
 
 // Each screen will have its own view
 typedef struct
 {
     ViewDispatcher *view_dispatcher;          // Switches between our views
-    View *view_weather;                       // The weather view
-    View *view_gps;                           // The GPS view
+    View *view_loader;                        // The screen that loads data from internet
     Submenu *submenu;                         // The main submenu
     Widget *widget;                           // The widget (about)
+    Widget *widget_result;                    // The widget that displays the result
     Popup *popup_error;                       // The error popup
     VariableItemList *variable_item_list;     // The variable item list (settngs)
     VariableItem *variable_item_ssid;         // The variable item
@@ -55,21 +55,14 @@ typedef struct
     uint32_t uart_text_input_buffer_size_password; // Size of the text input buffer
 } FlipWeatherApp;
 
-extern char city_data[48];
-extern char region_data[48];
-extern char country_data[48];
 extern char lat_data[32];
 extern char lon_data[32];
-extern char ip_data[32];
-extern char temperature_data[32];
-extern char precipitation_data[32];
-extern char rain_data[32];
-extern char showers_data[32];
-extern char snowfall_data[32];
-extern char time_data[32];
-extern char ip_address[16];
+
+extern char *total_data;
+extern char *weather_data;
 
 // Function to free the resources used by FlipWeatherApp
 void flip_weather_app_free(FlipWeatherApp *app);
+extern FlipWeatherApp *app_instance;
 
 #endif

+ 50 - 1
flipper_http/flipper_http.c

@@ -1467,4 +1467,53 @@ bool flipper_http_process_response_async(bool (*http_request)(void), bool (*pars
         return false;
     }
     return true;
-}
+}
+
+/**
+ * @brief Perform a task while displaying a loading screen
+ * @param http_request The function to send the request
+ * @param parse_response The function to parse the response
+ * @param success_view_id The view ID to switch to on success
+ * @param failure_view_id The view ID to switch to on failure
+ * @param view_dispatcher The view dispatcher to use
+ * @return
+ */
+void flipper_http_loading_task(bool (*http_request)(void),
+                               bool (*parse_response)(void),
+                               uint32_t success_view_id,
+                               uint32_t failure_view_id,
+                               ViewDispatcher **view_dispatcher)
+{
+    Loading *loading;
+    int32_t loading_view_id = 987654321; // Random ID
+
+    loading = loading_alloc();
+    if (!loading)
+    {
+        FURI_LOG_E(HTTP_TAG, "Failed to allocate loading");
+        view_dispatcher_switch_to_view(*view_dispatcher, failure_view_id);
+
+        return;
+    }
+
+    view_dispatcher_add_view(*view_dispatcher, loading_view_id, loading_get_view(loading));
+
+    // Switch to the loading view
+    view_dispatcher_switch_to_view(*view_dispatcher, loading_view_id);
+
+    // Make the request
+    if (!flipper_http_process_response_async(http_request, parse_response))
+    {
+        FURI_LOG_E(HTTP_TAG, "Failed to make request");
+        view_dispatcher_switch_to_view(*view_dispatcher, failure_view_id);
+        view_dispatcher_remove_view(*view_dispatcher, loading_view_id);
+        loading_free(loading);
+
+        return;
+    }
+
+    // Switch to the success view
+    view_dispatcher_switch_to_view(*view_dispatcher, success_view_id);
+    view_dispatcher_remove_view(*view_dispatcher, loading_view_id);
+    loading_free(loading);
+}

+ 19 - 0
flipper_http/flipper_http.h

@@ -2,6 +2,10 @@
 #ifndef FLIPPER_HTTP_H
 #define FLIPPER_HTTP_H
 
+#include <gui/gui.h>
+#include <gui/view.h>
+#include <gui/view_dispatcher.h>
+#include <gui/modules/loading.h>
 #include <furi.h>
 #include <furi_hal.h>
 #include <furi_hal_gpio.h>
@@ -363,4 +367,19 @@ char *trim(const char *str);
  */
 bool flipper_http_process_response_async(bool (*http_request)(void), bool (*parse_json)(void));
 
+/**
+ * @brief Perform a task while displaying a loading screen
+ * @param http_request The function to send the request
+ * @param parse_response The function to parse the response
+ * @param success_view_id The view ID to switch to on success
+ * @param failure_view_id The view ID to switch to on failure
+ * @param view_dispatcher The view dispatcher to use
+ * @return
+ */
+void flipper_http_loading_task(bool (*http_request)(void),
+                               bool (*parse_response)(void),
+                               uint32_t success_view_id,
+                               uint32_t failure_view_id,
+                               ViewDispatcher **view_dispatcher);
+
 #endif // FLIPPER_HTTP_H

+ 76 - 108
parse/flip_weather_parse.c

@@ -6,117 +6,88 @@ bool got_ip_address = false;
 bool geo_information_processed = false;
 bool weather_information_processed = false;
 
-bool flip_weather_parse_ip_address()
+bool send_geo_location_request()
 {
-    // load the received data from the saved file
-    FuriString *returned_data = flipper_http_load_from_file(fhttp.file_path);
-    if (returned_data == NULL)
-    {
-        FURI_LOG_E(TAG, "Failed to load received data from file.");
-        return false;
-    }
-    char *data_cstr = (char *)furi_string_get_cstr(returned_data);
-    if (data_cstr == NULL)
+    if (fhttp.state == INACTIVE)
     {
-        FURI_LOG_E(TAG, "Failed to get C-string from FuriString.");
-        furi_string_free(returned_data);
+        FURI_LOG_E(TAG, "Board is INACTIVE");
+        flipper_http_ping(); // ping the device
+        fhttp.state = ISSUE;
         return false;
     }
-    char *ip = get_json_value("origin", (char *)data_cstr, MAX_TOKENS);
-    if (ip == NULL)
+    if (!flipper_http_get_request_with_headers("https://ipwhois.app/json/", "{\"Content-Type\": \"application/json\"}"))
     {
-        FURI_LOG_E(TAG, "Failed to get IP address");
-        sent_get_request = true;
-        get_request_success = false;
+        FURI_LOG_E(TAG, "Failed to send GET request");
         fhttp.state = ISSUE;
-        furi_string_free(returned_data);
-        free(data_cstr);
         return false;
     }
-    snprintf(ip_address, 16, "%s", ip);
-    free(ip);
-    furi_string_free(returned_data);
-    free(data_cstr);
+    fhttp.state = RECEIVING;
     return true;
 }
 
-// handle the async-to-sync process to get and set the IP address
-bool flip_weather_handle_ip_address()
+bool send_geo_weather_request(DataLoaderModel *model)
 {
-    if (fhttp.state == INACTIVE)
+    UNUSED(model);
+    char url[512];
+    char *lattitude = lat_data + 10;
+    char *longitude = lon_data + 11;
+    snprintf(url, 512, "https://api.open-meteo.com/v1/forecast?latitude=%s&longitude=%s&current=temperature_2m,precipitation,rain,showers,snowfall&temperature_unit=celsius&wind_speed_unit=mph&precipitation_unit=inch&forecast_days=1", lattitude, longitude);
+    if (!flipper_http_get_request_with_headers(url, "{\"Content-Type\": \"application/json\"}"))
     {
-        FURI_LOG_E(TAG, "Board is INACTIVE");
-        flipper_http_ping(); // ping the device
+        FURI_LOG_E(TAG, "Failed to send GET request");
+        fhttp.state = ISSUE;
         return false;
     }
-    if (!got_ip_address)
-    {
-        got_ip_address = true;
-        snprintf(
-            fhttp.file_path,
-            sizeof(fhttp.file_path),
-            STORAGE_EXT_PATH_PREFIX "/apps_data/flip_weather/ip.txt");
-
-        fhttp.save_received_data = true;
-        if (!flipper_http_get_request("https://httpbin.org/get"))
-        {
-            FURI_LOG_E(TAG, "Failed to get IP address");
-            fhttp.state = ISSUE;
-            return false;
-        }
-        else
-        {
-            fhttp.state = RECEIVING;
-            furi_timer_start(fhttp.get_timeout_timer, TIMEOUT_DURATION_TICKS);
-        }
-        while (fhttp.state == RECEIVING && furi_timer_is_running(fhttp.get_timeout_timer) > 0)
-        {
-            // Wait for the feed to be received
-            furi_delay_ms(10);
-        }
-        furi_timer_stop(fhttp.get_timeout_timer);
-        if (!flip_weather_parse_ip_address())
-        {
-            FURI_LOG_E(TAG, "Failed to get IP address");
-            sent_get_request = true;
-            get_request_success = false;
-            fhttp.state = ISSUE;
-            return false;
-        }
-    }
+    fhttp.state = RECEIVING;
     return true;
 }
-
-bool send_geo_location_request()
+char *process_geo_location(DataLoaderModel *model)
 {
-    if (fhttp.state == INACTIVE)
+    UNUSED(model);
+    if (fhttp.last_response != NULL)
     {
-        FURI_LOG_E(TAG, "Board is INACTIVE");
-        flipper_http_ping(); // ping the device
-        return false;
-    }
-    if (!sent_get_request && fhttp.state == IDLE)
-    {
-        sent_get_request = true;
-        char *headers = jsmn("Content-Type", "application/json");
-        get_request_success = flipper_http_get_request_with_headers("https://ipwhois.app/json/", headers);
-        free(headers);
-        if (!get_request_success)
+        char *city = get_json_value("city", fhttp.last_response, MAX_TOKENS);
+        char *region = get_json_value("region", fhttp.last_response, MAX_TOKENS);
+        char *country = get_json_value("country", fhttp.last_response, MAX_TOKENS);
+        char *latitude = get_json_value("latitude", fhttp.last_response, MAX_TOKENS);
+        char *longitude = get_json_value("longitude", fhttp.last_response, MAX_TOKENS);
+
+        if (city == NULL || region == NULL || country == NULL || latitude == NULL || longitude == NULL)
         {
-            FURI_LOG_E(TAG, "Failed to send GET request");
+            FURI_LOG_E(TAG, "Failed to get geo location data");
             fhttp.state = ISSUE;
-            return false;
+            return NULL;
+        }
+
+        snprintf(lat_data, sizeof(lat_data), "Latitude: %s", latitude);
+        snprintf(lon_data, sizeof(lon_data), "Longitude: %s", longitude);
+
+        if (!total_data)
+        {
+            total_data = (char *)malloc(512);
+            if (!total_data)
+            {
+                FURI_LOG_E(TAG, "Failed to allocate memory for total_data");
+                fhttp.state = ISSUE;
+                return NULL;
+            }
         }
-        fhttp.state = RECEIVING;
+        snprintf(total_data, 512, "You are in %s, %s, %s. \nLatitude: %s, Longitude: %s", city, region, country, latitude, longitude);
+
+        fhttp.state = IDLE;
+        free(city);
+        free(region);
+        free(country);
+        free(latitude);
+        free(longitude);
     }
-    return true;
+    return total_data;
 }
 
-void process_geo_location()
+bool process_geo_location_2()
 {
-    if (!geo_information_processed && fhttp.last_response != NULL)
+    if (fhttp.last_response != NULL)
     {
-        geo_information_processed = true;
         char *city = get_json_value("city", fhttp.last_response, MAX_TOKENS);
         char *region = get_json_value("region", fhttp.last_response, MAX_TOKENS);
         char *country = get_json_value("country", fhttp.last_response, MAX_TOKENS);
@@ -127,15 +98,11 @@ void process_geo_location()
         {
             FURI_LOG_E(TAG, "Failed to get geo location data");
             fhttp.state = ISSUE;
-            return;
+            return false;
         }
 
-        snprintf(city_data, 64, "City: %s", city);
-        snprintf(region_data, 64, "Region: %s", region);
-        snprintf(country_data, 64, "Country: %s", country);
-        snprintf(lat_data, 64, "Latitude: %s", latitude);
-        snprintf(lon_data, 64, "Longitude: %s", longitude);
-        snprintf(ip_data, 64, "IP Address: %s", ip_address);
+        snprintf(lat_data, sizeof(lat_data), "Latitude: %s", latitude);
+        snprintf(lon_data, sizeof(lon_data), "Longitude: %s", longitude);
 
         fhttp.state = IDLE;
         free(city);
@@ -143,14 +110,16 @@ void process_geo_location()
         free(country);
         free(latitude);
         free(longitude);
+        return true;
     }
+    return false;
 }
 
-void process_weather()
+char *process_weather(DataLoaderModel *model)
 {
-    if (!weather_information_processed && fhttp.last_response != NULL)
+    UNUSED(model);
+    if (fhttp.last_response != NULL)
     {
-        weather_information_processed = true;
         char *current_data = get_json_value("current", fhttp.last_response, MAX_TOKENS);
         char *temperature = get_json_value("temperature_2m", current_data, MAX_TOKENS);
         char *precipitation = get_json_value("precipitation", current_data, MAX_TOKENS);
@@ -163,7 +132,7 @@ void process_weather()
         {
             FURI_LOG_E(TAG, "Failed to get weather data");
             fhttp.state = ISSUE;
-            return;
+            return NULL;
         }
 
         // replace the "T" in time with a space
@@ -173,12 +142,17 @@ void process_weather()
             *ptr = ' ';
         }
 
-        snprintf(temperature_data, 64, "Temperature (C): %s", temperature);
-        snprintf(precipitation_data, 64, "Precipitation: %s", precipitation);
-        snprintf(rain_data, 64, "Rain: %s", rain);
-        snprintf(showers_data, 64, "Showers: %s", showers);
-        snprintf(snowfall_data, 64, "Snowfall: %s", snowfall);
-        snprintf(time_data, 64, "Time: %s", time);
+        if (!weather_data)
+        {
+            weather_data = (char *)malloc(512);
+            if (!weather_data)
+            {
+                FURI_LOG_E(TAG, "Failed to allocate memory for weather_data");
+                fhttp.state = ISSUE;
+                return NULL;
+            }
+        }
+        snprintf(weather_data, 512, "Temperature: %s C\nPrecipitation: %s\nRain: %s\nShowers: %s\nSnowfall: %s\nTime: %s", temperature, precipitation, rain, showers, snowfall, time);
 
         fhttp.state = IDLE;
         free(current_data);
@@ -189,11 +163,5 @@ void process_weather()
         free(snowfall);
         free(time);
     }
-    else if (!weather_information_processed && fhttp.last_response == NULL)
-    {
-        FURI_LOG_E(TAG, "Failed to process weather data");
-        // store error message
-        snprintf(temperature_data, 64, "Failed. Update WiFi settings.");
-        fhttp.state = ISSUE;
-    }
+    return weather_data;
 }

+ 39 - 4
parse/flip_weather_parse.h

@@ -7,10 +7,45 @@ extern bool got_ip_address;
 extern bool geo_information_processed;
 extern bool weather_information_processed;
 
-bool flip_weather_parse_ip_address();
-bool flip_weather_handle_ip_address();
+// Add edits by Derek Jamison
+typedef enum DataState DataState;
+enum DataState
+{
+    DataStateInitial,
+    DataStateRequested,
+    DataStateReceived,
+    DataStateParsed,
+    DataStateParseError,
+    DataStateError,
+};
+
+typedef enum FlipWeatherCustomEvent FlipWeatherCustomEvent;
+enum FlipWeatherCustomEvent
+{
+    FlipWeatherCustomEventProcess,
+};
+
+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;
+};
+
 bool send_geo_location_request();
-void process_geo_location();
-void process_weather();
+char *process_geo_location(DataLoaderModel *model);
+bool process_geo_location_2();
+char *process_weather(DataLoaderModel *model);
+bool send_geo_weather_request(DataLoaderModel *model);
 
 #endif