flip_weather_callback.c 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717
  1. #include "callback/flip_weather_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. bool weather_request_success = false;
  12. bool sent_weather_request = false;
  13. bool got_weather_data = false;
  14. static void flip_weather_request_error_draw(Canvas *canvas)
  15. {
  16. if (canvas == NULL)
  17. {
  18. FURI_LOG_E(TAG, "flip_weather_request_error_draw - canvas is NULL");
  19. DEV_CRASH();
  20. return;
  21. }
  22. if (fhttp.last_response != NULL)
  23. {
  24. if (strstr(fhttp.last_response, "[ERROR] Not connected to Wifi. Failed to reconnect.") != NULL)
  25. {
  26. canvas_clear(canvas);
  27. canvas_draw_str(canvas, 0, 10, "[ERROR] Not connected to Wifi.");
  28. canvas_draw_str(canvas, 0, 50, "Update your WiFi settings.");
  29. canvas_draw_str(canvas, 0, 60, "Press BACK to return.");
  30. }
  31. else if (strstr(fhttp.last_response, "[ERROR] Failed to connect to Wifi.") != NULL)
  32. {
  33. canvas_clear(canvas);
  34. canvas_draw_str(canvas, 0, 10, "[ERROR] Not connected to Wifi.");
  35. canvas_draw_str(canvas, 0, 50, "Update your WiFi settings.");
  36. canvas_draw_str(canvas, 0, 60, "Press BACK to return.");
  37. }
  38. else if (strstr(fhttp.last_response, "[ERROR] GET request failed or returned empty data.") != NULL)
  39. {
  40. canvas_clear(canvas);
  41. canvas_draw_str(canvas, 0, 10, "[ERROR] WiFi error.");
  42. canvas_draw_str(canvas, 0, 50, "Update your WiFi settings.");
  43. canvas_draw_str(canvas, 0, 60, "Press BACK to return.");
  44. }
  45. else if (strstr(fhttp.last_response, "[PONG]") != NULL)
  46. {
  47. canvas_clear(canvas);
  48. canvas_draw_str(canvas, 0, 10, "[STATUS]Connecting to AP...");
  49. }
  50. else
  51. {
  52. canvas_clear(canvas);
  53. FURI_LOG_E(TAG, "Received an error: %s", fhttp.last_response);
  54. canvas_draw_str(canvas, 0, 10, "[ERROR] Unusual error...");
  55. canvas_draw_str(canvas, 0, 60, "Press BACK and retry.");
  56. }
  57. }
  58. else
  59. {
  60. canvas_clear(canvas);
  61. canvas_draw_str(canvas, 0, 10, "[ERROR] Unknown error.");
  62. canvas_draw_str(canvas, 0, 50, "Update your WiFi settings.");
  63. canvas_draw_str(canvas, 0, 60, "Press BACK to return.");
  64. }
  65. }
  66. static void flip_weather_gps_switch_to_view(FlipWeatherApp *app)
  67. {
  68. flip_weather_generic_switch_to_view(app, "Fetching GPS data..", send_geo_location_request, process_geo_location, 1, callback_to_submenu, FlipWeatherViewLoader);
  69. }
  70. static void flip_weather_weather_switch_to_view(FlipWeatherApp *app)
  71. {
  72. flip_weather_generic_switch_to_view(app, "Fetching Weather data..", send_geo_weather_request, process_weather, 1, callback_to_submenu, FlipWeatherViewLoader);
  73. }
  74. void callback_submenu_choices(void *context, uint32_t index)
  75. {
  76. FlipWeatherApp *app = (FlipWeatherApp *)context;
  77. if (!app)
  78. {
  79. FURI_LOG_E(TAG, "FlipWeatherApp is NULL");
  80. return;
  81. }
  82. switch (index)
  83. {
  84. case FlipWeatherSubmenuIndexWeather:
  85. flipper_http_loading_task(send_geo_location_request, process_geo_location_2, FlipWeatherViewSubmenu, FlipWeatherViewSubmenu, &app->view_dispatcher);
  86. flip_weather_weather_switch_to_view(app);
  87. break;
  88. case FlipWeatherSubmenuIndexGPS:
  89. flip_weather_gps_switch_to_view(app);
  90. break;
  91. case FlipWeatherSubmenuIndexAbout:
  92. view_dispatcher_switch_to_view(app->view_dispatcher, FlipWeatherViewAbout);
  93. break;
  94. case FlipWeatherSubmenuIndexSettings:
  95. view_dispatcher_switch_to_view(app->view_dispatcher, FlipWeatherViewSettings);
  96. break;
  97. default:
  98. break;
  99. }
  100. }
  101. void text_updated_ssid(void *context)
  102. {
  103. FlipWeatherApp *app = (FlipWeatherApp *)context;
  104. if (!app)
  105. {
  106. FURI_LOG_E(TAG, "FlipWeatherApp is NULL");
  107. return;
  108. }
  109. // store the entered text
  110. strncpy(app->uart_text_input_buffer_ssid, app->uart_text_input_temp_buffer_ssid, app->uart_text_input_buffer_size_ssid);
  111. // Ensure null-termination
  112. app->uart_text_input_buffer_ssid[app->uart_text_input_buffer_size_ssid - 1] = '\0';
  113. // update the variable item text
  114. if (app->variable_item_ssid)
  115. {
  116. variable_item_set_current_value_text(app->variable_item_ssid, app->uart_text_input_buffer_ssid);
  117. }
  118. // save settings
  119. save_settings(app->uart_text_input_buffer_ssid, app->uart_text_input_buffer_password);
  120. // save wifi settings to devboard
  121. if (strlen(app->uart_text_input_buffer_ssid) > 0 && strlen(app->uart_text_input_buffer_password) > 0)
  122. {
  123. if (!flipper_http_save_wifi(app->uart_text_input_buffer_ssid, app->uart_text_input_buffer_password))
  124. {
  125. FURI_LOG_E(TAG, "Failed to save wifi settings");
  126. }
  127. }
  128. // switch to the settings view
  129. view_dispatcher_switch_to_view(app->view_dispatcher, FlipWeatherViewSettings);
  130. }
  131. void text_updated_password(void *context)
  132. {
  133. FlipWeatherApp *app = (FlipWeatherApp *)context;
  134. if (!app)
  135. {
  136. FURI_LOG_E(TAG, "FlipWeatherApp is NULL");
  137. return;
  138. }
  139. // store the entered text
  140. strncpy(app->uart_text_input_buffer_password, app->uart_text_input_temp_buffer_password, app->uart_text_input_buffer_size_password);
  141. // Ensure null-termination
  142. app->uart_text_input_buffer_password[app->uart_text_input_buffer_size_password - 1] = '\0';
  143. // update the variable item text
  144. if (app->variable_item_password)
  145. {
  146. variable_item_set_current_value_text(app->variable_item_password, app->uart_text_input_buffer_password);
  147. }
  148. // save settings
  149. save_settings(app->uart_text_input_buffer_ssid, app->uart_text_input_buffer_password);
  150. // save wifi settings to devboard
  151. if (strlen(app->uart_text_input_buffer_ssid) > 0 && strlen(app->uart_text_input_buffer_password) > 0)
  152. {
  153. if (!flipper_http_save_wifi(app->uart_text_input_buffer_ssid, app->uart_text_input_buffer_password))
  154. {
  155. FURI_LOG_E(TAG, "Failed to save wifi settings");
  156. }
  157. }
  158. // switch to the settings view
  159. view_dispatcher_switch_to_view(app->view_dispatcher, FlipWeatherViewSettings);
  160. }
  161. uint32_t callback_to_submenu(void *context)
  162. {
  163. if (!context)
  164. {
  165. FURI_LOG_E(TAG, "Context is NULL");
  166. return VIEW_NONE;
  167. }
  168. UNUSED(context);
  169. sent_get_request = false;
  170. get_request_success = false;
  171. got_ip_address = false;
  172. got_weather_data = false;
  173. geo_information_processed = false;
  174. weather_information_processed = false;
  175. sent_weather_request = false;
  176. weather_request_success = false;
  177. if (weather_data != NULL)
  178. {
  179. free(weather_data);
  180. weather_data = NULL;
  181. }
  182. if (total_data != NULL)
  183. {
  184. free(total_data);
  185. total_data = NULL;
  186. }
  187. return FlipWeatherViewSubmenu;
  188. }
  189. void settings_item_selected(void *context, uint32_t index)
  190. {
  191. FlipWeatherApp *app = (FlipWeatherApp *)context;
  192. if (!app)
  193. {
  194. FURI_LOG_E(TAG, "FlipWeatherApp is NULL");
  195. return;
  196. }
  197. switch (index)
  198. {
  199. case 0: // Input SSID
  200. view_dispatcher_switch_to_view(app->view_dispatcher, FlipWeatherViewTextInputSSID);
  201. break;
  202. case 1: // Input Password
  203. view_dispatcher_switch_to_view(app->view_dispatcher, FlipWeatherViewTextInputPassword);
  204. break;
  205. default:
  206. FURI_LOG_E(TAG, "Unknown configuration item index");
  207. break;
  208. }
  209. }
  210. /**
  211. * @brief Navigation callback for exiting the application
  212. * @param context The context - unused
  213. * @return next view id (VIEW_NONE to exit the app)
  214. */
  215. uint32_t callback_exit_app(void *context)
  216. {
  217. // Exit the application
  218. if (!context)
  219. {
  220. FURI_LOG_E(TAG, "Context is NULL");
  221. return VIEW_NONE;
  222. }
  223. UNUSED(context);
  224. return VIEW_NONE; // Return VIEW_NONE to exit the app
  225. }
  226. uint32_t callback_to_wifi_settings(void *context)
  227. {
  228. if (!context)
  229. {
  230. FURI_LOG_E(TAG, "Context is NULL");
  231. return VIEW_NONE;
  232. }
  233. UNUSED(context);
  234. return FlipWeatherViewSettings;
  235. }
  236. static void flip_weather_widget_set_text(char *message, Widget **widget)
  237. {
  238. if (widget == NULL)
  239. {
  240. FURI_LOG_E(TAG, "flip_weather_set_widget_text - widget is NULL");
  241. DEV_CRASH();
  242. return;
  243. }
  244. if (message == NULL)
  245. {
  246. FURI_LOG_E(TAG, "flip_weather_set_widget_text - message is NULL");
  247. DEV_CRASH();
  248. return;
  249. }
  250. widget_reset(*widget);
  251. uint32_t message_length = strlen(message); // Length of the message
  252. uint32_t i = 0; // Index tracker
  253. uint32_t formatted_index = 0; // Tracker for where we are in the formatted message
  254. char *formatted_message; // Buffer to hold the final formatted message
  255. // Allocate buffer with double the message length plus one for safety
  256. if (!easy_flipper_set_buffer(&formatted_message, message_length * 2 + 1))
  257. {
  258. return;
  259. }
  260. while (i < message_length)
  261. {
  262. uint32_t max_line_length = 31; // Maximum characters per line
  263. uint32_t remaining_length = message_length - i; // Remaining characters
  264. uint32_t line_length = (remaining_length < max_line_length) ? remaining_length : max_line_length;
  265. // Check for newline character within the current segment
  266. uint32_t newline_pos = i;
  267. bool found_newline = false;
  268. for (; newline_pos < i + line_length && newline_pos < message_length; newline_pos++)
  269. {
  270. if (message[newline_pos] == '\n')
  271. {
  272. found_newline = true;
  273. break;
  274. }
  275. }
  276. if (found_newline)
  277. {
  278. // If newline found, set line_length up to the newline
  279. line_length = newline_pos - i;
  280. }
  281. // Temporary buffer to hold the current line
  282. char line[32];
  283. strncpy(line, message + i, line_length);
  284. line[line_length] = '\0';
  285. // If newline was found, skip it for the next iteration
  286. if (found_newline)
  287. {
  288. i += line_length + 1; // +1 to skip the '\n' character
  289. }
  290. else
  291. {
  292. // Check if the line ends in the middle of a word and adjust accordingly
  293. if (line_length == max_line_length && message[i + line_length] != '\0' && message[i + line_length] != ' ')
  294. {
  295. // Find the last space within the current line to avoid breaking a word
  296. char *last_space = strrchr(line, ' ');
  297. if (last_space != NULL)
  298. {
  299. // Adjust the line_length to avoid cutting the word
  300. line_length = last_space - line;
  301. line[line_length] = '\0'; // Null-terminate at the space
  302. }
  303. }
  304. // Move the index forward by the determined line_length
  305. i += line_length;
  306. // Skip any spaces at the beginning of the next line
  307. while (i < message_length && message[i] == ' ')
  308. {
  309. i++;
  310. }
  311. }
  312. // Manually copy the fixed line into the formatted_message buffer
  313. for (uint32_t j = 0; j < line_length; j++)
  314. {
  315. formatted_message[formatted_index++] = line[j];
  316. }
  317. // Add a newline character for line spacing
  318. formatted_message[formatted_index++] = '\n';
  319. }
  320. // Null-terminate the formatted_message
  321. formatted_message[formatted_index] = '\0';
  322. // Add the formatted message to the widget
  323. widget_add_text_scroll_element(*widget, 0, 0, 128, 64, formatted_message);
  324. }
  325. void flip_weather_loader_draw_callback(Canvas *canvas, void *model)
  326. {
  327. if (!canvas || !model)
  328. {
  329. FURI_LOG_E(TAG, "flip_weather_loader_draw_callback - canvas or model is NULL");
  330. return;
  331. }
  332. SerialState http_state = fhttp.state;
  333. DataLoaderModel *data_loader_model = (DataLoaderModel *)model;
  334. DataState data_state = data_loader_model->data_state;
  335. char *title = data_loader_model->title;
  336. canvas_set_font(canvas, FontSecondary);
  337. if (http_state == INACTIVE)
  338. {
  339. canvas_draw_str(canvas, 0, 7, "Wifi Dev Board disconnected.");
  340. canvas_draw_str(canvas, 0, 17, "Please connect to the board.");
  341. canvas_draw_str(canvas, 0, 32, "If your board is connected,");
  342. canvas_draw_str(canvas, 0, 42, "make sure you have flashed");
  343. canvas_draw_str(canvas, 0, 52, "your WiFi Devboard with the");
  344. canvas_draw_str(canvas, 0, 62, "latest FlipperHTTP flash.");
  345. return;
  346. }
  347. if (data_state == DataStateError || data_state == DataStateParseError)
  348. {
  349. flip_weather_request_error_draw(canvas);
  350. return;
  351. }
  352. canvas_draw_str(canvas, 0, 7, title);
  353. canvas_draw_str(canvas, 0, 17, "Loading...");
  354. if (data_state == DataStateInitial)
  355. {
  356. return;
  357. }
  358. if (http_state == SENDING)
  359. {
  360. canvas_draw_str(canvas, 0, 27, "Sending...");
  361. return;
  362. }
  363. if (http_state == RECEIVING || data_state == DataStateRequested)
  364. {
  365. canvas_draw_str(canvas, 0, 27, "Receiving...");
  366. return;
  367. }
  368. if (http_state == IDLE && data_state == DataStateReceived)
  369. {
  370. canvas_draw_str(canvas, 0, 27, "Processing...");
  371. return;
  372. }
  373. if (http_state == IDLE && data_state == DataStateParsed)
  374. {
  375. canvas_draw_str(canvas, 0, 27, "Processed...");
  376. return;
  377. }
  378. }
  379. static void flip_weather_loader_process_callback(void *context)
  380. {
  381. if (context == NULL)
  382. {
  383. FURI_LOG_E(TAG, "flip_weather_loader_process_callback - context is NULL");
  384. DEV_CRASH();
  385. return;
  386. }
  387. FlipWeatherApp *app = (FlipWeatherApp *)context;
  388. View *view = app->view_loader;
  389. DataState current_data_state;
  390. with_view_model(view, DataLoaderModel * model, { current_data_state = model->data_state; }, false);
  391. if (current_data_state == DataStateInitial)
  392. {
  393. with_view_model(
  394. view,
  395. DataLoaderModel * model,
  396. {
  397. model->data_state = DataStateRequested;
  398. DataLoaderFetch fetch = model->fetcher;
  399. if (fetch == NULL)
  400. {
  401. FURI_LOG_E(TAG, "Model doesn't have Fetch function assigned.");
  402. model->data_state = DataStateError;
  403. return;
  404. }
  405. // Clear any previous responses
  406. strncpy(fhttp.last_response, "", 1);
  407. bool request_status = fetch(model);
  408. if (!request_status)
  409. {
  410. model->data_state = DataStateError;
  411. }
  412. },
  413. true);
  414. }
  415. else if (current_data_state == DataStateRequested || current_data_state == DataStateError)
  416. {
  417. if (fhttp.state == IDLE && fhttp.last_response != NULL)
  418. {
  419. if (strstr(fhttp.last_response, "[PONG]") != NULL)
  420. {
  421. FURI_LOG_DEV(TAG, "PONG received.");
  422. }
  423. else if (strncmp(fhttp.last_response, "[SUCCESS]", 9) == 0)
  424. {
  425. FURI_LOG_DEV(TAG, "SUCCESS received. %s", fhttp.last_response ? fhttp.last_response : "NULL");
  426. }
  427. else if (strncmp(fhttp.last_response, "[ERROR]", 9) == 0)
  428. {
  429. FURI_LOG_DEV(TAG, "ERROR received. %s", fhttp.last_response ? fhttp.last_response : "NULL");
  430. }
  431. else if (strlen(fhttp.last_response) == 0)
  432. {
  433. // Still waiting on response
  434. }
  435. else
  436. {
  437. with_view_model(view, DataLoaderModel * model, { model->data_state = DataStateReceived; }, true);
  438. }
  439. }
  440. else if (fhttp.state == SENDING || fhttp.state == RECEIVING)
  441. {
  442. // continue waiting
  443. }
  444. else if (fhttp.state == INACTIVE)
  445. {
  446. // inactive. try again
  447. }
  448. else if (fhttp.state == ISSUE)
  449. {
  450. with_view_model(view, DataLoaderModel * model, { model->data_state = DataStateError; }, true);
  451. }
  452. else
  453. {
  454. FURI_LOG_DEV(TAG, "Unexpected state: %d lastresp: %s", fhttp.state, fhttp.last_response ? fhttp.last_response : "NULL");
  455. DEV_CRASH();
  456. }
  457. }
  458. else if (current_data_state == DataStateReceived)
  459. {
  460. with_view_model(
  461. view,
  462. DataLoaderModel * model,
  463. {
  464. char *data_text;
  465. if (model->parser == NULL)
  466. {
  467. data_text = NULL;
  468. FURI_LOG_DEV(TAG, "Parser is NULL");
  469. DEV_CRASH();
  470. }
  471. else
  472. {
  473. data_text = model->parser(model);
  474. }
  475. FURI_LOG_DEV(TAG, "Parsed data: %s\r\ntext: %s", fhttp.last_response ? fhttp.last_response : "NULL", data_text ? data_text : "NULL");
  476. model->data_text = data_text;
  477. if (data_text == NULL)
  478. {
  479. model->data_state = DataStateParseError;
  480. }
  481. else
  482. {
  483. model->data_state = DataStateParsed;
  484. }
  485. },
  486. true);
  487. }
  488. else if (current_data_state == DataStateParsed)
  489. {
  490. with_view_model(
  491. view,
  492. DataLoaderModel * model,
  493. {
  494. if (++model->request_index < model->request_count)
  495. {
  496. model->data_state = DataStateInitial;
  497. }
  498. else
  499. {
  500. flip_weather_widget_set_text(model->data_text != NULL ? model->data_text : "Empty result", &app_instance->widget_result);
  501. if (model->data_text != NULL)
  502. {
  503. free(model->data_text);
  504. model->data_text = NULL;
  505. }
  506. view_set_previous_callback(widget_get_view(app_instance->widget_result), model->back_callback);
  507. view_dispatcher_switch_to_view(app_instance->view_dispatcher, FlipWeatherViewWidgetResult);
  508. }
  509. },
  510. true);
  511. }
  512. }
  513. static void flip_weather_loader_timer_callback(void *context)
  514. {
  515. if (context == NULL)
  516. {
  517. FURI_LOG_E(TAG, "flip_weather_loader_timer_callback - context is NULL");
  518. DEV_CRASH();
  519. return;
  520. }
  521. FlipWeatherApp *app = (FlipWeatherApp *)context;
  522. view_dispatcher_send_custom_event(app->view_dispatcher, FlipWeatherCustomEventProcess);
  523. }
  524. static void flip_weather_loader_on_enter(void *context)
  525. {
  526. if (context == NULL)
  527. {
  528. FURI_LOG_E(TAG, "flip_weather_loader_on_enter - context is NULL");
  529. DEV_CRASH();
  530. return;
  531. }
  532. FlipWeatherApp *app = (FlipWeatherApp *)context;
  533. View *view = app->view_loader;
  534. with_view_model(
  535. view,
  536. DataLoaderModel * model,
  537. {
  538. view_set_previous_callback(view, model->back_callback);
  539. if (model->timer == NULL)
  540. {
  541. model->timer = furi_timer_alloc(flip_weather_loader_timer_callback, FuriTimerTypePeriodic, app);
  542. }
  543. furi_timer_start(model->timer, 250);
  544. },
  545. true);
  546. }
  547. static void flip_weather_loader_on_exit(void *context)
  548. {
  549. if (context == NULL)
  550. {
  551. FURI_LOG_E(TAG, "flip_weather_loader_on_exit - context is NULL");
  552. DEV_CRASH();
  553. return;
  554. }
  555. FlipWeatherApp *app = (FlipWeatherApp *)context;
  556. View *view = app->view_loader;
  557. with_view_model(
  558. view,
  559. DataLoaderModel * model,
  560. {
  561. if (model->timer)
  562. {
  563. furi_timer_stop(model->timer);
  564. }
  565. },
  566. false);
  567. }
  568. void flip_weather_loader_init(View *view)
  569. {
  570. if (view == NULL)
  571. {
  572. FURI_LOG_E(TAG, "flip_weather_loader_init - view is NULL");
  573. DEV_CRASH();
  574. return;
  575. }
  576. view_allocate_model(view, ViewModelTypeLocking, sizeof(DataLoaderModel));
  577. view_set_enter_callback(view, flip_weather_loader_on_enter);
  578. view_set_exit_callback(view, flip_weather_loader_on_exit);
  579. }
  580. void flip_weather_loader_free_model(View *view)
  581. {
  582. if (view == NULL)
  583. {
  584. FURI_LOG_E(TAG, "flip_weather_loader_free_model - view is NULL");
  585. DEV_CRASH();
  586. return;
  587. }
  588. with_view_model(
  589. view,
  590. DataLoaderModel * model,
  591. {
  592. if (model->timer)
  593. {
  594. furi_timer_free(model->timer);
  595. model->timer = NULL;
  596. }
  597. if (model->parser_context)
  598. {
  599. free(model->parser_context);
  600. model->parser_context = NULL;
  601. }
  602. },
  603. false);
  604. }
  605. bool flip_weather_custom_event_callback(void *context, uint32_t index)
  606. {
  607. if (context == NULL)
  608. {
  609. FURI_LOG_E(TAG, "flip_weather_custom_event_callback - context is NULL");
  610. DEV_CRASH();
  611. return false;
  612. }
  613. switch (index)
  614. {
  615. case FlipWeatherCustomEventProcess:
  616. flip_weather_loader_process_callback(context);
  617. return true;
  618. default:
  619. FURI_LOG_DEV(TAG, "flip_weather_custom_event_callback. Unknown index: %ld", index);
  620. return false;
  621. }
  622. }
  623. 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)
  624. {
  625. if (app == NULL)
  626. {
  627. FURI_LOG_E(TAG, "flip_weather_generic_switch_to_view - app is NULL");
  628. DEV_CRASH();
  629. return;
  630. }
  631. View *view = app->view_loader;
  632. if (view == NULL)
  633. {
  634. FURI_LOG_E(TAG, "flip_weather_generic_switch_to_view - view is NULL");
  635. DEV_CRASH();
  636. return;
  637. }
  638. with_view_model(
  639. view,
  640. DataLoaderModel * model,
  641. {
  642. model->title = title;
  643. model->fetcher = fetcher;
  644. model->parser = parser;
  645. model->request_index = 0;
  646. model->request_count = request_count;
  647. model->back_callback = back;
  648. model->data_state = DataStateInitial;
  649. model->data_text = NULL;
  650. },
  651. true);
  652. view_dispatcher_switch_to_view(app->view_dispatcher, view_id);
  653. }