callback.c 109 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947
  1. #include <callback/callback.h>
  2. #include "engine/engine.h"
  3. #include "engine/game_engine.h"
  4. #include "engine/game_manager_i.h"
  5. #include "engine/level_i.h"
  6. #include "engine/entity_i.h"
  7. #include "game/storage.h"
  8. #include "alloc/alloc.h"
  9. // Below added by Derek Jamison
  10. // FURI_LOG_DEV will log only during app development. Be sure that Settings/System/Log Device is "LPUART"; so we dont use serial port.
  11. #ifdef DEVELOPMENT
  12. #define FURI_LOG_DEV(tag, format, ...) furi_log_print_format(FuriLogLevelInfo, tag, format, ##__VA_ARGS__)
  13. #define DEV_CRASH() furi_crash()
  14. #else
  15. #define FURI_LOG_DEV(tag, format, ...)
  16. #define DEV_CRASH()
  17. #endif
  18. static void frame_cb(GameEngine *engine, Canvas *canvas, InputState input, void *context)
  19. {
  20. UNUSED(engine);
  21. GameManager *game_manager = context;
  22. game_manager_input_set(game_manager, input);
  23. game_manager_update(game_manager);
  24. game_manager_render(game_manager, canvas);
  25. }
  26. static int32_t game_app(void *p)
  27. {
  28. UNUSED(p);
  29. GameManager *game_manager = game_manager_alloc();
  30. if (!game_manager)
  31. {
  32. FURI_LOG_E("Game", "Failed to allocate game manager");
  33. return -1;
  34. }
  35. // Setup game engine settings...
  36. GameEngineSettings settings = game_engine_settings_init();
  37. settings.target_fps = atof_(fps_choices_str[fps_index]);
  38. settings.show_fps = game.show_fps;
  39. settings.always_backlight = strstr(yes_or_no_choices[screen_always_on_index], "Yes") != NULL;
  40. settings.frame_callback = frame_cb;
  41. settings.context = game_manager;
  42. GameEngine *engine = game_engine_alloc(settings);
  43. if (!engine)
  44. {
  45. FURI_LOG_E("Game", "Failed to allocate game engine");
  46. game_manager_free(game_manager);
  47. return -1;
  48. }
  49. game_manager_engine_set(game_manager, engine);
  50. // Allocate custom game context if needed
  51. void *game_context = NULL;
  52. if (game.context_size > 0)
  53. {
  54. game_context = malloc(game.context_size);
  55. game_manager_game_context_set(game_manager, game_context);
  56. }
  57. // Start the game
  58. game.start(game_manager, game_context);
  59. // 1) Run the engine
  60. game_engine_run(engine);
  61. // 2) Stop the game FIRST, so it can do any internal cleanup
  62. game.stop(game_context);
  63. // 3) Now free the engine
  64. game_engine_free(engine);
  65. // 4) Now free the manager
  66. game_manager_free(game_manager);
  67. // 5) Finally, free your custom context if it was allocated
  68. if (game_context)
  69. {
  70. free(game_context);
  71. }
  72. // 6) Check for leftover entities
  73. int32_t entities = entities_get_count();
  74. if (entities != 0)
  75. {
  76. FURI_LOG_E("Game", "Memory leak detected: %ld entities still allocated", entities);
  77. return -1;
  78. }
  79. return 0;
  80. }
  81. static void error_draw(Canvas *canvas, DataLoaderModel *model)
  82. {
  83. if (canvas == NULL)
  84. {
  85. FURI_LOG_E(TAG, "error_draw - canvas is NULL");
  86. DEV_CRASH();
  87. return;
  88. }
  89. if (model->fhttp->last_response != NULL)
  90. {
  91. if (strstr(model->fhttp->last_response, "[ERROR] Not connected to Wifi. Failed to reconnect.") != NULL)
  92. {
  93. canvas_clear(canvas);
  94. canvas_draw_str(canvas, 0, 10, "[ERROR] Not connected to Wifi.");
  95. canvas_draw_str(canvas, 0, 50, "Update your WiFi settings.");
  96. canvas_draw_str(canvas, 0, 60, "Press BACK to return.");
  97. }
  98. else if (strstr(model->fhttp->last_response, "[ERROR] Failed to connect to Wifi.") != NULL)
  99. {
  100. canvas_clear(canvas);
  101. canvas_draw_str(canvas, 0, 10, "[ERROR] Not connected to Wifi.");
  102. canvas_draw_str(canvas, 0, 50, "Update your WiFi settings.");
  103. canvas_draw_str(canvas, 0, 60, "Press BACK to return.");
  104. }
  105. else if (strstr(model->fhttp->last_response, "[ERROR] GET request failed or returned empty data.") != NULL)
  106. {
  107. canvas_clear(canvas);
  108. canvas_draw_str(canvas, 0, 10, "[ERROR] WiFi error.");
  109. canvas_draw_str(canvas, 0, 50, "Update your WiFi settings.");
  110. canvas_draw_str(canvas, 0, 60, "Press BACK to return.");
  111. }
  112. else if (strstr(model->fhttp->last_response, "[PONG]") != NULL)
  113. {
  114. canvas_clear(canvas);
  115. canvas_draw_str(canvas, 0, 10, "[STATUS]Connecting to AP...");
  116. }
  117. else
  118. {
  119. canvas_clear(canvas);
  120. FURI_LOG_E(TAG, "Received an error: %s", model->fhttp->last_response);
  121. canvas_draw_str(canvas, 0, 10, "[ERROR] Unusual error...");
  122. canvas_draw_str(canvas, 0, 60, "Press BACK and retry.");
  123. }
  124. }
  125. else
  126. {
  127. canvas_clear(canvas);
  128. canvas_draw_str(canvas, 0, 10, "[ERROR] Unknown error.");
  129. canvas_draw_str(canvas, 0, 50, "Update your WiFi settings.");
  130. canvas_draw_str(canvas, 0, 60, "Press BACK to return.");
  131. }
  132. }
  133. static bool alloc_message_view(void *context, MessageState state);
  134. static bool alloc_text_input_view(void *context, char *title);
  135. static bool alloc_variable_item_list(void *context, uint32_t view_id);
  136. //
  137. static void callback_submenu_lobby_choices(void *context, uint32_t index);
  138. //
  139. static void wifi_settings_select(void *context, uint32_t index);
  140. static void updated_wifi_ssid(void *context);
  141. static void updated_wifi_pass(void *context);
  142. static void updated_username(void *context);
  143. static void updated_password(void *context);
  144. //
  145. static void fps_change(VariableItem *item);
  146. static void game_settings_select(void *context, uint32_t index);
  147. static void user_settings_select(void *context, uint32_t index);
  148. static void screen_on_change(VariableItem *item);
  149. static void sound_on_change(VariableItem *item);
  150. static void vibration_on_change(VariableItem *item);
  151. static void player_on_change(VariableItem *item);
  152. static void vgm_x_change(VariableItem *item);
  153. static void vgm_y_change(VariableItem *item);
  154. //
  155. static uint8_t timer_iteration = 0; // timer iteration for the loading screen
  156. static uint8_t timer_refresh = 5; // duration for timer to refresh
  157. //
  158. void waiting_loader_process_callback(FlipperHTTP *fhttp, void *context);
  159. static void waiting_lobby(void *context);
  160. static uint32_t lobby_index = -1;
  161. static char *lobby_list[10];
  162. static bool fetch_lobby(FlipperHTTP *fhttp, char *lobby_name);
  163. bool user_hit_back = false;
  164. static bool message_input_callback(InputEvent *event, void *context)
  165. {
  166. FlipWorldApp *app = (FlipWorldApp *)context;
  167. furi_check(app);
  168. if (event->key == InputKeyBack)
  169. {
  170. FURI_LOG_I(TAG, "Message view - BACK pressed");
  171. user_hit_back = true;
  172. }
  173. return true;
  174. }
  175. uint32_t callback_to_submenu(void *context)
  176. {
  177. UNUSED(context);
  178. return FlipWorldViewSubmenu;
  179. }
  180. static uint32_t callback_to_wifi_settings(void *context)
  181. {
  182. UNUSED(context);
  183. return FlipWorldViewVariableItemList;
  184. }
  185. static uint32_t callback_to_settings(void *context)
  186. {
  187. UNUSED(context);
  188. return FlipWorldViewSubmenuOther;
  189. }
  190. static int32_t waiting_app_callback(void *p)
  191. {
  192. FlipWorldApp *app = (FlipWorldApp *)p;
  193. furi_check(app);
  194. FlipperHTTP *fhttp = flipper_http_alloc();
  195. if (!fhttp)
  196. {
  197. FURI_LOG_E(TAG, "Failed to allocate FlipperHTTP");
  198. easy_flipper_dialog("Error", "Failed to allocate FlipperHTTP");
  199. return -1;
  200. }
  201. user_hit_back = false;
  202. timer_iteration = 0;
  203. while (timer_iteration < 60 && !user_hit_back)
  204. {
  205. FURI_LOG_I(TAG, "Waiting for more players...");
  206. waiting_loader_process_callback(fhttp, app);
  207. FURI_LOG_I(TAG, "Waiting for more players... %d", timer_iteration);
  208. timer_iteration++;
  209. furi_delay_ms(1000 * timer_refresh);
  210. }
  211. // if we reach here, it means we timed out or the user hit back
  212. FURI_LOG_E(TAG, "No players joined within the timeout or user hit back");
  213. remove_player_from_lobby(fhttp);
  214. flipper_http_free(fhttp);
  215. view_dispatcher_switch_to_view(app->view_dispatcher, FlipWorldViewSubmenuOther);
  216. return 0;
  217. }
  218. static void message_draw_callback(Canvas *canvas, void *model)
  219. {
  220. MessageModel *message_model = model;
  221. canvas_clear(canvas);
  222. if (message_model->message_state == MessageStateAbout)
  223. {
  224. canvas_draw_str(canvas, 0, 10, VERSION_TAG);
  225. canvas_set_font_custom(canvas, FONT_SIZE_SMALL);
  226. canvas_draw_str(canvas, 0, 20, "Dev: JBlanked, codeallnight");
  227. canvas_draw_str(canvas, 0, 30, "GFX: the1anonlypr3");
  228. canvas_draw_str(canvas, 0, 40, "github.com/jblanked/FlipWorld");
  229. canvas_draw_str_multi(canvas, 0, 55, "The first open world multiplayer\ngame on the Flipper Zero.");
  230. }
  231. else if (message_model->message_state == MessageStateLoading)
  232. {
  233. canvas_set_font(canvas, FontPrimary);
  234. if (game_mode_index != 1)
  235. {
  236. canvas_draw_str_aligned(canvas, 64, 0, AlignCenter, AlignTop, "Starting FlipWorld");
  237. canvas_set_font(canvas, FontSecondary);
  238. canvas_draw_str(canvas, 0, 50, "Please wait while your");
  239. canvas_draw_str(canvas, 0, 60, "game is started.");
  240. }
  241. else
  242. {
  243. canvas_draw_str_aligned(canvas, 64, 0, AlignCenter, AlignTop, "Loading Lobbies");
  244. canvas_set_font(canvas, FontSecondary);
  245. canvas_draw_str(canvas, 0, 60, "Please wait....");
  246. }
  247. }
  248. // well this is only called once so let's make a while loop
  249. else if (message_model->message_state == MessageStateWaitingLobby)
  250. {
  251. canvas_draw_str(canvas, 0, 10, "Waiting for more players...");
  252. // time elapsed based on timer_iteration and timer_refresh
  253. // char str[32];
  254. // snprintf(str, sizeof(str), "Time elapsed: %d seconds", timer_iteration * timer_refresh);
  255. // canvas_draw_str(canvas, 0, 50, str);
  256. canvas_draw_str(canvas, 0, 60, "Press BACK to cancel.");
  257. canvas_commit(canvas); // make sure message is drawn
  258. }
  259. else
  260. {
  261. canvas_draw_str(canvas, 0, 10, "Unknown message state");
  262. }
  263. }
  264. // alloc
  265. static bool alloc_message_view(void *context, MessageState state)
  266. {
  267. FlipWorldApp *app = (FlipWorldApp *)context;
  268. furi_check(app);
  269. if (app->view_message)
  270. {
  271. FURI_LOG_E(TAG, "Message view already allocated");
  272. return false;
  273. }
  274. switch (state)
  275. {
  276. case MessageStateAbout:
  277. easy_flipper_set_view(&app->view_message, FlipWorldViewMessage, message_draw_callback, NULL, callback_to_submenu, &app->view_dispatcher, app);
  278. break;
  279. case MessageStateLoading:
  280. easy_flipper_set_view(&app->view_message, FlipWorldViewMessage, message_draw_callback, NULL, NULL, &app->view_dispatcher, app);
  281. break;
  282. case MessageStateWaitingLobby:
  283. easy_flipper_set_view(&app->view_message, FlipWorldViewMessage, message_draw_callback, message_input_callback, NULL, &app->view_dispatcher, app);
  284. break;
  285. }
  286. if (!app->view_message)
  287. {
  288. FURI_LOG_E(TAG, "Failed to allocate message view");
  289. return false;
  290. }
  291. view_allocate_model(app->view_message, ViewModelTypeLockFree, sizeof(MessageModel));
  292. MessageModel *model = view_get_model(app->view_message);
  293. model->message_state = state;
  294. return true;
  295. }
  296. static bool alloc_text_input_view(void *context, char *title)
  297. {
  298. FlipWorldApp *app = (FlipWorldApp *)context;
  299. furi_check(app);
  300. if (!title)
  301. {
  302. FURI_LOG_E(TAG, "Title is NULL");
  303. return false;
  304. }
  305. app->text_input_buffer_size = 64;
  306. if (!app->text_input_buffer)
  307. {
  308. if (!easy_flipper_set_buffer(&app->text_input_buffer, app->text_input_buffer_size))
  309. {
  310. return false;
  311. }
  312. }
  313. if (!app->text_input_temp_buffer)
  314. {
  315. if (!easy_flipper_set_buffer(&app->text_input_temp_buffer, app->text_input_buffer_size))
  316. {
  317. return false;
  318. }
  319. }
  320. if (!app->text_input)
  321. {
  322. if (!easy_flipper_set_uart_text_input(
  323. &app->text_input,
  324. FlipWorldViewTextInput,
  325. title,
  326. app->text_input_temp_buffer,
  327. app->text_input_buffer_size,
  328. is_str(title, "SSID") ? updated_wifi_ssid : is_str(title, "Password") ? updated_wifi_pass
  329. : is_str(title, "Username-Login") ? updated_username
  330. : updated_password,
  331. callback_to_wifi_settings,
  332. &app->view_dispatcher,
  333. app))
  334. {
  335. return false;
  336. }
  337. if (!app->text_input)
  338. {
  339. return false;
  340. }
  341. char ssid[64];
  342. char pass[64];
  343. char username[64];
  344. char password[64];
  345. if (load_settings(ssid, sizeof(ssid), pass, sizeof(pass), username, sizeof(username), password, sizeof(password)))
  346. {
  347. if (is_str(title, "SSID"))
  348. {
  349. strncpy(app->text_input_temp_buffer, ssid, app->text_input_buffer_size);
  350. }
  351. else if (is_str(title, "Password"))
  352. {
  353. strncpy(app->text_input_temp_buffer, pass, app->text_input_buffer_size);
  354. }
  355. else if (is_str(title, "Username-Login"))
  356. {
  357. strncpy(app->text_input_temp_buffer, username, app->text_input_buffer_size);
  358. }
  359. else if (is_str(title, "Password-Login"))
  360. {
  361. strncpy(app->text_input_temp_buffer, password, app->text_input_buffer_size);
  362. }
  363. }
  364. }
  365. return true;
  366. }
  367. static bool alloc_variable_item_list(void *context, uint32_t view_id)
  368. {
  369. FlipWorldApp *app = (FlipWorldApp *)context;
  370. if (!app)
  371. {
  372. FURI_LOG_E(TAG, "FlipWorldApp is NULL");
  373. return false;
  374. }
  375. char ssid[64];
  376. char pass[64];
  377. char username[64];
  378. char password[64];
  379. if (!app->variable_item_list)
  380. {
  381. switch (view_id)
  382. {
  383. case FlipWorldSubmenuIndexWiFiSettings:
  384. if (!easy_flipper_set_variable_item_list(&app->variable_item_list, FlipWorldViewVariableItemList, wifi_settings_select, callback_to_settings, &app->view_dispatcher, app))
  385. {
  386. FURI_LOG_E(TAG, "Failed to allocate variable item list");
  387. return false;
  388. }
  389. if (!app->variable_item_list)
  390. {
  391. FURI_LOG_E(TAG, "Variable item list is NULL");
  392. return false;
  393. }
  394. if (!app->variable_item_wifi_ssid)
  395. {
  396. app->variable_item_wifi_ssid = variable_item_list_add(app->variable_item_list, "SSID", 0, NULL, NULL);
  397. variable_item_set_current_value_text(app->variable_item_wifi_ssid, "");
  398. }
  399. if (!app->variable_item_wifi_pass)
  400. {
  401. app->variable_item_wifi_pass = variable_item_list_add(app->variable_item_list, "Password", 0, NULL, NULL);
  402. variable_item_set_current_value_text(app->variable_item_wifi_pass, "");
  403. }
  404. if (load_settings(ssid, sizeof(ssid), pass, sizeof(pass), username, sizeof(username), password, sizeof(password)))
  405. {
  406. variable_item_set_current_value_text(app->variable_item_wifi_ssid, ssid);
  407. // variable_item_set_current_value_text(app->variable_item_wifi_pass, pass);
  408. save_char("WiFi-SSID", ssid);
  409. save_char("WiFi-Password", pass);
  410. save_char("Flip-Social-Username", username);
  411. save_char("Flip-Social-Password", password);
  412. }
  413. break;
  414. case FlipWorldSubmenuIndexGameSettings:
  415. if (!easy_flipper_set_variable_item_list(&app->variable_item_list, FlipWorldViewVariableItemList, game_settings_select, callback_to_settings, &app->view_dispatcher, app))
  416. {
  417. FURI_LOG_E(TAG, "Failed to allocate variable item list");
  418. return false;
  419. }
  420. if (!app->variable_item_list)
  421. {
  422. FURI_LOG_E(TAG, "Variable item list is NULL");
  423. return false;
  424. }
  425. if (!app->variable_item_game_download_world)
  426. {
  427. app->variable_item_game_download_world = variable_item_list_add(app->variable_item_list, "Install Official World Pack", 0, NULL, NULL);
  428. variable_item_set_current_value_text(app->variable_item_game_download_world, "");
  429. }
  430. if (!app->variable_item_game_player_sprite)
  431. {
  432. app->variable_item_game_player_sprite = variable_item_list_add(app->variable_item_list, "Weapon", 4, player_on_change, NULL);
  433. variable_item_set_current_value_index(app->variable_item_game_player_sprite, 1);
  434. variable_item_set_current_value_text(app->variable_item_game_player_sprite, player_sprite_choices[1]);
  435. }
  436. if (!app->variable_item_game_fps)
  437. {
  438. app->variable_item_game_fps = variable_item_list_add(app->variable_item_list, "FPS", 4, fps_change, NULL);
  439. variable_item_set_current_value_index(app->variable_item_game_fps, 0);
  440. variable_item_set_current_value_text(app->variable_item_game_fps, fps_choices_str[0]);
  441. }
  442. if (!app->variable_item_game_vgm_x)
  443. {
  444. app->variable_item_game_vgm_x = variable_item_list_add(app->variable_item_list, "VGM Horizontal", 12, vgm_x_change, NULL);
  445. variable_item_set_current_value_index(app->variable_item_game_vgm_x, 2);
  446. variable_item_set_current_value_text(app->variable_item_game_vgm_x, vgm_levels[2]);
  447. }
  448. if (!app->variable_item_game_vgm_y)
  449. {
  450. app->variable_item_game_vgm_y = variable_item_list_add(app->variable_item_list, "VGM Vertical", 12, vgm_y_change, NULL);
  451. variable_item_set_current_value_index(app->variable_item_game_vgm_y, 2);
  452. variable_item_set_current_value_text(app->variable_item_game_vgm_y, vgm_levels[2]);
  453. }
  454. if (!app->variable_item_game_screen_always_on)
  455. {
  456. app->variable_item_game_screen_always_on = variable_item_list_add(app->variable_item_list, "Keep Screen On?", 2, screen_on_change, NULL);
  457. variable_item_set_current_value_index(app->variable_item_game_screen_always_on, 1);
  458. variable_item_set_current_value_text(app->variable_item_game_screen_always_on, yes_or_no_choices[1]);
  459. }
  460. if (!app->variable_item_game_sound_on)
  461. {
  462. app->variable_item_game_sound_on = variable_item_list_add(app->variable_item_list, "Sound On?", 2, sound_on_change, NULL);
  463. variable_item_set_current_value_index(app->variable_item_game_sound_on, 0);
  464. variable_item_set_current_value_text(app->variable_item_game_sound_on, yes_or_no_choices[0]);
  465. }
  466. if (!app->variable_item_game_vibration_on)
  467. {
  468. app->variable_item_game_vibration_on = variable_item_list_add(app->variable_item_list, "Vibration On?", 2, vibration_on_change, NULL);
  469. variable_item_set_current_value_index(app->variable_item_game_vibration_on, 0);
  470. variable_item_set_current_value_text(app->variable_item_game_vibration_on, yes_or_no_choices[0]);
  471. }
  472. char _game_player_sprite[8];
  473. if (load_char("Game-Player-Sprite", _game_player_sprite, sizeof(_game_player_sprite)))
  474. {
  475. int index = is_str(_game_player_sprite, "naked") ? 0 : is_str(_game_player_sprite, "sword") ? 1
  476. : is_str(_game_player_sprite, "axe") ? 2
  477. : is_str(_game_player_sprite, "bow") ? 3
  478. : 0;
  479. variable_item_set_current_value_index(app->variable_item_game_player_sprite, index);
  480. variable_item_set_current_value_text(
  481. app->variable_item_game_player_sprite,
  482. is_str(player_sprite_choices[index], "naked") ? "None" : player_sprite_choices[index]);
  483. }
  484. char _game_fps[8];
  485. if (load_char("Game-FPS", _game_fps, sizeof(_game_fps)))
  486. {
  487. int index = is_str(_game_fps, "30") ? 0 : is_str(_game_fps, "60") ? 1
  488. : is_str(_game_fps, "120") ? 2
  489. : is_str(_game_fps, "240") ? 3
  490. : 0;
  491. variable_item_set_current_value_text(app->variable_item_game_fps, fps_choices_str[index]);
  492. variable_item_set_current_value_index(app->variable_item_game_fps, index);
  493. }
  494. char _game_vgm_x[8];
  495. if (load_char("Game-VGM-X", _game_vgm_x, sizeof(_game_vgm_x)))
  496. {
  497. int vgm_x = atoi(_game_vgm_x);
  498. int index = vgm_x == -2 ? 0 : vgm_x == -1 ? 1
  499. : vgm_x == 0 ? 2
  500. : vgm_x == 1 ? 3
  501. : vgm_x == 2 ? 4
  502. : vgm_x == 3 ? 5
  503. : vgm_x == 4 ? 6
  504. : vgm_x == 5 ? 7
  505. : vgm_x == 6 ? 8
  506. : vgm_x == 7 ? 9
  507. : vgm_x == 8 ? 10
  508. : vgm_x == 9 ? 11
  509. : vgm_x == 10 ? 12
  510. : 2;
  511. variable_item_set_current_value_index(app->variable_item_game_vgm_x, index);
  512. variable_item_set_current_value_text(app->variable_item_game_vgm_x, vgm_levels[index]);
  513. }
  514. char _game_vgm_y[8];
  515. if (load_char("Game-VGM-Y", _game_vgm_y, sizeof(_game_vgm_y)))
  516. {
  517. int vgm_y = atoi(_game_vgm_y);
  518. int index = vgm_y == -2 ? 0 : vgm_y == -1 ? 1
  519. : vgm_y == 0 ? 2
  520. : vgm_y == 1 ? 3
  521. : vgm_y == 2 ? 4
  522. : vgm_y == 3 ? 5
  523. : vgm_y == 4 ? 6
  524. : vgm_y == 5 ? 7
  525. : vgm_y == 6 ? 8
  526. : vgm_y == 7 ? 9
  527. : vgm_y == 8 ? 10
  528. : vgm_y == 9 ? 11
  529. : vgm_y == 10 ? 12
  530. : 2;
  531. variable_item_set_current_value_index(app->variable_item_game_vgm_y, index);
  532. variable_item_set_current_value_text(app->variable_item_game_vgm_y, vgm_levels[index]);
  533. }
  534. char _game_screen_always_on[8];
  535. if (load_char("Game-Screen-Always-On", _game_screen_always_on, sizeof(_game_screen_always_on)))
  536. {
  537. int index = is_str(_game_screen_always_on, "No") ? 0 : is_str(_game_screen_always_on, "Yes") ? 1
  538. : 0;
  539. variable_item_set_current_value_text(app->variable_item_game_screen_always_on, yes_or_no_choices[index]);
  540. variable_item_set_current_value_index(app->variable_item_game_screen_always_on, index);
  541. }
  542. char _game_sound_on[8];
  543. if (load_char("Game-Sound-On", _game_sound_on, sizeof(_game_sound_on)))
  544. {
  545. int index = is_str(_game_sound_on, "No") ? 0 : is_str(_game_sound_on, "Yes") ? 1
  546. : 0;
  547. variable_item_set_current_value_text(app->variable_item_game_sound_on, yes_or_no_choices[index]);
  548. variable_item_set_current_value_index(app->variable_item_game_sound_on, index);
  549. }
  550. char _game_vibration_on[8];
  551. if (load_char("Game-Vibration-On", _game_vibration_on, sizeof(_game_vibration_on)))
  552. {
  553. int index = is_str(_game_vibration_on, "No") ? 0 : is_str(_game_vibration_on, "Yes") ? 1
  554. : 0;
  555. variable_item_set_current_value_text(app->variable_item_game_vibration_on, yes_or_no_choices[index]);
  556. variable_item_set_current_value_index(app->variable_item_game_vibration_on, index);
  557. }
  558. break;
  559. case FlipWorldSubmenuIndexUserSettings:
  560. if (!easy_flipper_set_variable_item_list(&app->variable_item_list, FlipWorldViewVariableItemList, user_settings_select, callback_to_settings, &app->view_dispatcher, app))
  561. {
  562. FURI_LOG_E(TAG, "Failed to allocate variable item list");
  563. return false;
  564. }
  565. if (!app->variable_item_list)
  566. {
  567. FURI_LOG_E(TAG, "Variable item list is NULL");
  568. return false;
  569. }
  570. // if logged in, show profile info, otherwise show login/register
  571. if (is_logged_in() || is_logged_in_to_flip_social())
  572. {
  573. if (!app->variable_item_user_username)
  574. {
  575. app->variable_item_user_username = variable_item_list_add(app->variable_item_list, "Username", 0, NULL, NULL);
  576. variable_item_set_current_value_text(app->variable_item_user_username, "");
  577. }
  578. if (!app->variable_item_user_password)
  579. {
  580. app->variable_item_user_password = variable_item_list_add(app->variable_item_list, "Password", 0, NULL, NULL);
  581. variable_item_set_current_value_text(app->variable_item_user_password, "");
  582. }
  583. if (load_settings(ssid, sizeof(ssid), pass, sizeof(pass), username, sizeof(username), password, sizeof(password)))
  584. {
  585. variable_item_set_current_value_text(app->variable_item_user_username, username);
  586. variable_item_set_current_value_text(app->variable_item_user_password, "*****");
  587. }
  588. }
  589. else
  590. {
  591. if (!app->variable_item_user_username)
  592. {
  593. app->variable_item_user_username = variable_item_list_add(app->variable_item_list, "Username", 0, NULL, NULL);
  594. variable_item_set_current_value_text(app->variable_item_user_username, "");
  595. }
  596. if (!app->variable_item_user_password)
  597. {
  598. app->variable_item_user_password = variable_item_list_add(app->variable_item_list, "Password", 0, NULL, NULL);
  599. variable_item_set_current_value_text(app->variable_item_user_password, "");
  600. }
  601. }
  602. break;
  603. }
  604. }
  605. return true;
  606. }
  607. static bool alloc_submenu_other(void *context, uint32_t view_id)
  608. {
  609. FlipWorldApp *app = (FlipWorldApp *)context;
  610. furi_check(app);
  611. if (app->submenu_other)
  612. {
  613. FURI_LOG_I(TAG, "Submenu already allocated");
  614. return true;
  615. }
  616. switch (view_id)
  617. {
  618. case FlipWorldViewSettings:
  619. if (!easy_flipper_set_submenu(&app->submenu_other, FlipWorldViewSubmenuOther, "Settings", callback_to_submenu, &app->view_dispatcher))
  620. {
  621. FURI_LOG_E(TAG, "Failed to allocate submenu settings");
  622. return false;
  623. }
  624. submenu_add_item(app->submenu_other, "WiFi", FlipWorldSubmenuIndexWiFiSettings, callback_submenu_choices, app);
  625. submenu_add_item(app->submenu_other, "Game", FlipWorldSubmenuIndexGameSettings, callback_submenu_choices, app);
  626. submenu_add_item(app->submenu_other, "User", FlipWorldSubmenuIndexUserSettings, callback_submenu_choices, app);
  627. return true;
  628. case FlipWorldViewLobby:
  629. return easy_flipper_set_submenu(&app->submenu_other, FlipWorldViewSubmenuOther, "Lobbies", callback_to_submenu, &app->view_dispatcher);
  630. default:
  631. return false;
  632. }
  633. }
  634. static bool alloc_game_submenu(void *context)
  635. {
  636. FlipWorldApp *app = (FlipWorldApp *)context;
  637. furi_check(app);
  638. if (!app->submenu_game)
  639. {
  640. if (!easy_flipper_set_submenu(&app->submenu_game, FlipWorldViewGameSubmenu, "Play", callback_to_submenu, &app->view_dispatcher))
  641. {
  642. return false;
  643. }
  644. if (!app->submenu_game)
  645. {
  646. return false;
  647. }
  648. submenu_add_item(app->submenu_game, "Tutorial", FlipWorldSubmenuIndexStory, callback_submenu_choices, app);
  649. submenu_add_item(app->submenu_game, "PvP (Beta)", FlipWorldSubmenuIndexPvP, callback_submenu_choices, app);
  650. submenu_add_item(app->submenu_game, "PvE", FlipWorldSubmenuIndexPvE, callback_submenu_choices, app);
  651. }
  652. return true;
  653. }
  654. void free_game_submenu(void *context)
  655. {
  656. FlipWorldApp *app = (FlipWorldApp *)context;
  657. if (!app)
  658. {
  659. FURI_LOG_E(TAG, "FlipWorldApp is NULL");
  660. return;
  661. }
  662. if (app->submenu_game)
  663. {
  664. view_dispatcher_remove_view(app->view_dispatcher, FlipWorldViewGameSubmenu);
  665. submenu_free(app->submenu_game);
  666. app->submenu_game = NULL;
  667. }
  668. }
  669. static void free_submenu_other(void *context)
  670. {
  671. FlipWorldApp *app = (FlipWorldApp *)context;
  672. furi_check(app);
  673. if (app->submenu_other)
  674. {
  675. view_dispatcher_remove_view(app->view_dispatcher, FlipWorldViewSubmenuOther);
  676. submenu_free(app->submenu_other);
  677. app->submenu_other = NULL;
  678. }
  679. for (int i = 0; i < 10; i++)
  680. {
  681. if (lobby_list[i])
  682. {
  683. free(lobby_list[i]);
  684. lobby_list[i] = NULL;
  685. }
  686. }
  687. }
  688. // free
  689. static void free_message_view(void *context)
  690. {
  691. FlipWorldApp *app = (FlipWorldApp *)context;
  692. if (!app)
  693. {
  694. FURI_LOG_E(TAG, "FlipWorldApp is NULL");
  695. return;
  696. }
  697. if (app->view_message)
  698. {
  699. view_dispatcher_remove_view(app->view_dispatcher, FlipWorldViewMessage);
  700. view_free(app->view_message);
  701. app->view_message = NULL;
  702. }
  703. }
  704. static void free_text_input_view(void *context)
  705. {
  706. FlipWorldApp *app = (FlipWorldApp *)context;
  707. if (!app)
  708. {
  709. FURI_LOG_E(TAG, "FlipWorldApp is NULL");
  710. return;
  711. }
  712. if (app->text_input)
  713. {
  714. view_dispatcher_remove_view(app->view_dispatcher, FlipWorldViewTextInput);
  715. uart_text_input_free(app->text_input);
  716. app->text_input = NULL;
  717. }
  718. if (app->text_input_buffer)
  719. {
  720. free(app->text_input_buffer);
  721. app->text_input_buffer = NULL;
  722. }
  723. if (app->text_input_temp_buffer)
  724. {
  725. free(app->text_input_temp_buffer);
  726. app->text_input_temp_buffer = NULL;
  727. }
  728. }
  729. static void free_variable_item_list(void *context)
  730. {
  731. FlipWorldApp *app = (FlipWorldApp *)context;
  732. if (!app)
  733. {
  734. FURI_LOG_E(TAG, "FlipWorldApp is NULL");
  735. return;
  736. }
  737. if (app->variable_item_list)
  738. {
  739. view_dispatcher_remove_view(app->view_dispatcher, FlipWorldViewVariableItemList);
  740. variable_item_list_free(app->variable_item_list);
  741. app->variable_item_list = NULL;
  742. }
  743. if (app->variable_item_wifi_ssid)
  744. {
  745. free(app->variable_item_wifi_ssid);
  746. app->variable_item_wifi_ssid = NULL;
  747. }
  748. if (app->variable_item_wifi_pass)
  749. {
  750. free(app->variable_item_wifi_pass);
  751. app->variable_item_wifi_pass = NULL;
  752. }
  753. if (app->variable_item_game_fps)
  754. {
  755. free(app->variable_item_game_fps);
  756. app->variable_item_game_fps = NULL;
  757. }
  758. if (app->variable_item_game_screen_always_on)
  759. {
  760. free(app->variable_item_game_screen_always_on);
  761. app->variable_item_game_screen_always_on = NULL;
  762. }
  763. if (app->variable_item_game_download_world)
  764. {
  765. free(app->variable_item_game_download_world);
  766. app->variable_item_game_download_world = NULL;
  767. }
  768. if (app->variable_item_game_sound_on)
  769. {
  770. free(app->variable_item_game_sound_on);
  771. app->variable_item_game_sound_on = NULL;
  772. }
  773. if (app->variable_item_game_vibration_on)
  774. {
  775. free(app->variable_item_game_vibration_on);
  776. app->variable_item_game_vibration_on = NULL;
  777. }
  778. if (app->variable_item_game_player_sprite)
  779. {
  780. free(app->variable_item_game_player_sprite);
  781. app->variable_item_game_player_sprite = NULL;
  782. }
  783. if (app->variable_item_game_vgm_x)
  784. {
  785. free(app->variable_item_game_vgm_x);
  786. app->variable_item_game_vgm_x = NULL;
  787. }
  788. if (app->variable_item_game_vgm_y)
  789. {
  790. free(app->variable_item_game_vgm_y);
  791. app->variable_item_game_vgm_y = NULL;
  792. }
  793. if (app->variable_item_user_username)
  794. {
  795. free(app->variable_item_user_username);
  796. app->variable_item_user_username = NULL;
  797. }
  798. if (app->variable_item_user_password)
  799. {
  800. free(app->variable_item_user_password);
  801. app->variable_item_user_password = NULL;
  802. }
  803. }
  804. static FuriThread *game_thread;
  805. static FuriThread *waiting_thread;
  806. static bool game_thread_running = false;
  807. static bool waiting_thread_running = false;
  808. static bool start_waiting_thread(void *context)
  809. {
  810. FlipWorldApp *app = (FlipWorldApp *)context;
  811. furi_check(app);
  812. // free game thread
  813. if (waiting_thread_running)
  814. {
  815. waiting_thread_running = false;
  816. if (waiting_thread)
  817. {
  818. furi_thread_flags_set(furi_thread_get_id(waiting_thread), WorkerEvtStop);
  819. furi_thread_join(waiting_thread);
  820. furi_thread_free(waiting_thread);
  821. }
  822. }
  823. // start waiting thread
  824. FuriThread *thread = furi_thread_alloc_ex("waiting_thread", 2048, waiting_app_callback, app);
  825. if (!thread)
  826. {
  827. FURI_LOG_E(TAG, "Failed to allocate waiting thread");
  828. easy_flipper_dialog("Error", "Failed to allocate waiting thread. Restart your Flipper.");
  829. return false;
  830. }
  831. furi_thread_start(thread);
  832. waiting_thread = thread;
  833. waiting_thread_running = true;
  834. return true;
  835. }
  836. void free_all_views(void *context, bool free_variable_list, bool free_settings_other, bool free_submenu_game)
  837. {
  838. FlipWorldApp *app = (FlipWorldApp *)context;
  839. furi_check(app);
  840. if (free_variable_list)
  841. {
  842. free_variable_item_list(app);
  843. }
  844. free_message_view(app);
  845. free_text_input_view(app);
  846. // free game thread
  847. if (game_thread_running)
  848. {
  849. game_thread_running = false;
  850. if (game_thread)
  851. {
  852. furi_thread_flags_set(furi_thread_get_id(game_thread), WorkerEvtStop);
  853. furi_thread_join(game_thread);
  854. furi_thread_free(game_thread);
  855. game_thread = NULL;
  856. }
  857. }
  858. if (free_settings_other)
  859. {
  860. free_submenu_other(app);
  861. }
  862. // free Derek's loader
  863. free_view_loader(app);
  864. if (free_submenu_game)
  865. {
  866. free_game_submenu(app);
  867. }
  868. // free waiting thread
  869. if (waiting_thread_running)
  870. {
  871. waiting_thread_running = false;
  872. if (waiting_thread)
  873. {
  874. furi_thread_flags_set(furi_thread_get_id(waiting_thread), WorkerEvtStop);
  875. furi_thread_join(waiting_thread);
  876. furi_thread_free(waiting_thread);
  877. waiting_thread = NULL;
  878. }
  879. }
  880. }
  881. static bool fetch_world_list(FlipperHTTP *fhttp)
  882. {
  883. if (!fhttp)
  884. {
  885. FURI_LOG_E(TAG, "fhttp is NULL");
  886. easy_flipper_dialog("Error", "fhttp is NULL. Press BACK to return.");
  887. return false;
  888. }
  889. // ensure flip_world directory exists
  890. char directory_path[128];
  891. snprintf(directory_path, sizeof(directory_path), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_world");
  892. Storage *storage = furi_record_open(RECORD_STORAGE);
  893. storage_common_mkdir(storage, directory_path);
  894. snprintf(directory_path, sizeof(directory_path), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_world/worlds");
  895. storage_common_mkdir(storage, directory_path);
  896. furi_record_close(RECORD_STORAGE);
  897. snprintf(fhttp->file_path, sizeof(fhttp->file_path), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_world/worlds/world_list.json");
  898. fhttp->save_received_data = true;
  899. return flipper_http_request(fhttp, GET, "https://www.jblanked.com/flipper/api/world/v5/list/10/", "{\"Content-Type\":\"application/json\"}", NULL);
  900. }
  901. // we will load the palyer stats from the API and save them
  902. // in player_spawn game method, it will load the player stats that we saved
  903. static bool fetch_player_stats(FlipperHTTP *fhttp)
  904. {
  905. if (!fhttp)
  906. {
  907. FURI_LOG_E(TAG, "fhttp is NULL");
  908. easy_flipper_dialog("Error", "fhttp is NULL. Press BACK to return.");
  909. return false;
  910. }
  911. char username[64];
  912. if (!load_char("Flip-Social-Username", username, sizeof(username)))
  913. {
  914. FURI_LOG_E(TAG, "Failed to load Flip-Social-Username");
  915. easy_flipper_dialog("Error", "Failed to load saved username. Go to settings to update.");
  916. return false;
  917. }
  918. char url[128];
  919. snprintf(url, sizeof(url), "https://www.jblanked.com/flipper/api/user/game-stats/%s/", username);
  920. // ensure the folders exist
  921. char directory_path[128];
  922. snprintf(directory_path, sizeof(directory_path), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_world");
  923. Storage *storage = furi_record_open(RECORD_STORAGE);
  924. storage_common_mkdir(storage, directory_path);
  925. snprintf(directory_path, sizeof(directory_path), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_world/data");
  926. storage_common_mkdir(storage, directory_path);
  927. snprintf(directory_path, sizeof(directory_path), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_world/data/player");
  928. storage_common_mkdir(storage, directory_path);
  929. furi_record_close(RECORD_STORAGE);
  930. snprintf(fhttp->file_path, sizeof(fhttp->file_path), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_world/data/player/player_stats.json");
  931. fhttp->save_received_data = true;
  932. return flipper_http_request(fhttp, GET, url, "{\"Content-Type\":\"application/json\"}", NULL);
  933. }
  934. // static bool fetch_app_update(FlipperHTTP *fhttp)
  935. // {
  936. // if (!fhttp)
  937. // {
  938. // FURI_LOG_E(TAG, "fhttp is NULL");
  939. // easy_flipper_dialog("Error", "fhttp is NULL. Press BACK to return.");
  940. // return false;
  941. // }
  942. // return flipper_http_get_request_with_headers(fhttp, "https://www.jblanked.com/flipper/api/app/last-updated/flip_world/", "{\"Content-Type\":\"application/json\"}");
  943. // }
  944. // static bool parse_app_update(FlipperHTTP *fhttp)
  945. // {
  946. // if (!fhttp)
  947. // {
  948. // FURI_LOG_E(TAG, "fhttp is NULL");
  949. // easy_flipper_dialog("Error", "fhttp is NULL. Press BACK to return.");
  950. // return false;
  951. // }
  952. // if (fhttp->last_response == NULL || strlen(fhttp->last_response) == 0)
  953. // {
  954. // FURI_LOG_E(TAG, "fhttp->last_response is NULL or empty");
  955. // easy_flipper_dialog("Error", "fhttp->last_response is NULL or empty. Press BACK to return.");
  956. // return false;
  957. // }
  958. // bool last_update_available = false;
  959. // char last_updated_old[32];
  960. // // load the previous last_updated
  961. // if (!load_char("last_updated", last_updated_old, sizeof(last_updated_old)))
  962. // {
  963. // FURI_LOG_E(TAG, "Failed to load last_updated");
  964. // // it's okay, we'll just update it
  965. // }
  966. // // save the new last_updated
  967. // save_char("last_updated", fhttp->last_response);
  968. // // compare the two
  969. // if (strlen(last_updated_old) == 0 || !is_str(last_updated_old, fhttp->last_response))
  970. // {
  971. // last_update_available = true;
  972. // }
  973. // if (last_update_available)
  974. // {
  975. // easy_flipper_dialog("Update Available", "An update is available. Press OK to update.");
  976. // return true;
  977. // }
  978. // else
  979. // {
  980. // easy_flipper_dialog("No Update Available", "No update is available. Press OK to continue.");
  981. // return false;
  982. // }
  983. // }
  984. static bool start_game_thread(void *context)
  985. {
  986. FlipWorldApp *app = (FlipWorldApp *)context;
  987. if (!app)
  988. {
  989. FURI_LOG_E(TAG, "app is NULL");
  990. easy_flipper_dialog("Error", "app is NULL. Press BACK to return.");
  991. return false;
  992. }
  993. // free everything but message_view
  994. free_variable_item_list(app);
  995. free_text_input_view(app);
  996. // free_submenu_other(app); // free lobby list or settings
  997. free_view_loader(app);
  998. free_game_submenu(app);
  999. // free game thread
  1000. if (game_thread_running)
  1001. {
  1002. game_thread_running = false;
  1003. if (game_thread)
  1004. {
  1005. furi_thread_flags_set(furi_thread_get_id(game_thread), WorkerEvtStop);
  1006. furi_thread_join(game_thread);
  1007. furi_thread_free(game_thread);
  1008. }
  1009. }
  1010. // start game thread
  1011. FuriThread *thread = furi_thread_alloc_ex("game", 2048, game_app, app);
  1012. if (!thread)
  1013. {
  1014. FURI_LOG_E(TAG, "Failed to allocate game thread");
  1015. easy_flipper_dialog("Error", "Failed to allocate game thread. Restart your Flipper.");
  1016. return false;
  1017. }
  1018. furi_thread_start(thread);
  1019. game_thread = thread;
  1020. game_thread_running = true;
  1021. return true;
  1022. }
  1023. // combine register, login, and world list fetch into one function to switch to the loader view
  1024. static bool _fetch_game(DataLoaderModel *model)
  1025. {
  1026. FlipWorldApp *app = (FlipWorldApp *)model->parser_context;
  1027. if (!app)
  1028. {
  1029. FURI_LOG_E(TAG, "app is NULL");
  1030. easy_flipper_dialog("Error", "app is NULL. Press BACK to return.");
  1031. return false;
  1032. }
  1033. if (model->request_index == 0)
  1034. {
  1035. // login
  1036. char username[64];
  1037. char password[64];
  1038. if (!load_char("Flip-Social-Username", username, sizeof(username)))
  1039. {
  1040. FURI_LOG_E(TAG, "Failed to load Flip-Social-Username");
  1041. view_dispatcher_switch_to_view(app->view_dispatcher,
  1042. FlipWorldViewSubmenu); // just go back to the main menu for now
  1043. easy_flipper_dialog("Error", "Failed to load saved username\nGo to user settings to update.");
  1044. return false;
  1045. }
  1046. if (!load_char("Flip-Social-Password", password, sizeof(password)))
  1047. {
  1048. FURI_LOG_E(TAG, "Failed to load Flip-Social-Password");
  1049. view_dispatcher_switch_to_view(app->view_dispatcher,
  1050. FlipWorldViewSubmenu); // just go back to the main menu for now
  1051. easy_flipper_dialog("Error", "Failed to load saved password\nGo to settings to update.");
  1052. return false;
  1053. }
  1054. char payload[256];
  1055. snprintf(payload, sizeof(payload), "{\"username\":\"%s\",\"password\":\"%s\"}", username, password);
  1056. return flipper_http_request(model->fhttp, POST, "https://www.jblanked.com/flipper/api/user/login/", "{\"Content-Type\":\"application/json\"}", payload);
  1057. }
  1058. else if (model->request_index == 1)
  1059. {
  1060. // check if login was successful
  1061. char is_logged_in[8];
  1062. if (!load_char("is_logged_in", is_logged_in, sizeof(is_logged_in)))
  1063. {
  1064. FURI_LOG_E(TAG, "Failed to load is_logged_in");
  1065. easy_flipper_dialog("Error", "Failed to load is_logged_in\nGo to user settings to update.");
  1066. view_dispatcher_switch_to_view(app->view_dispatcher,
  1067. FlipWorldViewSubmenu); // just go back to the main menu for now
  1068. return false;
  1069. }
  1070. if (is_str(is_logged_in, "false") && is_str(model->title, "Registering..."))
  1071. {
  1072. // register
  1073. char username[64];
  1074. char password[64];
  1075. if (!load_char("Flip-Social-Username", username, sizeof(username)))
  1076. {
  1077. FURI_LOG_E(TAG, "Failed to load Flip-Social-Username");
  1078. easy_flipper_dialog("Error", "Failed to load saved username. Go to settings to update.");
  1079. view_dispatcher_switch_to_view(app->view_dispatcher,
  1080. FlipWorldViewSubmenu); // just go back to the main menu for now
  1081. return false;
  1082. }
  1083. if (!load_char("Flip-Social-Password", password, sizeof(password)))
  1084. {
  1085. FURI_LOG_E(TAG, "Failed to load Flip-Social-Password");
  1086. easy_flipper_dialog("Error", "Failed to load saved password. Go to settings to update.");
  1087. view_dispatcher_switch_to_view(app->view_dispatcher,
  1088. FlipWorldViewSubmenu); // just go back to the main menu for now
  1089. return false;
  1090. }
  1091. char payload[172];
  1092. snprintf(payload, sizeof(payload), "{\"username\":\"%s\",\"password\":\"%s\"}", username, password);
  1093. model->title = "Registering...";
  1094. return flipper_http_request(model->fhttp, POST, "https://www.jblanked.com/flipper/api/user/register/", "{\"Content-Type\":\"application/json\"}", payload);
  1095. }
  1096. else
  1097. {
  1098. model->title = "Fetching World List..";
  1099. return fetch_world_list(model->fhttp);
  1100. }
  1101. }
  1102. else if (model->request_index == 2)
  1103. {
  1104. model->title = "Fetching World List..";
  1105. return fetch_world_list(model->fhttp);
  1106. }
  1107. else if (model->request_index == 3)
  1108. {
  1109. snprintf(model->fhttp->file_path, sizeof(model->fhttp->file_path), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_world/worlds/world_list.json");
  1110. FuriString *world_list = flipper_http_load_from_file(model->fhttp->file_path);
  1111. if (!world_list)
  1112. {
  1113. view_dispatcher_switch_to_view(app->view_dispatcher,
  1114. FlipWorldViewSubmenu); // just go back to the main menu for now
  1115. FURI_LOG_E(TAG, "Failed to load world list");
  1116. easy_flipper_dialog("Error", "Failed to load world list. Go to game settings to download packs.");
  1117. return false;
  1118. }
  1119. FuriString *first_world = get_json_array_value_furi("worlds", 0, world_list);
  1120. if (!first_world)
  1121. {
  1122. view_dispatcher_switch_to_view(app->view_dispatcher,
  1123. FlipWorldViewSubmenu); // just go back to the main menu for now
  1124. FURI_LOG_E(TAG, "Failed to get first world");
  1125. easy_flipper_dialog("Error", "Failed to get first world. Go to game settings to download packs.");
  1126. furi_string_free(world_list);
  1127. return false;
  1128. }
  1129. if (world_exists(furi_string_get_cstr(first_world)))
  1130. {
  1131. furi_string_free(world_list);
  1132. furi_string_free(first_world);
  1133. if (!start_game_thread(app))
  1134. {
  1135. FURI_LOG_E(TAG, "Failed to start game thread");
  1136. easy_flipper_dialog("Error", "Failed to start game thread. Press BACK to return.");
  1137. view_dispatcher_switch_to_view(app->view_dispatcher,
  1138. FlipWorldViewSubmenu); // just go back to the main menu for now
  1139. return "Failed to start game thread";
  1140. }
  1141. return true;
  1142. }
  1143. snprintf(model->fhttp->file_path, sizeof(model->fhttp->file_path), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_world/worlds/%s.json", furi_string_get_cstr(first_world));
  1144. model->fhttp->save_received_data = true;
  1145. char url[128];
  1146. snprintf(url, sizeof(url), "https://www.jblanked.com/flipper/api/world/v5/get/world/%s/", furi_string_get_cstr(first_world));
  1147. furi_string_free(world_list);
  1148. furi_string_free(first_world);
  1149. return flipper_http_request(model->fhttp, GET, url, "{\"Content-Type\":\"application/json\"}", NULL);
  1150. }
  1151. FURI_LOG_E(TAG, "Unknown request index");
  1152. return false;
  1153. }
  1154. static char *_parse_game(DataLoaderModel *model)
  1155. {
  1156. FlipWorldApp *app = (FlipWorldApp *)model->parser_context;
  1157. if (model->request_index == 0)
  1158. {
  1159. if (!model->fhttp->last_response)
  1160. {
  1161. save_char("is_logged_in", "false");
  1162. // Go back to the main menu
  1163. easy_flipper_dialog("Error", "Response is empty. Press BACK to return.");
  1164. view_dispatcher_switch_to_view(app->view_dispatcher, FlipWorldViewSubmenu);
  1165. return "Response is empty...";
  1166. }
  1167. // Check for successful conditions
  1168. if (strstr(model->fhttp->last_response, "[SUCCESS]") != NULL || strstr(model->fhttp->last_response, "User found") != NULL)
  1169. {
  1170. save_char("is_logged_in", "true");
  1171. model->title = "Login successful!";
  1172. model->title = "Fetching World List..";
  1173. return "Login successful!";
  1174. }
  1175. // Check if user not found
  1176. if (strstr(model->fhttp->last_response, "User not found") != NULL)
  1177. {
  1178. save_char("is_logged_in", "false");
  1179. model->title = "Registering...";
  1180. return "Account not found...\nRegistering now.."; // if they see this an issue happened switching to register
  1181. }
  1182. // If not success, not found, check length conditions
  1183. size_t resp_len = strlen(model->fhttp->last_response);
  1184. if (resp_len == 0 || resp_len > 127)
  1185. {
  1186. // Empty or too long means failed login
  1187. save_char("is_logged_in", "false");
  1188. // Go back to the main menu
  1189. easy_flipper_dialog("Error", "Failed to login. Press BACK to return.");
  1190. view_dispatcher_switch_to_view(app->view_dispatcher, FlipWorldViewSubmenu);
  1191. return "Failed to login...";
  1192. }
  1193. // Handle any other unknown response as a failure
  1194. save_char("is_logged_in", "false");
  1195. // Go back to the main menu
  1196. easy_flipper_dialog("Error", "Failed to login. Press BACK to return.");
  1197. view_dispatcher_switch_to_view(app->view_dispatcher, FlipWorldViewSubmenu);
  1198. return "Failed to login...";
  1199. }
  1200. else if (model->request_index == 1)
  1201. {
  1202. if (is_str(model->title, "Registering..."))
  1203. {
  1204. // check registration response
  1205. if (model->fhttp->last_response != NULL && (strstr(model->fhttp->last_response, "[SUCCESS]") != NULL || strstr(model->fhttp->last_response, "User created") != NULL))
  1206. {
  1207. save_char("is_logged_in", "true");
  1208. char username[64];
  1209. char password[64];
  1210. // load the username and password, then save them
  1211. if (!load_char("Flip-Social-Username", username, sizeof(username)))
  1212. {
  1213. FURI_LOG_E(TAG, "Failed to load Flip-Social-Username");
  1214. easy_flipper_dialog("Error", "Failed to load Flip-Social-Username");
  1215. view_dispatcher_switch_to_view(app->view_dispatcher, FlipWorldViewSubmenu);
  1216. return "Failed to load Flip-Social-Username";
  1217. }
  1218. if (!load_char("Flip-Social-Password", password, sizeof(password)))
  1219. {
  1220. FURI_LOG_E(TAG, "Failed to load Flip-Social-Password");
  1221. easy_flipper_dialog("Error", "Failed to load Flip-Social-Password");
  1222. view_dispatcher_switch_to_view(app->view_dispatcher, FlipWorldViewSubmenu);
  1223. return "Failed to load Flip-Social-Password";
  1224. }
  1225. // load wifi ssid,pass then save
  1226. char ssid[64];
  1227. char pass[64];
  1228. if (!load_char("WiFi-SSID", ssid, sizeof(ssid)))
  1229. {
  1230. FURI_LOG_E(TAG, "Failed to load WiFi-SSID");
  1231. easy_flipper_dialog("Error", "Failed to load WiFi-SSID");
  1232. view_dispatcher_switch_to_view(app->view_dispatcher, FlipWorldViewSubmenu);
  1233. return "Failed to load WiFi-SSID";
  1234. }
  1235. if (!load_char("WiFi-Password", pass, sizeof(pass)))
  1236. {
  1237. FURI_LOG_E(TAG, "Failed to load WiFi-Password");
  1238. easy_flipper_dialog("Error", "Failed to load WiFi-Password");
  1239. view_dispatcher_switch_to_view(app->view_dispatcher, FlipWorldViewSubmenu);
  1240. return "Failed to load WiFi-Password";
  1241. }
  1242. save_settings(ssid, pass, username, password);
  1243. model->title = "Fetching World List..";
  1244. return "Account created!";
  1245. }
  1246. else if (strstr(model->fhttp->last_response, "Username or password not provided") != NULL)
  1247. {
  1248. easy_flipper_dialog("Error", "Please enter your credentials.\nPress BACK to return.");
  1249. view_dispatcher_switch_to_view(app->view_dispatcher,
  1250. FlipWorldViewSubmenu); // just go back to the main menu for now
  1251. return "Please enter your credentials.";
  1252. }
  1253. else if (strstr(model->fhttp->last_response, "User already exists") != NULL || strstr(model->fhttp->last_response, "Multiple users found") != NULL)
  1254. {
  1255. easy_flipper_dialog("Error", "Registration failed...\nUsername already exists.\nPress BACK to return.");
  1256. view_dispatcher_switch_to_view(app->view_dispatcher,
  1257. FlipWorldViewSubmenu); // just go back to the main menu for now
  1258. return "Username already exists.";
  1259. }
  1260. else
  1261. {
  1262. easy_flipper_dialog("Error", "Registration failed...\nUpdate your credentials.\nPress BACK to return.");
  1263. view_dispatcher_switch_to_view(app->view_dispatcher,
  1264. FlipWorldViewSubmenu); // just go back to the main menu for now
  1265. return "Registration failed...";
  1266. }
  1267. }
  1268. else
  1269. {
  1270. if (!start_game_thread(app))
  1271. {
  1272. FURI_LOG_E(TAG, "Failed to start game thread");
  1273. easy_flipper_dialog("Error", "Failed to start game thread. Press BACK to return.");
  1274. view_dispatcher_switch_to_view(app->view_dispatcher,
  1275. FlipWorldViewSubmenu); // just go back to the main menu for now
  1276. return "Failed to start game thread";
  1277. }
  1278. return "Thanks for playing FlipWorld!\n\n\n\nPress BACK to return if this\ndoesn't automatically close.";
  1279. }
  1280. }
  1281. else if (model->request_index == 2)
  1282. {
  1283. return "Welcome to FlipWorld!\n\n\n\nPress BACK to return if this\ndoesn't automatically close.";
  1284. }
  1285. else if (model->request_index == 3)
  1286. {
  1287. if (!start_game_thread(app))
  1288. {
  1289. FURI_LOG_E(TAG, "Failed to start game thread");
  1290. easy_flipper_dialog("Error", "Failed to start game thread. Press BACK to return.");
  1291. view_dispatcher_switch_to_view(app->view_dispatcher,
  1292. FlipWorldViewSubmenu); // just go back to the main menu for now
  1293. return "Failed to start game thread";
  1294. }
  1295. return "Thanks for playing FlipWorld!\n\n\n\nPress BACK to return if this\ndoesn't automatically close.";
  1296. }
  1297. easy_flipper_dialog("Error", "Unknown error. Press BACK to return.");
  1298. view_dispatcher_switch_to_view(app->view_dispatcher, FlipWorldViewSubmenu); // just go back to the main menu for now
  1299. return "Unknown error";
  1300. }
  1301. static void switch_to_view_get_game(FlipWorldApp *app)
  1302. {
  1303. if (!alloc_view_loader(app))
  1304. {
  1305. FURI_LOG_E(TAG, "Failed to allocate view loader");
  1306. return;
  1307. }
  1308. generic_switch_to_view(app, "Starting Game..", _fetch_game, _parse_game, 5, callback_to_submenu, FlipWorldViewLoader);
  1309. }
  1310. static void run(FlipWorldApp *app)
  1311. {
  1312. if (!app)
  1313. {
  1314. FURI_LOG_E(TAG, "FlipWorldApp is NULL");
  1315. return;
  1316. }
  1317. free_all_views(app, true, true, false);
  1318. if (!is_enough_heap(60000, false)) // only need to check if they have 60k free
  1319. {
  1320. easy_flipper_dialog("Error", "Not enough heap memory.\nPlease restart your Flipper.");
  1321. return;
  1322. }
  1323. // check if logged in
  1324. if (is_logged_in() || is_logged_in_to_flip_social())
  1325. {
  1326. FlipperHTTP *fhttp = flipper_http_alloc();
  1327. if (!fhttp)
  1328. {
  1329. FURI_LOG_E(TAG, "Failed to allocate FlipperHTTP");
  1330. easy_flipper_dialog("Error", "Failed to allocate FlipperHTTP. Press BACK to return.");
  1331. return;
  1332. }
  1333. bool fetch_world_list_i()
  1334. {
  1335. return fetch_world_list(fhttp);
  1336. }
  1337. bool parse_world_list_i()
  1338. {
  1339. return fhttp->state != ISSUE;
  1340. }
  1341. bool fetch_player_stats_i()
  1342. {
  1343. return fetch_player_stats(fhttp);
  1344. }
  1345. if (!alloc_message_view(app, MessageStateLoading))
  1346. {
  1347. FURI_LOG_E(TAG, "Failed to allocate message view");
  1348. return;
  1349. }
  1350. view_dispatcher_switch_to_view(app->view_dispatcher, FlipWorldViewMessage);
  1351. // Make the request
  1352. if (game_mode_index != 1) // not GAME_MODE_PVP
  1353. {
  1354. if (!flipper_http_process_response_async(fhttp, fetch_world_list_i, parse_world_list_i) || !flipper_http_process_response_async(fhttp, fetch_player_stats_i, set_player_context))
  1355. {
  1356. FURI_LOG_E(HTTP_TAG, "Failed to make request");
  1357. flipper_http_free(fhttp);
  1358. }
  1359. else
  1360. {
  1361. flipper_http_free(fhttp);
  1362. }
  1363. if (!start_game_thread(app))
  1364. {
  1365. FURI_LOG_E(TAG, "Failed to start game thread");
  1366. easy_flipper_dialog("Error", "Failed to start game thread. Press BACK to return.");
  1367. return;
  1368. }
  1369. }
  1370. else
  1371. {
  1372. // load pvp info (this returns the lobbies available)
  1373. bool fetch_pvp_lobbies()
  1374. {
  1375. // ensure flip_world directory exists
  1376. char directory_path[128];
  1377. snprintf(directory_path, sizeof(directory_path), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_world");
  1378. Storage *storage = furi_record_open(RECORD_STORAGE);
  1379. storage_common_mkdir(storage, directory_path);
  1380. snprintf(directory_path, sizeof(directory_path), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_world/pvp");
  1381. storage_common_mkdir(storage, directory_path);
  1382. snprintf(directory_path, sizeof(directory_path), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_world/pvp/lobbies");
  1383. storage_common_mkdir(storage, directory_path);
  1384. furi_record_close(RECORD_STORAGE);
  1385. snprintf(fhttp->file_path, sizeof(fhttp->file_path), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_world/pvp/pvp_lobbies.json");
  1386. storage_simply_remove_recursive(storage, fhttp->file_path); // ensure the file is empty
  1387. fhttp->save_received_data = true;
  1388. // 2 players max, 10 lobbies
  1389. return flipper_http_request(fhttp, GET, "https://www.jblanked.com/flipper/api/world/pvp/lobbies/2/10/", "{\"Content-Type\":\"application/json\"}", NULL);
  1390. }
  1391. bool parse_pvp_lobbies()
  1392. {
  1393. free_submenu_other(app);
  1394. if (!alloc_submenu_other(app, FlipWorldViewLobby))
  1395. {
  1396. FURI_LOG_E(TAG, "Failed to allocate lobby submenu");
  1397. return false;
  1398. }
  1399. // add the lobbies to the submenu
  1400. FuriString *lobbies = flipper_http_load_from_file(fhttp->file_path);
  1401. if (!lobbies)
  1402. {
  1403. FURI_LOG_E(TAG, "Failed to load lobbies");
  1404. return false;
  1405. }
  1406. // parse the lobbies
  1407. for (uint32_t i = 0; i < 10; i++)
  1408. {
  1409. FuriString *lobby = get_json_array_value_furi("lobbies", i, lobbies);
  1410. if (!lobby)
  1411. {
  1412. FURI_LOG_I(TAG, "No more lobbies");
  1413. break;
  1414. }
  1415. FuriString *lobby_id = get_json_value_furi("id", lobby);
  1416. if (!lobby_id)
  1417. {
  1418. FURI_LOG_E(TAG, "Failed to get lobby id");
  1419. furi_string_free(lobby);
  1420. return false;
  1421. }
  1422. // add the lobby to the submenu
  1423. submenu_add_item(app->submenu_other, furi_string_get_cstr(lobby_id), FlipWorldSubmenuIndexLobby + i, callback_submenu_lobby_choices, app);
  1424. // add the lobby to the list
  1425. if (!easy_flipper_set_buffer(&lobby_list[i], 64))
  1426. {
  1427. FURI_LOG_E(TAG, "Failed to allocate lobby list");
  1428. furi_string_free(lobby);
  1429. furi_string_free(lobby_id);
  1430. return false;
  1431. }
  1432. snprintf(lobby_list[i], 64, "%s", furi_string_get_cstr(lobby_id));
  1433. furi_string_free(lobby);
  1434. furi_string_free(lobby_id);
  1435. }
  1436. furi_string_free(lobbies);
  1437. return true;
  1438. }
  1439. // load pvp lobbies and player stats
  1440. if (!flipper_http_process_response_async(fhttp, fetch_pvp_lobbies, parse_pvp_lobbies) || !flipper_http_process_response_async(fhttp, fetch_player_stats_i, set_player_context))
  1441. {
  1442. // unlike the pve/story, receiving data is necessary
  1443. // so send the user back to the main menu if it fails
  1444. FURI_LOG_E(HTTP_TAG, "Failed to make request");
  1445. easy_flipper_dialog("Error", "Failed to make request. Press BACK to return.");
  1446. view_dispatcher_switch_to_view(app->view_dispatcher,
  1447. FlipWorldViewSubmenu);
  1448. flipper_http_free(fhttp);
  1449. }
  1450. else
  1451. {
  1452. flipper_http_free(fhttp);
  1453. }
  1454. // switch to the lobby submenu
  1455. view_dispatcher_switch_to_view(app->view_dispatcher, FlipWorldViewSubmenuOther);
  1456. }
  1457. }
  1458. else
  1459. {
  1460. switch_to_view_get_game(app);
  1461. }
  1462. }
  1463. void callback_submenu_choices(void *context, uint32_t index)
  1464. {
  1465. FlipWorldApp *app = (FlipWorldApp *)context;
  1466. furi_check(app);
  1467. switch (index)
  1468. {
  1469. case FlipWorldSubmenuIndexGameSubmenu:
  1470. free_all_views(app, true, true, true);
  1471. if (!alloc_game_submenu(app))
  1472. {
  1473. FURI_LOG_E(TAG, "Failed to allocate game submenu");
  1474. return;
  1475. }
  1476. view_dispatcher_switch_to_view(app->view_dispatcher, FlipWorldViewGameSubmenu);
  1477. break;
  1478. case FlipWorldSubmenuIndexStory:
  1479. game_mode_index = 2; // GAME_MODE_STORY
  1480. run(app);
  1481. break;
  1482. case FlipWorldSubmenuIndexPvP:
  1483. game_mode_index = 1; // GAME_MODE_PVP
  1484. run(app);
  1485. break;
  1486. case FlipWorldSubmenuIndexPvE:
  1487. game_mode_index = 0; // GAME_MODE_PVE
  1488. run(app);
  1489. break;
  1490. case FlipWorldSubmenuIndexMessage:
  1491. // About menu.
  1492. free_all_views(app, true, true, true);
  1493. if (!alloc_message_view(app, MessageStateAbout))
  1494. {
  1495. FURI_LOG_E(TAG, "Failed to allocate message view");
  1496. return;
  1497. }
  1498. view_dispatcher_switch_to_view(app->view_dispatcher, FlipWorldViewMessage);
  1499. break;
  1500. case FlipWorldSubmenuIndexSettings:
  1501. free_all_views(app, true, true, true);
  1502. if (!alloc_submenu_other(app, FlipWorldViewSettings))
  1503. {
  1504. FURI_LOG_E(TAG, "Failed to allocate settings view");
  1505. return;
  1506. }
  1507. view_dispatcher_switch_to_view(app->view_dispatcher, FlipWorldViewSubmenuOther);
  1508. break;
  1509. case FlipWorldSubmenuIndexWiFiSettings:
  1510. free_all_views(app, true, false, true);
  1511. if (!alloc_variable_item_list(app, FlipWorldSubmenuIndexWiFiSettings))
  1512. {
  1513. FURI_LOG_E(TAG, "Failed to allocate variable item list");
  1514. return;
  1515. }
  1516. view_dispatcher_switch_to_view(app->view_dispatcher, FlipWorldViewVariableItemList);
  1517. break;
  1518. case FlipWorldSubmenuIndexGameSettings:
  1519. free_all_views(app, true, false, true);
  1520. if (!alloc_variable_item_list(app, FlipWorldSubmenuIndexGameSettings))
  1521. {
  1522. FURI_LOG_E(TAG, "Failed to allocate variable item list");
  1523. return;
  1524. }
  1525. view_dispatcher_switch_to_view(app->view_dispatcher, FlipWorldViewVariableItemList);
  1526. break;
  1527. case FlipWorldSubmenuIndexUserSettings:
  1528. free_all_views(app, true, false, true);
  1529. if (!alloc_variable_item_list(app, FlipWorldSubmenuIndexUserSettings))
  1530. {
  1531. FURI_LOG_E(TAG, "Failed to allocate variable item list");
  1532. return;
  1533. }
  1534. view_dispatcher_switch_to_view(app->view_dispatcher, FlipWorldViewVariableItemList);
  1535. break;
  1536. default:
  1537. break;
  1538. }
  1539. }
  1540. static bool fetch_lobby(FlipperHTTP *fhttp, char *lobby_name)
  1541. {
  1542. if (!fhttp)
  1543. {
  1544. FURI_LOG_E(TAG, "FlipperHTTP is NULL");
  1545. return false;
  1546. }
  1547. if (!lobby_name || strlen(lobby_name) == 0)
  1548. {
  1549. FURI_LOG_E(TAG, "Lobby name is NULL or empty");
  1550. return false;
  1551. }
  1552. char username[64];
  1553. if (!load_char("Flip-Social-Username", username, sizeof(username)))
  1554. {
  1555. FURI_LOG_E(TAG, "Failed to load Flip-Social-Username");
  1556. return false;
  1557. }
  1558. // send the request to fetch the lobby details, with player_username
  1559. char url[128];
  1560. snprintf(url, sizeof(url), "https://www.jblanked.com/flipper/api/world/pvp/lobby/get/%s/%s/", lobby_name, username);
  1561. snprintf(fhttp->file_path, sizeof(fhttp->file_path), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_world/pvp/lobbies/%s.json", lobby_name);
  1562. fhttp->save_received_data = true;
  1563. if (!flipper_http_request(fhttp, GET, url, "{\"Content-Type\":\"application/json\"}", NULL))
  1564. {
  1565. FURI_LOG_E(TAG, "Failed to fetch lobby details");
  1566. return false;
  1567. }
  1568. fhttp->state = RECEIVING;
  1569. while (fhttp->state != IDLE)
  1570. {
  1571. furi_delay_ms(100);
  1572. }
  1573. return true;
  1574. }
  1575. static bool join_lobby(FlipperHTTP *fhttp, char *lobby_name)
  1576. {
  1577. if (!fhttp)
  1578. {
  1579. FURI_LOG_E(TAG, "FlipperHTTP is NULL");
  1580. return false;
  1581. }
  1582. if (!lobby_name || strlen(lobby_name) == 0)
  1583. {
  1584. FURI_LOG_E(TAG, "Lobby name is NULL or empty");
  1585. return false;
  1586. }
  1587. char username[64];
  1588. if (!load_char("Flip-Social-Username", username, sizeof(username)))
  1589. {
  1590. FURI_LOG_E(TAG, "Failed to load Flip-Social-Username");
  1591. return false;
  1592. }
  1593. char url[128];
  1594. char payload[128];
  1595. snprintf(payload, sizeof(payload), "{\"username\":\"%s\", \"game_id\":\"%s\"}", username, lobby_name);
  1596. save_char("pvp_lobby_name", lobby_name); // save the lobby name
  1597. snprintf(url, sizeof(url), "https://www.jblanked.com/flipper/api/world/pvp/lobby/join/");
  1598. if (!flipper_http_request(fhttp, POST, url, "{\"Content-Type\":\"application/json\"}", payload))
  1599. {
  1600. FURI_LOG_E(TAG, "Failed to join lobby");
  1601. return false;
  1602. }
  1603. fhttp->state = RECEIVING;
  1604. while (fhttp->state != IDLE)
  1605. {
  1606. furi_delay_ms(100);
  1607. }
  1608. return true;
  1609. }
  1610. static bool create_pvp_enemy(FuriString *lobby_details)
  1611. {
  1612. if (!lobby_details)
  1613. {
  1614. FURI_LOG_E(TAG, "Failed to load lobby details");
  1615. return false;
  1616. }
  1617. char current_user[64];
  1618. if (!load_char("Flip-Social-Username", current_user, sizeof(current_user)))
  1619. {
  1620. FURI_LOG_E(TAG, "Failed to load Flip-Social-Username");
  1621. save_char("create_pvp_error", "Failed to load Flip-Social-Username");
  1622. return false;
  1623. }
  1624. for (uint8_t i = 0; i < 2; i++)
  1625. {
  1626. // parse the lobby details
  1627. FuriString *player_stats = get_json_array_value_furi("player_stats", i, lobby_details);
  1628. if (!player_stats)
  1629. {
  1630. FURI_LOG_E(TAG, "Failed to get player stats");
  1631. save_char("create_pvp_error", "Failed to get player stats array");
  1632. return false;
  1633. }
  1634. // available keys from player_stats
  1635. FuriString *username = get_json_value_furi("username", player_stats);
  1636. if (!username)
  1637. {
  1638. FURI_LOG_E(TAG, "Failed to get username");
  1639. save_char("create_pvp_error", "Failed to get username");
  1640. furi_string_free(player_stats);
  1641. return false;
  1642. }
  1643. // check if the username is the same as the current user
  1644. if (is_str(furi_string_get_cstr(username), current_user))
  1645. {
  1646. furi_string_free(player_stats);
  1647. furi_string_free(username);
  1648. continue; // skip the current user
  1649. }
  1650. FuriString *strength = get_json_value_furi("strength", player_stats);
  1651. FuriString *health = get_json_value_furi("health", player_stats);
  1652. FuriString *attack_timer = get_json_value_furi("attack_timer", player_stats);
  1653. if (!strength || !health || !attack_timer)
  1654. {
  1655. FURI_LOG_E(TAG, "Failed to get player stats");
  1656. save_char("create_pvp_error", "Failed to get player stats");
  1657. furi_string_free(player_stats);
  1658. furi_string_free(username);
  1659. if (strength)
  1660. furi_string_free(strength);
  1661. if (health)
  1662. furi_string_free(health);
  1663. if (attack_timer)
  1664. furi_string_free(attack_timer);
  1665. return false;
  1666. }
  1667. // create enemy data
  1668. FuriString *enemy_data = furi_string_alloc();
  1669. furi_string_printf(
  1670. enemy_data,
  1671. "{\"enemy_data\":[{\"id\":\"sword\",\"is_user\":\"true\",\"username\":\"%s\","
  1672. "\"index\":0,\"start_position\":{\"x\":350,\"y\":210},\"end_position\":{\"x\":350,\"y\":210},"
  1673. "\"move_timer\":1,\"speed\":1,\"attack_timer\":%f,\"strength\":%f,\"health\":%f}]}",
  1674. furi_string_get_cstr(username),
  1675. (double)atof_furi(attack_timer),
  1676. (double)atof_furi(strength),
  1677. (double)atof_furi(health));
  1678. char directory_path[128];
  1679. snprintf(directory_path, sizeof(directory_path), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_world");
  1680. Storage *storage = furi_record_open(RECORD_STORAGE);
  1681. storage_common_mkdir(storage, directory_path);
  1682. snprintf(directory_path, sizeof(directory_path), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_world/worlds");
  1683. storage_common_mkdir(storage, directory_path);
  1684. snprintf(directory_path, sizeof(directory_path), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_world/worlds/pvp_world");
  1685. storage_common_mkdir(storage, directory_path);
  1686. furi_record_close(RECORD_STORAGE);
  1687. snprintf(directory_path, sizeof(directory_path), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_world/worlds/pvp_world/pvp_world_enemy_data.json");
  1688. // remove the enemy_data file if it exists
  1689. storage_simply_remove_recursive(storage, directory_path);
  1690. File *file = storage_file_alloc(storage);
  1691. if (!storage_file_open(file, directory_path, FSAM_WRITE, FSOM_CREATE_ALWAYS))
  1692. {
  1693. FURI_LOG_E("Game", "Failed to open file for writing: %s", directory_path);
  1694. save_char("create_pvp_error", "Failed to open file for writing");
  1695. storage_file_free(file);
  1696. furi_record_close(RECORD_STORAGE);
  1697. furi_string_free(enemy_data);
  1698. furi_string_free(player_stats);
  1699. furi_string_free(username);
  1700. furi_string_free(strength);
  1701. furi_string_free(health);
  1702. furi_string_free(attack_timer);
  1703. return false;
  1704. }
  1705. size_t data_size = furi_string_size(enemy_data);
  1706. if (storage_file_write(file, furi_string_get_cstr(enemy_data), data_size) != data_size)
  1707. {
  1708. FURI_LOG_E("Game", "Failed to write enemy_data");
  1709. save_char("create_pvp_error", "Failed to write enemy_data");
  1710. }
  1711. storage_file_close(file);
  1712. furi_string_free(enemy_data);
  1713. furi_string_free(player_stats);
  1714. furi_string_free(username);
  1715. furi_string_free(strength);
  1716. furi_string_free(health);
  1717. furi_string_free(attack_timer);
  1718. // player is found so break
  1719. break;
  1720. }
  1721. return true;
  1722. }
  1723. // since we aren't using FURI_LOG, we will use easy_flipper_dialog and the last_error_message
  1724. // char last_error_message[64];
  1725. static size_t lobby_count(FlipperHTTP *fhttp, FuriString *lobby)
  1726. {
  1727. if (!fhttp)
  1728. {
  1729. FURI_LOG_E(TAG, "FlipperHTTP is NULL");
  1730. return -1;
  1731. }
  1732. if (!lobby)
  1733. {
  1734. FURI_LOG_E(TAG, "Lobby details are NULL");
  1735. return -1;
  1736. }
  1737. // check if the player is in the lobby
  1738. FuriString *player_count = get_json_value_furi("player_count", lobby);
  1739. if (!player_count)
  1740. {
  1741. FURI_LOG_E(TAG, "Failed to get player count");
  1742. return -1;
  1743. }
  1744. const size_t count = atoi(furi_string_get_cstr(player_count));
  1745. furi_string_free(player_count);
  1746. return count;
  1747. }
  1748. static bool in_lobby(FlipperHTTP *fhttp, FuriString *lobby)
  1749. {
  1750. if (!fhttp)
  1751. {
  1752. FURI_LOG_E(TAG, "FlipperHTTP is NULL");
  1753. return false;
  1754. }
  1755. if (!lobby)
  1756. {
  1757. FURI_LOG_E(TAG, "Lobby details are NULL");
  1758. return false;
  1759. }
  1760. // check if the player is in the lobby
  1761. FuriString *is_in_game = get_json_value_furi("is_in_game", lobby);
  1762. if (!is_in_game)
  1763. {
  1764. FURI_LOG_E(TAG, "Failed to get is_in_game");
  1765. furi_string_free(is_in_game);
  1766. return false;
  1767. }
  1768. const bool in_game = is_str(furi_string_get_cstr(is_in_game), "true");
  1769. furi_string_free(is_in_game);
  1770. return in_game;
  1771. }
  1772. static bool start_ws(FlipperHTTP *fhttp, char *lobby_name)
  1773. {
  1774. if (!fhttp)
  1775. {
  1776. FURI_LOG_E(TAG, "FlipperHTTP is NULL");
  1777. return false;
  1778. }
  1779. if (!lobby_name || strlen(lobby_name) == 0)
  1780. {
  1781. FURI_LOG_E(TAG, "Lobby name is NULL or empty");
  1782. return false;
  1783. }
  1784. fhttp->state = IDLE; // ensure it's set to IDLE for the next request
  1785. char websocket_url[128];
  1786. snprintf(websocket_url, sizeof(websocket_url), "ws://www.jblanked.com/ws/game/%s/", lobby_name);
  1787. if (!flipper_http_websocket_start(fhttp, websocket_url, 80, "{\"Content-Type\":\"application/json\"}"))
  1788. {
  1789. FURI_LOG_E(TAG, "Failed to start websocket");
  1790. return false;
  1791. }
  1792. fhttp->state = RECEIVING;
  1793. while (fhttp->state != IDLE)
  1794. {
  1795. furi_delay_ms(100);
  1796. }
  1797. return true;
  1798. }
  1799. // this will free both the fhttp and lobby
  1800. static void start_pvp(FlipperHTTP *fhttp, FuriString *lobby, void *context)
  1801. {
  1802. FlipWorldApp *app = (FlipWorldApp *)context;
  1803. furi_check(app, "FlipWorldApp is NULL");
  1804. // only thing left to do is create the enemy data and start the websocket session
  1805. if (!create_pvp_enemy(lobby))
  1806. {
  1807. FURI_LOG_E(TAG, "Failed to create pvp enemy context.");
  1808. easy_flipper_dialog("Error", "Failed to create pvp enemy context. Press BACK to return.");
  1809. flipper_http_free(fhttp);
  1810. furi_string_free(lobby);
  1811. return;
  1812. }
  1813. furi_string_free(lobby);
  1814. // start the websocket session
  1815. if (!start_ws(fhttp, lobby_list[lobby_index]))
  1816. {
  1817. FURI_LOG_E(TAG, "Failed to start websocket session");
  1818. easy_flipper_dialog("Error", "Failed to start websocket session. Press BACK to return.");
  1819. flipper_http_free(fhttp);
  1820. return;
  1821. }
  1822. flipper_http_free(fhttp);
  1823. // start the game thread
  1824. if (!start_game_thread(app))
  1825. {
  1826. FURI_LOG_E(TAG, "Failed to start game thread");
  1827. easy_flipper_dialog("Error", "Failed to start game thread. Press BACK to return.");
  1828. return;
  1829. }
  1830. };
  1831. void waiting_loader_process_callback(FlipperHTTP *fhttp, void *context)
  1832. {
  1833. FlipWorldApp *app = (FlipWorldApp *)context;
  1834. if (!app)
  1835. {
  1836. FURI_LOG_E(TAG, "FlipWorldApp is NULL");
  1837. return;
  1838. }
  1839. if (!fhttp)
  1840. {
  1841. FURI_LOG_E(TAG, "Failed to allocate FlipperHTTP");
  1842. easy_flipper_dialog("Error", "Failed to allocate FlipperHTTP. Press BACK to return.");
  1843. view_dispatcher_switch_to_view(app->view_dispatcher, FlipWorldViewSubmenuOther);
  1844. return;
  1845. }
  1846. // fetch the lobby details
  1847. if (!fetch_lobby(fhttp, lobby_list[lobby_index]))
  1848. {
  1849. FURI_LOG_E(TAG, "Failed to fetch lobby details");
  1850. flipper_http_free(fhttp);
  1851. easy_flipper_dialog("Error", "Failed to fetch lobby details. Press BACK to return.");
  1852. view_dispatcher_switch_to_view(app->view_dispatcher, FlipWorldViewSubmenuOther);
  1853. return;
  1854. }
  1855. // load the lobby details
  1856. FuriString *lobby = flipper_http_load_from_file(fhttp->file_path);
  1857. if (!lobby)
  1858. {
  1859. FURI_LOG_E(TAG, "Failed to load lobby details");
  1860. flipper_http_free(fhttp);
  1861. easy_flipper_dialog("Error", "Failed to load lobby details. Press BACK to return.");
  1862. view_dispatcher_switch_to_view(app->view_dispatcher, FlipWorldViewSubmenuOther);
  1863. return;
  1864. }
  1865. // get the player count
  1866. const size_t count = lobby_count(fhttp, lobby);
  1867. if (count == 2)
  1868. {
  1869. // break out of this and start the game
  1870. start_pvp(fhttp, lobby, app); // this will free both the fhttp and lobby
  1871. return;
  1872. }
  1873. furi_string_free(lobby);
  1874. }
  1875. static void waiting_lobby(void *context)
  1876. {
  1877. FlipWorldApp *app = (FlipWorldApp *)context;
  1878. furi_check(app, "waiting_lobby: FlipWorldApp is NULL");
  1879. if (!start_waiting_thread(app))
  1880. {
  1881. FURI_LOG_E(TAG, "Failed to start waiting thread");
  1882. easy_flipper_dialog("Error", "Failed to start waiting thread. Press BACK to return.");
  1883. return;
  1884. }
  1885. free_message_view(app);
  1886. if (!alloc_message_view(app, MessageStateWaitingLobby))
  1887. {
  1888. FURI_LOG_E(TAG, "Failed to allocate message view");
  1889. return;
  1890. }
  1891. // finally, switch to the waiting lobby view
  1892. view_dispatcher_switch_to_view(app->view_dispatcher, FlipWorldViewMessage);
  1893. };
  1894. static void callback_submenu_lobby_choices(void *context, uint32_t index)
  1895. {
  1896. /* Handle other game lobbies
  1897. 1. when clicked on, send request to fetch the selected game lobby details
  1898. 2. start the websocket session
  1899. 3. start the game thread (the rest will be handled by game_start and player_update)
  1900. */
  1901. FlipWorldApp *app = (FlipWorldApp *)context;
  1902. furi_check(app, "FlipWorldApp is NULL");
  1903. if (index >= FlipWorldSubmenuIndexLobby && index < FlipWorldSubmenuIndexLobby + 10)
  1904. {
  1905. lobby_index = index - FlipWorldSubmenuIndexLobby;
  1906. FlipperHTTP *fhttp = flipper_http_alloc();
  1907. if (!fhttp)
  1908. {
  1909. FURI_LOG_E(TAG, "Failed to allocate FlipperHTTP");
  1910. easy_flipper_dialog("Error", "Failed to allocate FlipperHTTP. Press BACK to return.");
  1911. return;
  1912. }
  1913. // fetch the lobby details
  1914. if (!fetch_lobby(fhttp, lobby_list[lobby_index]))
  1915. {
  1916. FURI_LOG_E(TAG, "Failed to fetch lobby details");
  1917. easy_flipper_dialog("Error", "Failed to fetch lobby details. Press BACK to return.");
  1918. flipper_http_free(fhttp);
  1919. return;
  1920. }
  1921. // load the lobby details
  1922. FuriString *lobby = flipper_http_load_from_file(fhttp->file_path);
  1923. if (!lobby)
  1924. {
  1925. FURI_LOG_E(TAG, "Failed to load lobby details");
  1926. flipper_http_free(fhttp);
  1927. return;
  1928. }
  1929. // if there are no players, add the user to the lobby and make the user wait until another player joins
  1930. // if there is one player and it's the user, make the user wait until another player joins
  1931. // if there is one player and it's not the user, parse_lobby and start websocket
  1932. // if there are 2 players (which there shouldn't be at this point), show an error message saying the lobby is full
  1933. switch (lobby_count(fhttp, lobby))
  1934. {
  1935. case -1:
  1936. FURI_LOG_E(TAG, "Failed to get player count");
  1937. easy_flipper_dialog("Error", "Failed to get player count. Press BACK to return.");
  1938. flipper_http_free(fhttp);
  1939. furi_string_free(lobby);
  1940. return;
  1941. case 0:
  1942. // add the user to the lobby
  1943. if (!join_lobby(fhttp, lobby_list[lobby_index]))
  1944. {
  1945. FURI_LOG_E(TAG, "Failed to join lobby");
  1946. easy_flipper_dialog("Error", "Failed to join lobby. Press BACK to return.");
  1947. flipper_http_free(fhttp);
  1948. furi_string_free(lobby);
  1949. return;
  1950. }
  1951. // send the user to the waiting screen
  1952. waiting_lobby(app);
  1953. return;
  1954. case 1:
  1955. // check if the user is in the lobby
  1956. if (in_lobby(fhttp, lobby))
  1957. {
  1958. // send the user to the waiting screen
  1959. FURI_LOG_I(TAG, "User is in the lobby");
  1960. flipper_http_free(fhttp);
  1961. furi_string_free(lobby);
  1962. waiting_lobby(app);
  1963. return;
  1964. }
  1965. // add the user to the lobby
  1966. if (!join_lobby(fhttp, lobby_list[lobby_index]))
  1967. {
  1968. FURI_LOG_E(TAG, "Failed to join lobby");
  1969. easy_flipper_dialog("Error", "Failed to join lobby. Press BACK to return.");
  1970. flipper_http_free(fhttp);
  1971. furi_string_free(lobby);
  1972. return;
  1973. }
  1974. break;
  1975. case 2:
  1976. // show an error message saying the lobby is full
  1977. FURI_LOG_E(TAG, "Lobby is full");
  1978. easy_flipper_dialog("Error", "Lobby is full. Press BACK to return.");
  1979. flipper_http_free(fhttp);
  1980. furi_string_free(lobby);
  1981. return;
  1982. };
  1983. start_pvp(fhttp, lobby, app); // this will free both the fhttp and lobby, and start the game
  1984. }
  1985. }
  1986. static void updated_wifi_ssid(void *context)
  1987. {
  1988. FlipWorldApp *app = (FlipWorldApp *)context;
  1989. if (!app)
  1990. {
  1991. FURI_LOG_E(TAG, "FlipWorldApp is NULL");
  1992. return;
  1993. }
  1994. // store the entered text
  1995. strncpy(app->text_input_buffer, app->text_input_temp_buffer, app->text_input_buffer_size);
  1996. // Ensure null-termination
  1997. app->text_input_buffer[app->text_input_buffer_size - 1] = '\0';
  1998. // save the setting
  1999. save_char("WiFi-SSID", app->text_input_buffer);
  2000. // update the variable item text
  2001. if (app->variable_item_wifi_ssid)
  2002. {
  2003. variable_item_set_current_value_text(app->variable_item_wifi_ssid, app->text_input_buffer);
  2004. // get value of password
  2005. char pass[64];
  2006. char username[64];
  2007. char password[64];
  2008. if (load_char("WiFi-Password", pass, sizeof(pass)))
  2009. {
  2010. if (strlen(pass) > 0 && strlen(app->text_input_buffer) > 0)
  2011. {
  2012. // save the settings
  2013. load_char("Flip-Social-Username", username, sizeof(username));
  2014. load_char("Flip-Social-Password", password, sizeof(password));
  2015. save_settings(app->text_input_buffer, pass, username, password);
  2016. // initialize the http
  2017. FlipperHTTP *fhttp = flipper_http_alloc();
  2018. if (fhttp)
  2019. {
  2020. // save the wifi if the device is connected
  2021. if (!flipper_http_save_wifi(fhttp, app->text_input_buffer, pass))
  2022. {
  2023. easy_flipper_dialog("FlipperHTTP Error", "Ensure your WiFi Developer\nBoard or Pico W is connected\nand the latest FlipperHTTP\nfirmware is installed.");
  2024. }
  2025. // free the resources
  2026. flipper_http_free(fhttp);
  2027. }
  2028. else
  2029. {
  2030. easy_flipper_dialog("FlipperHTTP Error", "The UART is likely busy.\nEnsure you have the correct\nflash for your board then\nrestart your Flipper Zero.");
  2031. }
  2032. }
  2033. }
  2034. }
  2035. // switch to the settings view
  2036. view_dispatcher_switch_to_view(app->view_dispatcher, FlipWorldViewVariableItemList);
  2037. }
  2038. static void updated_wifi_pass(void *context)
  2039. {
  2040. FlipWorldApp *app = (FlipWorldApp *)context;
  2041. if (!app)
  2042. {
  2043. FURI_LOG_E(TAG, "FlipWorldApp is NULL");
  2044. return;
  2045. }
  2046. // store the entered text
  2047. strncpy(app->text_input_buffer, app->text_input_temp_buffer, app->text_input_buffer_size);
  2048. // Ensure null-termination
  2049. app->text_input_buffer[app->text_input_buffer_size - 1] = '\0';
  2050. // save the setting
  2051. save_char("WiFi-Password", app->text_input_buffer);
  2052. // update the variable item text
  2053. if (app->variable_item_wifi_pass)
  2054. {
  2055. // variable_item_set_current_value_text(app->variable_item_wifi_pass, app->text_input_buffer);
  2056. }
  2057. // get value of ssid
  2058. char ssid[64];
  2059. char username[64];
  2060. char password[64];
  2061. if (load_char("WiFi-SSID", ssid, sizeof(ssid)))
  2062. {
  2063. if (strlen(ssid) > 0 && strlen(app->text_input_buffer) > 0)
  2064. {
  2065. // save the settings
  2066. load_char("Flip-Social-Username", username, sizeof(username));
  2067. load_char("Flip-Social-Password", password, sizeof(password));
  2068. save_settings(ssid, app->text_input_buffer, username, password);
  2069. // initialize the http
  2070. FlipperHTTP *fhttp = flipper_http_alloc();
  2071. if (fhttp)
  2072. {
  2073. // save the wifi if the device is connected
  2074. if (!flipper_http_save_wifi(fhttp, ssid, app->text_input_buffer))
  2075. {
  2076. easy_flipper_dialog("FlipperHTTP Error", "Ensure your WiFi Developer\nBoard or Pico W is connected\nand the latest FlipperHTTP\nfirmware is installed.");
  2077. }
  2078. // free the resources
  2079. flipper_http_free(fhttp);
  2080. }
  2081. else
  2082. {
  2083. easy_flipper_dialog("FlipperHTTP Error", "The UART is likely busy.\nEnsure you have the correct\nflash for your board then\nrestart your Flipper Zero.");
  2084. }
  2085. }
  2086. }
  2087. // switch to the settings view
  2088. view_dispatcher_switch_to_view(app->view_dispatcher, FlipWorldViewVariableItemList);
  2089. }
  2090. static void updated_username(void *context)
  2091. {
  2092. FlipWorldApp *app = (FlipWorldApp *)context;
  2093. if (!app)
  2094. {
  2095. FURI_LOG_E(TAG, "FlipWorldApp is NULL");
  2096. return;
  2097. }
  2098. // store the entered text
  2099. strncpy(app->text_input_buffer, app->text_input_temp_buffer, app->text_input_buffer_size);
  2100. // Ensure null-termination
  2101. app->text_input_buffer[app->text_input_buffer_size - 1] = '\0';
  2102. // save the setting
  2103. save_char("Flip-Social-Username", app->text_input_buffer);
  2104. // update the variable item text
  2105. if (app->variable_item_user_username)
  2106. {
  2107. variable_item_set_current_value_text(app->variable_item_user_username, app->text_input_buffer);
  2108. }
  2109. view_dispatcher_switch_to_view(app->view_dispatcher, FlipWorldViewVariableItemList); // back to user settings
  2110. }
  2111. static void updated_password(void *context)
  2112. {
  2113. FlipWorldApp *app = (FlipWorldApp *)context;
  2114. if (!app)
  2115. {
  2116. FURI_LOG_E(TAG, "FlipWorldApp is NULL");
  2117. return;
  2118. }
  2119. // store the entered text
  2120. strncpy(app->text_input_buffer, app->text_input_temp_buffer, app->text_input_buffer_size);
  2121. // Ensure null-termination
  2122. app->text_input_buffer[app->text_input_buffer_size - 1] = '\0';
  2123. // save the setting
  2124. save_char("Flip-Social-Password", app->text_input_buffer);
  2125. // update the variable item text
  2126. if (app->variable_item_user_password)
  2127. {
  2128. variable_item_set_current_value_text(app->variable_item_user_password, app->text_input_buffer);
  2129. }
  2130. // get value of username
  2131. char username[64];
  2132. char ssid[64];
  2133. char pass[64];
  2134. if (load_char("Flip-Social-Username", username, sizeof(username)))
  2135. {
  2136. if (strlen(username) > 0 && strlen(app->text_input_buffer) > 0)
  2137. {
  2138. // save the settings
  2139. load_char("WiFi-SSID", ssid, sizeof(ssid));
  2140. load_char("WiFi-Password", pass, sizeof(pass));
  2141. save_settings(ssid, pass, username, app->text_input_buffer);
  2142. }
  2143. }
  2144. view_dispatcher_switch_to_view(app->view_dispatcher, FlipWorldViewVariableItemList); // back to user settings
  2145. }
  2146. static void wifi_settings_select(void *context, uint32_t index)
  2147. {
  2148. FlipWorldApp *app = (FlipWorldApp *)context;
  2149. if (!app)
  2150. {
  2151. FURI_LOG_E(TAG, "FlipWorldApp is NULL");
  2152. return;
  2153. }
  2154. char ssid[64];
  2155. char pass[64];
  2156. char username[64];
  2157. char password[64];
  2158. switch (index)
  2159. {
  2160. case 0: // Input SSID
  2161. free_all_views(app, false, false, true);
  2162. if (!alloc_text_input_view(app, "SSID"))
  2163. {
  2164. FURI_LOG_E(TAG, "Failed to allocate text input view");
  2165. return;
  2166. }
  2167. // load SSID
  2168. if (load_settings(ssid, sizeof(ssid), pass, sizeof(pass), username, sizeof(username), password, sizeof(password)))
  2169. {
  2170. strncpy(app->text_input_temp_buffer, ssid, app->text_input_buffer_size - 1);
  2171. app->text_input_temp_buffer[app->text_input_buffer_size - 1] = '\0';
  2172. }
  2173. view_dispatcher_switch_to_view(app->view_dispatcher, FlipWorldViewTextInput);
  2174. break;
  2175. case 1: // Input Password
  2176. free_all_views(app, false, false, true);
  2177. if (!alloc_text_input_view(app, "Password"))
  2178. {
  2179. FURI_LOG_E(TAG, "Failed to allocate text input view");
  2180. return;
  2181. }
  2182. // load password
  2183. if (load_settings(ssid, sizeof(ssid), pass, sizeof(pass), username, sizeof(username), password, sizeof(password)))
  2184. {
  2185. strncpy(app->text_input_temp_buffer, pass, app->text_input_buffer_size - 1);
  2186. app->text_input_temp_buffer[app->text_input_buffer_size - 1] = '\0';
  2187. }
  2188. view_dispatcher_switch_to_view(app->view_dispatcher, FlipWorldViewTextInput);
  2189. break;
  2190. default:
  2191. FURI_LOG_E(TAG, "Unknown configuration item index");
  2192. break;
  2193. }
  2194. }
  2195. static void fps_change(VariableItem *item)
  2196. {
  2197. uint8_t index = variable_item_get_current_value_index(item);
  2198. fps_index = index;
  2199. variable_item_set_current_value_text(item, fps_choices_str[index]);
  2200. variable_item_set_current_value_index(item, index);
  2201. save_char("Game-FPS", fps_choices_str[index]);
  2202. }
  2203. static void screen_on_change(VariableItem *item)
  2204. {
  2205. uint8_t index = variable_item_get_current_value_index(item);
  2206. screen_always_on_index = index;
  2207. variable_item_set_current_value_text(item, yes_or_no_choices[index]);
  2208. variable_item_set_current_value_index(item, index);
  2209. save_char("Game-Screen-Always-On", yes_or_no_choices[index]);
  2210. }
  2211. static void sound_on_change(VariableItem *item)
  2212. {
  2213. uint8_t index = variable_item_get_current_value_index(item);
  2214. sound_on_index = index;
  2215. variable_item_set_current_value_text(item, yes_or_no_choices[index]);
  2216. variable_item_set_current_value_index(item, index);
  2217. save_char("Game-Sound-On", yes_or_no_choices[index]);
  2218. }
  2219. static void vibration_on_change(VariableItem *item)
  2220. {
  2221. uint8_t index = variable_item_get_current_value_index(item);
  2222. vibration_on_index = index;
  2223. variable_item_set_current_value_text(item, yes_or_no_choices[index]);
  2224. variable_item_set_current_value_index(item, index);
  2225. save_char("Game-Vibration-On", yes_or_no_choices[index]);
  2226. }
  2227. static void player_on_change(VariableItem *item)
  2228. {
  2229. uint8_t index = variable_item_get_current_value_index(item);
  2230. player_sprite_index = index;
  2231. variable_item_set_current_value_text(item, is_str(player_sprite_choices[index], "naked") ? "None" : player_sprite_choices[index]);
  2232. variable_item_set_current_value_index(item, index);
  2233. save_char("Game-Player-Sprite", player_sprite_choices[index]);
  2234. }
  2235. static void vgm_x_change(VariableItem *item)
  2236. {
  2237. uint8_t index = variable_item_get_current_value_index(item);
  2238. vgm_x_index = index;
  2239. variable_item_set_current_value_text(item, vgm_levels[index]);
  2240. variable_item_set_current_value_index(item, index);
  2241. save_char("Game-VGM-X", vgm_levels[index]);
  2242. }
  2243. static void vgm_y_change(VariableItem *item)
  2244. {
  2245. uint8_t index = variable_item_get_current_value_index(item);
  2246. vgm_y_index = index;
  2247. variable_item_set_current_value_text(item, vgm_levels[index]);
  2248. variable_item_set_current_value_index(item, index);
  2249. save_char("Game-VGM-Y", vgm_levels[index]);
  2250. }
  2251. static bool _fetch_worlds(DataLoaderModel *model)
  2252. {
  2253. if (!model || !model->fhttp)
  2254. {
  2255. FURI_LOG_E(TAG, "model or fhttp is NULL");
  2256. return false;
  2257. }
  2258. char directory_path[128];
  2259. snprintf(directory_path, sizeof(directory_path), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_world");
  2260. Storage *storage = furi_record_open(RECORD_STORAGE);
  2261. storage_common_mkdir(storage, directory_path);
  2262. snprintf(directory_path, sizeof(directory_path), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_world/worlds");
  2263. storage_common_mkdir(storage, directory_path);
  2264. furi_record_close(RECORD_STORAGE);
  2265. snprintf(model->fhttp->file_path, sizeof(model->fhttp->file_path), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_world/worlds/world_list_full.json");
  2266. model->fhttp->save_received_data = true;
  2267. return flipper_http_request(model->fhttp, GET, "https://www.jblanked.com/flipper/api/world/v5/get/10/", "{\"Content-Type\":\"application/json\"}", NULL);
  2268. }
  2269. static char *_parse_worlds(DataLoaderModel *model)
  2270. {
  2271. UNUSED(model);
  2272. return "World Pack Installed";
  2273. }
  2274. static void switch_to_view_get_worlds(FlipWorldApp *app)
  2275. {
  2276. if (!alloc_view_loader(app))
  2277. {
  2278. FURI_LOG_E(TAG, "Failed to allocate view loader");
  2279. return;
  2280. }
  2281. generic_switch_to_view(app, "Fetching World Pack..", _fetch_worlds, _parse_worlds, 1, callback_to_submenu, FlipWorldViewLoader);
  2282. }
  2283. static void game_settings_select(void *context, uint32_t index)
  2284. {
  2285. FlipWorldApp *app = (FlipWorldApp *)context;
  2286. if (!app)
  2287. {
  2288. FURI_LOG_E(TAG, "FlipWorldApp is NULL");
  2289. return;
  2290. }
  2291. switch (index)
  2292. {
  2293. case 0: // Download all world data as one huge json
  2294. switch_to_view_get_worlds(app);
  2295. case 1: // Player Sprite
  2296. break;
  2297. case 2: // Change FPS
  2298. break;
  2299. case 3: // VGM X
  2300. break;
  2301. case 4: // VGM Y
  2302. break;
  2303. case 5: // Screen Always On
  2304. break;
  2305. case 6: // Sound On
  2306. break;
  2307. case 7: // Vibration On
  2308. break;
  2309. }
  2310. }
  2311. static void user_settings_select(void *context, uint32_t index)
  2312. {
  2313. FlipWorldApp *app = (FlipWorldApp *)context;
  2314. if (!app)
  2315. {
  2316. FURI_LOG_E(TAG, "FlipWorldApp is NULL");
  2317. return;
  2318. }
  2319. switch (index)
  2320. {
  2321. case 0: // Username
  2322. free_all_views(app, false, false, true);
  2323. if (!alloc_text_input_view(app, "Username-Login"))
  2324. {
  2325. FURI_LOG_E(TAG, "Failed to allocate text input view");
  2326. return;
  2327. }
  2328. view_dispatcher_switch_to_view(app->view_dispatcher, FlipWorldViewTextInput);
  2329. break;
  2330. case 1: // Password
  2331. free_all_views(app, false, false, true);
  2332. if (!alloc_text_input_view(app, "Password-Login"))
  2333. {
  2334. FURI_LOG_E(TAG, "Failed to allocate text input view");
  2335. return;
  2336. }
  2337. view_dispatcher_switch_to_view(app->view_dispatcher, FlipWorldViewTextInput);
  2338. break;
  2339. }
  2340. }
  2341. static void widget_set_text(char *message, Widget **widget)
  2342. {
  2343. if (widget == NULL)
  2344. {
  2345. FURI_LOG_E(TAG, "set_widget_text - widget is NULL");
  2346. DEV_CRASH();
  2347. return;
  2348. }
  2349. if (message == NULL)
  2350. {
  2351. FURI_LOG_E(TAG, "set_widget_text - message is NULL");
  2352. DEV_CRASH();
  2353. return;
  2354. }
  2355. widget_reset(*widget);
  2356. uint32_t message_length = strlen(message); // Length of the message
  2357. uint32_t i = 0; // Index tracker
  2358. uint32_t formatted_index = 0; // Tracker for where we are in the formatted message
  2359. char *formatted_message; // Buffer to hold the final formatted message
  2360. // Allocate buffer with double the message length plus one for safety
  2361. if (!easy_flipper_set_buffer(&formatted_message, message_length * 2 + 1))
  2362. {
  2363. return;
  2364. }
  2365. while (i < message_length)
  2366. {
  2367. uint32_t max_line_length = 31; // Maximum characters per line
  2368. uint32_t remaining_length = message_length - i; // Remaining characters
  2369. uint32_t line_length = (remaining_length < max_line_length) ? remaining_length : max_line_length;
  2370. // Check for newline character within the current segment
  2371. uint32_t newline_pos = i;
  2372. bool found_newline = false;
  2373. for (; newline_pos < i + line_length && newline_pos < message_length; newline_pos++)
  2374. {
  2375. if (message[newline_pos] == '\n')
  2376. {
  2377. found_newline = true;
  2378. break;
  2379. }
  2380. }
  2381. if (found_newline)
  2382. {
  2383. // If newline found, set line_length up to the newline
  2384. line_length = newline_pos - i;
  2385. }
  2386. // Temporary buffer to hold the current line
  2387. char line[32];
  2388. strncpy(line, message + i, line_length);
  2389. line[line_length] = '\0';
  2390. // If newline was found, skip it for the next iteration
  2391. if (found_newline)
  2392. {
  2393. i += line_length + 1; // +1 to skip the '\n' character
  2394. }
  2395. else
  2396. {
  2397. // Check if the line ends in the middle of a word and adjust accordingly
  2398. if (line_length == max_line_length && message[i + line_length] != '\0' && message[i + line_length] != ' ')
  2399. {
  2400. // Find the last space within the current line to avoid breaking a word
  2401. char *last_space = strrchr(line, ' ');
  2402. if (last_space != NULL)
  2403. {
  2404. // Adjust the line_length to avoid cutting the word
  2405. line_length = last_space - line;
  2406. line[line_length] = '\0'; // Null-terminate at the space
  2407. }
  2408. }
  2409. // Move the index forward by the determined line_length
  2410. i += line_length;
  2411. // Skip any spaces at the beginning of the next line
  2412. while (i < message_length && message[i] == ' ')
  2413. {
  2414. i++;
  2415. }
  2416. }
  2417. // Manually copy the fixed line into the formatted_message buffer
  2418. for (uint32_t j = 0; j < line_length; j++)
  2419. {
  2420. formatted_message[formatted_index++] = line[j];
  2421. }
  2422. // Add a newline character for line spacing
  2423. formatted_message[formatted_index++] = '\n';
  2424. }
  2425. // Null-terminate the formatted_message
  2426. formatted_message[formatted_index] = '\0';
  2427. // Add the formatted message to the widget
  2428. widget_add_text_scroll_element(*widget, 0, 0, 128, 64, formatted_message);
  2429. }
  2430. void loader_draw_callback(Canvas *canvas, void *model)
  2431. {
  2432. if (!canvas || !model)
  2433. {
  2434. FURI_LOG_E(TAG, "loader_draw_callback - canvas or model is NULL");
  2435. return;
  2436. }
  2437. DataLoaderModel *data_loader_model = (DataLoaderModel *)model;
  2438. HTTPState http_state = data_loader_model->fhttp->state;
  2439. DataState data_state = data_loader_model->data_state;
  2440. char *title = data_loader_model->title;
  2441. canvas_set_font(canvas, FontSecondary);
  2442. if (http_state == INACTIVE)
  2443. {
  2444. canvas_draw_str(canvas, 0, 7, "Wifi Dev Board disconnected.");
  2445. canvas_draw_str(canvas, 0, 17, "Please connect to the board.");
  2446. canvas_draw_str(canvas, 0, 32, "If your board is connected,");
  2447. canvas_draw_str(canvas, 0, 42, "make sure you have flashed");
  2448. canvas_draw_str(canvas, 0, 52, "your WiFi Devboard with the");
  2449. canvas_draw_str(canvas, 0, 62, "latest FlipperHTTP flash.");
  2450. return;
  2451. }
  2452. if (data_state == DataStateError || data_state == DataStateParseError)
  2453. {
  2454. error_draw(canvas, data_loader_model);
  2455. return;
  2456. }
  2457. canvas_draw_str(canvas, 0, 7, title);
  2458. canvas_draw_str(canvas, 0, 17, "Loading...");
  2459. if (data_state == DataStateInitial)
  2460. {
  2461. return;
  2462. }
  2463. if (http_state == SENDING)
  2464. {
  2465. canvas_draw_str(canvas, 0, 27, "Fetching...");
  2466. return;
  2467. }
  2468. if (http_state == RECEIVING || data_state == DataStateRequested)
  2469. {
  2470. canvas_draw_str(canvas, 0, 27, "Receiving...");
  2471. return;
  2472. }
  2473. if (http_state == IDLE && data_state == DataStateReceived)
  2474. {
  2475. canvas_draw_str(canvas, 0, 27, "Processing...");
  2476. return;
  2477. }
  2478. if (http_state == IDLE && data_state == DataStateParsed)
  2479. {
  2480. canvas_draw_str(canvas, 0, 27, "Processed...");
  2481. return;
  2482. }
  2483. }
  2484. static void loader_process_callback(void *context)
  2485. {
  2486. if (context == NULL)
  2487. {
  2488. FURI_LOG_E(TAG, "loader_process_callback - context is NULL");
  2489. DEV_CRASH();
  2490. return;
  2491. }
  2492. FlipWorldApp *app = (FlipWorldApp *)context;
  2493. View *view = app->view_loader;
  2494. DataState current_data_state;
  2495. DataLoaderModel *loader_model = NULL;
  2496. with_view_model(
  2497. view,
  2498. DataLoaderModel * model,
  2499. {
  2500. current_data_state = model->data_state;
  2501. loader_model = model;
  2502. },
  2503. false);
  2504. if (!loader_model || !loader_model->fhttp)
  2505. {
  2506. FURI_LOG_E(TAG, "Model or fhttp is NULL");
  2507. DEV_CRASH();
  2508. return;
  2509. }
  2510. if (current_data_state == DataStateInitial)
  2511. {
  2512. with_view_model(
  2513. view,
  2514. DataLoaderModel * model,
  2515. {
  2516. model->data_state = DataStateRequested;
  2517. DataLoaderFetch fetch = model->fetcher;
  2518. if (fetch == NULL)
  2519. {
  2520. FURI_LOG_E(TAG, "Model doesn't have Fetch function assigned.");
  2521. model->data_state = DataStateError;
  2522. return;
  2523. }
  2524. // Clear any previous responses
  2525. strncpy(model->fhttp->last_response, "", 1);
  2526. bool request_status = fetch(model);
  2527. if (!request_status)
  2528. {
  2529. model->data_state = DataStateError;
  2530. }
  2531. },
  2532. true);
  2533. }
  2534. else if (current_data_state == DataStateRequested || current_data_state == DataStateError)
  2535. {
  2536. if (loader_model->fhttp->state == IDLE && loader_model->fhttp->last_response != NULL)
  2537. {
  2538. if (strstr(loader_model->fhttp->last_response, "[PONG]") != NULL)
  2539. {
  2540. FURI_LOG_DEV(TAG, "PONG received.");
  2541. }
  2542. else if (strncmp(loader_model->fhttp->last_response, "[SUCCESS]", 9))
  2543. {
  2544. FURI_LOG_DEV(TAG, "SUCCESS received. %s", loader_model->fhttp->last_response ? loader_model->fhttp->last_response : "NULL");
  2545. }
  2546. else if (strncmp(loader_model->fhttp->last_response, "[ERROR]", 9))
  2547. {
  2548. FURI_LOG_DEV(TAG, "ERROR received. %s", loader_model->fhttp->last_response ? loader_model->fhttp->last_response : "NULL");
  2549. }
  2550. else if (strlen(loader_model->fhttp->last_response))
  2551. {
  2552. // Still waiting on response
  2553. }
  2554. else
  2555. {
  2556. with_view_model(view, DataLoaderModel * model, { model->data_state = DataStateReceived; }, true);
  2557. }
  2558. }
  2559. else if (loader_model->fhttp->state == SENDING || loader_model->fhttp->state == RECEIVING)
  2560. {
  2561. // continue waiting
  2562. }
  2563. else if (loader_model->fhttp->state == INACTIVE)
  2564. {
  2565. // inactive. try again
  2566. }
  2567. else if (loader_model->fhttp->state == ISSUE)
  2568. {
  2569. with_view_model(view, DataLoaderModel * model, { model->data_state = DataStateError; }, true);
  2570. }
  2571. else
  2572. {
  2573. FURI_LOG_DEV(TAG, "Unexpected state: %d lastresp: %s", loader_model->fhttp->state, loader_model->fhttp->last_response ? loader_model->fhttp->last_response : "NULL");
  2574. DEV_CRASH();
  2575. }
  2576. }
  2577. else if (current_data_state == DataStateReceived)
  2578. {
  2579. with_view_model(
  2580. view,
  2581. DataLoaderModel * model,
  2582. {
  2583. char *data_text;
  2584. if (model->parser == NULL)
  2585. {
  2586. data_text = NULL;
  2587. FURI_LOG_DEV(TAG, "Parser is NULL");
  2588. DEV_CRASH();
  2589. }
  2590. else
  2591. {
  2592. data_text = model->parser(model);
  2593. }
  2594. FURI_LOG_DEV(TAG, "Parsed data: %s\r\ntext: %s", model->fhttp->last_response ? model->fhttp->last_response : "NULL", data_text ? data_text : "NULL");
  2595. model->data_text = data_text;
  2596. if (data_text == NULL)
  2597. {
  2598. model->data_state = DataStateParseError;
  2599. }
  2600. else
  2601. {
  2602. model->data_state = DataStateParsed;
  2603. }
  2604. },
  2605. true);
  2606. }
  2607. else if (current_data_state == DataStateParsed)
  2608. {
  2609. with_view_model(
  2610. view,
  2611. DataLoaderModel * model,
  2612. {
  2613. if (++model->request_index < model->request_count)
  2614. {
  2615. model->data_state = DataStateInitial;
  2616. }
  2617. else
  2618. {
  2619. widget_set_text(model->data_text != NULL ? model->data_text : "", &app->widget_result);
  2620. if (model->data_text != NULL)
  2621. {
  2622. free(model->data_text);
  2623. model->data_text = NULL;
  2624. }
  2625. view_set_previous_callback(widget_get_view(app->widget_result), model->back_callback);
  2626. view_dispatcher_switch_to_view(app->view_dispatcher, FlipWorldViewWidgetResult);
  2627. }
  2628. },
  2629. true);
  2630. }
  2631. }
  2632. static void loader_timer_callback(void *context)
  2633. {
  2634. if (context == NULL)
  2635. {
  2636. FURI_LOG_E(TAG, "loader_timer_callback - context is NULL");
  2637. DEV_CRASH();
  2638. return;
  2639. }
  2640. FlipWorldApp *app = (FlipWorldApp *)context;
  2641. view_dispatcher_send_custom_event(app->view_dispatcher, FlipWorldCustomEventProcess);
  2642. }
  2643. static void loader_on_enter(void *context)
  2644. {
  2645. if (context == NULL)
  2646. {
  2647. FURI_LOG_E(TAG, "loader_on_enter - context is NULL");
  2648. DEV_CRASH();
  2649. return;
  2650. }
  2651. FlipWorldApp *app = (FlipWorldApp *)context;
  2652. View *view = app->view_loader;
  2653. with_view_model(
  2654. view,
  2655. DataLoaderModel * model,
  2656. {
  2657. view_set_previous_callback(view, model->back_callback);
  2658. if (model->timer == NULL)
  2659. {
  2660. model->timer = furi_timer_alloc(loader_timer_callback, FuriTimerTypePeriodic, app);
  2661. }
  2662. furi_timer_start(model->timer, 250);
  2663. },
  2664. true);
  2665. }
  2666. static void loader_on_exit(void *context)
  2667. {
  2668. if (context == NULL)
  2669. {
  2670. FURI_LOG_E(TAG, "loader_on_exit - context is NULL");
  2671. DEV_CRASH();
  2672. return;
  2673. }
  2674. FlipWorldApp *app = (FlipWorldApp *)context;
  2675. View *view = app->view_loader;
  2676. with_view_model(
  2677. view,
  2678. DataLoaderModel * model,
  2679. {
  2680. if (model->timer)
  2681. {
  2682. furi_timer_stop(model->timer);
  2683. }
  2684. },
  2685. false);
  2686. }
  2687. void loader_init(View *view)
  2688. {
  2689. if (view == NULL)
  2690. {
  2691. FURI_LOG_E(TAG, "loader_init - view is NULL");
  2692. DEV_CRASH();
  2693. return;
  2694. }
  2695. view_allocate_model(view, ViewModelTypeLocking, sizeof(DataLoaderModel));
  2696. view_set_enter_callback(view, loader_on_enter);
  2697. view_set_exit_callback(view, loader_on_exit);
  2698. }
  2699. void loader_free_model(View *view)
  2700. {
  2701. if (view == NULL)
  2702. {
  2703. FURI_LOG_E(TAG, "loader_free_model - view is NULL");
  2704. DEV_CRASH();
  2705. return;
  2706. }
  2707. with_view_model(
  2708. view,
  2709. DataLoaderModel * model,
  2710. {
  2711. if (model->timer)
  2712. {
  2713. furi_timer_free(model->timer);
  2714. model->timer = NULL;
  2715. }
  2716. if (model->parser_context)
  2717. {
  2718. // do not free the context here, it is the app context
  2719. // free(model->parser_context);
  2720. // model->parser_context = NULL;
  2721. }
  2722. if (model->fhttp)
  2723. {
  2724. flipper_http_free(model->fhttp);
  2725. model->fhttp = NULL;
  2726. }
  2727. },
  2728. false);
  2729. }
  2730. bool custom_event_callback(void *context, uint32_t index)
  2731. {
  2732. if (context == NULL)
  2733. {
  2734. FURI_LOG_E(TAG, "custom_event_callback - context is NULL");
  2735. DEV_CRASH();
  2736. return false;
  2737. }
  2738. switch (index)
  2739. {
  2740. case FlipWorldCustomEventProcess:
  2741. loader_process_callback(context);
  2742. return true;
  2743. default:
  2744. FURI_LOG_DEV(TAG, "custom_event_callback. Unknown index: %ld", index);
  2745. return false;
  2746. }
  2747. }
  2748. void generic_switch_to_view(FlipWorldApp *app, char *title, DataLoaderFetch fetcher, DataLoaderParser parser, size_t request_count, ViewNavigationCallback back, uint32_t view_id)
  2749. {
  2750. if (app == NULL)
  2751. {
  2752. FURI_LOG_E(TAG, "generic_switch_to_view - app is NULL");
  2753. DEV_CRASH();
  2754. return;
  2755. }
  2756. View *view = app->view_loader;
  2757. if (view == NULL)
  2758. {
  2759. FURI_LOG_E(TAG, "generic_switch_to_view - view is NULL");
  2760. DEV_CRASH();
  2761. return;
  2762. }
  2763. with_view_model(
  2764. view,
  2765. DataLoaderModel * model,
  2766. {
  2767. model->title = title;
  2768. model->fetcher = fetcher;
  2769. model->parser = parser;
  2770. model->request_index = 0;
  2771. model->request_count = request_count;
  2772. model->back_callback = back;
  2773. model->data_state = DataStateInitial;
  2774. model->data_text = NULL;
  2775. //
  2776. model->parser_context = app;
  2777. if (!model->fhttp)
  2778. {
  2779. model->fhttp = flipper_http_alloc();
  2780. }
  2781. },
  2782. true);
  2783. view_dispatcher_switch_to_view(app->view_dispatcher, view_id);
  2784. }