flip_library_callback.c 25 KB

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