flip_trader_callback.c 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759
  1. #include <callback/flip_trader_callback.h>
  2. // Below added by Derek Jamison
  3. // FURI_LOG_DEV will log only during app development. Be sure that Settings/System/Log Device is "LPUART"; so we dont use serial port.
  4. #ifdef DEVELOPMENT
  5. #define FURI_LOG_DEV(tag, format, ...) furi_log_print_format(FuriLogLevelInfo, tag, format, ##__VA_ARGS__)
  6. #define DEV_CRASH() furi_crash()
  7. #else
  8. #define FURI_LOG_DEV(tag, format, ...)
  9. #define DEV_CRASH()
  10. #endif
  11. // hold the price of the asset
  12. char asset_price[64];
  13. bool sent_get_request = false;
  14. bool get_request_success = false;
  15. bool request_processed = false;
  16. static void flip_trader_request_error_draw(Canvas *canvas)
  17. {
  18. if (canvas == NULL)
  19. {
  20. FURI_LOG_E(TAG, "flip_trader_request_error_draw - canvas is NULL");
  21. DEV_CRASH();
  22. return;
  23. }
  24. if (fhttp.last_response != NULL)
  25. {
  26. if (strstr(fhttp.last_response, "[ERROR] Not connected to Wifi. Failed to reconnect.") != NULL)
  27. {
  28. canvas_clear(canvas);
  29. canvas_draw_str(canvas, 0, 10, "[ERROR] Not connected to Wifi.");
  30. canvas_draw_str(canvas, 0, 22, "Failed to reconnect.");
  31. canvas_draw_str(canvas, 0, 50, "Update your WiFi settings.");
  32. canvas_draw_str(canvas, 0, 60, "Press BACK to return.");
  33. }
  34. else if (strstr(fhttp.last_response, "[ERROR] Failed to connect to Wifi.") != NULL)
  35. {
  36. canvas_clear(canvas);
  37. canvas_draw_str(canvas, 0, 10, "[ERROR] Not connected to Wifi.");
  38. canvas_draw_str(canvas, 0, 50, "Update your WiFi settings.");
  39. canvas_draw_str(canvas, 0, 60, "Press BACK to return.");
  40. }
  41. else if (strstr(fhttp.last_response, "[PONG]") != NULL)
  42. {
  43. canvas_clear(canvas);
  44. canvas_draw_str(canvas, 0, 10, "[STATUS]Connecting to AP...");
  45. }
  46. else
  47. {
  48. canvas_clear(canvas);
  49. FURI_LOG_E(TAG, "Received an error: %s", fhttp.last_response);
  50. canvas_draw_str(canvas, 0, 10, "[ERROR] Unusual error...");
  51. canvas_draw_str(canvas, 0, 60, "Press BACK and retry.");
  52. }
  53. }
  54. else
  55. {
  56. canvas_clear(canvas);
  57. canvas_draw_str(canvas, 0, 10, "Failed to receive data.");
  58. canvas_draw_str(canvas, 0, 50, "Update your WiFi settings.");
  59. canvas_draw_str(canvas, 0, 60, "Press BACK to return.");
  60. }
  61. }
  62. static bool send_price_request(AssetLoaderModel* model)
  63. {
  64. UNUSED(model);
  65. if (fhttp.state == INACTIVE)
  66. {
  67. return false;
  68. }
  69. if (!sent_get_request && fhttp.state == IDLE)
  70. {
  71. sent_get_request = true;
  72. char url[128];
  73. char *headers = jsmn("Content-Type", "application/json");
  74. snprintf(
  75. fhttp.file_path,
  76. sizeof(fhttp.file_path),
  77. STORAGE_EXT_PATH_PREFIX "/apps_data/flip_trader/price.txt");
  78. fhttp.save_received_data = true;
  79. snprintf(url, 128, "https://www.alphavantage.co/query?function=GLOBAL_QUOTE&symbol=%s&apikey=2X90WLEFMP43OJKE", asset_names[asset_index]);
  80. get_request_success = flipper_http_get_request_with_headers(url, headers);
  81. free(headers);
  82. if (!get_request_success)
  83. {
  84. FURI_LOG_E(TAG, "Failed to send GET request");
  85. fhttp.state = ISSUE;
  86. return false;
  87. }
  88. fhttp.state = RECEIVING;
  89. }
  90. return true;
  91. }
  92. static char *process_asset_price(AssetLoaderModel* model)
  93. {
  94. UNUSED(model);
  95. if (!request_processed)
  96. {
  97. // load the received data from the saved file
  98. FuriString *price_data = flipper_http_load_from_file(fhttp.file_path);
  99. if (price_data == NULL)
  100. {
  101. FURI_LOG_E(TAG, "Failed to load received data from file.");
  102. fhttp.state = ISSUE;
  103. return NULL;
  104. }
  105. char *data_cstr = (char *)furi_string_get_cstr(price_data);
  106. if (data_cstr == NULL)
  107. {
  108. FURI_LOG_E(TAG, "Failed to get C-string from FuriString.");
  109. furi_string_free(price_data);
  110. fhttp.state = ISSUE;
  111. return NULL;
  112. }
  113. request_processed = true;
  114. char *global_quote = get_json_value("Global Quote", data_cstr, MAX_TOKENS);
  115. if (global_quote == NULL)
  116. {
  117. FURI_LOG_E(TAG, "Failed to get Global Quote");
  118. fhttp.state = ISSUE;
  119. furi_string_free(price_data);
  120. free(global_quote);
  121. free(data_cstr);
  122. return NULL;
  123. }
  124. char *price = get_json_value("05. price", global_quote, MAX_TOKENS);
  125. if (price == NULL)
  126. {
  127. FURI_LOG_E(TAG, "Failed to get price");
  128. fhttp.state = ISSUE;
  129. furi_string_free(price_data);
  130. free(global_quote);
  131. free(price);
  132. free(data_cstr);
  133. return NULL;
  134. }
  135. // store the price "Asset: $price"
  136. snprintf(asset_price, 64, "%s: $%s", asset_names[asset_index], price);
  137. fhttp.state = IDLE;
  138. furi_string_free(price_data);
  139. free(global_quote);
  140. free(price);
  141. free(data_cstr);
  142. }
  143. return asset_price;
  144. }
  145. static void flip_trader_asset_switch_to_view(FlipTraderApp *app)
  146. {
  147. flip_trader_generic_switch_to_view(app, "Fetching..", send_price_request, process_asset_price, 1, callback_to_assets_submenu, FlipTraderViewLoader);
  148. }
  149. void callback_submenu_choices(void *context, uint32_t index)
  150. {
  151. FlipTraderApp *app = (FlipTraderApp *)context;
  152. if (!app)
  153. {
  154. FURI_LOG_E(TAG, "FlipTraderApp is NULL");
  155. return;
  156. }
  157. switch (index)
  158. {
  159. // view the assets submenu
  160. case FlipTradeSubmenuIndexAssets:
  161. view_dispatcher_switch_to_view(app->view_dispatcher, FlipTraderViewAssetsSubmenu);
  162. break;
  163. // view the about screen
  164. case FlipTraderSubmenuIndexAbout:
  165. view_dispatcher_switch_to_view(app->view_dispatcher, FlipTraderViewAbout);
  166. break;
  167. // view the wifi settings screen
  168. case FlipTraderSubmenuIndexSettings:
  169. view_dispatcher_switch_to_view(app->view_dispatcher, FlipTraderViewWiFiSettings);
  170. break;
  171. default:
  172. // handle FlipTraderSubmenuIndexAssetStartIndex + index
  173. if (index >= FlipTraderSubmenuIndexAssetStartIndex)
  174. {
  175. asset_index = index - FlipTraderSubmenuIndexAssetStartIndex;
  176. flip_trader_asset_switch_to_view(app);
  177. }
  178. else
  179. {
  180. FURI_LOG_E(TAG, "Unknown submenu index");
  181. }
  182. break;
  183. }
  184. }
  185. void text_updated_ssid(void *context)
  186. {
  187. FlipTraderApp *app = (FlipTraderApp *)context;
  188. if (!app)
  189. {
  190. FURI_LOG_E(TAG, "FlipTraderApp is NULL");
  191. return;
  192. }
  193. // store the entered text
  194. strncpy(app->uart_text_input_buffer_ssid, app->uart_text_input_temp_buffer_ssid, app->uart_text_input_buffer_size_ssid);
  195. // Ensure null-termination
  196. app->uart_text_input_buffer_ssid[app->uart_text_input_buffer_size_ssid - 1] = '\0';
  197. // update the variable item text
  198. if (app->variable_item_ssid)
  199. {
  200. variable_item_set_current_value_text(app->variable_item_ssid, app->uart_text_input_buffer_ssid);
  201. }
  202. // save settings
  203. save_settings(app->uart_text_input_buffer_ssid, app->uart_text_input_buffer_password);
  204. // save wifi settings to devboard
  205. if (strlen(app->uart_text_input_buffer_ssid) > 0 && strlen(app->uart_text_input_buffer_password) > 0)
  206. {
  207. if (!flipper_http_save_wifi(app->uart_text_input_buffer_ssid, app->uart_text_input_buffer_password))
  208. {
  209. FURI_LOG_E(TAG, "Failed to save wifi settings");
  210. }
  211. }
  212. // switch to the settings view
  213. view_dispatcher_switch_to_view(app->view_dispatcher, FlipTraderViewWiFiSettings);
  214. }
  215. void text_updated_password(void *context)
  216. {
  217. FlipTraderApp *app = (FlipTraderApp *)context;
  218. if (!app)
  219. {
  220. FURI_LOG_E(TAG, "FlipTraderApp is NULL");
  221. return;
  222. }
  223. // store the entered text
  224. strncpy(app->uart_text_input_buffer_password, app->uart_text_input_temp_buffer_password, app->uart_text_input_buffer_size_password);
  225. // Ensure null-termination
  226. app->uart_text_input_buffer_password[app->uart_text_input_buffer_size_password - 1] = '\0';
  227. // update the variable item text
  228. if (app->variable_item_password)
  229. {
  230. variable_item_set_current_value_text(app->variable_item_password, app->uart_text_input_buffer_password);
  231. }
  232. // save settings
  233. save_settings(app->uart_text_input_buffer_ssid, app->uart_text_input_buffer_password);
  234. // save wifi settings to devboard
  235. if (strlen(app->uart_text_input_buffer_ssid) > 0 && strlen(app->uart_text_input_buffer_password) > 0)
  236. {
  237. if (!flipper_http_save_wifi(app->uart_text_input_buffer_ssid, app->uart_text_input_buffer_password))
  238. {
  239. FURI_LOG_E(TAG, "Failed to save wifi settings");
  240. }
  241. }
  242. // switch to the settings view
  243. view_dispatcher_switch_to_view(app->view_dispatcher, FlipTraderViewWiFiSettings);
  244. }
  245. uint32_t callback_to_submenu(void *context)
  246. {
  247. if (!context)
  248. {
  249. FURI_LOG_E(TAG, "Context is NULL");
  250. return VIEW_NONE;
  251. }
  252. UNUSED(context);
  253. sent_get_request = false;
  254. get_request_success = false;
  255. request_processed = false;
  256. asset_index = 0;
  257. return FlipTraderViewMainSubmenu;
  258. }
  259. uint32_t callback_to_wifi_settings(void *context)
  260. {
  261. if (!context)
  262. {
  263. FURI_LOG_E(TAG, "Context is NULL");
  264. return VIEW_NONE;
  265. }
  266. UNUSED(context);
  267. return FlipTraderViewWiFiSettings;
  268. }
  269. uint32_t callback_to_assets_submenu(void *context)
  270. {
  271. if (!context)
  272. {
  273. FURI_LOG_E(TAG, "Context is NULL");
  274. return VIEW_NONE;
  275. }
  276. UNUSED(context);
  277. sent_get_request = false;
  278. get_request_success = false;
  279. request_processed = false;
  280. asset_index = 0;
  281. return FlipTraderViewAssetsSubmenu;
  282. }
  283. void settings_item_selected(void *context, uint32_t index)
  284. {
  285. FlipTraderApp *app = (FlipTraderApp *)context;
  286. if (!app)
  287. {
  288. FURI_LOG_E(TAG, "FlipTraderApp is NULL");
  289. return;
  290. }
  291. switch (index)
  292. {
  293. case 0: // Input SSID
  294. view_dispatcher_switch_to_view(app->view_dispatcher, FlipTraderViewTextInputSSID);
  295. break;
  296. case 1: // Input Password
  297. view_dispatcher_switch_to_view(app->view_dispatcher, FlipTraderViewTextInputPassword);
  298. break;
  299. default:
  300. FURI_LOG_E(TAG, "Unknown configuration item index");
  301. break;
  302. }
  303. }
  304. static void flip_trader_widget_set_text(char *message, Widget **widget)
  305. {
  306. if (widget == NULL)
  307. {
  308. FURI_LOG_E(TAG, "flip_trader_set_widget_text - widget is NULL");
  309. DEV_CRASH();
  310. return;
  311. }
  312. if (message == NULL)
  313. {
  314. FURI_LOG_E(TAG, "flip_trader_set_widget_text - message is NULL");
  315. DEV_CRASH();
  316. return;
  317. }
  318. widget_reset(*widget);
  319. uint32_t message_length = strlen(message); // Length of the message
  320. uint32_t i = 0; // Index tracker
  321. uint32_t formatted_index = 0; // Tracker for where we are in the formatted message
  322. char *formatted_message; // Buffer to hold the final formatted message
  323. if (!easy_flipper_set_buffer(&formatted_message, message_length * 2 + 1))
  324. {
  325. return;
  326. }
  327. while (i < message_length)
  328. {
  329. // TODO: Use canvas_glyph_width to calculate the maximum characters for the line
  330. uint32_t max_line_length = 29; // Maximum characters per line
  331. uint32_t remaining_length = message_length - i; // Remaining characters
  332. uint32_t line_length = (remaining_length < max_line_length) ? remaining_length : max_line_length;
  333. // Temporary buffer to hold the current line
  334. char line[30];
  335. strncpy(line, message + i, line_length);
  336. line[line_length] = '\0';
  337. // Check if the line ends in the middle of a word and adjust accordingly
  338. if (line_length == 29 && message[i + line_length] != '\0' && message[i + line_length] != ' ')
  339. {
  340. // Find the last space within the 30-character segment
  341. char *last_space = strrchr(line, ' ');
  342. if (last_space != NULL)
  343. {
  344. // Adjust the line length to avoid cutting the word
  345. line_length = last_space - line;
  346. line[line_length] = '\0'; // Null-terminate at the space
  347. }
  348. }
  349. // Manually copy the fixed line into the formatted_message buffer
  350. for (uint32_t j = 0; j < line_length; j++)
  351. {
  352. formatted_message[formatted_index++] = line[j];
  353. }
  354. // Add a newline character for line spacing
  355. formatted_message[formatted_index++] = '\n';
  356. // Move i forward to the start of the next word
  357. i += line_length;
  358. // Skip spaces at the beginning of the next line
  359. while (message[i] == ' ')
  360. {
  361. i++;
  362. }
  363. }
  364. // Add the formatted message to the widget
  365. widget_add_text_scroll_element(*widget, 0, 0, 128, 64, formatted_message);
  366. }
  367. void flip_trader_loader_draw_callback(Canvas *canvas, void *model)
  368. {
  369. if (!canvas || !model)
  370. {
  371. FURI_LOG_E(TAG, "flip_trader_loader_draw_callback - canvas or model is NULL");
  372. return;
  373. }
  374. SerialState http_state = fhttp.state;
  375. AssetLoaderModel *asset_loader_model = (AssetLoaderModel *)model;
  376. AssetState asset_state = asset_loader_model->asset_state;
  377. char *title = asset_loader_model->title;
  378. canvas_set_font(canvas, FontSecondary);
  379. if (http_state == INACTIVE)
  380. {
  381. canvas_draw_str(canvas, 0, 7, "Wifi Dev Board disconnected.");
  382. canvas_draw_str(canvas, 0, 17, "Please connect to the board.");
  383. canvas_draw_str(canvas, 0, 32, "If your board is connected,");
  384. canvas_draw_str(canvas, 0, 42, "make sure you have flashed");
  385. canvas_draw_str(canvas, 0, 52, "your WiFi Devboard with the");
  386. canvas_draw_str(canvas, 0, 62, "latest FlipperHTTP flash.");
  387. return;
  388. }
  389. if (asset_state == AssetStateError || asset_state == AssetStateParseError)
  390. {
  391. flip_trader_request_error_draw(canvas);
  392. return;
  393. }
  394. canvas_draw_str(canvas, 0, 7, title);
  395. canvas_draw_str(canvas, 0, 17, "Loading...");
  396. if (asset_state == AssetStateInitial)
  397. {
  398. return;
  399. }
  400. if (http_state == SENDING)
  401. {
  402. canvas_draw_str(canvas, 0, 27, "Sending...");
  403. return;
  404. }
  405. if (http_state == RECEIVING || asset_state == AssetStateRequested)
  406. {
  407. canvas_draw_str(canvas, 0, 27, "Receiving...");
  408. return;
  409. }
  410. if (http_state == IDLE && asset_state == AssetStateReceived)
  411. {
  412. canvas_draw_str(canvas, 0, 27, "Processing...");
  413. return;
  414. }
  415. if (http_state == IDLE && asset_state == AssetStateParsed)
  416. {
  417. canvas_draw_str(canvas, 0, 27, "Processed...");
  418. return;
  419. }
  420. }
  421. static void flip_trader_loader_process_callback(void *context)
  422. {
  423. if (context == NULL)
  424. {
  425. FURI_LOG_E(TAG, "flip_trader_loader_process_callback - context is NULL");
  426. DEV_CRASH();
  427. return;
  428. }
  429. FlipTraderApp *app = (FlipTraderApp *)context;
  430. View *view = app->view_loader;
  431. AssetState current_asset_state;
  432. with_view_model(view, AssetLoaderModel * model, { current_asset_state = model->asset_state; }, false);
  433. if (current_asset_state == AssetStateInitial)
  434. {
  435. with_view_model(
  436. view,
  437. AssetLoaderModel * model,
  438. {
  439. model->asset_state = AssetStateRequested;
  440. AssetLoaderFetch fetch = model->fetcher;
  441. if (fetch == NULL)
  442. {
  443. FURI_LOG_E(TAG, "Model doesn't have Fetch function assigned.");
  444. model->asset_state = AssetStateError;
  445. return;
  446. }
  447. // Clear any previous responses
  448. strncpy(fhttp.last_response, "", 1);
  449. bool request_status = fetch(model);
  450. if (!request_status)
  451. {
  452. model->asset_state = AssetStateError;
  453. }
  454. },
  455. true);
  456. }
  457. else if (current_asset_state == AssetStateRequested || current_asset_state == AssetStateError)
  458. {
  459. if (fhttp.state == IDLE && fhttp.last_response != NULL)
  460. {
  461. if (strstr(fhttp.last_response, "[PONG]") != NULL)
  462. {
  463. FURI_LOG_DEV(TAG, "PONG received.");
  464. }
  465. else if (strncmp(fhttp.last_response, "[SUCCESS]", 9) == 0)
  466. {
  467. FURI_LOG_DEV(TAG, "SUCCESS received. %s", fhttp.last_response ? fhttp.last_response : "NULL");
  468. }
  469. else if (strncmp(fhttp.last_response, "[ERROR]", 9) == 0)
  470. {
  471. FURI_LOG_DEV(TAG, "ERROR received. %s", fhttp.last_response ? fhttp.last_response : "NULL");
  472. }
  473. else if (strlen(fhttp.last_response) == 0)
  474. {
  475. // Still waiting on response
  476. }
  477. else
  478. {
  479. with_view_model(view, AssetLoaderModel * model, { model->asset_state = AssetStateReceived; }, true);
  480. }
  481. }
  482. else if (fhttp.state == SENDING || fhttp.state == RECEIVING)
  483. {
  484. // continue waiting
  485. }
  486. else if (fhttp.state == INACTIVE)
  487. {
  488. // inactive. try again
  489. }
  490. else if (fhttp.state == ISSUE)
  491. {
  492. with_view_model(view, AssetLoaderModel * model, { model->asset_state = AssetStateError; }, true);
  493. }
  494. else
  495. {
  496. FURI_LOG_DEV(TAG, "Unexpected state: %d lastresp: %s", fhttp.state, fhttp.last_response ? fhttp.last_response : "NULL");
  497. DEV_CRASH();
  498. }
  499. }
  500. else if (current_asset_state == AssetStateReceived)
  501. {
  502. with_view_model(
  503. view,
  504. AssetLoaderModel * model,
  505. {
  506. char *asset_text;
  507. if (model->parser == NULL)
  508. {
  509. asset_text = NULL;
  510. FURI_LOG_DEV(TAG, "Parser is NULL");
  511. DEV_CRASH();
  512. }
  513. else
  514. {
  515. asset_text = model->parser(model);
  516. }
  517. FURI_LOG_DEV(TAG, "Parsed asset: %s\r\ntext: %s", fhttp.last_response ? fhttp.last_response : "NULL", asset_text ? asset_text : "NULL");
  518. model->asset_text = asset_text;
  519. if (asset_text == NULL)
  520. {
  521. model->asset_state = AssetStateParseError;
  522. }
  523. else
  524. {
  525. model->asset_state = AssetStateParsed;
  526. }
  527. },
  528. true);
  529. }
  530. else if (current_asset_state == AssetStateParsed)
  531. {
  532. with_view_model(
  533. view,
  534. AssetLoaderModel * model,
  535. {
  536. if (++model->request_index < model->request_count)
  537. {
  538. model->asset_state = AssetStateInitial;
  539. }
  540. else
  541. {
  542. flip_trader_widget_set_text(model->asset_text != NULL ? model->asset_text : "Empty result", &app_instance->widget_result);
  543. if (model->asset_text != NULL)
  544. {
  545. free(model->asset_text);
  546. model->asset_text = NULL;
  547. }
  548. view_set_previous_callback(widget_get_view(app_instance->widget_result), model->back_callback);
  549. view_dispatcher_switch_to_view(app_instance->view_dispatcher, FlipTraderViewWidgetResult);
  550. }
  551. },
  552. true);
  553. }
  554. }
  555. static void flip_trader_loader_timer_callback(void *context)
  556. {
  557. if (context == NULL)
  558. {
  559. FURI_LOG_E(TAG, "flip_trader_loader_timer_callback - context is NULL");
  560. DEV_CRASH();
  561. return;
  562. }
  563. FlipTraderApp *app = (FlipTraderApp *)context;
  564. view_dispatcher_send_custom_event(app->view_dispatcher, FlipTraderCustomEventProcess);
  565. }
  566. static void flip_trader_loader_on_enter(void *context)
  567. {
  568. if (context == NULL)
  569. {
  570. FURI_LOG_E(TAG, "flip_trader_loader_on_enter - context is NULL");
  571. DEV_CRASH();
  572. return;
  573. }
  574. FlipTraderApp *app = (FlipTraderApp *)context;
  575. View *view = app->view_loader;
  576. with_view_model(
  577. view,
  578. AssetLoaderModel * model,
  579. {
  580. view_set_previous_callback(view, model->back_callback);
  581. if (model->timer == NULL)
  582. {
  583. model->timer = furi_timer_alloc(flip_trader_loader_timer_callback, FuriTimerTypePeriodic, app);
  584. }
  585. furi_timer_start(model->timer, 250);
  586. },
  587. true);
  588. }
  589. static void flip_trader_loader_on_exit(void *context)
  590. {
  591. if (context == NULL)
  592. {
  593. FURI_LOG_E(TAG, "flip_trader_loader_on_exit - context is NULL");
  594. DEV_CRASH();
  595. return;
  596. }
  597. FlipTraderApp *app = (FlipTraderApp *)context;
  598. View *view = app->view_loader;
  599. with_view_model(
  600. view,
  601. AssetLoaderModel * model,
  602. {
  603. if (model->timer)
  604. {
  605. furi_timer_stop(model->timer);
  606. }
  607. },
  608. false);
  609. }
  610. void flip_trader_loader_init(View *view)
  611. {
  612. if (view == NULL)
  613. {
  614. FURI_LOG_E(TAG, "flip_trader_loader_init - view is NULL");
  615. DEV_CRASH();
  616. return;
  617. }
  618. view_allocate_model(view, ViewModelTypeLocking, sizeof(AssetLoaderModel));
  619. view_set_enter_callback(view, flip_trader_loader_on_enter);
  620. view_set_exit_callback(view, flip_trader_loader_on_exit);
  621. }
  622. void flip_trader_loader_free_model(View *view)
  623. {
  624. if (view == NULL)
  625. {
  626. FURI_LOG_E(TAG, "flip_trader_loader_free_model - view is NULL");
  627. DEV_CRASH();
  628. return;
  629. }
  630. with_view_model(
  631. view,
  632. AssetLoaderModel * model,
  633. {
  634. if (model->timer)
  635. {
  636. furi_timer_free(model->timer);
  637. model->timer = NULL;
  638. }
  639. if (model->parser_context)
  640. {
  641. free(model->parser_context);
  642. model->parser_context = NULL;
  643. }
  644. },
  645. false);
  646. }
  647. bool flip_trader_custom_event_callback(void *context, uint32_t index)
  648. {
  649. if (context == NULL)
  650. {
  651. FURI_LOG_E(TAG, "flip_trader_custom_event_callback - context is NULL");
  652. DEV_CRASH();
  653. return false;
  654. }
  655. switch (index)
  656. {
  657. case FlipTraderCustomEventProcess:
  658. flip_trader_loader_process_callback(context);
  659. return true;
  660. default:
  661. FURI_LOG_DEV(TAG, "flip_trader_custom_event_callback. Unknown index: %ld", index);
  662. return false;
  663. }
  664. }
  665. 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)
  666. {
  667. if (app == NULL)
  668. {
  669. FURI_LOG_E(TAG, "flip_trader_generic_switch_to_view - app is NULL");
  670. DEV_CRASH();
  671. return;
  672. }
  673. View *view = app->view_loader;
  674. if (view == NULL)
  675. {
  676. FURI_LOG_E(TAG, "flip_trader_generic_switch_to_view - view is NULL");
  677. DEV_CRASH();
  678. return;
  679. }
  680. with_view_model(
  681. view,
  682. AssetLoaderModel * model,
  683. {
  684. model->title = title;
  685. model->fetcher = fetcher;
  686. model->parser = parser;
  687. model->request_index = 0;
  688. model->request_count = request_count;
  689. model->back_callback = back;
  690. model->asset_state = AssetStateInitial;
  691. model->asset_text = NULL;
  692. },
  693. true);
  694. view_dispatcher_switch_to_view(app->view_dispatcher, view_id);
  695. }