flip_store_callback.c 49 KB

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