Przeglądaj źródła

FlipTrader - v1.2

jblanked 1 rok temu
rodzic
commit
fa1c0b5510

+ 5 - 0
CHANGELOG.md

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

+ 9 - 4
alloc/flip_trader_alloc.c

@@ -45,15 +45,20 @@ FlipTraderApp *flip_trader_app_alloc()
     {
     {
         return NULL;
         return NULL;
     }
     }
-
+    view_dispatcher_set_custom_event_callback(app->view_dispatcher, flip_trader_custom_event_callback);
     // Main view
     // Main view
-    if (!easy_flipper_set_view(&app->view_main, FlipTraderViewMain, flip_trader_view_draw_callback, NULL, callback_to_assets_submenu, &app->view_dispatcher, app))
+    if (!easy_flipper_set_view(&app->view_loader, FlipTraderViewLoader, flip_trader_loader_draw_callback, NULL, callback_to_assets_submenu, &app->view_dispatcher, app))
     {
     {
         return NULL;
         return NULL;
     }
     }
+    flip_trader_loader_init(app->view_loader);
 
 
     // Widget
     // Widget
-    if (!easy_flipper_set_widget(&app->widget, FlipTraderViewAbout, "FlipTrader v1.1\n-----\nUse WiFi to get the price of\nstocks and currency pairs.\n-----\nwww.github.com/jblanked", callback_to_submenu, &app->view_dispatcher))
+    if (!easy_flipper_set_widget(&app->widget_about, FlipTraderViewAbout, "FlipTrader v1.2\n-----\nUse WiFi to get the price of\nstocks and currency pairs.\n-----\nwww.github.com/jblanked", callback_to_submenu, &app->view_dispatcher))
+    {
+        return NULL;
+    }
+    if (!easy_flipper_set_widget(&app->widget_result, FlipTraderViewWidgetResult, "Error, try again.", callback_to_assets_submenu, &app->view_dispatcher))
     {
     {
         return NULL;
         return NULL;
     }
     }
@@ -79,7 +84,7 @@ FlipTraderApp *flip_trader_app_alloc()
     variable_item_set_current_value_text(app->variable_item_password, "");
     variable_item_set_current_value_text(app->variable_item_password, "");
 
 
     // Submenu
     // Submenu
-    if (!easy_flipper_set_submenu(&app->submenu_main, FlipTraderViewMainSubmenu, "FlipTrader v1.1", easy_flipper_callback_exit_app, &app->view_dispatcher))
+    if (!easy_flipper_set_submenu(&app->submenu_main, FlipTraderViewMainSubmenu, "FlipTrader v1.2", easy_flipper_callback_exit_app, &app->view_dispatcher))
     {
     {
         return NULL;
         return NULL;
     }
     }

+ 34 - 4
app.c

@@ -8,8 +8,8 @@ int32_t flip_trader_app(void *p)
     UNUSED(p);
     UNUSED(p);
 
 
     // Initialize the FlipTrader application
     // Initialize the FlipTrader application
-    FlipTraderApp *app = flip_trader_app_alloc();
-    if (!app)
+    app_instance = flip_trader_app_alloc();
+    if (!app_instance)
     {
     {
         FURI_LOG_E(TAG, "Failed to allocate FlipTraderApp");
         FURI_LOG_E(TAG, "Failed to allocate FlipTraderApp");
         return -1;
         return -1;
@@ -21,11 +21,41 @@ int32_t flip_trader_app(void *p)
         return -1;
         return -1;
     }
     }
 
 
+    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
     // Run the view dispatcher
-    view_dispatcher_run(app->view_dispatcher);
+    view_dispatcher_run(app_instance->view_dispatcher);
 
 
     // Free the resources used by the FlipTrader application
     // Free the resources used by the FlipTrader application
-    flip_trader_app_free(app);
+    flip_trader_app_free(app_instance);
 
 
     // Return 0 to indicate success
     // Return 0 to indicate success
     return 0;
     return 0;

+ 1 - 1
application.fam

@@ -10,5 +10,5 @@ App(
     fap_description="Use WiFi to get the price of stocks and currency pairs on your Flipper Zero.",
     fap_description="Use WiFi to get the price of stocks and currency pairs on your Flipper Zero.",
     fap_author="JBlanked",
     fap_author="JBlanked",
     fap_weburl="https://github.com/jblanked/FlipTrader",
     fap_weburl="https://github.com/jblanked/FlipTrader",
-    fap_version = "1.1",
+    fap_version = "1.2",
 )
 )

BIN
assets/01-main.png


+ 461 - 70
callback/flip_trader_callback.c

@@ -1,19 +1,36 @@
 #include <callback/flip_trader_callback.h>
 #include <callback/flip_trader_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
+
 // hold the price of the asset
 // hold the price of the asset
 char asset_price[64];
 char asset_price[64];
 bool sent_get_request = false;
 bool sent_get_request = false;
 bool get_request_success = false;
 bool get_request_success = false;
 bool request_processed = false;
 bool request_processed = false;
 
 
-void flip_trader_request_error(Canvas *canvas)
+static void flip_trader_request_error_draw(Canvas *canvas)
 {
 {
+    if (canvas == NULL)
+    {
+        FURI_LOG_E(TAG, "flip_trader_request_error_draw - canvas is NULL");
+        DEV_CRASH();
+        return;
+    }
     if (fhttp.last_response != NULL)
     if (fhttp.last_response != NULL)
     {
     {
         if (strstr(fhttp.last_response, "[ERROR] Not connected to Wifi. Failed to reconnect.") != NULL)
         if (strstr(fhttp.last_response, "[ERROR] Not connected to Wifi. Failed to reconnect.") != NULL)
         {
         {
             canvas_clear(canvas);
             canvas_clear(canvas);
             canvas_draw_str(canvas, 0, 10, "[ERROR] Not connected to Wifi.");
             canvas_draw_str(canvas, 0, 10, "[ERROR] Not connected to Wifi.");
+            canvas_draw_str(canvas, 0, 22, "Failed to reconnect.");
             canvas_draw_str(canvas, 0, 50, "Update your WiFi settings.");
             canvas_draw_str(canvas, 0, 50, "Update your WiFi settings.");
             canvas_draw_str(canvas, 0, 60, "Press BACK to return.");
             canvas_draw_str(canvas, 0, 60, "Press BACK to return.");
         }
         }
@@ -24,12 +41,10 @@ void flip_trader_request_error(Canvas *canvas)
             canvas_draw_str(canvas, 0, 50, "Update your WiFi settings.");
             canvas_draw_str(canvas, 0, 50, "Update your WiFi settings.");
             canvas_draw_str(canvas, 0, 60, "Press BACK to return.");
             canvas_draw_str(canvas, 0, 60, "Press BACK to return.");
         }
         }
-        else if (strstr(fhttp.last_response, "[ERROR] WiFi SSID or Password is empty") != NULL)
+        else if (strstr(fhttp.last_response, "[PONG]") != NULL)
         {
         {
             canvas_clear(canvas);
             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.");
+            canvas_draw_str(canvas, 0, 10, "[STATUS]Connecting to AP...");
         }
         }
         else
         else
         {
         {
@@ -42,13 +57,13 @@ void flip_trader_request_error(Canvas *canvas)
     else
     else
     {
     {
         canvas_clear(canvas);
         canvas_clear(canvas);
-        canvas_draw_str(canvas, 0, 10, "[ERROR] Unknown error.");
+        canvas_draw_str(canvas, 0, 10, "Failed to receive data.");
         canvas_draw_str(canvas, 0, 50, "Update your WiFi settings.");
         canvas_draw_str(canvas, 0, 50, "Update your WiFi settings.");
         canvas_draw_str(canvas, 0, 60, "Press BACK to return.");
         canvas_draw_str(canvas, 0, 60, "Press BACK to return.");
     }
     }
 }
 }
 
 
-bool send_price_request()
+static bool send_price_request()
 {
 {
     if (fhttp.state == INACTIVE)
     if (fhttp.state == INACTIVE)
     {
     {
@@ -79,7 +94,7 @@ bool send_price_request()
     return true;
     return true;
 }
 }
 
 
-void process_asset_price()
+static char *process_asset_price()
 {
 {
     if (!request_processed)
     if (!request_processed)
     {
     {
@@ -89,7 +104,7 @@ void process_asset_price()
         {
         {
             FURI_LOG_E(TAG, "Failed to load received data from file.");
             FURI_LOG_E(TAG, "Failed to load received data from file.");
             fhttp.state = ISSUE;
             fhttp.state = ISSUE;
-            return;
+            return NULL;
         }
         }
         char *data_cstr = (char *)furi_string_get_cstr(price_data);
         char *data_cstr = (char *)furi_string_get_cstr(price_data);
         if (data_cstr == NULL)
         if (data_cstr == NULL)
@@ -97,7 +112,7 @@ void process_asset_price()
             FURI_LOG_E(TAG, "Failed to get C-string from FuriString.");
             FURI_LOG_E(TAG, "Failed to get C-string from FuriString.");
             furi_string_free(price_data);
             furi_string_free(price_data);
             fhttp.state = ISSUE;
             fhttp.state = ISSUE;
-            return;
+            return NULL;
         }
         }
         request_processed = true;
         request_processed = true;
         char *global_quote = get_json_value("Global Quote", data_cstr, MAX_TOKENS);
         char *global_quote = get_json_value("Global Quote", data_cstr, MAX_TOKENS);
@@ -108,7 +123,7 @@ void process_asset_price()
             furi_string_free(price_data);
             furi_string_free(price_data);
             free(global_quote);
             free(global_quote);
             free(data_cstr);
             free(data_cstr);
-            return;
+            return NULL;
         }
         }
         char *price = get_json_value("05. price", global_quote, MAX_TOKENS);
         char *price = get_json_value("05. price", global_quote, MAX_TOKENS);
         if (price == NULL)
         if (price == NULL)
@@ -119,7 +134,7 @@ void process_asset_price()
             free(global_quote);
             free(global_quote);
             free(price);
             free(price);
             free(data_cstr);
             free(data_cstr);
-            return;
+            return NULL;
         }
         }
         // store the price "Asset: $price"
         // store the price "Asset: $price"
         snprintf(asset_price, 64, "%s: $%s", asset_names[asset_index], price);
         snprintf(asset_price, 64, "%s: $%s", asset_names[asset_index], price);
@@ -130,65 +145,12 @@ void process_asset_price()
         free(price);
         free(price);
         free(data_cstr);
         free(data_cstr);
     }
     }
+    return asset_price;
 }
 }
 
 
-// Callback for drawing the main screen
-void flip_trader_view_draw_callback(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...");
-    // canvas_draw_str(canvas, 0, 10, asset_names[asset_index]);
-
-    // start the process
-    if (!send_price_request())
-    {
-        flip_trader_request_error(canvas);
-    }
-    // wait until the request is processed
-    if (!sent_get_request || !get_request_success || fhttp.state == RECEIVING)
-    {
-        return;
-    }
-    // check status
-    if (fhttp.state == ISSUE || fhttp.last_response == NULL)
-    {
-        flip_trader_request_error(canvas);
-    }
-    // success, process the data
-    process_asset_price();
-    canvas_clear(canvas);
-    canvas_draw_str(canvas, 0, 10, asset_price);
-}
-
-// Input callback for the view (async input handling)
-bool flip_trader_view_input_callback(InputEvent *event, void *context)
+static void flip_trader_asset_switch_to_view(FlipTraderApp *app)
 {
 {
-    FlipTraderApp *app = (FlipTraderApp *)context;
-    if (event->type == InputTypePress && event->key == InputKeyBack)
-    {
-        // Exit the app when the back button is pressed
-        view_dispatcher_stop(app->view_dispatcher);
-        return true;
-    }
-    return false;
+    flip_trader_generic_switch_to_view(app, "Fetching..", send_price_request, process_asset_price, 1, callback_to_assets_submenu, FlipTraderViewLoader);
 }
 }
 
 
 void callback_submenu_choices(void *context, uint32_t index)
 void callback_submenu_choices(void *context, uint32_t index)
@@ -218,7 +180,7 @@ void callback_submenu_choices(void *context, uint32_t index)
         if (index >= FlipTraderSubmenuIndexAssetStartIndex)
         if (index >= FlipTraderSubmenuIndexAssetStartIndex)
         {
         {
             asset_index = index - FlipTraderSubmenuIndexAssetStartIndex;
             asset_index = index - FlipTraderSubmenuIndexAssetStartIndex;
-            view_dispatcher_switch_to_view(app->view_dispatcher, FlipTraderViewMain);
+            flip_trader_asset_switch_to_view(app);
         }
         }
         else
         else
         {
         {
@@ -363,4 +325,433 @@ void settings_item_selected(void *context, uint32_t index)
         FURI_LOG_E(TAG, "Unknown configuration item index");
         FURI_LOG_E(TAG, "Unknown configuration item index");
         break;
         break;
     }
     }
-}
+}
+
+static void flip_trader_widget_set_text(char *message, Widget **widget)
+{
+    if (widget == NULL)
+    {
+        FURI_LOG_E(TAG, "flip_trader_set_widget_text - widget is NULL");
+        DEV_CRASH();
+        return;
+    }
+    if (message == NULL)
+    {
+        FURI_LOG_E(TAG, "flip_trader_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
+    if (!easy_flipper_set_buffer(&formatted_message, message_length * 2 + 1))
+    {
+        return;
+    }
+
+    while (i < message_length)
+    {
+        // TODO: Use canvas_glyph_width to calculate the maximum characters for the line
+        uint32_t max_line_length = 29;                  // 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;
+
+        // Temporary buffer to hold the current line
+        char line[30];
+        strncpy(line, message + i, line_length);
+        line[line_length] = '\0';
+
+        // Check if the line ends in the middle of a word and adjust accordingly
+        if (line_length == 29 && message[i + line_length] != '\0' && message[i + line_length] != ' ')
+        {
+            // Find the last space within the 30-character segment
+            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
+            }
+        }
+
+        // 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';
+
+        // Move i forward to the start of the next word
+        i += line_length;
+
+        // Skip spaces at the beginning of the next line
+        while (message[i] == ' ')
+        {
+            i++;
+        }
+    }
+
+    // Add the formatted message to the widget
+    widget_add_text_scroll_element(*widget, 0, 0, 128, 64, formatted_message);
+}
+
+void flip_trader_loader_draw_callback(Canvas *canvas, void *model)
+{
+    if (!canvas || !model)
+    {
+        FURI_LOG_E(TAG, "flip_trader_loader_draw_callback - canvas or model is NULL");
+        return;
+    }
+
+    SerialState http_state = fhttp.state;
+    AssetLoaderModel *asset_loader_model = (AssetLoaderModel *)model;
+    AssetState asset_state = asset_loader_model->asset_state;
+    char *title = asset_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 (asset_state == AssetStateError || asset_state == AssetStateParseError)
+    {
+        flip_trader_request_error_draw(canvas);
+        return;
+    }
+
+    canvas_draw_str(canvas, 0, 7, title);
+    canvas_draw_str(canvas, 0, 17, "Loading...");
+
+    if (asset_state == AssetStateInitial)
+    {
+        return;
+    }
+
+    if (http_state == SENDING)
+    {
+        canvas_draw_str(canvas, 0, 27, "Sending...");
+        return;
+    }
+
+    if (http_state == RECEIVING || asset_state == AssetStateRequested)
+    {
+        canvas_draw_str(canvas, 0, 27, "Receiving...");
+        return;
+    }
+
+    if (http_state == IDLE && asset_state == AssetStateReceived)
+    {
+        canvas_draw_str(canvas, 0, 27, "Processing...");
+        return;
+    }
+
+    if (http_state == IDLE && asset_state == AssetStateParsed)
+    {
+        canvas_draw_str(canvas, 0, 27, "Processed...");
+        return;
+    }
+}
+
+static void flip_trader_loader_process_callback(void *context)
+{
+    if (context == NULL)
+    {
+        FURI_LOG_E(TAG, "flip_trader_loader_process_callback - context is NULL");
+        DEV_CRASH();
+        return;
+    }
+
+    FlipTraderApp *app = (FlipTraderApp *)context;
+    View *view = app->view_loader;
+
+    AssetState current_asset_state;
+    with_view_model(view, AssetLoaderModel * model, { current_asset_state = model->asset_state; }, false);
+
+    if (current_asset_state == AssetStateInitial)
+    {
+        with_view_model(
+            view,
+            AssetLoaderModel * model,
+            {
+                model->asset_state = AssetStateRequested;
+                AssetLoaderFetch fetch = model->fetcher;
+                if (fetch == NULL)
+                {
+                    FURI_LOG_E(TAG, "Model doesn't have Fetch function assigned.");
+                    model->asset_state = AssetStateError;
+                    return;
+                }
+
+                // Clear any previous responses
+                strncpy(fhttp.last_response, "", 1);
+                bool request_status = fetch(model);
+                if (!request_status)
+                {
+                    model->asset_state = AssetStateError;
+                }
+            },
+            true);
+    }
+    else if (current_asset_state == AssetStateRequested || current_asset_state == AssetStateError)
+    {
+        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, AssetLoaderModel * model, { model->asset_state = AssetStateReceived; }, 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, AssetLoaderModel * model, { model->asset_state = AssetStateError; }, 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_asset_state == AssetStateReceived)
+    {
+        with_view_model(
+            view,
+            AssetLoaderModel * model,
+            {
+                char *asset_text;
+                if (model->parser == NULL)
+                {
+                    asset_text = NULL;
+                    FURI_LOG_DEV(TAG, "Parser is NULL");
+                    DEV_CRASH();
+                }
+                else
+                {
+                    asset_text = model->parser(model);
+                }
+                FURI_LOG_DEV(TAG, "Parsed asset: %s\r\ntext: %s", fhttp.last_response ? fhttp.last_response : "NULL", asset_text ? asset_text : "NULL");
+                model->asset_text = asset_text;
+                if (asset_text == NULL)
+                {
+                    model->asset_state = AssetStateParseError;
+                }
+                else
+                {
+                    model->asset_state = AssetStateParsed;
+                }
+            },
+            true);
+    }
+    else if (current_asset_state == AssetStateParsed)
+    {
+        with_view_model(
+            view,
+            AssetLoaderModel * model,
+            {
+                if (++model->request_index < model->request_count)
+                {
+                    model->asset_state = AssetStateInitial;
+                }
+                else
+                {
+                    flip_trader_widget_set_text(model->asset_text != NULL ? model->asset_text : "Empty result", &app_instance->widget_result);
+                    if (model->asset_text != NULL)
+                    {
+                        free(model->asset_text);
+                        model->asset_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, FlipTraderViewWidgetResult);
+                }
+            },
+            true);
+    }
+}
+
+static void flip_trader_loader_timer_callback(void *context)
+{
+    if (context == NULL)
+    {
+        FURI_LOG_E(TAG, "flip_trader_loader_timer_callback - context is NULL");
+        DEV_CRASH();
+        return;
+    }
+    FlipTraderApp *app = (FlipTraderApp *)context;
+    view_dispatcher_send_custom_event(app->view_dispatcher, FlipTraderCustomEventProcess);
+}
+
+static void flip_trader_loader_on_enter(void *context)
+{
+    if (context == NULL)
+    {
+        FURI_LOG_E(TAG, "flip_trader_loader_on_enter - context is NULL");
+        DEV_CRASH();
+        return;
+    }
+    FlipTraderApp *app = (FlipTraderApp *)context;
+    View *view = app->view_loader;
+    with_view_model(
+        view,
+        AssetLoaderModel * model,
+        {
+            view_set_previous_callback(view, model->back_callback);
+            if (model->timer == NULL)
+            {
+                model->timer = furi_timer_alloc(flip_trader_loader_timer_callback, FuriTimerTypePeriodic, app);
+            }
+            furi_timer_start(model->timer, 250);
+        },
+        true);
+}
+
+static void flip_trader_loader_on_exit(void *context)
+{
+    if (context == NULL)
+    {
+        FURI_LOG_E(TAG, "flip_trader_loader_on_exit - context is NULL");
+        DEV_CRASH();
+        return;
+    }
+    FlipTraderApp *app = (FlipTraderApp *)context;
+    View *view = app->view_loader;
+    with_view_model(
+        view,
+        AssetLoaderModel * model,
+        {
+            if (model->timer)
+            {
+                furi_timer_stop(model->timer);
+            }
+        },
+        false);
+}
+
+void flip_trader_loader_init(View *view)
+{
+    if (view == NULL)
+    {
+        FURI_LOG_E(TAG, "flip_trader_loader_init - view is NULL");
+        DEV_CRASH();
+        return;
+    }
+    view_allocate_model(view, ViewModelTypeLocking, sizeof(AssetLoaderModel));
+    view_set_enter_callback(view, flip_trader_loader_on_enter);
+    view_set_exit_callback(view, flip_trader_loader_on_exit);
+}
+
+void flip_trader_loader_free_model(View *view)
+{
+    if (view == NULL)
+    {
+        FURI_LOG_E(TAG, "flip_trader_loader_free_model - view is NULL");
+        DEV_CRASH();
+        return;
+    }
+    with_view_model(
+        view,
+        AssetLoaderModel * 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_trader_custom_event_callback(void *context, uint32_t index)
+{
+    if (context == NULL)
+    {
+        FURI_LOG_E(TAG, "flip_trader_custom_event_callback - context is NULL");
+        DEV_CRASH();
+        return false;
+    }
+
+    switch (index)
+    {
+    case FlipTraderCustomEventProcess:
+        flip_trader_loader_process_callback(context);
+        return true;
+    default:
+        FURI_LOG_DEV(TAG, "flip_trader_custom_event_callback. Unknown index: %ld", index);
+        return false;
+    }
+}
+
+void flip_trader_generic_switch_to_view(FlipTraderApp *app, char *title, AssetLoaderFetch fetcher, AssetLoaderParser parser, size_t request_count, ViewNavigationCallback back, uint32_t view_id)
+{
+    if (app == NULL)
+    {
+        FURI_LOG_E(TAG, "flip_trader_generic_switch_to_view - app is NULL");
+        DEV_CRASH();
+        return;
+    }
+
+    View *view = app->view_loader;
+    if (view == NULL)
+    {
+        FURI_LOG_E(TAG, "flip_trader_generic_switch_to_view - view is NULL");
+        DEV_CRASH();
+        return;
+    }
+
+    with_view_model(
+        view,
+        AssetLoaderModel * model,
+        {
+            model->title = title;
+            model->fetcher = fetcher;
+            model->parser = parser;
+            model->request_index = 0;
+            model->request_count = request_count;
+            model->back_callback = back;
+            model->asset_state = AssetStateInitial;
+            model->asset_text = NULL;
+        },
+        true);
+
+    view_dispatcher_switch_to_view(app->view_dispatcher, view_id);
+}

+ 45 - 8
callback/flip_trader_callback.h

@@ -11,14 +11,6 @@ extern bool sent_get_request;
 extern bool get_request_success;
 extern bool get_request_success;
 extern bool request_processed;
 extern bool request_processed;
 
 
-void flip_trader_request_error(Canvas *canvas);
-bool send_price_request();
-void process_asset_price();
-
-// Callback for drawing the main screen
-void flip_trader_view_draw_callback(Canvas *canvas, void *model);
-// Input callback for the view (async input handling)
-bool flip_trader_view_input_callback(InputEvent *event, void *context);
 void callback_submenu_choices(void *context, uint32_t index);
 void callback_submenu_choices(void *context, uint32_t index);
 void text_updated_ssid(void *context);
 void text_updated_ssid(void *context);
 
 
@@ -29,4 +21,49 @@ uint32_t callback_to_submenu(void *context);
 uint32_t callback_to_wifi_settings(void *context);
 uint32_t callback_to_wifi_settings(void *context);
 uint32_t callback_to_assets_submenu(void *context);
 uint32_t callback_to_assets_submenu(void *context);
 void settings_item_selected(void *context, uint32_t index);
 void settings_item_selected(void *context, uint32_t index);
+
+// Add edits by Derek Jamison
+typedef enum AssetState AssetState;
+enum AssetState
+{
+    AssetStateInitial,
+    AssetStateRequested,
+    AssetStateReceived,
+    AssetStateParsed,
+    AssetStateParseError,
+    AssetStateError,
+};
+
+typedef enum FlipTraderCustomEvent FlipTraderCustomEvent;
+enum FlipTraderCustomEvent
+{
+    FlipTraderCustomEventProcess,
+};
+
+typedef struct AssetLoaderModel AssetLoaderModel;
+typedef bool (*AssetLoaderFetch)(AssetLoaderModel *model);
+typedef char *(*AssetLoaderParser)(AssetLoaderModel *model);
+struct AssetLoaderModel
+{
+    char *title;
+    char *asset_text;
+    AssetState asset_state;
+    AssetLoaderFetch fetcher;
+    AssetLoaderParser parser;
+    void *parser_context;
+    size_t request_index;
+    size_t request_count;
+    ViewNavigationCallback back_callback;
+    FuriTimer *timer;
+};
+
+void flip_trader_generic_switch_to_view(FlipTraderApp *app, char *title, AssetLoaderFetch fetcher, AssetLoaderParser parser, size_t request_count, ViewNavigationCallback back, uint32_t view_id);
+
+void flip_trader_loader_draw_callback(Canvas *canvas, void *model);
+
+void flip_trader_loader_init(View *view);
+
+void flip_trader_loader_free_model(View *view);
+
+bool flip_trader_custom_event_callback(void *context, uint32_t index);
 #endif // FLIP_TRADER_CALLBACK_H
 #endif // FLIP_TRADER_CALLBACK_H

+ 13 - 6
flip_trader.c

@@ -1,4 +1,5 @@
 #include <flip_trader.h>
 #include <flip_trader.h>
+void flip_trader_loader_free_model(View *view);
 
 
 void asset_names_free(char **names)
 void asset_names_free(char **names)
 {
 {
@@ -72,7 +73,7 @@ char **asset_names_alloc()
 char **asset_names = NULL;
 char **asset_names = NULL;
 // index
 // index
 uint32_t asset_index = 0;
 uint32_t asset_index = 0;
-
+FlipTraderApp *app_instance = NULL;
 // Function to free the resources used by FlipTraderApp
 // Function to free the resources used by FlipTraderApp
 void flip_trader_app_free(FlipTraderApp *app)
 void flip_trader_app_free(FlipTraderApp *app)
 {
 {
@@ -83,10 +84,11 @@ void flip_trader_app_free(FlipTraderApp *app)
     }
     }
 
 
     // Free View(s)
     // Free View(s)
-    if (app->view_main)
+    if (app->view_loader)
     {
     {
-        view_dispatcher_remove_view(app->view_dispatcher, FlipTraderViewMain);
-        view_free(app->view_main);
+        view_dispatcher_remove_view(app->view_dispatcher, FlipTraderViewLoader);
+        flip_trader_loader_free_model(app->view_loader);
+        view_free(app->view_loader);
     }
     }
 
 
     // Free Submenu(s)
     // Free Submenu(s)
@@ -102,10 +104,15 @@ void flip_trader_app_free(FlipTraderApp *app)
     }
     }
 
 
     // Free Widget(s)
     // Free Widget(s)
-    if (app->widget)
+    if (app->widget_about)
     {
     {
         view_dispatcher_remove_view(app->view_dispatcher, FlipTraderViewAbout);
         view_dispatcher_remove_view(app->view_dispatcher, FlipTraderViewAbout);
-        widget_free(app->widget);
+        widget_free(app->widget_about);
+    }
+    if (app->widget_result)
+    {
+        view_dispatcher_remove_view(app->view_dispatcher, FlipTraderViewWidgetResult);
+        widget_free(app->widget_result);
     }
     }
 
 
     // Free Variable Item List(s)
     // Free Variable Item List(s)

+ 6 - 3
flip_trader.h

@@ -37,16 +37,19 @@ typedef enum
     FlipTraderViewTextInputPassword, // The text input screen for the password
     FlipTraderViewTextInputPassword, // The text input screen for the password
     //
     //
     FlipTraderViewAssetsSubmenu, // The submenu for the assets
     FlipTraderViewAssetsSubmenu, // The submenu for the assets
+    FlipTraderViewWidgetResult,  // The text box that displays the random fact
+    FlipTraderViewLoader,        // The loader screen retrieves data from the internet
 } FlipTraderView;
 } FlipTraderView;
 
 
 // Each screen will have its own view
 // Each screen will have its own view
 typedef struct
 typedef struct
 {
 {
     ViewDispatcher *view_dispatcher;           // Switches between our views
     ViewDispatcher *view_dispatcher;           // Switches between our views
-    View *view_main;                           // The main screen that displays "Hello, World!"
+    View *view_loader;                         // The screen that loads data from internet
     Submenu *submenu_main;                     // The submenu
     Submenu *submenu_main;                     // The submenu
     Submenu *submenu_assets;                   // The submenu for the assets
     Submenu *submenu_assets;                   // The submenu for the assets
-    Widget *widget;                            // The widget
+    Widget *widget_about;                      // The widget
+    Widget *widget_result;                     // The widget that displays the result
     VariableItemList *variable_item_list_wifi; // The variable item list (settngs)
     VariableItemList *variable_item_list_wifi; // The variable item list (settngs)
     VariableItem *variable_item_ssid;          // The variable item for the SSID
     VariableItem *variable_item_ssid;          // The variable item for the SSID
     VariableItem *variable_item_password;      // The variable item for the password
     VariableItem *variable_item_password;      // The variable item for the password
@@ -73,5 +76,5 @@ extern uint32_t asset_index;
 void flip_trader_app_free(FlipTraderApp *app);
 void flip_trader_app_free(FlipTraderApp *app);
 char **asset_names_alloc();
 char **asset_names_alloc();
 void asset_names_free(char **names);
 void asset_names_free(char **names);
-
+extern FlipTraderApp *app_instance;
 #endif // FLIP_TRADE_E_H
 #endif // FLIP_TRADE_E_H