flip_store_callback.h 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779
  1. #ifndef FLIP_STORE_CALLBACK_H
  2. #define FLIP_STORE_CALLBACK_H
  3. #include <stdio.h>
  4. #include <string.h>
  5. #include <stdlib.h>
  6. #include "jsmn.h"
  7. #include <ctype.h>
  8. #include <stdbool.h>
  9. // Define maximum limits
  10. #define MAX_APP_NAME_LENGTH 32
  11. #define MAX_APP_COUNT 200
  12. #define MAX_TOKENS 1600 // there are currently 1505 tokens in the JSON response
  13. typedef struct
  14. {
  15. char *app_name;
  16. char *app_id;
  17. } FlipStoreAppInfo;
  18. static FlipStoreAppInfo flip_catalog[MAX_APP_COUNT];
  19. static uint32_t app_selected_index = 0;
  20. static bool flip_store_sent_request = false;
  21. static bool flip_store_success = false;
  22. static bool flip_store_saved_data = false;
  23. static bool flip_store_saved_success = false;
  24. // Function to free the flip_catalog
  25. static void flip_catalog_free()
  26. {
  27. for (int i = 0; i < MAX_APP_COUNT; i++)
  28. {
  29. if (flip_catalog[i].app_name != NULL)
  30. {
  31. free(flip_catalog[i].app_name);
  32. flip_catalog[i].app_name = NULL;
  33. }
  34. if (flip_catalog[i].app_id != NULL)
  35. {
  36. free(flip_catalog[i].app_id);
  37. flip_catalog[i].app_id = NULL;
  38. }
  39. }
  40. }
  41. // Helper function to compare JSON keys
  42. int jsoneq(const char *json, jsmntok_t *tok, const char *s)
  43. {
  44. if (tok->type == JSMN_STRING &&
  45. (int)strlen(s) == tok->end - tok->start &&
  46. strncmp(json + tok->start, s, tok->end - tok->start) == 0)
  47. {
  48. return 0;
  49. }
  50. return -1;
  51. }
  52. // Function to clean app name string
  53. void clean_app_name(char *name)
  54. {
  55. // Remove leading and trailing whitespace (if needed)
  56. char *end;
  57. while (isspace((unsigned char)*name))
  58. name++; // Trim leading
  59. end = name + strlen(name) - 1;
  60. while (end > name && isspace((unsigned char)*end))
  61. end--; // Trim trailing
  62. *(end + 1) = '\0'; // Null terminate
  63. }
  64. // Function to skip tokens correctly
  65. int skip_tokens(jsmntok_t *tokens, int index, int total_tokens)
  66. {
  67. int skip = 1; // Start with 1 to skip the current token
  68. int child_count = tokens[index].size;
  69. for (int i = 0; i < child_count; i++)
  70. {
  71. if ((index + skip) >= total_tokens)
  72. break;
  73. skip += skip_tokens(tokens, index + skip, total_tokens);
  74. }
  75. return skip;
  76. }
  77. bool flip_store_process_app_list(char *json_data)
  78. {
  79. if (json_data == NULL)
  80. {
  81. FURI_LOG_E(TAG, "JSON data is NULL.");
  82. return false;
  83. }
  84. // Free existing catalog to prevent memory leaks
  85. flip_catalog_free();
  86. jsmn_parser parser;
  87. jsmn_init(&parser);
  88. // Initial token allocation
  89. jsmntok_t *tokens = (jsmntok_t *)malloc(sizeof(jsmntok_t) * MAX_TOKENS);
  90. if (tokens == NULL)
  91. {
  92. FURI_LOG_E(TAG, "Failed to allocate memory for JSON tokens.");
  93. return false;
  94. }
  95. int ret = jsmn_parse(&parser, json_data, strlen(json_data), tokens, MAX_TOKENS);
  96. if (ret < 0)
  97. {
  98. FURI_LOG_E(TAG, "Failed to parse JSON: %d", ret);
  99. free(tokens);
  100. return false;
  101. }
  102. if (ret < 1 || tokens[0].type != JSMN_OBJECT)
  103. {
  104. FURI_LOG_E(TAG, "Root element is not an object.");
  105. free(tokens);
  106. return false;
  107. }
  108. int app_count = 0;
  109. int i = 1;
  110. while (i < ret)
  111. {
  112. if (jsoneq(json_data, &tokens[i], "apps") == 0)
  113. {
  114. jsmntok_t *apps_array = &tokens[i + 1];
  115. if (apps_array->type != JSMN_ARRAY)
  116. {
  117. FURI_LOG_E(TAG, "\"apps\" is not an array.");
  118. free(tokens);
  119. return false;
  120. }
  121. int current = i + 2;
  122. for (int j = 0; j < apps_array->size; j++)
  123. {
  124. if (current >= ret)
  125. {
  126. FURI_LOG_E(TAG, "Token index out of bounds while accessing apps.");
  127. break;
  128. }
  129. jsmntok_t *app_token = &tokens[current];
  130. if (app_token->type != JSMN_OBJECT)
  131. {
  132. FURI_LOG_E(TAG, "App entry is not an object.");
  133. current++;
  134. continue;
  135. }
  136. int app_size = app_token->size;
  137. int app_token_index = current + 1;
  138. char name_value[MAX_APP_NAME_LENGTH] = {0};
  139. char id_value[MAX_APP_NAME_LENGTH] = {0};
  140. for (int k = 0; k < app_size; k++)
  141. {
  142. if (app_token_index + 1 >= ret)
  143. {
  144. FURI_LOG_E(TAG, "Token index out of bounds while accessing app properties.");
  145. break;
  146. }
  147. jsmntok_t *key_token = &tokens[app_token_index];
  148. jsmntok_t *val_token = &tokens[app_token_index + 1];
  149. if (jsoneq(json_data, key_token, "name") == 0)
  150. {
  151. int val_length = val_token->end - val_token->start;
  152. if (val_length >= MAX_APP_NAME_LENGTH)
  153. val_length = MAX_APP_NAME_LENGTH - 1;
  154. strncpy(name_value, json_data + val_token->start, val_length);
  155. name_value[val_length] = '\0';
  156. clean_app_name(name_value);
  157. }
  158. else if (jsoneq(json_data, key_token, "id") == 0)
  159. {
  160. int val_length = val_token->end - val_token->start;
  161. if (val_length >= MAX_APP_NAME_LENGTH)
  162. val_length = MAX_APP_NAME_LENGTH - 1;
  163. strncpy(id_value, json_data + val_token->start, val_length);
  164. id_value[val_length] = '\0';
  165. clean_app_name(id_value);
  166. }
  167. app_token_index += 2;
  168. }
  169. if (app_count >= MAX_APP_COUNT)
  170. {
  171. FURI_LOG_E(TAG, "Reached maximum app count limit.");
  172. break;
  173. }
  174. // Allocate memory for app_name and app_id
  175. flip_catalog[app_count].app_name = (char *)malloc(MAX_APP_NAME_LENGTH);
  176. flip_catalog[app_count].app_id = (char *)malloc(MAX_APP_NAME_LENGTH);
  177. if (flip_catalog[app_count].app_name == NULL || flip_catalog[app_count].app_id == NULL)
  178. {
  179. FURI_LOG_E(TAG, "Memory allocation failed for app_name or app_id.");
  180. // Cleanup already allocated entries
  181. for (int cleanup = 0; cleanup < app_count; cleanup++)
  182. {
  183. if (flip_catalog[cleanup].app_name != NULL)
  184. {
  185. free(flip_catalog[cleanup].app_name);
  186. flip_catalog[cleanup].app_name = NULL;
  187. }
  188. if (flip_catalog[cleanup].app_id != NULL)
  189. {
  190. free(flip_catalog[cleanup].app_id);
  191. flip_catalog[cleanup].app_id = NULL;
  192. }
  193. }
  194. free(tokens);
  195. return false;
  196. }
  197. strncpy(flip_catalog[app_count].app_name, name_value, MAX_APP_NAME_LENGTH - 1);
  198. flip_catalog[app_count].app_name[MAX_APP_NAME_LENGTH - 1] = '\0';
  199. strncpy(flip_catalog[app_count].app_id, id_value, MAX_APP_NAME_LENGTH - 1);
  200. flip_catalog[app_count].app_id[MAX_APP_NAME_LENGTH - 1] = '\0';
  201. app_count++;
  202. // Update current to skip the current app object tokens
  203. int tokens_to_skip = 1 + 2 * app_size;
  204. current += tokens_to_skip;
  205. }
  206. break;
  207. }
  208. else
  209. {
  210. i += 2;
  211. }
  212. }
  213. free(tokens);
  214. return true;
  215. }
  216. bool flip_store_get_fap_file(char *app_id)
  217. {
  218. char payload[164];
  219. snprintf(payload, sizeof(payload), "{\"app_id\":\"%s\"}", app_id);
  220. return flipper_http_post_request_bytes("https://www.flipsocial.net/api/app/compile/", "{\"Content-Type\":\"application/json\"}", payload);
  221. }
  222. void flip_store_request_error(Canvas *canvas)
  223. {
  224. if (fhttp.received_data == NULL)
  225. {
  226. if (fhttp.last_response != NULL)
  227. {
  228. if (strstr(fhttp.last_response, "[ERROR] Not connected to Wifi. Failed to reconnect.") != NULL)
  229. {
  230. canvas_clear(canvas);
  231. canvas_draw_str(canvas, 0, 10, "[ERROR] Not connected to Wifi.");
  232. canvas_draw_str(canvas, 0, 50, "Update your WiFi settings.");
  233. canvas_draw_str(canvas, 0, 60, "Press BACK to return.");
  234. }
  235. else if (strstr(fhttp.last_response, "[ERROR] Failed to connect to Wifi.") != NULL)
  236. {
  237. canvas_clear(canvas);
  238. canvas_draw_str(canvas, 0, 10, "[ERROR] Not connected to Wifi.");
  239. canvas_draw_str(canvas, 0, 50, "Update your WiFi settings.");
  240. canvas_draw_str(canvas, 0, 60, "Press BACK to return.");
  241. }
  242. else
  243. {
  244. FURI_LOG_E(TAG, "Received an error: %s", fhttp.last_response);
  245. canvas_draw_str(canvas, 0, 42, "Unusual error...");
  246. canvas_draw_str(canvas, 0, 50, "Update your WiFi settings.");
  247. canvas_draw_str(canvas, 0, 60, "Press BACK to return.");
  248. }
  249. }
  250. else
  251. {
  252. canvas_clear(canvas);
  253. canvas_draw_str(canvas, 0, 10, "[ERROR] Unknown error.");
  254. canvas_draw_str(canvas, 0, 50, "Update your WiFi settings.");
  255. canvas_draw_str(canvas, 0, 60, "Press BACK to return.");
  256. }
  257. }
  258. else
  259. {
  260. canvas_clear(canvas);
  261. canvas_draw_str(canvas, 0, 10, "Failed to receive data.");
  262. canvas_draw_str(canvas, 0, 60, "Press BACK to return.");
  263. }
  264. }
  265. // function to handle the entire installation process "asynchronously"
  266. bool flip_store_install_app(Canvas *canvas)
  267. {
  268. // create /apps/FlipStore directory if it doesn't exist
  269. char directory_path[128];
  270. snprintf(directory_path, sizeof(directory_path), STORAGE_EXT_PATH_PREFIX "/apps/FlipStore");
  271. // Create the directory
  272. Storage *storage = furi_record_open(RECORD_STORAGE);
  273. storage_common_mkdir(storage, directory_path);
  274. // Adjusted to access flip_catalog as an array of structures
  275. char *app_name = flip_catalog[app_selected_index].app_name;
  276. char installing_text[128];
  277. snprintf(installing_text, sizeof(installing_text), "Installing %s", app_name);
  278. char bin_path[256];
  279. snprintf(bin_path, sizeof(bin_path), STORAGE_EXT_PATH_PREFIX "/apps/FlipStore/%s.fap", flip_catalog[app_selected_index].app_id);
  280. strncpy(fhttp.file_path, bin_path, sizeof(fhttp.file_path) - 1);
  281. canvas_draw_str(canvas, 0, 10, installing_text);
  282. canvas_draw_str(canvas, 0, 20, "Sending request..");
  283. if (fhttp.state != INACTIVE && flip_store_get_fap_file(flip_catalog[app_selected_index].app_id))
  284. {
  285. canvas_draw_str(canvas, 0, 30, "Request sent.");
  286. fhttp.state = RECEIVING;
  287. // furi_timer_start(fhttp.get_timeout_timer, TIMEOUT_DURATION_TICKS);
  288. canvas_draw_str(canvas, 0, 40, "Receiving...");
  289. }
  290. else
  291. {
  292. FURI_LOG_E(TAG, "Failed to send the request");
  293. flip_store_success = false;
  294. return false;
  295. }
  296. while (fhttp.state == RECEIVING && furi_timer_is_running(fhttp.get_timeout_timer) > 0)
  297. {
  298. // Wait for the feed to be received
  299. // furi_delay_ms(100);
  300. }
  301. // furi_timer_stop(fhttp.get_timeout_timer);
  302. if (fhttp.state == ISSUE || fhttp.received_data == NULL)
  303. {
  304. flip_store_request_error(canvas);
  305. flip_store_success = false;
  306. return false;
  307. }
  308. flip_store_success = true;
  309. return true;
  310. }
  311. // Callback for drawing the main screen
  312. static void flip_store_view_draw_callback_main(Canvas *canvas, void *model)
  313. {
  314. UNUSED(model);
  315. canvas_set_font(canvas, FontSecondary);
  316. if (fhttp.state == INACTIVE)
  317. {
  318. canvas_draw_str(canvas, 0, 7, "Wifi Dev Board disconnected.");
  319. canvas_draw_str(canvas, 0, 17, "Please connect to the board.");
  320. canvas_draw_str(canvas, 0, 32, "If your board is connected,");
  321. canvas_draw_str(canvas, 0, 42, "make sure you have flashed");
  322. canvas_draw_str(canvas, 0, 52, "your WiFi Devboard with the");
  323. canvas_draw_str(canvas, 0, 62, "latest FlipperHTTP flash.");
  324. return;
  325. }
  326. if (!flip_store_sent_request)
  327. {
  328. flip_store_sent_request = true;
  329. if (!flip_store_install_app(canvas))
  330. {
  331. canvas_clear(canvas);
  332. canvas_draw_str(canvas, 0, 10, "Failed to install app.");
  333. canvas_draw_str(canvas, 0, 60, "Press BACK to return.");
  334. }
  335. else
  336. {
  337. canvas_clear(canvas);
  338. canvas_draw_str(canvas, 0, 10, "App installed successfully.");
  339. canvas_draw_str(canvas, 0, 60, "Press BACK to return.");
  340. }
  341. }
  342. else
  343. {
  344. if (flip_store_success)
  345. {
  346. canvas_clear(canvas);
  347. canvas_draw_str(canvas, 0, 10, "App installed successfully.");
  348. canvas_draw_str(canvas, 0, 60, "Press BACK to return.");
  349. }
  350. else
  351. {
  352. canvas_clear(canvas);
  353. canvas_draw_str(canvas, 0, 10, "Failed to install app.");
  354. canvas_draw_str(canvas, 0, 60, "Press BACK to return.");
  355. }
  356. }
  357. }
  358. static void flip_store_view_draw_callback_app_list(Canvas *canvas, void *model)
  359. {
  360. UNUSED(model);
  361. canvas_clear(canvas);
  362. canvas_set_font(canvas, FontPrimary);
  363. // Adjusted to access flip_catalog as an array of structures
  364. canvas_draw_str(canvas, 0, 10, flip_catalog[app_selected_index].app_name);
  365. // canvas_draw_icon(canvas, 0, 53, &I_ButtonLeft_4x7); (future implementation)
  366. // canvas_draw_str_aligned(canvas, 7, 54, AlignLeft, AlignTop, "Delete"); (future implementation)
  367. canvas_draw_icon(canvas, 0, 53, &I_ButtonBACK_10x8);
  368. canvas_draw_str_aligned(canvas, 12, 54, AlignLeft, AlignTop, "Back");
  369. canvas_draw_icon(canvas, 90, 53, &I_ButtonRight_4x7);
  370. canvas_draw_str_aligned(canvas, 97, 54, AlignLeft, AlignTop, "Install");
  371. }
  372. static bool flip_store_input_callback(InputEvent *event, void *context)
  373. {
  374. FlipStoreApp *app = (FlipStoreApp *)context;
  375. if (!app)
  376. {
  377. FURI_LOG_E(TAG, "FlipStoreApp is NULL");
  378. return false;
  379. }
  380. if (event->type == InputTypeShort)
  381. {
  382. // Future implementation
  383. // if (event->key == InputKeyLeft)
  384. //{
  385. // Left button clicked, delete the app with DialogEx confirmation
  386. view_dispatcher_switch_to_view(app->view_dispatcher, FlipStoreViewAppDelete);
  387. // return true;
  388. //}
  389. if (event->key == InputKeyRight)
  390. {
  391. // Right button clicked, download the app
  392. view_dispatcher_switch_to_view(app->view_dispatcher, FlipStoreViewMain);
  393. return true;
  394. }
  395. }
  396. else if (event->type == InputTypePress)
  397. {
  398. if (event->key == InputKeyBack)
  399. {
  400. // Back button clicked, switch to the previous view.
  401. view_dispatcher_switch_to_view(app->view_dispatcher, FlipStoreViewAppList);
  402. return true;
  403. }
  404. }
  405. return false;
  406. }
  407. static void flip_store_text_updated_ssid(void *context)
  408. {
  409. FlipStoreApp *app = (FlipStoreApp *)context;
  410. if (!app)
  411. {
  412. FURI_LOG_E(TAG, "FlipStoreApp is NULL");
  413. return;
  414. }
  415. // store the entered text
  416. strncpy(app->uart_text_input_buffer_ssid, app->uart_text_input_temp_buffer_ssid, app->uart_text_input_buffer_size_ssid);
  417. // Ensure null-termination
  418. app->uart_text_input_buffer_ssid[app->uart_text_input_buffer_size_ssid - 1] = '\0';
  419. // update the variable item text
  420. if (app->variable_item_ssid)
  421. {
  422. variable_item_set_current_value_text(app->variable_item_ssid, app->uart_text_input_buffer_ssid);
  423. }
  424. // save the settings
  425. save_settings(app->uart_text_input_buffer_ssid, app->uart_text_input_buffer_pass);
  426. // if SSID and PASS are not empty, connect to the WiFi
  427. if (strlen(app->uart_text_input_buffer_ssid) > 0 && strlen(app->uart_text_input_buffer_pass) > 0)
  428. {
  429. // save wifi settings
  430. if (!flipper_http_save_wifi(app->uart_text_input_buffer_ssid, app->uart_text_input_buffer_pass))
  431. {
  432. FURI_LOG_E(TAG, "Failed to save WiFi settings");
  433. }
  434. }
  435. // switch to the settings view
  436. view_dispatcher_switch_to_view(app->view_dispatcher, FlipStoreViewSettings);
  437. }
  438. static void flip_store_text_updated_pass(void *context)
  439. {
  440. FlipStoreApp *app = (FlipStoreApp *)context;
  441. if (!app)
  442. {
  443. FURI_LOG_E(TAG, "FlipStoreApp is NULL");
  444. return;
  445. }
  446. // store the entered text
  447. strncpy(app->uart_text_input_buffer_pass, app->uart_text_input_temp_buffer_pass, app->uart_text_input_buffer_size_pass);
  448. // Ensure null-termination
  449. app->uart_text_input_buffer_pass[app->uart_text_input_buffer_size_pass - 1] = '\0';
  450. // update the variable item text
  451. if (app->variable_item_pass)
  452. {
  453. variable_item_set_current_value_text(app->variable_item_pass, app->uart_text_input_buffer_pass);
  454. }
  455. // save the settings
  456. save_settings(app->uart_text_input_buffer_ssid, app->uart_text_input_buffer_pass);
  457. // if SSID and PASS are not empty, connect to the WiFi
  458. if (strlen(app->uart_text_input_buffer_ssid) > 0 && strlen(app->uart_text_input_buffer_pass) > 0)
  459. {
  460. // save wifi settings
  461. if (!flipper_http_save_wifi(app->uart_text_input_buffer_ssid, app->uart_text_input_buffer_pass))
  462. {
  463. FURI_LOG_E(TAG, "Failed to save WiFi settings");
  464. }
  465. }
  466. // switch to the settings view
  467. view_dispatcher_switch_to_view(app->view_dispatcher, FlipStoreViewSettings);
  468. }
  469. static uint32_t callback_to_submenu(void *context)
  470. {
  471. if (!context)
  472. {
  473. FURI_LOG_E(TAG, "Context is NULL");
  474. return VIEW_NONE;
  475. }
  476. UNUSED(context);
  477. // free the app list
  478. flip_catalog_free();
  479. return FlipStoreViewSubmenu;
  480. }
  481. static uint32_t callback_to_app_list(void *context)
  482. {
  483. if (!context)
  484. {
  485. FURI_LOG_E(TAG, "Context is NULL");
  486. return VIEW_NONE;
  487. }
  488. UNUSED(context);
  489. flip_store_sent_request = false;
  490. flip_store_success = false;
  491. flip_store_saved_data = false;
  492. flip_store_saved_success = false;
  493. return FlipStoreViewAppList;
  494. }
  495. static void settings_item_selected(void *context, uint32_t index)
  496. {
  497. FlipStoreApp *app = (FlipStoreApp *)context;
  498. if (!app)
  499. {
  500. FURI_LOG_E(TAG, "FlipStoreApp is NULL");
  501. return;
  502. }
  503. switch (index)
  504. {
  505. case 0: // Input SSID
  506. view_dispatcher_switch_to_view(app->view_dispatcher, FlipStoreViewTextInputSSID);
  507. break;
  508. case 1: // Input Password
  509. view_dispatcher_switch_to_view(app->view_dispatcher, FlipStoreViewTextInputPass);
  510. break;
  511. default:
  512. FURI_LOG_E(TAG, "Unknown configuration item index");
  513. break;
  514. }
  515. }
  516. void dialog_callback(DialogExResult result, void *context)
  517. {
  518. furi_assert(context);
  519. FlipStoreApp *app = (FlipStoreApp *)context;
  520. if (result == DialogExResultLeft) // No
  521. {
  522. view_dispatcher_switch_to_view(app->view_dispatcher, FlipStoreViewAppList);
  523. }
  524. else if (result == DialogExResultRight)
  525. {
  526. // delete the app then return to the app list
  527. // pop up a message
  528. popup_set_header(app->popup, "Success", 0, 0, AlignLeft, AlignTop);
  529. popup_set_text(app->popup, "App deleted successfully.", 0, 60, AlignLeft, AlignTop);
  530. view_dispatcher_switch_to_view(app->view_dispatcher, FlipStoreViewPopup);
  531. furi_delay_ms(2000); // delay for 2 seconds
  532. view_dispatcher_switch_to_view(app->view_dispatcher, FlipStoreViewAppList);
  533. }
  534. }
  535. void popup_callback(void *context)
  536. {
  537. FlipStoreApp *app = (FlipStoreApp *)context;
  538. if (!app)
  539. {
  540. FURI_LOG_E(TAG, "FlipStoreApp is NULL");
  541. return;
  542. }
  543. view_dispatcher_switch_to_view(app->view_dispatcher, FlipStoreViewSubmenu);
  544. }
  545. /**
  546. * @brief Navigation callback for exiting the application
  547. * @param context The context - unused
  548. * @return next view id (VIEW_NONE to exit the app)
  549. */
  550. static uint32_t callback_exit_app(void *context)
  551. {
  552. // Exit the application
  553. if (!context)
  554. {
  555. FURI_LOG_E(TAG, "Context is NULL");
  556. return VIEW_NONE;
  557. }
  558. UNUSED(context);
  559. return VIEW_NONE; // Return VIEW_NONE to exit the app
  560. }
  561. static void callback_submenu_choices(void *context, uint32_t index)
  562. {
  563. FlipStoreApp *app = (FlipStoreApp *)context;
  564. if (!app)
  565. {
  566. FURI_LOG_E(TAG, "FlipStoreApp is NULL");
  567. return;
  568. }
  569. switch (index)
  570. {
  571. case FlipStoreSubmenuIndexMain:
  572. view_dispatcher_switch_to_view(app->view_dispatcher, FlipStoreViewMain);
  573. break;
  574. case FlipStoreSubmenuIndexAbout:
  575. view_dispatcher_switch_to_view(app->view_dispatcher, FlipStoreViewAbout);
  576. break;
  577. case FlipStoreSubmenuIndexSettings:
  578. view_dispatcher_switch_to_view(app->view_dispatcher, FlipStoreViewSettings);
  579. break;
  580. // Ideally users should be sent to a draw callback view to show to request process (like in FlipSocial and WebCrawler)
  581. case FlipStoreSubmenuIndexAppList:
  582. // initialize the flip_catalog[MAX_APP_COUNT];
  583. // if (!flip_catalog_init())
  584. // {
  585. // FURI_LOG_E(TAG, "Failed to initialize flip catalog");
  586. // return;
  587. // }
  588. // async call to the app list with timer
  589. if (fhttp.state != INACTIVE && flipper_http_get_request_with_headers("https://www.flipsocial.net/api/flipper/apps/", "{\"Content-Type\":\"application/json\"}"))
  590. {
  591. furi_timer_start(fhttp.get_timeout_timer, TIMEOUT_DURATION_TICKS);
  592. fhttp.state = RECEIVING;
  593. }
  594. else
  595. {
  596. FURI_LOG_E(TAG, "Failed to send the request");
  597. view_dispatcher_switch_to_view(app->view_dispatcher, FlipStoreViewPopup);
  598. return;
  599. }
  600. while (fhttp.state == RECEIVING && furi_timer_is_running(fhttp.get_timeout_timer) > 0)
  601. {
  602. // Wait for the feed to be received
  603. furi_delay_ms(100);
  604. }
  605. furi_timer_stop(fhttp.get_timeout_timer);
  606. if (fhttp.state == ISSUE || fhttp.received_data == NULL)
  607. {
  608. if (fhttp.received_data == NULL)
  609. {
  610. FURI_LOG_E(TAG, "Failed to receive data");
  611. if (fhttp.last_response != NULL)
  612. {
  613. if (strstr(fhttp.last_response, "[ERROR] Not connected to Wifi. Failed to reconnect.") != NULL)
  614. {
  615. popup_set_text(app->popup, "[ERROR] WiFi Disconnected.\n\n\nUpdate your WiFi settings.\nPress BACK to return.", 0, 10, AlignLeft, AlignTop);
  616. }
  617. else if (strstr(fhttp.last_response, "[ERROR] Failed to connect to Wifi.") != NULL)
  618. {
  619. popup_set_text(app->popup, "[ERROR] WiFi Disconnected.\n\n\nUpdate your WiFi settings.\nPress BACK to return.", 0, 10, AlignLeft, AlignTop);
  620. }
  621. else
  622. {
  623. popup_set_text(app->popup, fhttp.last_response, 0, 10, AlignLeft, AlignTop);
  624. }
  625. }
  626. else
  627. {
  628. popup_set_text(app->popup, "[ERROR] Unknown Error.\n\n\nUpdate your WiFi settings.\nPress BACK to return.", 0, 10, AlignLeft, AlignTop);
  629. }
  630. view_dispatcher_switch_to_view(app->view_dispatcher, FlipStoreViewPopup);
  631. return;
  632. }
  633. else
  634. {
  635. FURI_LOG_E(TAG, "Failed to receive data");
  636. popup_set_text(app->popup, "Failed to received data.", 0, 10, AlignLeft, AlignTop);
  637. view_dispatcher_switch_to_view(app->view_dispatcher, FlipStoreViewPopup);
  638. return;
  639. }
  640. }
  641. else
  642. {
  643. // process the app list
  644. if (flip_store_process_app_list(fhttp.received_data))
  645. {
  646. submenu_reset(app->submenu_app_list); // clear the submenu
  647. // add each app name to submenu
  648. for (int i = 0; i < MAX_APP_COUNT; i++)
  649. {
  650. if (flip_catalog[i].app_name != NULL && strlen(flip_catalog[i].app_name) > 0)
  651. {
  652. submenu_add_item(app->submenu_app_list, flip_catalog[i].app_name, FlipStoreSubmenuIndexStartAppList + i, callback_submenu_choices, app);
  653. }
  654. }
  655. view_dispatcher_switch_to_view(app->view_dispatcher, FlipStoreViewAppList);
  656. }
  657. else
  658. {
  659. FURI_LOG_E(TAG, "Failed to process the app list");
  660. popup_set_text(app->popup, "Failed to process the app list", 0, 10, AlignLeft, AlignTop);
  661. view_dispatcher_switch_to_view(app->view_dispatcher, FlipStoreViewPopup);
  662. return;
  663. }
  664. }
  665. break;
  666. default:
  667. // Check if the index is within the app list range
  668. if (index >= FlipStoreSubmenuIndexStartAppList && index < FlipStoreSubmenuIndexStartAppList + MAX_APP_COUNT)
  669. {
  670. // Get the app index
  671. uint32_t app_index = index - FlipStoreSubmenuIndexStartAppList;
  672. // Check if the app index is valid
  673. if ((int)app_index >= 0 && app_index < MAX_APP_COUNT)
  674. {
  675. // Get the app name
  676. char *app_name = flip_catalog[app_index].app_name;
  677. // Check if the app name is valid
  678. if (app_name != NULL && strlen(app_name) > 0)
  679. {
  680. app_selected_index = app_index;
  681. view_dispatcher_switch_to_view(app->view_dispatcher, FlipStoreViewAppInfo);
  682. }
  683. else
  684. {
  685. FURI_LOG_E(TAG, "Invalid app name");
  686. }
  687. }
  688. else
  689. {
  690. FURI_LOG_E(TAG, "Invalid app index");
  691. }
  692. }
  693. else
  694. {
  695. FURI_LOG_E(TAG, "Unknown submenu index");
  696. }
  697. break;
  698. }
  699. }
  700. #endif // FLIP_STORE_CALLBACK_H