flip_store_callback.c 53 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654
  1. #include <callback/flip_store_callback.h>
  2. // Below added by Derek Jamison
  3. // FURI_LOG_DEV will log only during app development. Be sure that Settings/System/Log Device is "LPUART"; so we dont use serial port.
  4. #ifdef DEVELOPMENT
  5. #define FURI_LOG_DEV(tag, format, ...) furi_log_print_format(FuriLogLevelInfo, tag, format, ##__VA_ARGS__)
  6. #define DEV_CRASH() furi_crash()
  7. #else
  8. #define FURI_LOG_DEV(tag, format, ...)
  9. #define DEV_CRASH()
  10. #endif
  11. bool flip_store_app_does_exist = false;
  12. uint32_t selected_firmware_index = 0;
  13. static uint32_t callback_to_app_category_list(void *context);
  14. static bool flip_store_dl_app_fetch(DataLoaderModel *model)
  15. {
  16. if (!model->fhttp)
  17. {
  18. FURI_LOG_E(TAG, "FlipperHTTP is NULL");
  19. return false;
  20. }
  21. return flip_store_install_app(model->fhttp, categories[flip_store_category_index]);
  22. }
  23. static char *flip_store_dl_app_parse(DataLoaderModel *model)
  24. {
  25. if (!model->fhttp || model->fhttp->state != IDLE)
  26. {
  27. FURI_LOG_E(TAG, "FlipperHTTP is NULL or not IDLE");
  28. return "Failed to install app.";
  29. }
  30. return "App installed successfully.";
  31. }
  32. static void flip_store_dl_app_switch_to_view(FlipStoreApp *app)
  33. {
  34. flip_store_generic_switch_to_view(app, flip_catalog[app_selected_index].app_name, flip_store_dl_app_fetch, flip_store_dl_app_parse, 1, callback_to_app_category_list, FlipStoreViewLoader);
  35. }
  36. //
  37. static bool flip_store_fetch_firmware(DataLoaderModel *model)
  38. {
  39. if (!model->fhttp)
  40. {
  41. FURI_LOG_E(TAG, "FlipperHTTP is NULL");
  42. return false;
  43. }
  44. model->fhttp->state = IDLE;
  45. if (model->request_index == 0)
  46. {
  47. firmware_free();
  48. firmwares = firmware_alloc();
  49. if (!firmwares)
  50. {
  51. return false;
  52. }
  53. return flip_store_get_firmware_file(
  54. model->fhttp,
  55. firmwares[selected_firmware_index].links[0],
  56. firmwares[selected_firmware_index].name,
  57. strrchr(firmwares[selected_firmware_index].links[0], '/') + 1);
  58. }
  59. else if (model->request_index == 1)
  60. {
  61. return flip_store_get_firmware_file(
  62. model->fhttp,
  63. firmwares[selected_firmware_index].links[1],
  64. firmwares[selected_firmware_index].name,
  65. strrchr(firmwares[selected_firmware_index].links[1], '/') + 1);
  66. }
  67. else if (model->request_index == 2)
  68. {
  69. return flip_store_get_firmware_file(
  70. model->fhttp,
  71. firmwares[selected_firmware_index].links[2],
  72. firmwares[selected_firmware_index].name,
  73. strrchr(firmwares[selected_firmware_index].links[2], '/') + 1);
  74. }
  75. return false;
  76. }
  77. static char *flip_store_parse_firmware(DataLoaderModel *model)
  78. {
  79. if (model->request_index == 0)
  80. {
  81. return "File 1 installed.";
  82. }
  83. else if (model->request_index == 1)
  84. {
  85. return "File 2 installed.";
  86. }
  87. else if (model->request_index == 2)
  88. {
  89. return "Firmware downloaded successfully";
  90. }
  91. return "Failed to download firmware.";
  92. }
  93. static void flip_store_switch_to_firmware_list(FlipStoreApp *app)
  94. {
  95. flip_store_generic_switch_to_view(app, firmwares[selected_firmware_index].name, flip_store_fetch_firmware, flip_store_parse_firmware, FIRMWARE_LINKS, callback_to_firmware_list, FlipStoreViewLoader);
  96. }
  97. //
  98. static bool flip_store_fetch_vgm_firmware(DataLoaderModel *model)
  99. {
  100. if (!model->fhttp)
  101. {
  102. FURI_LOG_E(TAG, "FlipperHTTP is NULL");
  103. return false;
  104. }
  105. model->fhttp->state = IDLE;
  106. if (model->request_index == 0)
  107. {
  108. vgm_firmware_free();
  109. vgm_firmwares = vgm_firmware_alloc();
  110. if (!vgm_firmwares)
  111. {
  112. return false;
  113. }
  114. return flip_store_get_firmware_file(
  115. model->fhttp,
  116. vgm_firmwares[selected_firmware_index].link,
  117. vgm_firmwares[selected_firmware_index].name,
  118. strrchr(vgm_firmwares[selected_firmware_index].link, '/') + 1);
  119. }
  120. return false;
  121. }
  122. static char *flip_store_parse_vgm_firmware(DataLoaderModel *model)
  123. {
  124. UNUSED(model);
  125. return "Firmware downloaded successfully";
  126. }
  127. static void flip_store_switch_to_vgm_firmware_list(FlipStoreApp *app)
  128. {
  129. flip_store_generic_switch_to_view(app, vgm_firmwares[selected_firmware_index].name, flip_store_fetch_vgm_firmware, flip_store_parse_vgm_firmware, 1, callback_to_vgm_firmware_list, FlipStoreViewLoader);
  130. }
  131. // Function to draw the message on the canvas with word wrapping
  132. static void draw_description(Canvas *canvas, const char *description, int x, int y)
  133. {
  134. if (description == NULL || strlen(description) == 0)
  135. {
  136. FURI_LOG_E(TAG, "User message is NULL.");
  137. return;
  138. }
  139. if (!canvas)
  140. {
  141. FURI_LOG_E(TAG, "Canvas is NULL.");
  142. return;
  143. }
  144. size_t msg_length = strlen(description);
  145. size_t start = 0;
  146. int line_num = 0;
  147. char line[MAX_LINE_LENGTH + 1]; // Buffer for the current line (+1 for null terminator)
  148. while (start < msg_length && line_num < 4)
  149. {
  150. size_t remaining = msg_length - start;
  151. size_t len = (remaining > MAX_LINE_LENGTH) ? MAX_LINE_LENGTH : remaining;
  152. if (remaining > MAX_LINE_LENGTH)
  153. {
  154. // Find the last space within the first 'len' characters
  155. size_t last_space = len;
  156. while (last_space > 0 && description[start + last_space - 1] != ' ')
  157. {
  158. last_space--;
  159. }
  160. if (last_space > 0)
  161. {
  162. len = last_space; // Adjust len to the position of the last space
  163. }
  164. }
  165. // Copy the substring to 'line' and null-terminate it
  166. memcpy(line, description + start, len);
  167. line[len] = '\0'; // Ensure the string is null-terminated
  168. // Draw the string on the canvas
  169. // Adjust the y-coordinate based on the line number
  170. canvas_draw_str_aligned(canvas, x, y + line_num * 10, AlignLeft, AlignTop, line);
  171. // Update the start position for the next line
  172. start += len;
  173. // Skip any spaces to avoid leading spaces on the next line
  174. while (start < msg_length && description[start] == ' ')
  175. {
  176. start++;
  177. }
  178. // Increment the line number
  179. line_num++;
  180. }
  181. }
  182. static void flip_store_view_draw_callback_app_list(Canvas *canvas, void *model)
  183. {
  184. UNUSED(model);
  185. canvas_clear(canvas);
  186. canvas_set_font(canvas, FontPrimary);
  187. char title[64];
  188. snprintf(title, 64, "%s (v.%s)", flip_catalog[app_selected_index].app_name, flip_catalog[app_selected_index].app_version);
  189. canvas_draw_str(canvas, 0, 10, title);
  190. canvas_set_font(canvas, FontSecondary);
  191. draw_description(canvas, flip_catalog[app_selected_index].app_description, 0, 13);
  192. if (flip_store_app_does_exist)
  193. {
  194. canvas_draw_icon(canvas, 0, 53, &I_ButtonLeft_4x7);
  195. canvas_draw_str_aligned(canvas, 7, 54, AlignLeft, AlignTop, "Delete");
  196. canvas_draw_icon(canvas, 45, 53, &I_ButtonBACK_10x8);
  197. canvas_draw_str_aligned(canvas, 57, 54, AlignLeft, AlignTop, "Back");
  198. }
  199. else
  200. {
  201. canvas_draw_icon(canvas, 0, 53, &I_ButtonBACK_10x8);
  202. canvas_draw_str_aligned(canvas, 12, 54, AlignLeft, AlignTop, "Back");
  203. }
  204. canvas_draw_icon(canvas, 90, 53, &I_ButtonRight_4x7);
  205. canvas_draw_str_aligned(canvas, 97, 54, AlignLeft, AlignTop, "Install");
  206. }
  207. static bool flip_store_input_callback(InputEvent *event, void *context)
  208. {
  209. FlipStoreApp *app = (FlipStoreApp *)context;
  210. if (!app)
  211. {
  212. FURI_LOG_E(TAG, "FlipStoreApp is NULL");
  213. return false;
  214. }
  215. if (event->type == InputTypeShort)
  216. {
  217. if (event->key == InputKeyLeft && flip_store_app_does_exist)
  218. {
  219. // Left button clicked, delete the app
  220. view_dispatcher_switch_to_view(app->view_dispatcher, FlipStoreViewAppDelete);
  221. return true;
  222. }
  223. if (event->key == InputKeyRight)
  224. {
  225. // Right button clicked, download the app
  226. flip_store_dl_app_switch_to_view(app);
  227. return true;
  228. }
  229. }
  230. else if (event->type == InputTypePress)
  231. {
  232. if (event->key == InputKeyBack)
  233. {
  234. // Back button clicked, switch to the previous view.
  235. view_dispatcher_switch_to_view(app->view_dispatcher, FlipStoreViewAppListCategory);
  236. return true;
  237. }
  238. }
  239. return false;
  240. }
  241. static void flip_store_text_updated_ssid(void *context)
  242. {
  243. FlipStoreApp *app = (FlipStoreApp *)context;
  244. if (!app)
  245. {
  246. FURI_LOG_E(TAG, "FlipStoreApp is NULL");
  247. return;
  248. }
  249. // store the entered text
  250. strncpy(app->uart_text_input_buffer, app->uart_text_input_temp_buffer, app->uart_text_input_buffer_size);
  251. // Ensure null-termination
  252. app->uart_text_input_buffer[app->uart_text_input_buffer_size - 1] = '\0';
  253. // save the setting
  254. save_char("WiFi-SSID", app->uart_text_input_buffer);
  255. // update the variable item text
  256. if (app->variable_item_ssid)
  257. {
  258. variable_item_set_current_value_text(app->variable_item_ssid, app->uart_text_input_buffer);
  259. // get value of password
  260. char pass[64];
  261. if (load_char("WiFi-Password", pass, sizeof(pass)))
  262. {
  263. if (strlen(pass) > 0 && strlen(app->uart_text_input_buffer) > 0)
  264. {
  265. // save the settings
  266. save_settings(app->uart_text_input_buffer, pass);
  267. // initialize the http
  268. FlipperHTTP *fhttp = flipper_http_alloc();
  269. if (fhttp)
  270. {
  271. // save the wifi if the device is connected
  272. if (!flipper_http_save_wifi(fhttp, app->uart_text_input_buffer, pass))
  273. {
  274. easy_flipper_dialog("FlipperHTTP Error", "Ensure your WiFi Developer\nBoard or Pico W is connected\nand the latest FlipperHTTP\nfirmware is installed.");
  275. }
  276. // free the resources
  277. flipper_http_free(fhttp);
  278. }
  279. else
  280. {
  281. easy_flipper_dialog("FlipperHTTP Error", "The UART is likely busy.\nEnsure you have the correct\nflash for your board then\nrestart your Flipper Zero.");
  282. }
  283. }
  284. }
  285. }
  286. // switch to the settings view
  287. view_dispatcher_switch_to_view(app->view_dispatcher, FlipStoreViewSettings);
  288. }
  289. static void flip_store_text_updated_pass(void *context)
  290. {
  291. FlipStoreApp *app = (FlipStoreApp *)context;
  292. if (!app)
  293. {
  294. FURI_LOG_E(TAG, "FlipStoreApp is NULL");
  295. return;
  296. }
  297. // store the entered text
  298. strncpy(app->uart_text_input_buffer, app->uart_text_input_temp_buffer, app->uart_text_input_buffer_size);
  299. // Ensure null-termination
  300. app->uart_text_input_buffer[app->uart_text_input_buffer_size - 1] = '\0';
  301. // save the setting
  302. save_char("WiFi-Password", app->uart_text_input_buffer);
  303. // update the variable item text
  304. if (app->variable_item_pass)
  305. {
  306. // variable_item_set_current_value_text(app->variable_item_pass, app->uart_text_input_buffer);
  307. }
  308. // get value of ssid
  309. char ssid[64];
  310. if (load_char("WiFi-SSID", ssid, sizeof(ssid)))
  311. {
  312. if (strlen(ssid) > 0 && strlen(app->uart_text_input_buffer) > 0)
  313. {
  314. // save the settings
  315. save_settings(ssid, app->uart_text_input_buffer);
  316. // initialize the http
  317. FlipperHTTP *fhttp = flipper_http_alloc();
  318. if (fhttp)
  319. {
  320. // save the wifi if the device is connected
  321. if (!flipper_http_save_wifi(fhttp, ssid, app->uart_text_input_buffer))
  322. {
  323. easy_flipper_dialog("FlipperHTTP Error", "Ensure your WiFi Developer\nBoard or Pico W is connected\nand the latest FlipperHTTP\nfirmware is installed.");
  324. }
  325. // free the resources
  326. flipper_http_free(fhttp);
  327. }
  328. else
  329. {
  330. easy_flipper_dialog("FlipperHTTP Error", "The UART is likely busy.\nEnsure you have the correct\nflash for your board then\nrestart your Flipper Zero.");
  331. }
  332. }
  333. }
  334. // switch to the settings view
  335. view_dispatcher_switch_to_view(app->view_dispatcher, FlipStoreViewSettings);
  336. }
  337. static void free_category_submenu(FlipStoreApp *app)
  338. {
  339. if (!app)
  340. {
  341. FURI_LOG_E(TAG, "FlipStoreApp is NULL");
  342. return;
  343. }
  344. if (app->submenu_app_list_category)
  345. {
  346. view_dispatcher_remove_view(app->view_dispatcher, FlipStoreViewAppListCategory);
  347. submenu_free(app->submenu_app_list_category);
  348. app->submenu_app_list_category = NULL;
  349. }
  350. }
  351. static void free_variable_item_list(FlipStoreApp *app);
  352. uint32_t callback_to_submenu(void *context)
  353. {
  354. UNUSED(context);
  355. firmware_free();
  356. return FlipStoreViewSubmenu;
  357. }
  358. uint32_t callback_to_firmware_list(void *context)
  359. {
  360. UNUSED(context);
  361. return FlipStoreViewFirmwares;
  362. }
  363. uint32_t callback_to_vgm_firmware_list(void *context)
  364. {
  365. UNUSED(context);
  366. return FlipStoreViewVGMFirmwares;
  367. }
  368. static uint32_t callback_to_app_category_list(void *context)
  369. {
  370. UNUSED(context);
  371. return FlipStoreViewAppListCategory;
  372. }
  373. uint32_t callback_to_app_list(void *context)
  374. {
  375. UNUSED(context);
  376. return FlipStoreViewAppList;
  377. }
  378. static uint32_t callback_to_wifi_settings(void *context)
  379. {
  380. UNUSED(context);
  381. return FlipStoreViewSettings;
  382. }
  383. static void dialog_firmware_callback(DialogExResult result, void *context)
  384. {
  385. FlipStoreApp *app = (FlipStoreApp *)context;
  386. if (!app)
  387. {
  388. FURI_LOG_E(TAG, "FlipStoreApp is NULL");
  389. return;
  390. }
  391. if (result == DialogExResultLeft) // No
  392. {
  393. // switch to the firmware list
  394. if (is_esp32_firmware)
  395. {
  396. view_dispatcher_switch_to_view(app->view_dispatcher, FlipStoreViewFirmwares);
  397. }
  398. else
  399. {
  400. view_dispatcher_switch_to_view(app->view_dispatcher, FlipStoreViewVGMFirmwares);
  401. }
  402. }
  403. else if (result == DialogExResultRight)
  404. {
  405. // download the firmware then return to the firmware list
  406. if (is_esp32_firmware)
  407. {
  408. flip_store_switch_to_firmware_list(app);
  409. }
  410. else
  411. {
  412. flip_store_switch_to_vgm_firmware_list(app);
  413. }
  414. }
  415. }
  416. static bool alloc_about_view(FlipStoreApp *app)
  417. {
  418. if (!app)
  419. {
  420. FURI_LOG_E(TAG, "FlipStoreApp is NULL");
  421. return false;
  422. }
  423. if (!app->widget_about)
  424. {
  425. if (!easy_flipper_set_widget(
  426. &app->widget_about,
  427. FlipStoreViewAbout,
  428. "Welcome to the FlipStore!\n------\nDownload apps via WiFi and\nrun them on your Flipper!\n------\nwww.github.com/jblanked",
  429. callback_to_submenu,
  430. &app->view_dispatcher))
  431. {
  432. return false;
  433. }
  434. if (!app->widget_about)
  435. {
  436. return false;
  437. }
  438. }
  439. return true;
  440. }
  441. static void free_about_view(FlipStoreApp *app)
  442. {
  443. if (app && app->widget_about != NULL)
  444. {
  445. view_dispatcher_remove_view(app->view_dispatcher, FlipStoreViewAbout);
  446. widget_free(app->widget_about);
  447. app->widget_about = NULL;
  448. }
  449. }
  450. static bool alloc_text_input_view(void *context, char *title)
  451. {
  452. FlipStoreApp *app = (FlipStoreApp *)context;
  453. if (!app)
  454. {
  455. FURI_LOG_E(TAG, "FlipStoreApp is NULL");
  456. return false;
  457. }
  458. if (!title)
  459. {
  460. FURI_LOG_E(TAG, "Title is NULL");
  461. return false;
  462. }
  463. app->uart_text_input_buffer_size = 64;
  464. if (!app->uart_text_input_buffer)
  465. {
  466. if (!easy_flipper_set_buffer(&app->uart_text_input_buffer, app->uart_text_input_buffer_size))
  467. {
  468. return false;
  469. }
  470. }
  471. if (!app->uart_text_input_temp_buffer)
  472. {
  473. if (!easy_flipper_set_buffer(&app->uart_text_input_temp_buffer, app->uart_text_input_buffer_size))
  474. {
  475. return false;
  476. }
  477. }
  478. if (!app->uart_text_input)
  479. {
  480. if (!easy_flipper_set_uart_text_input(
  481. &app->uart_text_input,
  482. FlipStoreViewTextInput,
  483. title,
  484. app->uart_text_input_temp_buffer,
  485. app->uart_text_input_buffer_size,
  486. strcmp(title, "SSID") == 0 ? flip_store_text_updated_ssid : flip_store_text_updated_pass,
  487. callback_to_wifi_settings,
  488. &app->view_dispatcher,
  489. app))
  490. {
  491. return false;
  492. }
  493. if (!app->uart_text_input)
  494. {
  495. return false;
  496. }
  497. char ssid[64];
  498. char pass[64];
  499. if (load_settings(ssid, sizeof(ssid), pass, sizeof(pass)))
  500. {
  501. if (strcmp(title, "SSID") == 0)
  502. {
  503. strncpy(app->uart_text_input_temp_buffer, ssid, app->uart_text_input_buffer_size);
  504. }
  505. else
  506. {
  507. strncpy(app->uart_text_input_temp_buffer, pass, app->uart_text_input_buffer_size);
  508. }
  509. }
  510. }
  511. return true;
  512. }
  513. static void free_text_input_view(FlipStoreApp *app)
  514. {
  515. if (!app)
  516. {
  517. FURI_LOG_E(TAG, "FlipStoreApp is NULL");
  518. return;
  519. }
  520. if (app->uart_text_input)
  521. {
  522. view_dispatcher_remove_view(app->view_dispatcher, FlipStoreViewTextInput);
  523. uart_text_input_free(app->uart_text_input);
  524. app->uart_text_input = NULL;
  525. }
  526. if (app->uart_text_input_buffer)
  527. {
  528. free(app->uart_text_input_buffer);
  529. app->uart_text_input_buffer = NULL;
  530. }
  531. if (app->uart_text_input_temp_buffer)
  532. {
  533. free(app->uart_text_input_temp_buffer);
  534. app->uart_text_input_temp_buffer = NULL;
  535. }
  536. }
  537. static void settings_item_selected(void *context, uint32_t index);
  538. static bool alloc_variable_item_list(void *context)
  539. {
  540. FlipStoreApp *app = (FlipStoreApp *)context;
  541. if (!app)
  542. {
  543. FURI_LOG_E(TAG, "FlipStoreApp is NULL");
  544. return false;
  545. }
  546. if (!app->variable_item_list)
  547. {
  548. if (!easy_flipper_set_variable_item_list(&app->variable_item_list, FlipStoreViewSettings, settings_item_selected, callback_to_submenu, &app->view_dispatcher, app))
  549. return false;
  550. if (!app->variable_item_list)
  551. return false;
  552. if (!app->variable_item_ssid)
  553. {
  554. app->variable_item_ssid = variable_item_list_add(app->variable_item_list, "SSID", 0, NULL, NULL);
  555. variable_item_set_current_value_text(app->variable_item_ssid, "");
  556. }
  557. if (!app->variable_item_pass)
  558. {
  559. app->variable_item_pass = variable_item_list_add(app->variable_item_list, "Password", 0, NULL, NULL);
  560. variable_item_set_current_value_text(app->variable_item_pass, "");
  561. }
  562. char ssid[64];
  563. char pass[64];
  564. if (load_settings(ssid, sizeof(ssid), pass, sizeof(pass)))
  565. {
  566. variable_item_set_current_value_text(app->variable_item_ssid, ssid);
  567. // variable_item_set_current_value_text(app->variable_item_pass, pass);
  568. save_char("WiFi-SSID", ssid);
  569. save_char("WiFi-Password", pass);
  570. }
  571. }
  572. return true;
  573. }
  574. static void free_variable_item_list(FlipStoreApp *app)
  575. {
  576. if (!app)
  577. {
  578. FURI_LOG_E(TAG, "FlipStoreApp is NULL");
  579. return;
  580. }
  581. if (app->variable_item_list)
  582. {
  583. view_dispatcher_remove_view(app->view_dispatcher, FlipStoreViewSettings);
  584. variable_item_list_free(app->variable_item_list);
  585. app->variable_item_list = NULL;
  586. }
  587. if (app->variable_item_ssid)
  588. {
  589. free(app->variable_item_ssid);
  590. app->variable_item_ssid = NULL;
  591. }
  592. if (app->variable_item_pass)
  593. {
  594. free(app->variable_item_pass);
  595. app->variable_item_pass = NULL;
  596. }
  597. }
  598. static bool alloc_dialog_firmware(void *context)
  599. {
  600. FlipStoreApp *app = (FlipStoreApp *)context;
  601. if (!app)
  602. {
  603. FURI_LOG_E(TAG, "FlipStoreApp is NULL");
  604. return false;
  605. }
  606. if (!app->dialog_firmware)
  607. {
  608. if (!easy_flipper_set_dialog_ex(
  609. &app->dialog_firmware,
  610. FlipStoreViewFirmwareDialog,
  611. is_esp32_firmware ? "Download ESP32 Firmware" : "Download VGM Firmware",
  612. 0,
  613. 0,
  614. "Are you sure you want to\ndownload this firmware?",
  615. 0,
  616. 10,
  617. "No",
  618. "Yes",
  619. NULL,
  620. dialog_firmware_callback,
  621. callback_to_firmware_list,
  622. &app->view_dispatcher,
  623. app))
  624. {
  625. return false;
  626. }
  627. if (!app->dialog_firmware)
  628. {
  629. return false;
  630. }
  631. if (is_esp32_firmware)
  632. {
  633. dialog_ex_set_header(app->dialog_firmware, firmwares[selected_firmware_index].name, 0, 0, AlignLeft, AlignTop);
  634. }
  635. else
  636. {
  637. dialog_ex_set_header(app->dialog_firmware, vgm_firmwares[selected_firmware_index].name, 0, 0, AlignLeft, AlignTop);
  638. }
  639. }
  640. return true;
  641. }
  642. static void free_dialog_firmware(FlipStoreApp *app)
  643. {
  644. if (app && app->dialog_firmware)
  645. {
  646. view_dispatcher_remove_view(app->view_dispatcher, FlipStoreViewFirmwareDialog);
  647. dialog_ex_free(app->dialog_firmware);
  648. app->dialog_firmware = NULL;
  649. }
  650. }
  651. static bool alloc_app_info_view(FlipStoreApp *app)
  652. {
  653. if (!app)
  654. {
  655. FURI_LOG_E(TAG, "FlipStoreApp is NULL");
  656. return false;
  657. }
  658. if (!app->view_app_info)
  659. {
  660. if (!easy_flipper_set_view(
  661. &app->view_app_info,
  662. FlipStoreViewAppInfo,
  663. flip_store_view_draw_callback_app_list,
  664. flip_store_input_callback,
  665. callback_to_app_category_list,
  666. &app->view_dispatcher,
  667. app))
  668. {
  669. return false;
  670. }
  671. if (!app->view_app_info)
  672. {
  673. return false;
  674. }
  675. }
  676. return true;
  677. }
  678. static void free_app_info_view(FlipStoreApp *app)
  679. {
  680. if (!app)
  681. {
  682. FURI_LOG_E(TAG, "FlipStoreApp is NULL");
  683. return;
  684. }
  685. if (app->view_app_info)
  686. {
  687. view_dispatcher_remove_view(app->view_dispatcher, FlipStoreViewAppInfo);
  688. view_free(app->view_app_info);
  689. app->view_app_info = NULL;
  690. }
  691. }
  692. uint32_t callback_to_submenu_options(void *context)
  693. {
  694. FlipStoreApp *app = (FlipStoreApp *)context;
  695. if (!app)
  696. {
  697. FURI_LOG_E(TAG, "FlipStoreApp is NULL");
  698. return FlipStoreViewSubmenuOptions;
  699. }
  700. firmware_free();
  701. flip_catalog_free();
  702. free_category_submenu(app);
  703. return FlipStoreViewSubmenuOptions;
  704. }
  705. void free_all_views(FlipStoreApp *app, bool should_free_variable_item_list)
  706. {
  707. if (!app)
  708. {
  709. FURI_LOG_E(TAG, "FlipStoreApp is NULL");
  710. return;
  711. }
  712. free_about_view(app);
  713. flip_catalog_free();
  714. if (should_free_variable_item_list)
  715. {
  716. free_variable_item_list(app);
  717. }
  718. free_category_submenu(app);
  719. free_text_input_view(app);
  720. free_dialog_firmware(app);
  721. free_app_info_view(app);
  722. firmware_free();
  723. vgm_firmware_free();
  724. }
  725. uint32_t callback_exit_app(void *context)
  726. {
  727. UNUSED(context);
  728. return VIEW_NONE; // Return VIEW_NONE to exit the app
  729. }
  730. static bool set_appropriate_list(FlipperHTTP *fhttp, FlipStoreApp *app)
  731. {
  732. if (!fhttp || !app)
  733. {
  734. FURI_LOG_E(TAG, "FlipperHTTP oor app is NULL");
  735. return false;
  736. }
  737. if (!easy_flipper_set_submenu(&app->submenu_app_list_category, FlipStoreViewAppListCategory, categories[flip_store_category_index], callback_to_app_list, &app->view_dispatcher))
  738. {
  739. FURI_LOG_E(TAG, "Failed to set submenu");
  740. return false;
  741. }
  742. if (flip_store_process_app_list(fhttp) && app->submenu_app_list_category && flip_catalog)
  743. {
  744. submenu_reset(app->submenu_app_list_category);
  745. submenu_set_header(app->submenu_app_list_category, categories[flip_store_category_index]);
  746. // add each app name to submenu
  747. for (size_t i = 0; i < flip_catalog->count; i++)
  748. {
  749. if (strlen(flip_catalog[i].app_name) > 0)
  750. {
  751. submenu_add_item(app->submenu_app_list_category, flip_catalog[i].app_name, FlipStoreSubmenuIndexStartAppList + i, callback_submenu_choices, app);
  752. }
  753. else
  754. {
  755. break;
  756. }
  757. }
  758. // add [LOAD NEXT] to submenu
  759. submenu_add_item(app->submenu_app_list_category, "[LOAD NEXT]", FlipStoreSubmenuIndexStartAppList + flip_catalog->count, callback_submenu_choices, app);
  760. return true;
  761. }
  762. FURI_LOG_E(TAG, "Failed to process the app list");
  763. return false;
  764. }
  765. // we'll have to loop 8 at a time due to memory constraints
  766. static void fetch_appropiate_app_list(FlipStoreApp *app, int iteration)
  767. {
  768. FlipperHTTP *fhttp = flipper_http_alloc();
  769. if (!fhttp)
  770. {
  771. FURI_LOG_E(TAG, "Failed to allocate FlipperHTTP");
  772. return;
  773. }
  774. bool fetch_app_list()
  775. {
  776. fhttp->state = IDLE;
  777. flip_catalog_free();
  778. snprintf(
  779. fhttp->file_path,
  780. sizeof(fhttp->file_path),
  781. STORAGE_EXT_PATH_PREFIX "/apps_data/flip_store/%s.json", categories[flip_store_category_index]);
  782. fhttp->save_received_data = true;
  783. fhttp->is_bytes_request = false;
  784. char url[256];
  785. // load 8 at a time
  786. snprintf(url, sizeof(url), "https://catalog.flipperzero.one/api/v0/0/application?limit=8&is_latest_release_version=true&offset=%d&sort_by=updated_at&sort_order=-1&category_id=%s", iteration, category_ids[flip_store_category_index]);
  787. return flipper_http_get_request_with_headers(fhttp, url, "{\"Content-Type\":\"application/json\"}");
  788. }
  789. bool parse_app_list()
  790. {
  791. return set_appropriate_list(fhttp, app);
  792. }
  793. flipper_http_loading_task(fhttp, fetch_app_list, parse_app_list, FlipStoreViewAppListCategory, FlipStoreViewSubmenuOptions, &app->view_dispatcher);
  794. flipper_http_free(fhttp);
  795. }
  796. void callback_submenu_choices(void *context, uint32_t index)
  797. {
  798. FlipStoreApp *app = (FlipStoreApp *)context;
  799. if (!app)
  800. {
  801. FURI_LOG_E(TAG, "FlipStoreApp is NULL");
  802. return;
  803. }
  804. switch (index)
  805. {
  806. case FlipStoreSubmenuIndexAbout:
  807. free_all_views(app, true);
  808. if (!alloc_about_view(app))
  809. {
  810. FURI_LOG_E(TAG, "Failed to set about view");
  811. return;
  812. }
  813. view_dispatcher_switch_to_view(app->view_dispatcher, FlipStoreViewAbout);
  814. break;
  815. case FlipStoreSubmenuIndexSettings:
  816. free_all_views(app, true);
  817. if (!alloc_variable_item_list(app))
  818. {
  819. FURI_LOG_E(TAG, "Failed to allocate variable item list");
  820. return;
  821. }
  822. view_dispatcher_switch_to_view(app->view_dispatcher, FlipStoreViewSettings);
  823. break;
  824. case FlipStoreSubmenuIndexOptions:
  825. free_all_views(app, true);
  826. view_dispatcher_switch_to_view(app->view_dispatcher, FlipStoreViewSubmenuOptions);
  827. break;
  828. case FlipStoreSubmenuIndexAppList:
  829. flip_store_category_index = 0;
  830. flip_store_app_does_exist = false;
  831. view_dispatcher_switch_to_view(app->view_dispatcher, FlipStoreViewAppList);
  832. break;
  833. case FlipStoreSubmenuIndexFirmwares: // esp32 firmwares
  834. firmwares = firmware_alloc();
  835. if (firmwares == NULL)
  836. {
  837. FURI_LOG_E(TAG, "Failed to allocate memory for firmwares");
  838. return;
  839. }
  840. submenu_reset(app->submenu_firmwares);
  841. submenu_set_header(app->submenu_firmwares, "ESP32 Firmwares");
  842. for (int i = 0; i < FIRMWARE_COUNT; i++)
  843. {
  844. submenu_add_item(app->submenu_firmwares, firmwares[i].name, FlipStoreSubmenuIndexStartFirmwares + i, callback_submenu_choices, app);
  845. }
  846. view_dispatcher_switch_to_view(app->view_dispatcher, FlipStoreViewFirmwares);
  847. break;
  848. case FlipStoreSubmenuIndexVGMFirmwares: // vgm firmwares
  849. vgm_firmwares = vgm_firmware_alloc();
  850. if (vgm_firmwares == NULL)
  851. {
  852. FURI_LOG_E(TAG, "Failed to allocate memory for vgm firmwares");
  853. return;
  854. }
  855. submenu_reset(app->submenu_vgm_firmwares);
  856. submenu_set_header(app->submenu_vgm_firmwares, "VGM Firmwares");
  857. for (int i = 0; i < VGM_FIRMWARE_COUNT; i++)
  858. {
  859. submenu_add_item(app->submenu_vgm_firmwares, vgm_firmwares[i].name, FlipStoreSubmenuIndexStartVGMFirmwares + i, callback_submenu_choices, app);
  860. }
  861. view_dispatcher_switch_to_view(app->view_dispatcher, FlipStoreViewVGMFirmwares);
  862. break;
  863. case FlipStoreSubmenuIndexAppListBluetooth:
  864. free_all_views(app, true);
  865. flip_store_category_index = 0;
  866. flip_store_app_does_exist = false;
  867. fetch_appropiate_app_list(app, 0);
  868. break;
  869. case FlipStoreSubmenuIndexAppListGames:
  870. free_all_views(app, true);
  871. flip_store_category_index = 1;
  872. flip_store_app_does_exist = false;
  873. fetch_appropiate_app_list(app, 0);
  874. break;
  875. case FlipStoreSubmenuIndexAppListGPIO:
  876. free_all_views(app, true);
  877. flip_store_category_index = 2;
  878. flip_store_app_does_exist = false;
  879. fetch_appropiate_app_list(app, 0);
  880. break;
  881. case FlipStoreSubmenuIndexAppListInfrared:
  882. free_all_views(app, true);
  883. flip_store_category_index = 3;
  884. flip_store_app_does_exist = false;
  885. fetch_appropiate_app_list(app, 0);
  886. break;
  887. case FlipStoreSubmenuIndexAppListiButton:
  888. free_all_views(app, true);
  889. flip_store_category_index = 4;
  890. flip_store_app_does_exist = false;
  891. fetch_appropiate_app_list(app, 0);
  892. break;
  893. case FlipStoreSubmenuIndexAppListMedia:
  894. free_all_views(app, true);
  895. flip_store_category_index = 5;
  896. flip_store_app_does_exist = false;
  897. fetch_appropiate_app_list(app, 0);
  898. break;
  899. case FlipStoreSubmenuIndexAppListNFC:
  900. free_all_views(app, true);
  901. flip_store_category_index = 6;
  902. flip_store_app_does_exist = false;
  903. fetch_appropiate_app_list(app, 0);
  904. break;
  905. case FlipStoreSubmenuIndexAppListRFID:
  906. free_all_views(app, true);
  907. flip_store_category_index = 7;
  908. flip_store_app_does_exist = false;
  909. fetch_appropiate_app_list(app, 0);
  910. break;
  911. case FlipStoreSubmenuIndexAppListSubGHz:
  912. free_all_views(app, true);
  913. flip_store_category_index = 8;
  914. flip_store_app_does_exist = false;
  915. fetch_appropiate_app_list(app, 0);
  916. break;
  917. case FlipStoreSubmenuIndexAppListTools:
  918. free_all_views(app, true);
  919. flip_store_category_index = 9;
  920. flip_store_app_does_exist = false;
  921. fetch_appropiate_app_list(app, 0);
  922. break;
  923. case FlipStoreSubmenuIndexAppListUSB:
  924. free_all_views(app, true);
  925. flip_store_category_index = 10;
  926. flip_store_app_does_exist = false;
  927. fetch_appropiate_app_list(app, 0);
  928. break;
  929. default:
  930. // Check if the index is within the ESP32 firmwares list range
  931. if (index >= FlipStoreSubmenuIndexStartFirmwares && index < FlipStoreSubmenuIndexStartFirmwares + FIRMWARE_COUNT)
  932. {
  933. // Get the firmware index
  934. uint32_t firmware_index = index - FlipStoreSubmenuIndexStartFirmwares;
  935. // Check if the firmware index is valid
  936. if ((int)firmware_index >= 0 && firmware_index < FIRMWARE_COUNT)
  937. {
  938. // Get the firmware name
  939. selected_firmware_index = firmware_index;
  940. is_esp32_firmware = true;
  941. // Switch to the firmware download view
  942. free_dialog_firmware(app);
  943. if (!alloc_dialog_firmware(app))
  944. {
  945. FURI_LOG_E(TAG, "Failed to allocate dialog firmware");
  946. return;
  947. }
  948. view_dispatcher_switch_to_view(app->view_dispatcher, FlipStoreViewFirmwareDialog);
  949. }
  950. else
  951. {
  952. FURI_LOG_E(TAG, "Invalid firmware index");
  953. easy_flipper_dialog("Error", "Issue parsing firmware.");
  954. }
  955. }
  956. // Check if the index is within the VGM firmwares list range
  957. else if (index >= FlipStoreSubmenuIndexStartVGMFirmwares && index < FlipStoreSubmenuIndexStartVGMFirmwares + VGM_FIRMWARE_COUNT)
  958. {
  959. // Get the firmware index
  960. uint32_t firmware_index = index - FlipStoreSubmenuIndexStartVGMFirmwares;
  961. // Check if the firmware index is valid
  962. if ((int)firmware_index >= 0 && firmware_index < VGM_FIRMWARE_COUNT)
  963. {
  964. // Get the firmware name
  965. selected_firmware_index = firmware_index;
  966. is_esp32_firmware = false;
  967. // Switch to the firmware download view
  968. free_dialog_firmware(app);
  969. if (!alloc_dialog_firmware(app))
  970. {
  971. FURI_LOG_E(TAG, "Failed to allocate dialog firmware");
  972. return;
  973. }
  974. view_dispatcher_switch_to_view(app->view_dispatcher, FlipStoreViewFirmwareDialog);
  975. }
  976. else
  977. {
  978. FURI_LOG_E(TAG, "Invalid firmware index");
  979. easy_flipper_dialog("Error", "Issue parsing firmware.");
  980. }
  981. }
  982. // Check if the index is within the app list range
  983. else if (index >= FlipStoreSubmenuIndexStartAppList && index < FlipStoreSubmenuIndexStartAppList + flip_catalog->count)
  984. {
  985. // Get the app index
  986. uint32_t app_index = index - FlipStoreSubmenuIndexStartAppList;
  987. // Check if the app index is valid
  988. if ((int)app_index >= 0 && app_index < flip_catalog->count)
  989. {
  990. // Get the app name
  991. char *app_name = flip_catalog[app_index].app_name;
  992. // Check if the app name is valid
  993. if (app_name != NULL && strlen(app_name) > 0)
  994. {
  995. app_selected_index = app_index;
  996. flip_store_app_does_exist = app_exists(flip_catalog[app_selected_index].app_id, categories[flip_store_category_index]);
  997. free_app_info_view(app);
  998. if (!alloc_app_info_view(app))
  999. {
  1000. FURI_LOG_E(TAG, "Failed to allocate app info view");
  1001. return;
  1002. }
  1003. view_dispatcher_switch_to_view(app->view_dispatcher, FlipStoreViewAppInfo);
  1004. }
  1005. else
  1006. {
  1007. FURI_LOG_E(TAG, "Invalid app name");
  1008. }
  1009. }
  1010. else
  1011. {
  1012. FURI_LOG_E(TAG, "Invalid app index");
  1013. }
  1014. }
  1015. // Check if the index is for loading the next set of apps
  1016. else if (index == FlipStoreSubmenuIndexStartAppList + flip_catalog->count)
  1017. {
  1018. catalog_iteration = flip_catalog->iteration + 8;
  1019. view_dispatcher_switch_to_view(app->view_dispatcher, FlipStoreViewWidgetResult);
  1020. free_category_submenu(app);
  1021. flip_catalog_free();
  1022. fetch_appropiate_app_list(app, catalog_iteration);
  1023. }
  1024. else
  1025. {
  1026. FURI_LOG_E(TAG, "Unknown submenu index");
  1027. }
  1028. break;
  1029. }
  1030. }
  1031. static void settings_item_selected(void *context, uint32_t index)
  1032. {
  1033. FlipStoreApp *app = (FlipStoreApp *)context;
  1034. if (!app)
  1035. {
  1036. FURI_LOG_E(TAG, "FlipStoreApp is NULL");
  1037. return;
  1038. }
  1039. char ssid[64];
  1040. char pass[64];
  1041. switch (index)
  1042. {
  1043. case 0: // Input SSID
  1044. // Text Input
  1045. free_all_views(app, false);
  1046. if (!alloc_text_input_view(app, "SSID"))
  1047. {
  1048. FURI_LOG_E(TAG, "Failed to allocate text input view");
  1049. return;
  1050. }
  1051. // load SSID
  1052. if (load_settings(ssid, sizeof(ssid), pass, sizeof(pass)))
  1053. {
  1054. strncpy(app->uart_text_input_temp_buffer, ssid, app->uart_text_input_buffer_size - 1);
  1055. app->uart_text_input_temp_buffer[app->uart_text_input_buffer_size - 1] = '\0';
  1056. }
  1057. view_dispatcher_switch_to_view(app->view_dispatcher, FlipStoreViewTextInput);
  1058. break;
  1059. case 1: // Input Password
  1060. free_all_views(app, false);
  1061. if (!alloc_text_input_view(app, "Password"))
  1062. {
  1063. FURI_LOG_E(TAG, "Failed to allocate text input view");
  1064. return;
  1065. }
  1066. // load password
  1067. if (load_settings(ssid, sizeof(ssid), pass, sizeof(pass)))
  1068. {
  1069. strncpy(app->uart_text_input_temp_buffer, pass, app->uart_text_input_buffer_size - 1);
  1070. app->uart_text_input_temp_buffer[app->uart_text_input_buffer_size - 1] = '\0';
  1071. }
  1072. view_dispatcher_switch_to_view(app->view_dispatcher, FlipStoreViewTextInput);
  1073. break;
  1074. default:
  1075. FURI_LOG_E(TAG, "Unknown configuration item index");
  1076. break;
  1077. }
  1078. }
  1079. static void flip_store_widget_set_text(char *message, Widget **widget)
  1080. {
  1081. if (widget == NULL)
  1082. {
  1083. FURI_LOG_E(TAG, "flip_store_set_widget_text - widget is NULL");
  1084. DEV_CRASH();
  1085. return;
  1086. }
  1087. if (message == NULL)
  1088. {
  1089. FURI_LOG_E(TAG, "flip_store_set_widget_text - message is NULL");
  1090. DEV_CRASH();
  1091. return;
  1092. }
  1093. widget_reset(*widget);
  1094. uint32_t message_length = strlen(message); // Length of the message
  1095. uint32_t i = 0; // Index tracker
  1096. uint32_t formatted_index = 0; // Tracker for where we are in the formatted message
  1097. char *formatted_message; // Buffer to hold the final formatted message
  1098. // Allocate buffer with double the message length plus one for safety
  1099. if (!easy_flipper_set_buffer(&formatted_message, message_length * 2 + 1))
  1100. {
  1101. return;
  1102. }
  1103. while (i < message_length)
  1104. {
  1105. uint32_t max_line_length = 31; // Maximum characters per line
  1106. uint32_t remaining_length = message_length - i; // Remaining characters
  1107. uint32_t line_length = (remaining_length < max_line_length) ? remaining_length : max_line_length;
  1108. // Check for newline character within the current segment
  1109. uint32_t newline_pos = i;
  1110. bool found_newline = false;
  1111. for (; newline_pos < i + line_length && newline_pos < message_length; newline_pos++)
  1112. {
  1113. if (message[newline_pos] == '\n')
  1114. {
  1115. found_newline = true;
  1116. break;
  1117. }
  1118. }
  1119. if (found_newline)
  1120. {
  1121. // If newline found, set line_length up to the newline
  1122. line_length = newline_pos - i;
  1123. }
  1124. // Temporary buffer to hold the current line
  1125. char line[32];
  1126. strncpy(line, message + i, line_length);
  1127. line[line_length] = '\0';
  1128. // If newline was found, skip it for the next iteration
  1129. if (found_newline)
  1130. {
  1131. i += line_length + 1; // +1 to skip the '\n' character
  1132. }
  1133. else
  1134. {
  1135. // Check if the line ends in the middle of a word and adjust accordingly
  1136. if (line_length == max_line_length && message[i + line_length] != '\0' && message[i + line_length] != ' ')
  1137. {
  1138. // Find the last space within the current line to avoid breaking a word
  1139. char *last_space = strrchr(line, ' ');
  1140. if (last_space != NULL)
  1141. {
  1142. // Adjust the line_length to avoid cutting the word
  1143. line_length = last_space - line;
  1144. line[line_length] = '\0'; // Null-terminate at the space
  1145. }
  1146. }
  1147. // Move the index forward by the determined line_length
  1148. i += line_length;
  1149. // Skip any spaces at the beginning of the next line
  1150. while (i < message_length && message[i] == ' ')
  1151. {
  1152. i++;
  1153. }
  1154. }
  1155. // Manually copy the fixed line into the formatted_message buffer
  1156. for (uint32_t j = 0; j < line_length; j++)
  1157. {
  1158. formatted_message[formatted_index++] = line[j];
  1159. }
  1160. // Add a newline character for line spacing
  1161. formatted_message[formatted_index++] = '\n';
  1162. }
  1163. // Null-terminate the formatted_message
  1164. formatted_message[formatted_index] = '\0';
  1165. // Add the formatted message to the widget
  1166. widget_add_text_scroll_element(*widget, 0, 0, 128, 64, formatted_message);
  1167. }
  1168. static void flip_store_request_error(Canvas *canvas, FlipperHTTP *fhttp)
  1169. {
  1170. if (!canvas)
  1171. {
  1172. FURI_LOG_E(TAG, "Canvas is NULL");
  1173. return;
  1174. }
  1175. if (!fhttp)
  1176. {
  1177. FURI_LOG_E(TAG, "FlipperHTTP is NULL");
  1178. return;
  1179. }
  1180. if (fhttp->last_response != NULL)
  1181. {
  1182. if (strstr(fhttp->last_response, "[ERROR] Not connected to Wifi. Failed to reconnect.") != NULL)
  1183. {
  1184. canvas_clear(canvas);
  1185. canvas_draw_str(canvas, 0, 10, "[ERROR] Not connected to Wifi.");
  1186. canvas_draw_str(canvas, 0, 50, "Update your WiFi settings.");
  1187. canvas_draw_str(canvas, 0, 60, "Press BACK to return.");
  1188. }
  1189. else if (strstr(fhttp->last_response, "[ERROR] Failed to connect to Wifi.") != NULL)
  1190. {
  1191. canvas_clear(canvas);
  1192. canvas_draw_str(canvas, 0, 10, "[ERROR] Not connected to Wifi.");
  1193. canvas_draw_str(canvas, 0, 50, "Update your WiFi settings.");
  1194. canvas_draw_str(canvas, 0, 60, "Press BACK to return.");
  1195. }
  1196. else
  1197. {
  1198. FURI_LOG_E(TAG, "Received an error: %s", fhttp->last_response);
  1199. canvas_draw_str(canvas, 0, 42, "Unusual error...");
  1200. canvas_draw_str(canvas, 0, 50, "Update your WiFi settings.");
  1201. canvas_draw_str(canvas, 0, 60, "Press BACK to return.");
  1202. }
  1203. }
  1204. else
  1205. {
  1206. canvas_clear(canvas);
  1207. canvas_draw_str(canvas, 0, 10, "[ERROR] Unknown error.");
  1208. canvas_draw_str(canvas, 0, 50, "Update your WiFi settings.");
  1209. canvas_draw_str(canvas, 0, 60, "Press BACK to return.");
  1210. }
  1211. }
  1212. void flip_store_loader_draw_callback(Canvas *canvas, void *model)
  1213. {
  1214. if (!canvas || !model)
  1215. {
  1216. FURI_LOG_E(TAG, "flip_store_loader_draw_callback - canvas or model is NULL");
  1217. return;
  1218. }
  1219. DataLoaderModel *data_loader_model = (DataLoaderModel *)model;
  1220. SerialState http_state = data_loader_model->fhttp->state;
  1221. DataState data_state = data_loader_model->data_state;
  1222. char *title = data_loader_model->title;
  1223. canvas_set_font(canvas, FontSecondary);
  1224. if (http_state == INACTIVE)
  1225. {
  1226. canvas_draw_str(canvas, 0, 7, "Wifi Dev Board disconnected.");
  1227. canvas_draw_str(canvas, 0, 17, "Please connect to the board.");
  1228. canvas_draw_str(canvas, 0, 32, "If your board is connected,");
  1229. canvas_draw_str(canvas, 0, 42, "make sure you have flashed");
  1230. canvas_draw_str(canvas, 0, 52, "your WiFi Devboard with the");
  1231. canvas_draw_str(canvas, 0, 62, "latest FlipperHTTP flash.");
  1232. return;
  1233. }
  1234. if (data_state == DataStateError || data_state == DataStateParseError)
  1235. {
  1236. flip_store_request_error(canvas, data_loader_model->fhttp);
  1237. return;
  1238. }
  1239. canvas_draw_str(canvas, 0, 7, title);
  1240. canvas_draw_str(canvas, 0, 17, "Loading...");
  1241. if (data_state == DataStateInitial)
  1242. {
  1243. return;
  1244. }
  1245. if (http_state == SENDING)
  1246. {
  1247. canvas_draw_str(canvas, 0, 27, "Fetching...");
  1248. return;
  1249. }
  1250. if (http_state == RECEIVING || data_state == DataStateRequested)
  1251. {
  1252. canvas_draw_str(canvas, 0, 27, "Receiving...");
  1253. return;
  1254. }
  1255. if (http_state == IDLE && data_state == DataStateReceived)
  1256. {
  1257. canvas_draw_str(canvas, 0, 27, "Processing...");
  1258. return;
  1259. }
  1260. if (http_state == IDLE && data_state == DataStateParsed)
  1261. {
  1262. canvas_draw_str(canvas, 0, 27, "Processed...");
  1263. return;
  1264. }
  1265. }
  1266. static void flip_store_loader_process_callback(void *context)
  1267. {
  1268. if (context == NULL)
  1269. {
  1270. FURI_LOG_E(TAG, "flip_store_loader_process_callback - context is NULL");
  1271. DEV_CRASH();
  1272. return;
  1273. }
  1274. FlipStoreApp *app = (FlipStoreApp *)context;
  1275. View *view = app->view_loader;
  1276. DataState current_data_state;
  1277. DataLoaderModel *loader_model = NULL;
  1278. with_view_model(view, DataLoaderModel * model, { current_data_state = model->data_state; loader_model = model; }, false);
  1279. if (!loader_model || !loader_model->fhttp)
  1280. {
  1281. FURI_LOG_E(TAG, "Model or fhttp is NULL");
  1282. DEV_CRASH();
  1283. return;
  1284. }
  1285. if (current_data_state == DataStateInitial)
  1286. {
  1287. with_view_model(
  1288. view,
  1289. DataLoaderModel * model,
  1290. {
  1291. model->data_state = DataStateRequested;
  1292. DataLoaderFetch fetch = model->fetcher;
  1293. if (fetch == NULL)
  1294. {
  1295. FURI_LOG_E(TAG, "Model doesn't have Fetch function assigned.");
  1296. model->data_state = DataStateError;
  1297. return;
  1298. }
  1299. // Clear any previous responses
  1300. strncpy(model->fhttp->last_response, "", 1);
  1301. bool request_status = fetch(model);
  1302. if (!request_status)
  1303. {
  1304. model->data_state = DataStateError;
  1305. }
  1306. },
  1307. true);
  1308. }
  1309. else if (current_data_state == DataStateRequested || current_data_state == DataStateError)
  1310. {
  1311. if (loader_model->fhttp->state == IDLE && loader_model->fhttp->last_response != NULL)
  1312. {
  1313. if (strstr(loader_model->fhttp->last_response, "[PONG]") != NULL)
  1314. {
  1315. FURI_LOG_DEV(TAG, "PONG received.");
  1316. }
  1317. else if (strncmp(loader_model->fhttp->last_response, "[SUCCESS]", 9) == 0)
  1318. {
  1319. FURI_LOG_DEV(TAG, "SUCCESS received. %s", loader_model->fhttp->last_response ? loader_model->fhttp->last_response : "NULL");
  1320. }
  1321. else if (strncmp(loader_model->fhttp->last_response, "[ERROR]", 9) == 0)
  1322. {
  1323. FURI_LOG_DEV(TAG, "ERROR received. %s", loader_model->fhttp->last_response ? loader_model->fhttp->last_response : "NULL");
  1324. }
  1325. else if (strlen(loader_model->fhttp->last_response) == 0)
  1326. {
  1327. // Still waiting on response
  1328. }
  1329. else
  1330. {
  1331. with_view_model(view, DataLoaderModel * model, { model->data_state = DataStateReceived; }, true);
  1332. }
  1333. }
  1334. else if (loader_model->fhttp->state == SENDING || loader_model->fhttp->state == RECEIVING)
  1335. {
  1336. // continue waiting
  1337. }
  1338. else if (loader_model->fhttp->state == INACTIVE)
  1339. {
  1340. // inactive. try again
  1341. }
  1342. else if (loader_model->fhttp->state == ISSUE)
  1343. {
  1344. with_view_model(view, DataLoaderModel * model, { model->data_state = DataStateError; }, true);
  1345. }
  1346. else
  1347. {
  1348. FURI_LOG_DEV(TAG, "Unexpected state: %d lastresp: %s", loader_model->fhttp->state, loader_model->fhttp->last_response ? loader_model->fhttp->last_response : "NULL");
  1349. DEV_CRASH();
  1350. }
  1351. }
  1352. else if (current_data_state == DataStateReceived)
  1353. {
  1354. with_view_model(
  1355. view,
  1356. DataLoaderModel * model,
  1357. {
  1358. char *data_text;
  1359. if (model->parser == NULL)
  1360. {
  1361. data_text = NULL;
  1362. FURI_LOG_DEV(TAG, "Parser is NULL");
  1363. DEV_CRASH();
  1364. }
  1365. else
  1366. {
  1367. data_text = model->parser(model);
  1368. }
  1369. FURI_LOG_DEV(TAG, "Parsed data: %s\r\ntext: %s", model->fhttp->last_response ? model->fhttp->last_response : "NULL", data_text ? data_text : "NULL");
  1370. model->data_text = data_text;
  1371. if (data_text == NULL)
  1372. {
  1373. model->data_state = DataStateParseError;
  1374. }
  1375. else
  1376. {
  1377. model->data_state = DataStateParsed;
  1378. }
  1379. },
  1380. true);
  1381. }
  1382. else if (current_data_state == DataStateParsed)
  1383. {
  1384. with_view_model(
  1385. view,
  1386. DataLoaderModel * model,
  1387. {
  1388. if (++model->request_index < model->request_count)
  1389. {
  1390. model->data_state = DataStateInitial;
  1391. }
  1392. else
  1393. {
  1394. flip_store_widget_set_text(model->data_text != NULL ? model->data_text : "", &app->widget_result);
  1395. if (model->data_text != NULL)
  1396. {
  1397. free(model->data_text);
  1398. model->data_text = NULL;
  1399. }
  1400. view_set_previous_callback(widget_get_view(app->widget_result), model->back_callback);
  1401. view_dispatcher_switch_to_view(app->view_dispatcher, FlipStoreViewWidgetResult);
  1402. }
  1403. },
  1404. true);
  1405. }
  1406. }
  1407. static void flip_store_loader_timer_callback(void *context)
  1408. {
  1409. if (context == NULL)
  1410. {
  1411. FURI_LOG_E(TAG, "flip_store_loader_timer_callback - context is NULL");
  1412. DEV_CRASH();
  1413. return;
  1414. }
  1415. FlipStoreApp *app = (FlipStoreApp *)context;
  1416. view_dispatcher_send_custom_event(app->view_dispatcher, FlipStoreCustomEventProcess);
  1417. }
  1418. static void flip_store_loader_on_enter(void *context)
  1419. {
  1420. if (context == NULL)
  1421. {
  1422. FURI_LOG_E(TAG, "flip_store_loader_on_enter - context is NULL");
  1423. DEV_CRASH();
  1424. return;
  1425. }
  1426. FlipStoreApp *app = (FlipStoreApp *)context;
  1427. View *view = app->view_loader;
  1428. with_view_model(
  1429. view,
  1430. DataLoaderModel * model,
  1431. {
  1432. view_set_previous_callback(view, model->back_callback);
  1433. if (model->timer == NULL)
  1434. {
  1435. model->timer = furi_timer_alloc(flip_store_loader_timer_callback, FuriTimerTypePeriodic, app);
  1436. }
  1437. furi_timer_start(model->timer, 250);
  1438. },
  1439. true);
  1440. }
  1441. static void flip_store_loader_on_exit(void *context)
  1442. {
  1443. if (context == NULL)
  1444. {
  1445. FURI_LOG_E(TAG, "flip_store_loader_on_exit - context is NULL");
  1446. DEV_CRASH();
  1447. return;
  1448. }
  1449. FlipStoreApp *app = (FlipStoreApp *)context;
  1450. View *view = app->view_loader;
  1451. with_view_model(
  1452. view,
  1453. DataLoaderModel * model,
  1454. {
  1455. if (model->timer)
  1456. {
  1457. furi_timer_stop(model->timer);
  1458. }
  1459. },
  1460. false);
  1461. }
  1462. void flip_store_loader_init(View *view)
  1463. {
  1464. if (view == NULL)
  1465. {
  1466. FURI_LOG_E(TAG, "flip_store_loader_init - view is NULL");
  1467. DEV_CRASH();
  1468. return;
  1469. }
  1470. view_allocate_model(view, ViewModelTypeLocking, sizeof(DataLoaderModel));
  1471. view_set_enter_callback(view, flip_store_loader_on_enter);
  1472. view_set_exit_callback(view, flip_store_loader_on_exit);
  1473. }
  1474. void flip_store_loader_free_model(View *view)
  1475. {
  1476. if (view == NULL)
  1477. {
  1478. FURI_LOG_E(TAG, "flip_store_loader_free_model - view is NULL");
  1479. DEV_CRASH();
  1480. return;
  1481. }
  1482. with_view_model(
  1483. view,
  1484. DataLoaderModel * model,
  1485. {
  1486. if (model->timer)
  1487. {
  1488. furi_timer_free(model->timer);
  1489. model->timer = NULL;
  1490. }
  1491. if (model->parser_context)
  1492. {
  1493. // do not free the context here, it is the app context
  1494. // free(model->parser_context);
  1495. // model->parser_context = NULL;
  1496. }
  1497. if (model->fhttp)
  1498. {
  1499. flipper_http_free(model->fhttp);
  1500. model->fhttp = NULL;
  1501. }
  1502. },
  1503. false);
  1504. }
  1505. bool flip_store_custom_event_callback(void *context, uint32_t index)
  1506. {
  1507. if (context == NULL)
  1508. {
  1509. FURI_LOG_E(TAG, "flip_store_custom_event_callback - context is NULL");
  1510. DEV_CRASH();
  1511. return false;
  1512. }
  1513. switch (index)
  1514. {
  1515. case FlipStoreCustomEventProcess:
  1516. flip_store_loader_process_callback(context);
  1517. return true;
  1518. default:
  1519. FURI_LOG_DEV(TAG, "flip_store_custom_event_callback. Unknown index: %ld", index);
  1520. return false;
  1521. }
  1522. }
  1523. void flip_store_generic_switch_to_view(FlipStoreApp *app, char *title, DataLoaderFetch fetcher, DataLoaderParser parser, size_t request_count, ViewNavigationCallback back, uint32_t view_id)
  1524. {
  1525. if (app == NULL)
  1526. {
  1527. FURI_LOG_E(TAG, "flip_store_generic_switch_to_view - app is NULL");
  1528. DEV_CRASH();
  1529. return;
  1530. }
  1531. View *view = app->view_loader;
  1532. if (view == NULL)
  1533. {
  1534. FURI_LOG_E(TAG, "flip_store_generic_switch_to_view - view is NULL");
  1535. DEV_CRASH();
  1536. return;
  1537. }
  1538. with_view_model(
  1539. view,
  1540. DataLoaderModel * model,
  1541. {
  1542. model->title = title;
  1543. model->fetcher = fetcher;
  1544. model->parser = parser;
  1545. model->request_index = 0;
  1546. model->request_count = request_count;
  1547. model->back_callback = back;
  1548. model->data_state = DataStateInitial;
  1549. model->data_text = NULL;
  1550. //
  1551. model->parser_context = app;
  1552. if (!model->fhttp)
  1553. {
  1554. model->fhttp = flipper_http_alloc();
  1555. }
  1556. },
  1557. true);
  1558. view_dispatcher_switch_to_view(app->view_dispatcher, view_id);
  1559. }