flip_library_callback.c 27 KB

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