flip_trader_callback.c 23 KB

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