flip_library_callback.c 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858
  1. #include <callback/flip_library_callback.h>
  2. // FURI_LOG_DEV will log only during app development. Be sure that Settings/System/Log Device is "LPUART"; so we dont use serial port.
  3. #ifdef DEVELOPMENT
  4. #define FURI_LOG_DEV(tag, format, ...) furi_log_print_format(FuriLogLevelInfo, tag, format, ##__VA_ARGS__)
  5. #define DEV_CRASH() furi_crash()
  6. #else
  7. #define FURI_LOG_DEV(tag, format, ...)
  8. #define DEV_CRASH()
  9. #endif
  10. static bool flip_library_wiki_fetch(FactLoaderModel *model)
  11. {
  12. UNUSED(model);
  13. snprintf(
  14. fhttp.file_path,
  15. sizeof(fhttp.file_path),
  16. STORAGE_EXT_PATH_PREFIX "/apps_data/flip_library/wiki.json");
  17. // encode spaces for url
  18. for (size_t i = 0; i < strlen(app_instance->uart_text_input_buffer_query); i++)
  19. {
  20. if (app_instance->uart_text_input_buffer_query[i] == ' ')
  21. {
  22. app_instance->uart_text_input_buffer_query[i] = '_';
  23. }
  24. }
  25. char url[260];
  26. snprintf(url, sizeof(url), "https://api.wikimedia.org/core/v1/wikipedia/en/search/title?q=%s&limit=1", app_instance->uart_text_input_buffer_query);
  27. Storage *storage = furi_record_open(RECORD_STORAGE);
  28. storage_simply_remove_recursive(storage, fhttp.file_path);
  29. fhttp.save_received_data = true;
  30. return flipper_http_get_request_with_headers(url, "{\"Content-Type\":\"application/json\"}");
  31. }
  32. static char *flip_library_wiki_parse(FactLoaderModel *model)
  33. {
  34. UNUSED(model);
  35. FuriString *data = flipper_http_load_from_file(fhttp.file_path);
  36. if (data == NULL)
  37. {
  38. FURI_LOG_E(TAG, "Failed to load received data from file.");
  39. return "Failed to load received data from file.";
  40. }
  41. char *data_cstr = (char *)furi_string_get_cstr(data);
  42. if (data_cstr == NULL)
  43. {
  44. FURI_LOG_E(TAG, "Failed to get C-string from FuriString.");
  45. furi_string_free(data);
  46. return "Failed to get C-string from FuriString.";
  47. }
  48. char *pages = get_json_array_value("pages", 0, data_cstr, MAX_TOKENS);
  49. if (pages == NULL)
  50. {
  51. furi_string_free(data);
  52. return data_cstr;
  53. }
  54. char *description = get_json_value("description", pages, 64);
  55. if (description == NULL)
  56. {
  57. furi_string_free(data);
  58. return data_cstr;
  59. }
  60. return description;
  61. }
  62. static void flip_library_wiki_switch_to_view(FlipLibraryApp *app)
  63. {
  64. uart_text_input_set_header_text(app->uart_text_input_query, "Search Wikipedia");
  65. flip_library_generic_switch_to_view(app, "Searching..", flip_library_wiki_fetch, flip_library_wiki_parse, 1, callback_to_submenu, FlipLibraryViewTextInputQuery);
  66. }
  67. static bool flip_library_random_fact_fetch(FactLoaderModel *model)
  68. {
  69. UNUSED(model);
  70. return flipper_http_get_request("https://uselessfacts.jsph.pl/api/v2/facts/random");
  71. }
  72. static char *flip_library_random_fact_parse(FactLoaderModel *model)
  73. {
  74. UNUSED(model);
  75. return get_json_value("text", fhttp.last_response, 128);
  76. }
  77. static void flip_library_random_fact_switch_to_view(FlipLibraryApp *app)
  78. {
  79. flip_library_generic_switch_to_view(app, "Random Fact", flip_library_random_fact_fetch, flip_library_random_fact_parse, 1, callback_to_random_facts, FlipLibraryViewLoader);
  80. }
  81. static bool flip_library_cat_fact_fetch(FactLoaderModel *model)
  82. {
  83. UNUSED(model);
  84. return flipper_http_get_request_with_headers("https://catfact.ninja/fact", "{\"Content-Type\":\"application/json\"}");
  85. }
  86. static char *flip_library_cat_fact_parse(FactLoaderModel *model)
  87. {
  88. UNUSED(model);
  89. return get_json_value("fact", fhttp.last_response, 128);
  90. }
  91. static void flip_library_cat_fact_switch_to_view(FlipLibraryApp *app)
  92. {
  93. flip_library_generic_switch_to_view(app, "Random Cat Fact", flip_library_cat_fact_fetch, flip_library_cat_fact_parse, 1, callback_to_random_facts, FlipLibraryViewLoader);
  94. }
  95. static bool flip_library_dog_fact_fetch(FactLoaderModel *model)
  96. {
  97. UNUSED(model);
  98. return flipper_http_get_request_with_headers("https://dog-api.kinduff.com/api/facts", "{\"Content-Type\":\"application/json\"}");
  99. }
  100. static char *flip_library_dog_fact_parse(FactLoaderModel *model)
  101. {
  102. UNUSED(model);
  103. return get_json_array_value("facts", 0, fhttp.last_response, 256);
  104. }
  105. static void flip_library_dog_fact_switch_to_view(FlipLibraryApp *app)
  106. {
  107. flip_library_generic_switch_to_view(app, "Random Dog Fact", flip_library_dog_fact_fetch, flip_library_dog_fact_parse, 1, callback_to_random_facts, FlipLibraryViewLoader);
  108. }
  109. static bool flip_library_quote_fetch(FactLoaderModel *model)
  110. {
  111. UNUSED(model);
  112. return flipper_http_get_request("https://zenquotes.io/api/random");
  113. }
  114. static char *flip_library_quote_parse(FactLoaderModel *model)
  115. {
  116. UNUSED(model);
  117. // remove [ and ] from the start and end of the string
  118. char *response = fhttp.last_response;
  119. if (response[0] == '[')
  120. {
  121. response++;
  122. }
  123. if (response[strlen(response) - 1] == ']')
  124. {
  125. response[strlen(response) - 1] = '\0';
  126. }
  127. // remove white space from both sides
  128. while (response[0] == ' ')
  129. {
  130. response++;
  131. }
  132. while (response[strlen(response) - 1] == ' ')
  133. {
  134. response[strlen(response) - 1] = '\0';
  135. }
  136. return get_json_value("q", response, 128);
  137. }
  138. static void flip_library_quote_switch_to_view(FlipLibraryApp *app)
  139. {
  140. flip_library_generic_switch_to_view(app, "Random Quote", flip_library_quote_fetch, flip_library_quote_parse, 1, callback_to_random_facts, FlipLibraryViewLoader);
  141. }
  142. static bool flip_library_dictionary_fetch(FactLoaderModel *model)
  143. {
  144. UNUSED(model);
  145. char payload[128];
  146. snprintf(payload, sizeof(payload), "{\"word\":\"%s\"}", app_instance->uart_text_input_buffer_query);
  147. return flipper_http_post_request_with_headers("https://www.flipsocial.net/api/define/", "{\"Content-Type\":\"application/json\"}", payload);
  148. }
  149. static char *flip_library_dictionary_parse(FactLoaderModel *model)
  150. {
  151. UNUSED(model);
  152. char *defn = get_json_value("definition", fhttp.last_response, 16);
  153. if (defn == NULL)
  154. {
  155. defn = get_json_value("[ERROR]", fhttp.last_response, 16);
  156. }
  157. return defn;
  158. }
  159. static void flip_library_dictionary_switch_to_view(FlipLibraryApp *app)
  160. {
  161. uart_text_input_set_header_text(app->uart_text_input_query, "Enter a word");
  162. flip_library_generic_switch_to_view(app, "Defining", flip_library_dictionary_fetch, flip_library_dictionary_parse, 1, callback_to_submenu, FlipLibraryViewTextInputQuery);
  163. }
  164. static void flip_library_request_error_draw(Canvas *canvas)
  165. {
  166. if (canvas == NULL)
  167. {
  168. FURI_LOG_E(TAG, "flip_library_request_error_draw - canvas is NULL");
  169. DEV_CRASH();
  170. return;
  171. }
  172. if (fhttp.last_response != NULL)
  173. {
  174. if (strstr(fhttp.last_response, "[ERROR] Not connected to Wifi. Failed to reconnect.") != NULL)
  175. {
  176. canvas_clear(canvas);
  177. canvas_draw_str(canvas, 0, 10, "[ERROR] Not connected to Wifi.");
  178. canvas_draw_str(canvas, 0, 22, "Failed to reconnect.");
  179. canvas_draw_str(canvas, 0, 50, "Update your WiFi settings.");
  180. canvas_draw_str(canvas, 0, 60, "Press BACK to return.");
  181. }
  182. else if (strstr(fhttp.last_response, "[ERROR] Failed to connect to Wifi.") != NULL)
  183. {
  184. canvas_clear(canvas);
  185. canvas_draw_str(canvas, 0, 10, "[ERROR] Not connected to Wifi.");
  186. canvas_draw_str(canvas, 0, 50, "Update your WiFi settings.");
  187. canvas_draw_str(canvas, 0, 60, "Press BACK to return.");
  188. }
  189. else if (strstr(fhttp.last_response, "[PONG]") != NULL)
  190. {
  191. canvas_clear(canvas);
  192. canvas_draw_str(canvas, 0, 10, "[STATUS]Connecting to AP...");
  193. }
  194. else
  195. {
  196. canvas_clear(canvas);
  197. FURI_LOG_E(TAG, "Received an error: %s", fhttp.last_response);
  198. canvas_draw_str(canvas, 0, 10, "[ERROR] Unusual error...");
  199. canvas_draw_str(canvas, 0, 60, "Press BACK and retry.");
  200. }
  201. }
  202. else
  203. {
  204. canvas_clear(canvas);
  205. canvas_draw_str(canvas, 0, 10, "Failed to receive data.");
  206. canvas_draw_str(canvas, 0, 60, "Press BACK to return.");
  207. }
  208. }
  209. static void flip_library_widget_set_text(char *message, Widget **widget)
  210. {
  211. if (widget == NULL)
  212. {
  213. FURI_LOG_E(TAG, "flip_library_set_widget_text - widget is NULL");
  214. DEV_CRASH();
  215. return;
  216. }
  217. if (message == NULL)
  218. {
  219. FURI_LOG_E(TAG, "flip_library_set_widget_text - message is NULL");
  220. DEV_CRASH();
  221. return;
  222. }
  223. widget_reset(*widget);
  224. uint32_t message_length = strlen(message); // Length of the message
  225. uint32_t i = 0; // Index tracker
  226. uint32_t formatted_index = 0; // Tracker for where we are in the formatted message
  227. char *formatted_message; // Buffer to hold the final formatted message
  228. if (!easy_flipper_set_buffer(&formatted_message, message_length * 2 + 1))
  229. {
  230. return;
  231. }
  232. while (i < message_length)
  233. {
  234. // TODO: Use canvas_glyph_width to calculate the maximum characters for the line
  235. uint32_t max_line_length = 29; // Maximum characters per line
  236. uint32_t remaining_length = message_length - i; // Remaining characters
  237. uint32_t line_length = (remaining_length < max_line_length) ? remaining_length : max_line_length;
  238. // Temporary buffer to hold the current line
  239. char line[30];
  240. strncpy(line, message + i, line_length);
  241. line[line_length] = '\0';
  242. // Check if the line ends in the middle of a word and adjust accordingly
  243. if (line_length == 29 && message[i + line_length] != '\0' && message[i + line_length] != ' ')
  244. {
  245. // Find the last space within the 30-character segment
  246. char *last_space = strrchr(line, ' ');
  247. if (last_space != NULL)
  248. {
  249. // Adjust the line length to avoid cutting the word
  250. line_length = last_space - line;
  251. line[line_length] = '\0'; // Null-terminate at the space
  252. }
  253. }
  254. // Manually copy the fixed line into the formatted_message buffer
  255. for (uint32_t j = 0; j < line_length; j++)
  256. {
  257. formatted_message[formatted_index++] = line[j];
  258. }
  259. // Add a newline character for line spacing
  260. formatted_message[formatted_index++] = '\n';
  261. // Move i forward to the start of the next word
  262. i += line_length;
  263. // Skip spaces at the beginning of the next line
  264. while (message[i] == ' ')
  265. {
  266. i++;
  267. }
  268. }
  269. // Add the formatted message to the widget
  270. widget_add_text_scroll_element(*widget, 0, 0, 128, 64, formatted_message);
  271. }
  272. void flip_library_loader_draw_callback(Canvas *canvas, void *model)
  273. {
  274. if (!canvas || !model)
  275. {
  276. FURI_LOG_E(TAG, "flip_library_loader_draw_callback - canvas or model is NULL");
  277. return;
  278. }
  279. SerialState http_state = fhttp.state;
  280. FactLoaderModel *fact_loader_model = (FactLoaderModel *)model;
  281. FactState fact_state = fact_loader_model->fact_state;
  282. char *title = fact_loader_model->title;
  283. canvas_set_font(canvas, FontSecondary);
  284. if (http_state == INACTIVE)
  285. {
  286. canvas_draw_str(canvas, 0, 7, "Wifi Dev Board disconnected.");
  287. canvas_draw_str(canvas, 0, 17, "Please connect to the board.");
  288. canvas_draw_str(canvas, 0, 32, "If your board is connected,");
  289. canvas_draw_str(canvas, 0, 42, "make sure you have flashed");
  290. canvas_draw_str(canvas, 0, 52, "your WiFi Devboard with the");
  291. canvas_draw_str(canvas, 0, 62, "latest FlipperHTTP flash.");
  292. return;
  293. }
  294. if (fact_state == FactStateError || fact_state == FactStateParseError)
  295. {
  296. flip_library_request_error_draw(canvas);
  297. return;
  298. }
  299. canvas_draw_str(canvas, 0, 7, title);
  300. canvas_draw_str(canvas, 0, 15, "Loading...");
  301. if (fact_state == FactStateInitial)
  302. {
  303. return;
  304. }
  305. if (http_state == SENDING)
  306. {
  307. canvas_draw_str(canvas, 0, 22, "Sending...");
  308. return;
  309. }
  310. if (http_state == RECEIVING || fact_state == FactStateRequested)
  311. {
  312. canvas_draw_str(canvas, 0, 22, "Receiving...");
  313. return;
  314. }
  315. if (http_state == IDLE && fact_state == FactStateReceived)
  316. {
  317. canvas_draw_str(canvas, 0, 22, "Processing...");
  318. return;
  319. }
  320. if (http_state == IDLE && fact_state == FactStateParsed)
  321. {
  322. canvas_draw_str(canvas, 0, 22, "Processed...");
  323. return;
  324. }
  325. }
  326. static void flip_library_loader_process_callback(void *context)
  327. {
  328. if (context == NULL)
  329. {
  330. FURI_LOG_E(TAG, "flip_library_loader_process_callback - context is NULL");
  331. DEV_CRASH();
  332. return;
  333. }
  334. FlipLibraryApp *app = (FlipLibraryApp *)context;
  335. View *view = app->view_loader;
  336. FactState current_fact_state;
  337. with_view_model(view, FactLoaderModel * model, { current_fact_state = model->fact_state; }, false);
  338. if (current_fact_state == FactStateInitial)
  339. {
  340. with_view_model(
  341. view,
  342. FactLoaderModel * model,
  343. {
  344. model->fact_state = FactStateRequested;
  345. FactLoaderFetch fetch = model->fetcher;
  346. if (fetch == NULL)
  347. {
  348. FURI_LOG_E(TAG, "Model doesn't have Fetch function assigned.");
  349. model->fact_state = FactStateError;
  350. return;
  351. }
  352. // Clear any previous responses
  353. strncpy(fhttp.last_response, "", 1);
  354. bool request_status = fetch(model);
  355. if (!request_status)
  356. {
  357. model->fact_state = FactStateError;
  358. }
  359. },
  360. true);
  361. }
  362. else if (current_fact_state == FactStateRequested || current_fact_state == FactStateError)
  363. {
  364. if (fhttp.state == IDLE && fhttp.last_response != NULL)
  365. {
  366. if (strstr(fhttp.last_response, "[PONG]") != NULL)
  367. {
  368. FURI_LOG_DEV(TAG, "PONG received.");
  369. }
  370. else if (strncmp(fhttp.last_response, "[SUCCESS]", 9) == 0)
  371. {
  372. FURI_LOG_DEV(TAG, "SUCCESS received. %s", fhttp.last_response ? fhttp.last_response : "NULL");
  373. }
  374. else if (strncmp(fhttp.last_response, "[ERROR]", 9) == 0)
  375. {
  376. FURI_LOG_DEV(TAG, "ERROR received. %s", fhttp.last_response ? fhttp.last_response : "NULL");
  377. }
  378. else if (strlen(fhttp.last_response) == 0)
  379. {
  380. // Still waiting on response
  381. }
  382. else
  383. {
  384. with_view_model(view, FactLoaderModel * model, { model->fact_state = FactStateReceived; }, true);
  385. }
  386. }
  387. else if (fhttp.state == SENDING || fhttp.state == RECEIVING)
  388. {
  389. // continue waiting
  390. }
  391. else if (fhttp.state == INACTIVE)
  392. {
  393. // inactive. try again
  394. }
  395. else if (fhttp.state == ISSUE)
  396. {
  397. with_view_model(view, FactLoaderModel * model, { model->fact_state = FactStateError; }, true);
  398. }
  399. else
  400. {
  401. FURI_LOG_DEV(TAG, "Unexpected state: %d lastresp: %s", fhttp.state, fhttp.last_response ? fhttp.last_response : "NULL");
  402. DEV_CRASH();
  403. }
  404. }
  405. else if (current_fact_state == FactStateReceived)
  406. {
  407. with_view_model(
  408. view,
  409. FactLoaderModel * model,
  410. {
  411. char *fact_text;
  412. if (model->parser == NULL)
  413. {
  414. fact_text = NULL;
  415. FURI_LOG_DEV(TAG, "Parser is NULL");
  416. DEV_CRASH();
  417. }
  418. else
  419. {
  420. fact_text = model->parser(model);
  421. }
  422. FURI_LOG_DEV(TAG, "Parsed fact: %s\r\ntext: %s", fhttp.last_response ? fhttp.last_response : "NULL", fact_text ? fact_text : "NULL");
  423. model->fact_text = fact_text;
  424. if (fact_text == NULL)
  425. {
  426. model->fact_state = FactStateParseError;
  427. }
  428. else
  429. {
  430. model->fact_state = FactStateParsed;
  431. }
  432. },
  433. true);
  434. }
  435. else if (current_fact_state == FactStateParsed)
  436. {
  437. with_view_model(
  438. view,
  439. FactLoaderModel * model,
  440. {
  441. if (++model->request_index < model->request_count)
  442. {
  443. model->fact_state = FactStateInitial;
  444. }
  445. else
  446. {
  447. flip_library_widget_set_text(model->fact_text != NULL ? model->fact_text : "Empty result", &app_instance->widget_result);
  448. if (model->fact_text != NULL)
  449. {
  450. free(model->fact_text);
  451. model->fact_text = NULL;
  452. }
  453. view_set_previous_callback(widget_get_view(app_instance->widget_result), model->back_callback);
  454. view_dispatcher_switch_to_view(app_instance->view_dispatcher, FlipLibraryViewWidgetResult);
  455. }
  456. },
  457. true);
  458. }
  459. }
  460. static void flip_library_loader_timer_callback(void *context)
  461. {
  462. if (context == NULL)
  463. {
  464. FURI_LOG_E(TAG, "flip_library_loader_timer_callback - context is NULL");
  465. DEV_CRASH();
  466. return;
  467. }
  468. FlipLibraryApp *app = (FlipLibraryApp *)context;
  469. view_dispatcher_send_custom_event(app->view_dispatcher, FlipLibraryCustomEventProcess);
  470. }
  471. static void flip_library_loader_on_enter(void *context)
  472. {
  473. if (context == NULL)
  474. {
  475. FURI_LOG_E(TAG, "flip_library_loader_on_enter - context is NULL");
  476. DEV_CRASH();
  477. return;
  478. }
  479. FlipLibraryApp *app = (FlipLibraryApp *)context;
  480. View *view = app->view_loader;
  481. with_view_model(
  482. view,
  483. FactLoaderModel * model,
  484. {
  485. view_set_previous_callback(view, model->back_callback);
  486. if (model->timer == NULL)
  487. {
  488. model->timer = furi_timer_alloc(flip_library_loader_timer_callback, FuriTimerTypePeriodic, app);
  489. }
  490. furi_timer_start(model->timer, 250);
  491. },
  492. true);
  493. }
  494. static void flip_library_loader_on_exit(void *context)
  495. {
  496. if (context == NULL)
  497. {
  498. FURI_LOG_E(TAG, "flip_library_loader_on_exit - context is NULL");
  499. DEV_CRASH();
  500. return;
  501. }
  502. FlipLibraryApp *app = (FlipLibraryApp *)context;
  503. View *view = app->view_loader;
  504. with_view_model(
  505. view,
  506. FactLoaderModel * model,
  507. {
  508. if (model->timer)
  509. {
  510. furi_timer_stop(model->timer);
  511. }
  512. },
  513. false);
  514. }
  515. void flip_library_loader_init(View *view)
  516. {
  517. if (view == NULL)
  518. {
  519. FURI_LOG_E(TAG, "flip_library_loader_init - view is NULL");
  520. DEV_CRASH();
  521. return;
  522. }
  523. view_allocate_model(view, ViewModelTypeLocking, sizeof(FactLoaderModel));
  524. view_set_enter_callback(view, flip_library_loader_on_enter);
  525. view_set_exit_callback(view, flip_library_loader_on_exit);
  526. }
  527. void flip_library_loader_free_model(View *view)
  528. {
  529. if (view == NULL)
  530. {
  531. FURI_LOG_E(TAG, "flip_library_loader_free_model - view is NULL");
  532. DEV_CRASH();
  533. return;
  534. }
  535. with_view_model(
  536. view,
  537. FactLoaderModel * model,
  538. {
  539. if (model->timer)
  540. {
  541. furi_timer_free(model->timer);
  542. model->timer = NULL;
  543. }
  544. if (model->parser_context)
  545. {
  546. free(model->parser_context);
  547. model->parser_context = NULL;
  548. }
  549. },
  550. false);
  551. }
  552. bool flip_library_custom_event_callback(void *context, uint32_t index)
  553. {
  554. if (context == NULL)
  555. {
  556. FURI_LOG_E(TAG, "flip_library_custom_event_callback - context is NULL");
  557. DEV_CRASH();
  558. return false;
  559. }
  560. switch (index)
  561. {
  562. case FlipLibraryCustomEventProcess:
  563. flip_library_loader_process_callback(context);
  564. return true;
  565. default:
  566. FURI_LOG_DEV(TAG, "flip_library_custom_event_callback. Unknown index: %ld", index);
  567. return false;
  568. }
  569. }
  570. void callback_submenu_choices(void *context, uint32_t index)
  571. {
  572. if (context == NULL)
  573. {
  574. FURI_LOG_E(TAG, "callback_submenu_choices - context is NULL");
  575. DEV_CRASH();
  576. return;
  577. }
  578. FlipLibraryApp *app = (FlipLibraryApp *)context;
  579. switch (index)
  580. {
  581. case FlipLibrarySubmenuIndexRandomFacts:
  582. view_dispatcher_switch_to_view(app->view_dispatcher, FlipLibraryViewRandomFacts);
  583. break;
  584. case FlipLibrarySubmenuIndexAbout:
  585. view_dispatcher_switch_to_view(app->view_dispatcher, FlipLibraryViewAbout);
  586. break;
  587. case FlipLibrarySubmenuIndexSettings:
  588. view_dispatcher_switch_to_view(app->view_dispatcher, FlipLibraryViewSettings);
  589. break;
  590. case FlipLibrarySubmenuIndexDictionary:
  591. flip_library_dictionary_switch_to_view(app);
  592. break;
  593. case FlipLibrarySubmenuIndexRandomFactsCats:
  594. flip_library_cat_fact_switch_to_view(app);
  595. break;
  596. case FlipLibrarySubmenuIndexRandomFactsDogs:
  597. flip_library_dog_fact_switch_to_view(app);
  598. break;
  599. case FlipLibrarySubmenuIndexRandomFactsQuotes:
  600. flip_library_quote_switch_to_view(app);
  601. break;
  602. case FlipLibrarySubmenuIndexRandomFactsAll:
  603. flip_library_random_fact_switch_to_view(app);
  604. break;
  605. case FlipLibrarySubmenuIndexRandomFactsWiki:
  606. flip_library_wiki_switch_to_view(app);
  607. break;
  608. default:
  609. break;
  610. }
  611. }
  612. void text_updated_ssid(void *context)
  613. {
  614. FlipLibraryApp *app = (FlipLibraryApp *)context;
  615. if (!app)
  616. {
  617. FURI_LOG_E(TAG, "text_updated_ssid - FlipLibraryApp is NULL");
  618. DEV_CRASH();
  619. return;
  620. }
  621. // store the entered text
  622. strncpy(app->uart_text_input_buffer_ssid, app->uart_text_input_temp_buffer_ssid, app->uart_text_input_buffer_size_ssid);
  623. // Ensure null-termination
  624. app->uart_text_input_buffer_ssid[app->uart_text_input_buffer_size_ssid - 1] = '\0';
  625. // update the variable item text
  626. if (app->variable_item_ssid)
  627. {
  628. variable_item_set_current_value_text(app->variable_item_ssid, app->uart_text_input_buffer_ssid);
  629. }
  630. // save settings
  631. save_settings(app->uart_text_input_buffer_ssid, app->uart_text_input_buffer_password);
  632. // save wifi settings to devboard
  633. if (strlen(app->uart_text_input_buffer_ssid) > 0 && strlen(app->uart_text_input_buffer_password) > 0)
  634. {
  635. if (!flipper_http_save_wifi(app->uart_text_input_buffer_ssid, app->uart_text_input_buffer_password))
  636. {
  637. FURI_LOG_E(TAG, "Failed to save wifi settings.");
  638. }
  639. }
  640. // switch to the settings view
  641. view_dispatcher_switch_to_view(app->view_dispatcher, FlipLibraryViewSettings);
  642. }
  643. void text_updated_password(void *context)
  644. {
  645. FlipLibraryApp *app = (FlipLibraryApp *)context;
  646. if (!app)
  647. {
  648. FURI_LOG_E(TAG, "text_updated_password - FlipLibraryApp is NULL");
  649. DEV_CRASH();
  650. return;
  651. }
  652. // store the entered text
  653. strncpy(app->uart_text_input_buffer_password, app->uart_text_input_temp_buffer_password, app->uart_text_input_buffer_size_password);
  654. // Ensure null-termination
  655. app->uart_text_input_buffer_password[app->uart_text_input_buffer_size_password - 1] = '\0';
  656. // update the variable item text
  657. if (app->variable_item_password)
  658. {
  659. variable_item_set_current_value_text(app->variable_item_password, app->uart_text_input_buffer_password);
  660. }
  661. // save settings
  662. save_settings(app->uart_text_input_buffer_ssid, app->uart_text_input_buffer_password);
  663. // save wifi settings to devboard
  664. if (strlen(app->uart_text_input_buffer_ssid) > 0 && strlen(app->uart_text_input_buffer_password) > 0)
  665. {
  666. if (!flipper_http_save_wifi(app->uart_text_input_buffer_ssid, app->uart_text_input_buffer_password))
  667. {
  668. FURI_LOG_E(TAG, "Failed to save wifi settings");
  669. }
  670. }
  671. // switch to the settings view
  672. view_dispatcher_switch_to_view(app->view_dispatcher, FlipLibraryViewSettings);
  673. }
  674. void text_updated_query(void *context)
  675. {
  676. FlipLibraryApp *app = (FlipLibraryApp *)context;
  677. if (!app)
  678. {
  679. FURI_LOG_E(TAG, "text_updated_query - FlipLibraryApp is NULL");
  680. DEV_CRASH();
  681. return;
  682. }
  683. // store the entered text
  684. strncpy(app->uart_text_input_buffer_query, app->uart_text_input_temp_buffer_query, app->uart_text_input_buffer_size_query);
  685. // Ensure null-termination
  686. app->uart_text_input_buffer_query[app->uart_text_input_buffer_size_query - 1] = '\0';
  687. // switch to the loader view
  688. view_dispatcher_switch_to_view(app->view_dispatcher, FlipLibraryViewLoader);
  689. }
  690. uint32_t callback_to_submenu(void *context)
  691. {
  692. UNUSED(context);
  693. return FlipLibraryViewSubmenuMain;
  694. }
  695. uint32_t callback_to_wifi_settings(void *context)
  696. {
  697. UNUSED(context);
  698. return FlipLibraryViewSettings;
  699. }
  700. uint32_t callback_to_random_facts(void *context)
  701. {
  702. UNUSED(context);
  703. return FlipLibraryViewRandomFacts;
  704. }
  705. void settings_item_selected(void *context, uint32_t index)
  706. {
  707. FlipLibraryApp *app = (FlipLibraryApp *)context;
  708. if (!app)
  709. {
  710. FURI_LOG_E(TAG, "settings_item_selected - FlipLibraryApp is NULL");
  711. DEV_CRASH();
  712. return;
  713. }
  714. switch (index)
  715. {
  716. case 0: // Input SSID
  717. view_dispatcher_switch_to_view(app->view_dispatcher, FlipLibraryViewTextInputSSID);
  718. break;
  719. case 1: // Input Password
  720. view_dispatcher_switch_to_view(app->view_dispatcher, FlipLibraryViewTextInputPassword);
  721. break;
  722. default:
  723. FURI_LOG_E(TAG, "Unknown configuration item index");
  724. break;
  725. }
  726. }
  727. /**
  728. * @brief Navigation callback for exiting the application
  729. * @param context The context - unused
  730. * @return next view id (VIEW_NONE to exit the app)
  731. */
  732. uint32_t callback_exit_app(void *context)
  733. {
  734. // Exit the application
  735. if (!context)
  736. {
  737. FURI_LOG_E(TAG, "callback_exit_app - Context is NULL");
  738. return VIEW_NONE;
  739. }
  740. UNUSED(context);
  741. return VIEW_NONE; // Return VIEW_NONE to exit the app
  742. }
  743. void flip_library_generic_switch_to_view(FlipLibraryApp *app, char *title, FactLoaderFetch fetcher, FactLoaderParser parser, size_t request_count, ViewNavigationCallback back, uint32_t view_id)
  744. {
  745. if (app == NULL)
  746. {
  747. FURI_LOG_E(TAG, "flip_library_generic_switch_to_view - app is NULL");
  748. DEV_CRASH();
  749. return;
  750. }
  751. View *view = app->view_loader;
  752. if (view == NULL)
  753. {
  754. FURI_LOG_E(TAG, "flip_library_generic_switch_to_view - view is NULL");
  755. DEV_CRASH();
  756. return;
  757. }
  758. with_view_model(
  759. view,
  760. FactLoaderModel * model,
  761. {
  762. model->title = title;
  763. model->fetcher = fetcher;
  764. model->parser = parser;
  765. model->request_index = 0;
  766. model->request_count = request_count;
  767. model->back_callback = back;
  768. model->fact_state = FactStateInitial;
  769. model->fact_text = NULL;
  770. },
  771. true);
  772. view_dispatcher_switch_to_view(app->view_dispatcher, view_id);
  773. }