From 7b35afef88910d35a98f05793c375310a9f82e93 Mon Sep 17 00:00:00 2001 From: jblanked <82678820+jblanked@users.noreply.github.com> Date: Wed, 6 Nov 2024 20:48:52 -0500 Subject: [PATCH 1/3] FlipSocial - v0.6 - Improved memory allocation - Fixed bugs in Direct Messaging View - Fixed bugs in Pre-Save View --- README.md | 7 +- alloc/flip_social_alloc.c | 541 +++++++++++ alloc/flip_social_alloc.h | 14 + app.c | 14 +- application.fam | 2 +- assets/CHANGELOG.md | 5 + assets/README.md | 10 +- callback/flip_social_callback.c | 1085 +++++++++++++++++++++ callback/flip_social_callback.h | 256 +++++ draw/flip_social_draw.c | 1013 ++++++++++++++++++++ draw/flip_social_draw.h | 60 ++ easy_flipper/easy_flipper.c | 570 +++++++++++ easy_flipper/easy_flipper.h | 262 +++++ explore/flip_social_explore.c | 162 ++++ explore/flip_social_explore.h | 9 + feed/flip_social_feed.c | 226 +++++ feed/flip_social_feed.h | 11 + flip_social.c | 297 ++++++ flip_social.h | 297 ++++++ flip_storage/flip_social_storage.c | 371 ++++++++ flip_storage/flip_social_storage.h | 37 + flipper_http/flipper_http.c | 1425 ++++++++++++++++++++++++++++ flipper_http/flipper_http.h | 363 +++++++ friends/flip_social_friends.c | 181 ++++ friends/flip_social_friends.h | 11 + jsmn/jsmn.c | 747 +++++++++++++++ jsmn/jsmn.h | 132 +++ messages/flip_social_messages.c | 454 +++++++++ messages/flip_social_messages.h | 27 + text_input/uart_text_input.c | 784 +++++++++++++++ text_input/uart_text_input.h | 83 ++ 31 files changed, 9438 insertions(+), 18 deletions(-) create mode 100644 alloc/flip_social_alloc.c create mode 100644 alloc/flip_social_alloc.h create mode 100644 callback/flip_social_callback.c create mode 100644 callback/flip_social_callback.h create mode 100644 draw/flip_social_draw.c create mode 100644 draw/flip_social_draw.h create mode 100644 easy_flipper/easy_flipper.c create mode 100644 easy_flipper/easy_flipper.h create mode 100644 explore/flip_social_explore.c create mode 100644 explore/flip_social_explore.h create mode 100644 feed/flip_social_feed.c create mode 100644 feed/flip_social_feed.h create mode 100644 flip_social.c create mode 100644 flip_social.h create mode 100644 flip_storage/flip_social_storage.c create mode 100644 flip_storage/flip_social_storage.h create mode 100644 flipper_http/flipper_http.c create mode 100644 flipper_http/flipper_http.h create mode 100644 friends/flip_social_friends.c create mode 100644 friends/flip_social_friends.h create mode 100644 jsmn/jsmn.c create mode 100644 jsmn/jsmn.h create mode 100644 messages/flip_social_messages.c create mode 100644 messages/flip_social_messages.h create mode 100644 text_input/uart_text_input.c create mode 100644 text_input/uart_text_input.h diff --git a/README.md b/README.md index 11bad0298..cee9b4982 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ The first social media app for Flipper Zero. Connect with other users directly o The highlight of this app is customizable pre-saves, which, as explained below, aim to address the challenges of typing with the directional pad. -FlipSocial uses the FlipperHTTP flash for the WiFi Devboard, first introduced in the WebCrawler app:https://github.com/jblanked/FlipperHTTP +FlipSocial uses the FlipperHTTP flash for the WiFi Devboard, first introduced in the WebCrawler app: https://github.com/jblanked/WebCrawler-FlipperZero/tree/main/assets/FlipperHTTP ## Requirements - WiFi Dev Board or Raspberry Pi Pico W for Flipper Zero with FlipperHTTP Flash: https://github.com/jblanked/FlipperHTTP @@ -51,9 +51,12 @@ FlipSocial uses the FlipperHTTP flash for the WiFi Devboard, first introduced in - Raspberry Pi Pico W Support **v0.6** -- Improved User Profile (Bio, friend count, block) +- Improved memory allocation +- Fixed bugs in Direct Messaging View +- Fixed bugs in Pre-Save View **v0.7** +- Improved User Profile (Bio, friend count, block) - Improved Explore Page **v0.8** diff --git a/alloc/flip_social_alloc.c b/alloc/flip_social_alloc.c new file mode 100644 index 000000000..79dfd327b --- /dev/null +++ b/alloc/flip_social_alloc.c @@ -0,0 +1,541 @@ +#include + +FlipSocialApp *flip_social_app_alloc() +{ + // Initiailize the app + FlipSocialApp *app = (FlipSocialApp *)malloc(sizeof(FlipSocialApp)); + + // Initialize gui + Gui *gui = furi_record_open(RECORD_GUI); + + // Initialize UART + if (!flipper_http_init(flipper_http_rx_callback, app)) + { + FURI_LOG_E(TAG, "Failed to initialize UART"); + return NULL; + } + + // Allocate ViewDispatcher + if (!easy_flipper_set_view_dispatcher(&app->view_dispatcher, gui, app)) + { + return NULL; + } + + // Allocate the text input buffers + app->wifi_ssid_logged_out_temp_buffer_size = MAX_USER_LENGTH; + app->wifi_password_logged_out_temp_buffer_size = MAX_USER_LENGTH; + app->login_username_logged_out_temp_buffer_size = MAX_USER_LENGTH; + app->login_password_logged_out_temp_buffer_size = MAX_USER_LENGTH; + app->register_username_logged_out_temp_buffer_size = MAX_USER_LENGTH; + app->register_password_logged_out_temp_buffer_size = MAX_USER_LENGTH; + app->register_password_2_logged_out_temp_buffer_size = MAX_USER_LENGTH; + app->change_password_logged_in_temp_buffer_size = MAX_USER_LENGTH; + app->compose_pre_save_logged_in_temp_buffer_size = MAX_MESSAGE_LENGTH; + app->wifi_ssid_logged_in_temp_buffer_size = MAX_USER_LENGTH; + app->wifi_password_logged_in_temp_buffer_size = MAX_USER_LENGTH; + app->is_logged_in_size = 8; + app->login_username_logged_in_temp_buffer_size = MAX_USER_LENGTH; + app->messages_new_message_logged_in_temp_buffer_size = MAX_MESSAGE_LENGTH; + app->message_user_choice_logged_in_temp_buffer_size = MAX_MESSAGE_LENGTH; + if (!easy_flipper_set_buffer(&app->wifi_ssid_logged_out_temp_buffer, app->wifi_ssid_logged_out_temp_buffer_size)) + { + return NULL; + } + if (!easy_flipper_set_buffer(&app->wifi_password_logged_out_temp_buffer, app->wifi_password_logged_out_temp_buffer_size)) + { + return NULL; + } + if (!easy_flipper_set_buffer(&app->login_username_logged_out_temp_buffer, app->login_username_logged_out_temp_buffer_size)) + { + return NULL; + } + if (!easy_flipper_set_buffer(&app->login_password_logged_out_temp_buffer, app->login_password_logged_out_temp_buffer_size)) + { + return NULL; + } + if (!easy_flipper_set_buffer(&app->register_username_logged_out_temp_buffer, app->register_username_logged_out_temp_buffer_size)) + { + return NULL; + } + if (!easy_flipper_set_buffer(&app->register_password_logged_out_temp_buffer, app->register_password_logged_out_temp_buffer_size)) + { + return NULL; + } + if (!easy_flipper_set_buffer(&app->register_password_2_logged_out_temp_buffer, app->register_password_2_logged_out_temp_buffer_size)) + { + return NULL; + } + if (!easy_flipper_set_buffer(&app->change_password_logged_in_temp_buffer, app->change_password_logged_in_temp_buffer_size)) + { + return NULL; + } + if (!easy_flipper_set_buffer(&app->compose_pre_save_logged_in_temp_buffer, app->compose_pre_save_logged_in_temp_buffer_size)) + { + return NULL; + } + if (!easy_flipper_set_buffer(&app->wifi_ssid_logged_in_temp_buffer, app->wifi_ssid_logged_in_temp_buffer_size)) + { + return NULL; + } + if (!easy_flipper_set_buffer(&app->wifi_password_logged_in_temp_buffer, app->wifi_password_logged_in_temp_buffer_size)) + { + return NULL; + } + if (!easy_flipper_set_buffer(&app->is_logged_in, app->is_logged_in_size)) + { + return NULL; + } + if (!easy_flipper_set_buffer(&app->login_username_logged_in_temp_buffer, app->login_username_logged_in_temp_buffer_size)) + { + return NULL; + } + + if (!easy_flipper_set_buffer(&app->wifi_ssid_logged_out, app->wifi_ssid_logged_out_temp_buffer_size)) + { + return NULL; + } + if (!easy_flipper_set_buffer(&app->wifi_password_logged_out, app->wifi_password_logged_out_temp_buffer_size)) + { + return NULL; + } + if (!easy_flipper_set_buffer(&app->login_username_logged_out, app->login_username_logged_out_temp_buffer_size)) + { + return NULL; + } + if (!easy_flipper_set_buffer(&app->login_password_logged_out, app->login_password_logged_out_temp_buffer_size)) + { + return NULL; + } + if (!easy_flipper_set_buffer(&app->register_username_logged_out, app->register_username_logged_out_temp_buffer_size)) + { + return NULL; + } + if (!easy_flipper_set_buffer(&app->register_password_logged_out, app->register_password_logged_out_temp_buffer_size)) + { + return NULL; + } + if (!easy_flipper_set_buffer(&app->register_password_2_logged_out, app->register_password_2_logged_out_temp_buffer_size)) + { + return NULL; + } + if (!easy_flipper_set_buffer(&app->change_password_logged_in, app->change_password_logged_in_temp_buffer_size)) + { + return NULL; + } + if (!easy_flipper_set_buffer(&app->compose_pre_save_logged_in, app->compose_pre_save_logged_in_temp_buffer_size)) + { + return NULL; + } + if (!easy_flipper_set_buffer(&app->wifi_ssid_logged_in, app->wifi_ssid_logged_in_temp_buffer_size)) + { + return NULL; + } + if (!easy_flipper_set_buffer(&app->wifi_password_logged_in, app->wifi_password_logged_in_temp_buffer_size)) + { + return NULL; + } + if (!easy_flipper_set_buffer(&app->login_username_logged_in, app->login_username_logged_in_temp_buffer_size)) + { + return NULL; + } + // + if (!easy_flipper_set_buffer(&app->messages_new_message_logged_in, app->messages_new_message_logged_in_temp_buffer_size)) + { + return NULL; + } + if (!easy_flipper_set_buffer(&app->messages_new_message_logged_in_temp_buffer, app->messages_new_message_logged_in_temp_buffer_size)) + { + return NULL; + } + if (!easy_flipper_set_buffer(&app->message_user_choice_logged_in, app->message_user_choice_logged_in_temp_buffer_size)) + { + return NULL; + } + if (!easy_flipper_set_buffer(&app->message_user_choice_logged_in_temp_buffer, app->message_user_choice_logged_in_temp_buffer_size)) + { + return NULL; + } + if (!easy_flipper_set_buffer(&selected_message, app->message_user_choice_logged_in_temp_buffer_size)) + { + return NULL; + } + if (!easy_flipper_set_buffer(&last_explore_response, app->message_user_choice_logged_in_temp_buffer_size)) + { + return NULL; + } + + // Allocate Submenu(s) + if (!easy_flipper_set_submenu(&app->submenu_logged_out, FlipSocialViewLoggedOutSubmenu, "FlipSocial v0.6", flip_social_callback_exit_app, &app->view_dispatcher)) + { + return NULL; + } + if (!easy_flipper_set_submenu(&app->submenu_logged_in, FlipSocialViewLoggedInSubmenu, "FlipSocial v0.6", flip_social_callback_exit_app, &app->view_dispatcher)) + { + return NULL; + } + if (!easy_flipper_set_submenu(&app->submenu_compose, FlipSocialViewLoggedInCompose, "Create A Post", flip_social_callback_to_submenu_logged_in, &app->view_dispatcher)) + { + return NULL; + } + if (!easy_flipper_set_submenu(&app->submenu_explore, FlipSocialViewLoggedInExploreSubmenu, "Explore", flip_social_callback_to_submenu_logged_in, &app->view_dispatcher)) + { + return NULL; + } + if (!easy_flipper_set_submenu(&app->submenu_friends, FlipSocialViewLoggedInFriendsSubmenu, "Friends", flip_social_callback_to_profile_logged_in, &app->view_dispatcher)) + { + return NULL; + } + if (!easy_flipper_set_submenu(&app->submenu_messages, FlipSocialViewLoggedInMessagesSubmenu, "Messages", flip_social_callback_to_submenu_logged_in, &app->view_dispatcher)) + { + return NULL; + } + if (!easy_flipper_set_submenu(&app->submenu_messages_user_choices, FlipSocialViewLoggedInMessagesUserChoices, "Users", flip_social_callback_to_messages_logged_in, &app->view_dispatcher)) + { + return NULL; + } + + submenu_add_item(app->submenu_logged_out, "Login", FlipSocialSubmenuLoggedOutIndexLogin, flip_social_callback_submenu_choices, app); + submenu_add_item(app->submenu_logged_out, "Register", FlipSocialSubmenuLoggedOutIndexRegister, flip_social_callback_submenu_choices, app); + submenu_add_item(app->submenu_logged_out, "About", FlipSocialSubmenuLoggedOutIndexAbout, flip_social_callback_submenu_choices, app); + submenu_add_item(app->submenu_logged_out, "Settings", FlipSocialSubmenuLoggedOutIndexWifiSettings, flip_social_callback_submenu_choices, app); + // + submenu_add_item(app->submenu_logged_in, "Explore", FlipSocialSubmenuExploreIndex, flip_social_callback_submenu_choices, app); + submenu_add_item(app->submenu_logged_in, "Feed", FlipSocialSubmenuLoggedInIndexFeed, flip_social_callback_submenu_choices, app); + submenu_add_item(app->submenu_logged_in, "Post", FlipSocialSubmenuLoggedInIndexCompose, flip_social_callback_submenu_choices, app); + submenu_add_item(app->submenu_logged_in, "Messages", FlipSocialSubmenuLoggedInIndexMessages, flip_social_callback_submenu_choices, app); + submenu_add_item(app->submenu_logged_in, "Profile", FlipSocialSubmenuLoggedInIndexProfile, flip_social_callback_submenu_choices, app); + submenu_add_item(app->submenu_logged_in, "Settings", FlipSocialSubmenuLoggedInIndexSettings, flip_social_callback_submenu_choices, app); + submenu_add_item(app->submenu_logged_in, "Sign Out", FlipSocialSubmenuLoggedInSignOutButton, flip_social_callback_submenu_choices, app); + // + submenu_add_item(app->submenu_compose, "Add Pre-Save", FlipSocialSubmenuComposeIndexAddPreSave, flip_social_callback_submenu_choices, app); + // + + // Allocate View(s) + if (!easy_flipper_set_view(&app->view_process_login, FlipSocialViewLoggedOutProcessLogin, flip_social_callback_draw_login, NULL, flip_social_callback_to_login_logged_out, &app->view_dispatcher, app)) + { + return NULL; + } + if (!easy_flipper_set_view(&app->view_process_register, FlipSocialViewLoggedOutProcessRegister, flip_social_callback_draw_register, NULL, flip_social_callback_to_register_logged_out, &app->view_dispatcher, app)) + { + return NULL; + } + if (!easy_flipper_set_view(&app->view_process_feed, FlipSocialViewLoggedInFeed, flip_social_callback_draw_feed, NULL, flip_social_callback_to_submenu_logged_in, &app->view_dispatcher, app)) + { + return NULL; + } + if (!easy_flipper_set_view(&app->view_process_compose, FlipSocialViewLoggedInProcessCompose, flip_social_callback_draw_compose, NULL, flip_social_callback_to_compose_logged_in, &app->view_dispatcher, app)) + { + return NULL; + } + if (!easy_flipper_set_view(&app->view_process_explore, FlipSocialViewLoggedInExploreProccess, flip_social_callback_draw_explore, NULL, flip_social_callback_to_explore_logged_in, &app->view_dispatcher, app)) + { + return NULL; + } + if (!easy_flipper_set_view(&app->view_process_friends, FlipSocialViewLoggedInFriendsProcess, flip_social_callback_draw_friends, NULL, flip_social_callback_to_friends_logged_in, &app->view_dispatcher, app)) + { + return NULL; + } + if (!easy_flipper_set_view(&app->view_process_messages, FlipSocialViewLoggedInMessagesProcess, flip_social_callback_draw_messages, NULL, flip_social_callback_to_messages_logged_in, &app->view_dispatcher, app)) + { + return NULL; + } + + // Setup Variable Item List(s) + if (!easy_flipper_set_variable_item_list(&app->variable_item_list_logged_out_wifi_settings, FlipSocialViewLoggedOutWifiSettings, flip_social_text_input_logged_out_wifi_settings_item_selected, flip_social_callback_to_submenu_logged_out, &app->view_dispatcher, app)) + { + return NULL; + } + if (!easy_flipper_set_variable_item_list(&app->variable_item_list_logged_out_login, FlipSocialViewLoggedOutLogin, flip_social_text_input_logged_out_login_item_selected, flip_social_callback_to_submenu_logged_out, &app->view_dispatcher, app)) + { + return NULL; + } + if (!easy_flipper_set_variable_item_list(&app->variable_item_list_logged_out_register, FlipSocialViewLoggedOutRegister, flip_social_text_input_logged_out_register_item_selected, flip_social_callback_to_submenu_logged_out, &app->view_dispatcher, app)) + { + return NULL; + } + if (!easy_flipper_set_variable_item_list(&app->variable_item_list_logged_in_profile, FlipSocialViewLoggedInProfile, flip_social_text_input_logged_in_profile_item_selected, flip_social_callback_to_submenu_logged_in, &app->view_dispatcher, app)) + { + return NULL; + } + if (!easy_flipper_set_variable_item_list(&app->variable_item_list_logged_in_settings, FlipSocialViewLoggedInSettings, flip_social_text_input_logged_in_settings_item_selected, flip_social_callback_to_submenu_logged_in, &app->view_dispatcher, app)) + { + return NULL; + } + if (!easy_flipper_set_variable_item_list(&app->variable_item_list_logged_in_settings_wifi, FlipSocialViewLoggedInSettingsWifi, flip_social_text_input_logged_in_wifi_settings_item_selected, flip_social_callback_to_settings_logged_in, &app->view_dispatcher, app)) + { + return NULL; + } + + app->variable_item_logged_out_wifi_settings_ssid = variable_item_list_add(app->variable_item_list_logged_out_wifi_settings, "SSID", 1, NULL, NULL); + app->variable_item_logged_out_wifi_settings_password = variable_item_list_add(app->variable_item_list_logged_out_wifi_settings, "Password", 1, NULL, NULL); + // + app->variable_item_logged_out_login_username = variable_item_list_add(app->variable_item_list_logged_out_login, "Username", 1, NULL, NULL); + app->variable_item_logged_out_login_password = variable_item_list_add(app->variable_item_list_logged_out_login, "Password", 1, NULL, NULL); + app->variable_item_logged_out_login_button = variable_item_list_add(app->variable_item_list_logged_out_login, "Login", 0, NULL, NULL); + // + app->variable_item_logged_out_register_username = variable_item_list_add(app->variable_item_list_logged_out_register, "Username", 1, NULL, NULL); + app->variable_item_logged_out_register_password = variable_item_list_add(app->variable_item_list_logged_out_register, "Password", 1, NULL, NULL); + app->variable_item_logged_out_register_password_2 = variable_item_list_add(app->variable_item_list_logged_out_register, "Confirm Password", 1, NULL, NULL); + app->variable_item_logged_out_register_button = variable_item_list_add(app->variable_item_list_logged_out_register, "Register", 0, NULL, NULL); + // + app->variable_item_logged_in_profile_username = variable_item_list_add(app->variable_item_list_logged_in_profile, "Username", 0, NULL, NULL); + app->variable_item_logged_in_profile_change_password = variable_item_list_add(app->variable_item_list_logged_in_profile, "Change Password", 0, NULL, NULL); + app->variable_item_logged_in_profile_friends = variable_item_list_add(app->variable_item_list_logged_in_profile, "Friends", 0, NULL, NULL); + // + app->variable_item_logged_in_settings_about = variable_item_list_add(app->variable_item_list_logged_in_settings, "About", 0, NULL, NULL); + app->variable_item_logged_in_settings_wifi = variable_item_list_add(app->variable_item_list_logged_in_settings, "WiFi", 0, NULL, NULL); + // + app->variable_item_logged_in_wifi_settings_ssid = variable_item_list_add(app->variable_item_list_logged_in_settings_wifi, "SSID", 1, NULL, NULL); + app->variable_item_logged_in_wifi_settings_password = variable_item_list_add(app->variable_item_list_logged_in_settings_wifi, "Password", 1, NULL, NULL); + + // Setup Text Input(s) + if (!easy_flipper_set_uart_text_input(&app->text_input_logged_out_wifi_settings_ssid, FlipSocialViewLoggedOutWifiSettingsSSIDInput, "Enter SSID", app->wifi_ssid_logged_out_temp_buffer, app->wifi_ssid_logged_out_temp_buffer_size, flip_social_logged_out_wifi_settings_ssid_updated, flip_social_callback_to_wifi_settings_logged_out, &app->view_dispatcher, app)) + { + return NULL; + } + if (!easy_flipper_set_uart_text_input(&app->text_input_logged_out_wifi_settings_password, FlipSocialViewLoggedOutWifiSettingsPasswordInput, "Enter Password", app->wifi_password_logged_out_temp_buffer, app->wifi_password_logged_out_temp_buffer_size, flip_social_logged_out_wifi_settings_password_updated, flip_social_callback_to_wifi_settings_logged_out, &app->view_dispatcher, app)) + { + return NULL; + } + if (!easy_flipper_set_uart_text_input(&app->text_input_logged_out_login_username, FlipSocialViewLoggedOutLoginUsernameInput, "Enter Username", app->login_username_logged_out_temp_buffer, app->login_username_logged_out_temp_buffer_size, flip_social_logged_out_login_username_updated, flip_social_callback_to_login_logged_out, &app->view_dispatcher, app)) + { + return NULL; + } + if (!easy_flipper_set_uart_text_input(&app->text_input_logged_out_login_password, FlipSocialViewLoggedOutLoginPasswordInput, "Enter Password", app->login_password_logged_out_temp_buffer, app->login_password_logged_out_temp_buffer_size, flip_social_logged_out_login_password_updated, flip_social_callback_to_login_logged_out, &app->view_dispatcher, app)) + { + return NULL; + } + if (!easy_flipper_set_uart_text_input(&app->text_input_logged_out_register_username, FlipSocialViewLoggedOutRegisterUsernameInput, "Enter Username", app->register_username_logged_out_temp_buffer, app->register_username_logged_out_temp_buffer_size, flip_social_logged_out_register_username_updated, flip_social_callback_to_register_logged_out, &app->view_dispatcher, app)) + { + return NULL; + } + if (!easy_flipper_set_uart_text_input(&app->text_input_logged_out_register_password, FlipSocialViewLoggedOutRegisterPasswordInput, "Enter Password", app->register_password_logged_out_temp_buffer, app->register_password_logged_out_temp_buffer_size, flip_social_logged_out_register_password_updated, flip_social_callback_to_register_logged_out, &app->view_dispatcher, app)) + { + return NULL; + } + if (!easy_flipper_set_uart_text_input(&app->text_input_logged_out_register_password_2, FlipSocialViewLoggedOutRegisterPassword2Input, "Confirm Password", app->register_password_2_logged_out_temp_buffer, app->register_password_2_logged_out_temp_buffer_size, flip_social_logged_out_register_password_2_updated, flip_social_callback_to_register_logged_out, &app->view_dispatcher, app)) + { + return NULL; + } + // + if (!easy_flipper_set_uart_text_input(&app->text_input_logged_in_change_password, FlipSocialViewLoggedInChangePasswordInput, "Enter New Password", app->change_password_logged_in_temp_buffer, app->change_password_logged_in_temp_buffer_size, flip_social_logged_in_profile_change_password_updated, flip_social_callback_to_profile_logged_in, &app->view_dispatcher, app)) + { + return NULL; + } + if (!easy_flipper_set_uart_text_input(&app->text_input_logged_in_compose_pre_save_input, FlipSocialViewLoggedInComposeAddPreSaveInput, "Enter Pre-Save Message", app->compose_pre_save_logged_in_temp_buffer, app->compose_pre_save_logged_in_temp_buffer_size, flip_social_logged_in_compose_pre_save_updated, flip_social_callback_to_compose_logged_in, &app->view_dispatcher, app)) + { + return NULL; + } + if (!easy_flipper_set_uart_text_input(&app->text_input_logged_in_wifi_settings_ssid, FlipSocialViewLoggedInWifiSettingsSSIDInput, "Enter SSID", app->wifi_ssid_logged_in_temp_buffer, app->wifi_ssid_logged_in_temp_buffer_size, flip_social_logged_in_wifi_settings_ssid_updated, flip_social_callback_to_wifi_settings_logged_in, &app->view_dispatcher, app)) + { + return NULL; + } + if (!easy_flipper_set_uart_text_input(&app->text_input_logged_in_wifi_settings_password, FlipSocialViewLoggedInWifiSettingsPasswordInput, "Enter Password", app->wifi_password_logged_in_temp_buffer, app->wifi_password_logged_in_temp_buffer_size, flip_social_logged_in_wifi_settings_password_updated, flip_social_callback_to_wifi_settings_logged_in, &app->view_dispatcher, app)) + { + return NULL; + } + // + if (!easy_flipper_set_uart_text_input(&app->text_input_logged_in_messages_new_message, FlipSocialViewLoggedInMessagesNewMessageInput, "Enter Message", app->messages_new_message_logged_in_temp_buffer, app->messages_new_message_logged_in_temp_buffer_size, flip_social_logged_in_messages_new_message_updated, flip_social_callback_to_messages_logged_in, &app->view_dispatcher, app)) + { + return NULL; + } + if (!easy_flipper_set_uart_text_input(&app->text_input_logged_in_messages_new_message_user_choices, FlipSocialViewLoggedInMessagesNewMessageUserChoicesInput, "Enter Message", app->message_user_choice_logged_in_temp_buffer, app->message_user_choice_logged_in_temp_buffer_size, flip_social_logged_in_messages_user_choice_message_updated, flip_social_callback_to_messages_user_choices, &app->view_dispatcher, app)) + { + return NULL; + } + + // Setup About(s) + if (!easy_flipper_set_widget(&app->widget_logged_out_about, FlipSocialViewLoggedOutAbout, "Welcome to FlipSocial\n---\nThe social media app for\nFlipper Zero, created by\nJBlanked: www.flipsocial.net\n---\nPress BACK to return.", flip_social_callback_to_submenu_logged_out, &app->view_dispatcher)) + { + return NULL; + } + if (!easy_flipper_set_widget(&app->widget_logged_in_about, FlipSocialViewLoggedInSettingsAbout, "Welcome to FlipSocial\n---\nThe social media app for\nFlipper Zero, created by\nJBlanked: www.flipsocial.net\n---\nPress BACK to return.", flip_social_callback_to_settings_logged_in, &app->view_dispatcher)) + { + return NULL; + } + + // load the playlist + if (load_playlist(&app->pre_saved_messages)) + { + // Update the playlist submenu + for (uint32_t i = 0; i < app->pre_saved_messages.count; i++) + { + submenu_add_item(app->submenu_compose, app->pre_saved_messages.messages[i], FlipSocialSubemnuComposeIndexStartIndex + i, flip_social_callback_submenu_choices, app); + } + } + + // Load the settings + if (!load_settings(app->wifi_ssid_logged_out, + app->wifi_ssid_logged_out_temp_buffer_size, + app->wifi_password_logged_out, + app->wifi_password_logged_out_temp_buffer_size, + app->login_username_logged_out, + app->login_username_logged_out_temp_buffer_size, + app->login_username_logged_in, + app->login_username_logged_in_temp_buffer_size, + app->login_password_logged_out, + app->login_password_logged_out_temp_buffer_size, + app->change_password_logged_in, + app->change_password_logged_in_temp_buffer_size, + app->is_logged_in, + app->is_logged_in_size)) + + { + FURI_LOG_E(TAG, "Failed to load settings"); + + if (app->is_logged_in == NULL) + { + app->is_logged_in = (char *)malloc(app->is_logged_in_size); + app->is_logged_in = "false"; + } + app_instance = app; + view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedOutSubmenu); + } + else + { + // Copy items into their temp buffers with safety checks + if (app->wifi_ssid_logged_out && app->wifi_ssid_logged_out_temp_buffer) + { + strncpy(app->wifi_ssid_logged_out_temp_buffer, app->wifi_ssid_logged_out, app->wifi_ssid_logged_out_temp_buffer_size - 1); + app->wifi_ssid_logged_out_temp_buffer[app->wifi_ssid_logged_out_temp_buffer_size - 1] = '\0'; + } + if (app->wifi_password_logged_out && app->wifi_password_logged_out_temp_buffer) + { + strncpy(app->wifi_password_logged_out_temp_buffer, app->wifi_password_logged_out, app->wifi_password_logged_out_temp_buffer_size - 1); + app->wifi_password_logged_out_temp_buffer[app->wifi_password_logged_out_temp_buffer_size - 1] = '\0'; + } + if (app->login_username_logged_out && app->login_username_logged_out_temp_buffer) + { + strncpy(app->login_username_logged_out_temp_buffer, app->login_username_logged_out, app->login_username_logged_out_temp_buffer_size - 1); + app->login_username_logged_out_temp_buffer[app->login_username_logged_out_temp_buffer_size - 1] = '\0'; + } + if (app->login_password_logged_out && app->login_password_logged_out_temp_buffer) + { + strncpy(app->login_password_logged_out_temp_buffer, app->login_password_logged_out, app->login_password_logged_out_temp_buffer_size - 1); + app->login_password_logged_out_temp_buffer[app->login_password_logged_out_temp_buffer_size - 1] = '\0'; + } + if (app->register_username_logged_out && app->register_username_logged_out_temp_buffer) + { + strncpy(app->register_username_logged_out_temp_buffer, app->register_username_logged_out, app->register_username_logged_out_temp_buffer_size - 1); + app->register_username_logged_out_temp_buffer[app->register_username_logged_out_temp_buffer_size - 1] = '\0'; + } + if (app->register_password_logged_out && app->register_password_logged_out_temp_buffer) + { + strncpy(app->register_password_logged_out_temp_buffer, app->register_password_logged_out, app->register_password_logged_out_temp_buffer_size - 1); + app->register_password_logged_out_temp_buffer[app->register_password_logged_out_temp_buffer_size - 1] = '\0'; + } + if (app->register_password_2_logged_out && app->register_password_2_logged_out_temp_buffer) + { + strncpy(app->register_password_2_logged_out_temp_buffer, app->register_password_2_logged_out, app->register_password_2_logged_out_temp_buffer_size - 1); + app->register_password_2_logged_out_temp_buffer[app->register_password_2_logged_out_temp_buffer_size - 1] = '\0'; + } + if (app->change_password_logged_in && app->change_password_logged_in_temp_buffer) + { + strncpy(app->change_password_logged_in_temp_buffer, app->change_password_logged_in, app->change_password_logged_in_temp_buffer_size - 1); + app->change_password_logged_in_temp_buffer[app->change_password_logged_in_temp_buffer_size - 1] = '\0'; + } + if (app->compose_pre_save_logged_in && app->compose_pre_save_logged_in_temp_buffer) + { + strncpy(app->compose_pre_save_logged_in_temp_buffer, app->compose_pre_save_logged_in, app->compose_pre_save_logged_in_temp_buffer_size - 1); + app->compose_pre_save_logged_in_temp_buffer[app->compose_pre_save_logged_in_temp_buffer_size - 1] = '\0'; + } + if (app->wifi_ssid_logged_in && app->wifi_ssid_logged_in_temp_buffer) + { + strncpy(app->wifi_ssid_logged_in_temp_buffer, app->wifi_ssid_logged_in, app->wifi_ssid_logged_in_temp_buffer_size - 1); + app->wifi_ssid_logged_in_temp_buffer[app->wifi_ssid_logged_in_temp_buffer_size - 1] = '\0'; + } + if (app->wifi_password_logged_in && app->wifi_password_logged_in_temp_buffer) + { + strncpy(app->wifi_password_logged_in_temp_buffer, app->wifi_password_logged_in, app->wifi_password_logged_in_temp_buffer_size - 1); + app->wifi_password_logged_in_temp_buffer[app->wifi_password_logged_in_temp_buffer_size - 1] = '\0'; + } + if (app->login_username_logged_in && app->login_username_logged_in_temp_buffer) + { + strncpy(app->login_username_logged_in_temp_buffer, app->login_username_logged_in, app->login_username_logged_in_temp_buffer_size - 1); + app->login_username_logged_in_temp_buffer[app->login_username_logged_in_temp_buffer_size - 1] = '\0'; + } + + // if login username is empty but logged out isnt, copy it over + if (strlen(app->login_username_logged_out) > 0 && strlen(app->login_username_logged_in) == 0) + { + strncpy(app->login_username_logged_in, app->login_username_logged_out, app->login_username_logged_in_temp_buffer_size - 1); + strncpy(app->login_username_logged_in_temp_buffer, app->login_username_logged_out, app->login_username_logged_in_temp_buffer_size - 1); + app->login_username_logged_in[app->login_username_logged_in_temp_buffer_size - 1] = '\0'; + app->login_username_logged_in_temp_buffer[app->login_username_logged_in_temp_buffer_size - 1] = '\0'; + } + // logout username is empty but logged in isnt, copy it over + if (strlen(app->login_username_logged_in) > 0 && strlen(app->login_username_logged_out) == 0) + { + strncpy(app->login_username_logged_out, app->login_username_logged_in, app->login_username_logged_in_temp_buffer_size - 1); + strncpy(app->login_username_logged_out_temp_buffer, app->login_username_logged_in, app->login_username_logged_in_temp_buffer_size - 1); + app->login_username_logged_out[app->login_username_logged_in_temp_buffer_size - 1] = '\0'; + app->login_username_logged_out_temp_buffer[app->login_username_logged_in_temp_buffer_size - 1] = '\0'; + } + // if login password is empty but logged out isnt, copy it over + if (strlen(app->login_password_logged_out) > 0 && strlen(app->change_password_logged_in) == 0) + { + strncpy(app->change_password_logged_in, app->login_password_logged_out, app->login_password_logged_out_temp_buffer_size - 1); + strncpy(app->change_password_logged_in_temp_buffer, app->login_password_logged_out, app->login_password_logged_out_temp_buffer_size - 1); + app->change_password_logged_in[app->login_password_logged_out_temp_buffer_size - 1] = '\0'; + app->change_password_logged_in_temp_buffer[app->login_password_logged_out_temp_buffer_size - 1] = '\0'; + } + // if logout password is empty but logged in isnt, copy it over + if (strlen(app->change_password_logged_in) > 0 && strlen(app->login_password_logged_out) == 0) + { + strncpy(app->login_password_logged_out, app->change_password_logged_in, app->login_password_logged_out_temp_buffer_size - 1); + strncpy(app->login_password_logged_out_temp_buffer, app->change_password_logged_in, app->login_password_logged_out_temp_buffer_size - 1); + app->login_password_logged_out[app->login_password_logged_out_temp_buffer_size - 1] = '\0'; + app->login_password_logged_out_temp_buffer[app->login_password_logged_out_temp_buffer_size - 1] = '\0'; + } + // if wifi password is empty but logged out isnt, copy it over + if (strlen(app->wifi_password_logged_out) > 0 && strlen(app->wifi_password_logged_in) == 0) + { + strncpy(app->wifi_password_logged_in, app->wifi_password_logged_out, app->wifi_password_logged_in_temp_buffer_size - 1); + strncpy(app->wifi_password_logged_in_temp_buffer, app->wifi_password_logged_out, app->wifi_password_logged_in_temp_buffer_size - 1); + app->wifi_password_logged_in[app->wifi_password_logged_in_temp_buffer_size - 1] = '\0'; + app->wifi_password_logged_in_temp_buffer[app->wifi_password_logged_in_temp_buffer_size - 1] = '\0'; + } + // ifi wifi password is empty but logged in isnt, copy it over + if (strlen(app->wifi_password_logged_in) > 0 && strlen(app->wifi_password_logged_out) == 0) + { + strncpy(app->wifi_password_logged_out, app->wifi_password_logged_in, app->wifi_password_logged_in_temp_buffer_size - 1); + strncpy(app->wifi_password_logged_out_temp_buffer, app->wifi_password_logged_in, app->wifi_password_logged_in_temp_buffer_size - 1); + app->wifi_password_logged_out[app->wifi_password_logged_in_temp_buffer_size - 1] = '\0'; + app->wifi_password_logged_out_temp_buffer[app->wifi_password_logged_in_temp_buffer_size - 1] = '\0'; + } + // if wifi ssid is empty but logged out isnt, copy it over + if (strlen(app->wifi_ssid_logged_out) > 0 && strlen(app->wifi_ssid_logged_in) == 0) + { + strncpy(app->wifi_ssid_logged_in, app->wifi_ssid_logged_out, app->wifi_ssid_logged_in_temp_buffer_size - 1); + strncpy(app->wifi_ssid_logged_in_temp_buffer, app->wifi_ssid_logged_out, app->wifi_ssid_logged_in_temp_buffer_size - 1); + app->wifi_ssid_logged_in[app->wifi_ssid_logged_in_temp_buffer_size - 1] = '\0'; + app->wifi_ssid_logged_in_temp_buffer[app->wifi_ssid_logged_in_temp_buffer_size - 1] = '\0'; + } + // if wifi ssid is empty but logged in isnt, copy it over + if (strlen(app->wifi_ssid_logged_in) > 0 && strlen(app->wifi_ssid_logged_out) == 0) + { + strncpy(app->wifi_ssid_logged_out, app->wifi_ssid_logged_in, app->wifi_ssid_logged_in_temp_buffer_size - 1); + strncpy(app->wifi_ssid_logged_out_temp_buffer, app->wifi_ssid_logged_in, app->wifi_ssid_logged_in_temp_buffer_size - 1); + app->wifi_ssid_logged_out[app->wifi_ssid_logged_in_temp_buffer_size - 1] = '\0'; + app->wifi_ssid_logged_out_temp_buffer[app->wifi_ssid_logged_in_temp_buffer_size - 1] = '\0'; + } + + // set variable item text (ommit the passwords) + variable_item_set_current_value_text(app->variable_item_logged_in_wifi_settings_ssid, app->wifi_ssid_logged_in); + variable_item_set_current_value_text(app->variable_item_logged_out_wifi_settings_ssid, app->wifi_ssid_logged_out); + variable_item_set_current_value_text(app->variable_item_logged_out_login_username, app->login_username_logged_out); + variable_item_set_current_value_text(app->variable_item_logged_in_profile_username, app->login_username_logged_in); + // + + if (app->is_logged_in != NULL && strcmp(app->is_logged_in, "true") == 0) + { + view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedInSubmenu); + } + else + { + view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedOutSubmenu); + } + } + + return app; +} \ No newline at end of file diff --git a/alloc/flip_social_alloc.h b/alloc/flip_social_alloc.h new file mode 100644 index 000000000..3546d421e --- /dev/null +++ b/alloc/flip_social_alloc.h @@ -0,0 +1,14 @@ +#ifndef FLIP_SOCIAL_ALLOC_H +#define FLIP_SOCIAL_ALLOC_H +#include +#include +#include +#include + +/** + * @brief Function to allocate resources for the FlipSocialApp. + * @details Initializes all components and views of the application. + * @return Pointer to the initialized FlipSocialApp, or NULL on failure. + */ +FlipSocialApp *flip_social_app_alloc(); +#endif \ No newline at end of file diff --git a/app.c b/app.c index 7f0542ccb..85986b3da 100644 --- a/app.c +++ b/app.c @@ -1,16 +1,6 @@ // app.c -#include // Include cJSON -#include // Include the text input widget -#include // Include the FlipSocialApp structure -#include // Include the storage functions -#include "flip_social_draw.h" -#include "flip_social_feed.h" -#include "flip_social_explore.h" -#include "flip_social_friends.h" -#include "flip_social_messages.h" -#include // Include the callback functions -#include // Include the initialization functions -#include // Include the cleanup functions +#include // Include the FlipSocialApp structure +#include // Include the allocation functions /** * @brief Entry point for the Hello World application. diff --git a/application.fam b/application.fam index 2474fa091..5a0ac6483 100644 --- a/application.fam +++ b/application.fam @@ -9,6 +9,6 @@ App( fap_icon_assets="assets", fap_author="jblanked", fap_weburl="https://github.com/jblanked/FlipSocial", - fap_version="0.5", + fap_version="0.6", fap_description="Social media platform for the Flipper Zero.", ) diff --git a/assets/CHANGELOG.md b/assets/CHANGELOG.md index 27083b4b9..766613409 100644 --- a/assets/CHANGELOG.md +++ b/assets/CHANGELOG.md @@ -1,3 +1,8 @@ +## 0.6 +- Improved memory allocation +- Fixed bugs in Direct Messaging View +- Fixed bugs in Pre-Save View + ## 0.5 - Improved memory allocation - Improved Feed Page diff --git a/assets/README.md b/assets/README.md index ab6336bb2..cee9b4982 100644 --- a/assets/README.md +++ b/assets/README.md @@ -3,12 +3,13 @@ The first social media app for Flipper Zero. Connect with other users directly o The highlight of this app is customizable pre-saves, which, as explained below, aim to address the challenges of typing with the directional pad. -FlipSocial uses the FlipperHTTP flash for the WiFi Devboard, first introduced in the WebCrawler app: [WebCrawler](https://github.com/jblanked/WebCrawler-FlipperZero/tree/main/assets/FlipperHTTP) +FlipSocial uses the FlipperHTTP flash for the WiFi Devboard, first introduced in the WebCrawler app: https://github.com/jblanked/WebCrawler-FlipperZero/tree/main/assets/FlipperHTTP ## Requirements -- WiFi Dev Board for Flipper Zero with FlipperHTTP Flash: [FlipperHTTP](https://github.com/jblanked/WebCrawler-FlipperZero/tree/main/assets/FlipperHTTP) +- WiFi Dev Board or Raspberry Pi Pico W for Flipper Zero with FlipperHTTP Flash: https://github.com/jblanked/FlipperHTTP - WiFi Access Point + ## Features - Login/Logout - Registration @@ -50,9 +51,12 @@ FlipSocial uses the FlipperHTTP flash for the WiFi Devboard, first introduced in - Raspberry Pi Pico W Support **v0.6** -- Improved User Profile (Bio, friend count, block) +- Improved memory allocation +- Fixed bugs in Direct Messaging View +- Fixed bugs in Pre-Save View **v0.7** +- Improved User Profile (Bio, friend count, block) - Improved Explore Page **v0.8** diff --git a/callback/flip_social_callback.c b/callback/flip_social_callback.c new file mode 100644 index 000000000..2ba3b706c --- /dev/null +++ b/callback/flip_social_callback.c @@ -0,0 +1,1085 @@ +#include + +/** + * @brief Navigation callback to go back to the submenu Logged out. + * @param context The context - unused + * @return next view id (FlipSocialViewLoggedOutSubmenu) + */ +uint32_t flip_social_callback_to_submenu_logged_out(void *context) +{ + UNUSED(context); + return FlipSocialViewLoggedOutSubmenu; +} + +/** + * @brief Navigation callback to go back to the submenu Logged in. + * @param context The context - unused + * @return next view id (FlipSocialViewLoggedInSubmenu) + */ +uint32_t flip_social_callback_to_submenu_logged_in(void *context) +{ + UNUSED(context); + flip_social_free_explore(); + flip_social_free_feed(); + flip_social_free_friends(); + flip_social_free_message_users(); + flip_social_free_messages(); + return FlipSocialViewLoggedInSubmenu; +} + +/** + * @brief Navigation callback to bring the user back to the (Logged out) Login screen + * @param context The context - unused + * @return next view id (FlipSocialViewLoggedOutLogin) + */ +uint32_t flip_social_callback_to_login_logged_out(void *context) +{ + UNUSED(context); + flip_social_sent_login_request = false; + flip_social_login_success = false; + return FlipSocialViewLoggedOutLogin; +} + +/** + * @brief Navigation callback to bring the user back to the (Logged out) Register screen + * @param context The context - unused + * @return next view id (FlipSocialViewLoggedOutRegister) + */ +uint32_t flip_social_callback_to_register_logged_out(void *context) +{ + UNUSED(context); + flip_social_sent_register_request = false; + flip_social_register_success = false; + return FlipSocialViewLoggedOutRegister; +} + +/** + * @brief Navigation callback to bring the user back to the (Logged out) Wifi Settings screen + * @param context The context - unused + * @return next view id (FlipSocialViewLoggedOutWifiSettings) + */ +uint32_t flip_social_callback_to_wifi_settings_logged_out(void *context) +{ + UNUSED(context); + return FlipSocialViewLoggedOutWifiSettings; +} + +/** + * @brief Navigation callback to bring the user back to the (Logged in) Wifi Settings screen + * @param context The context - unused + * @return next view id (FlipSocialViewLoggedInSettingsWifi) + */ +uint32_t flip_social_callback_to_wifi_settings_logged_in(void *context) +{ + UNUSED(context); + return FlipSocialViewLoggedInSettingsWifi; +} + +/** + * @brief Navigation callback to bring the user back to the (Logged in) Settings screen + * @param context The context - unused + * @return next view id (FlipSocialViewLoggedInSettingsWifi) + */ +uint32_t flip_social_callback_to_settings_logged_in(void *context) +{ + UNUSED(context); + return FlipSocialViewLoggedInSettings; +} + +/** + * @brief Navigation callback to bring the user back to the (Logged in) Compose screen + * @param context The context - unused + * @return next view id (FlipSocialViewLoggedInCompose) + */ +uint32_t flip_social_callback_to_compose_logged_in(void *context) +{ + UNUSED(context); + return FlipSocialViewLoggedInCompose; +} + +/** + * @brief Navigation callback to bring the user back to the (Logged in) Profile screen + * @param context The context - unused + * @return next view id (FlipSocialViewLoggedInProfile) + */ +uint32_t flip_social_callback_to_profile_logged_in(void *context) +{ + UNUSED(context); + return FlipSocialViewLoggedInProfile; +} + +/** + * @brief Navigation callback to bring the user back to the Explore submenu + * @param context The context - unused + * @return next view id (FlipSocialViewLoggedInExploreSubmenu) + */ +uint32_t flip_social_callback_to_explore_logged_in(void *context) +{ + UNUSED(context); + flip_social_dialog_stop = true; + last_explore_response = ""; + flip_social_dialog_shown = false; + flip_social_explore->index = 0; + action = ActionNone; + return FlipSocialViewLoggedInExploreSubmenu; +} + +/** + * @brief Navigation callback to bring the user back to the Friends submenu + * @param context The context - unused + * @return next view id (FlipSocialViewLoggedInFriendsSubmenu) + */ +uint32_t flip_social_callback_to_friends_logged_in(void *context) +{ + UNUSED(context); + flip_social_dialog_stop = true; + last_explore_response = ""; + flip_social_dialog_shown = false; + flip_social_friends->index = 0; + action = ActionNone; + return FlipSocialViewLoggedInFriendsSubmenu; +} + +/** + * @brief Navigation callback to bring the user back to the Messages submenu + * @param context The context - unused + * @return next view id (FlipSocialViewLoggedInMessagesSubmenu) + */ +uint32_t flip_social_callback_to_messages_logged_in(void *context) +{ + UNUSED(context); + return FlipSocialViewLoggedInMessagesSubmenu; +} + +/** + * @brief Navigation callback to bring the user back to the User Choices screen + * @param context The context - unused + * @return next view id (FlipSocialViewLoggedInMessagesUserChoices) + */ +uint32_t flip_social_callback_to_messages_user_choices(void *context) +{ + UNUSED(context); + return FlipSocialViewLoggedInMessagesUserChoices; +} + +/** + * @brief Navigation callback for exiting the application + * @param context The context - unused + * @return next view id (VIEW_NONE to exit the app) + */ +uint32_t flip_social_callback_exit_app(void *context) +{ + // Exit the application + if (!context) + { + FURI_LOG_E(TAG, "Context is NULL"); + return VIEW_NONE; + } + UNUSED(context); + return VIEW_NONE; // Return VIEW_NONE to exit the app +} + +/** + * @brief Handle ALL submenu item selections. + * @param context The context - FlipSocialApp object. + * @param index The FlipSocialSubmenuIndex item that was clicked. + * @return void + */ +void flip_social_callback_submenu_choices(void *context, uint32_t index) +{ + FlipSocialApp *app = (FlipSocialApp *)context; + if (!app) + { + FURI_LOG_E(TAG, "FlipSocialApp is NULL"); + return; + } + switch (index) + { + case FlipSocialSubmenuLoggedOutIndexLogin: + flip_social_sent_login_request = false; + flip_social_login_success = false; + view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedOutLogin); + break; + case FlipSocialSubmenuLoggedOutIndexRegister: + flip_social_sent_register_request = false; + flip_social_register_success = false; + view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedOutRegister); + break; + case FlipSocialSubmenuLoggedOutIndexAbout: + view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedOutAbout); + break; + case FlipSocialSubmenuLoggedOutIndexWifiSettings: + view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedOutWifiSettings); + break; + case FlipSocialSubmenuLoggedInIndexProfile: + view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedInProfile); + break; + case FlipSocialSubmenuLoggedInIndexMessages: + if (flipper_http_process_response_async(flip_social_get_message_users, flip_social_parse_json_message_users)) + { + view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedInMessagesSubmenu); + } + break; + case FlipSocialSubmenuLoggedInIndexMessagesNewMessage: + if (flipper_http_process_response_async(flip_social_get_explore, flip_social_parse_json_message_user_choices)) + { + view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedInMessagesUserChoices); + } + break; + case FlipSocialSubmenuLoggedInIndexFeed: + if (flipper_http_process_response_async(flip_social_get_feed, flip_social_parse_json_feed)) + { + view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedInFeed); + } + else + { + // Set failure FlipSocialFeed object + if (!flip_social_temp_feed()) + { + return; + } + view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedInFeed); + } + break; + case FlipSocialSubmenuExploreIndex: + if (flipper_http_process_response_async(flip_social_get_explore, flip_social_parse_json_explore)) + { + view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedInExploreSubmenu); + } + break; + case FlipSocialSubmenuLoggedInIndexCompose: + view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedInCompose); + break; + case FlipSocialSubmenuLoggedInIndexSettings: + view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedInSettings); + break; + case FlipSocialSubmenuLoggedInSignOutButton: + app->is_logged_in = "false"; + + save_settings(app->wifi_ssid_logged_out, app->wifi_password_logged_out, app->login_username_logged_out, app->login_username_logged_in, app->login_password_logged_out, app->change_password_logged_in, app->is_logged_in); + + view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedOutSubmenu); + break; + case FlipSocialSubmenuComposeIndexAddPreSave: + view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedInComposeAddPreSaveInput); + break; + default: + action = ActionNone; + // Handle the pre-saved message selection (has a max of 25 items) + if (index >= FlipSocialSubemnuComposeIndexStartIndex && index < FlipSocialSubemnuComposeIndexStartIndex + MAX_PRE_SAVED_MESSAGES) + { + app->pre_saved_messages.index = index - FlipSocialSubemnuComposeIndexStartIndex; + snprintf(selected_message, MAX_MESSAGE_LENGTH, "%s", app->pre_saved_messages.messages[app->pre_saved_messages.index]); + if (!selected_message) + { + FURI_LOG_E(TAG, "Selected message is NULL"); + return; + } + view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedInProcessCompose); + } + + // Handle the explore selection + else if (index >= FlipSocialSubmenuExploreIndexStartIndex && index < FlipSocialSubmenuExploreIndexStartIndex + MAX_EXPLORE_USERS) + { + if (!flip_social_explore) + { + FURI_LOG_E(TAG, "FlipSocialExplore is NULL"); + return; + } + flip_social_explore->index = index - FlipSocialSubmenuExploreIndexStartIndex; + view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedInExploreProccess); + } + + // handle the friends selection + else if (index >= FlipSocialSubmenuLoggedInIndexFriendsStart && index < FlipSocialSubmenuLoggedInIndexFriendsStart + MAX_FRIENDS) + { + if (!flip_social_friends) + { + FURI_LOG_E(TAG, "FlipSocialFriends is NULL"); + return; + } + flip_social_friends->index = index - FlipSocialSubmenuLoggedInIndexFriendsStart; + view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedInFriendsProcess); + } + + // handle the messages selection + else if (index >= FlipSocialSubmenuLoggedInIndexMessagesUsersStart && index < FlipSocialSubmenuLoggedInIndexMessagesUsersStart + MAX_MESSAGE_USERS) + { + if (!flip_social_message_users) + { + FURI_LOG_E(TAG, "FlipSocialMessageUsers is NULL"); + return; + } + flip_social_message_users->index = index - FlipSocialSubmenuLoggedInIndexMessagesUsersStart; + if (flipper_http_process_response_async(flip_social_get_messages_with_user, flip_social_parse_json_messages)) + { + view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedInMessagesProcess); + } + } + + // handle the messages user choices selection + else if (index >= FlipSocialSubmenuLoggedInIndexMessagesUserChoicesIndexStart && index < FlipSocialSubmenuLoggedInIndexMessagesUserChoicesIndexStart + MAX_EXPLORE_USERS) + { + if (!flip_social_explore) + { + FURI_LOG_E(TAG, "FlipSocialExplore is NULL"); + return; + } + flip_social_explore->index = index - FlipSocialSubmenuLoggedInIndexMessagesUserChoicesIndexStart; + view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedInMessagesNewMessageUserChoicesInput); + } + else + { + FURI_LOG_E(TAG, "Unknown submenu index"); + } + + break; + } +} + +/** + * @brief Text input callback for when the user finishes entering their SSID on the wifi settings (logged out) screen. + * @param context The context - FlipSocialApp object. + * @return void + */ +void flip_social_logged_out_wifi_settings_ssid_updated(void *context) +{ + FlipSocialApp *app = (FlipSocialApp *)context; + if (!app) + { + FURI_LOG_E(TAG, "FlipSocialApp is NULL"); + return; + } + // Store the entered name + strncpy(app->wifi_ssid_logged_out, app->wifi_ssid_logged_out_temp_buffer, app->wifi_ssid_logged_out_temp_buffer_size); + + // Store the entered name in the logged in name field + strncpy(app->wifi_ssid_logged_in, app->wifi_ssid_logged_out_temp_buffer, app->wifi_ssid_logged_out_temp_buffer_size); + strncpy(app->wifi_ssid_logged_in_temp_buffer, app->wifi_ssid_logged_out_temp_buffer, app->wifi_ssid_logged_out_temp_buffer_size); + + // Ensure null-termination + app->wifi_ssid_logged_out[app->wifi_ssid_logged_out_temp_buffer_size - 1] = '\0'; + + // Update the name item text + if (app->variable_item_logged_out_wifi_settings_ssid) + { + variable_item_set_current_value_text(app->variable_item_logged_out_wifi_settings_ssid, app->wifi_ssid_logged_out); + } + + // update the wifi settings + if (!flipper_http_save_wifi(app->wifi_ssid_logged_out, app->wifi_password_logged_out)) + { + FURI_LOG_E(TAG, "Failed to save wifi settings via UART"); + FURI_LOG_E(TAG, "Make sure the Flipper is connected to the Wifi Dev Board"); + } + + // Save the settings + save_settings(app_instance->wifi_ssid_logged_out, app_instance->wifi_password_logged_out, app_instance->login_username_logged_out, app_instance->login_username_logged_in, app_instance->login_password_logged_out, app_instance->change_password_logged_in, app_instance->is_logged_in); + + view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedOutWifiSettings); +} + +/** + * @brief Text input callback for when the user finishes entering their password on the wifi settings (logged out) screen. + * @param context The context - FlipSocialApp object. + * @return void + */ +void flip_social_logged_out_wifi_settings_password_updated(void *context) +{ + FlipSocialApp *app = (FlipSocialApp *)context; + if (!app) + { + FURI_LOG_E(TAG, "FlipSocialApp is NULL"); + return; + } + // Store the entered WiFi password + strncpy(app->wifi_password_logged_out, app->wifi_password_logged_out_temp_buffer, app->wifi_password_logged_out_temp_buffer_size); + + // Store the entered WiFi password in the logged in password field + strncpy(app->wifi_password_logged_in, app->wifi_password_logged_out_temp_buffer, app->wifi_password_logged_out_temp_buffer_size); + strncpy(app->wifi_password_logged_in_temp_buffer, app->wifi_password_logged_out_temp_buffer, app->wifi_password_logged_out_temp_buffer_size); + + // Ensure null-termination + app->wifi_password_logged_out[app->wifi_password_logged_out_temp_buffer_size - 1] = '\0'; + + // Update the password item text + if (app->variable_item_logged_out_wifi_settings_password) + { + variable_item_set_current_value_text(app->variable_item_logged_out_wifi_settings_password, app->wifi_password_logged_out); + } + + // update the wifi settings + if (!flipper_http_save_wifi(app->wifi_ssid_logged_out, app->wifi_password_logged_out)) + { + FURI_LOG_E(TAG, "Failed to save wifi settings via UART"); + FURI_LOG_E(TAG, "Make sure the Flipper is connected to the Wifi Dev Board"); + } + + // Save the settings + save_settings(app_instance->wifi_ssid_logged_out, app_instance->wifi_password_logged_out, app_instance->login_username_logged_out, app_instance->login_username_logged_in, app_instance->login_password_logged_out, app_instance->change_password_logged_in, app_instance->is_logged_in); + + view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedOutWifiSettings); +} + +/** + * @brief Callback when the user selects a menu item in the wifi settings (logged out) screen. + * @param context The context - FlipSocialApp object. + * @param index The index of the selected item. + * @return void + */ +void flip_social_text_input_logged_out_wifi_settings_item_selected(void *context, uint32_t index) +{ + FlipSocialApp *app = (FlipSocialApp *)context; + if (!app) + { + FURI_LOG_E(TAG, "FlipSocialApp is NULL"); + return; + } + switch (index) + { + case 0: // Input SSID + view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedOutWifiSettingsSSIDInput); + break; + case 1: // Input Password + view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedOutWifiSettingsPasswordInput); + break; + default: + FURI_LOG_E(TAG, "Unknown configuration item index"); + break; + } +} + +/** + * @brief Text input callback for when the user finishes entering their username on the login (logged out) screen. + * @param context The context - FlipSocialApp object. + * @return void + */ +void flip_social_logged_out_login_username_updated(void *context) +{ + FlipSocialApp *app = (FlipSocialApp *)context; + if (!app) + { + FURI_LOG_E(TAG, "FlipSocialApp is NULL"); + return; + } + // Store the entered name + strncpy(app->login_username_logged_out, app->login_username_logged_out_temp_buffer, app->login_username_logged_out_temp_buffer_size); + + // Store the entered name in the logged in username field + strncpy(app->login_username_logged_in, app->login_username_logged_out_temp_buffer, app->login_username_logged_out_temp_buffer_size); + strncpy(app->login_username_logged_in_temp_buffer, app->login_username_logged_out_temp_buffer, app->login_username_logged_out_temp_buffer_size); + + // Ensure null-termination + app->login_username_logged_out[app->login_username_logged_out_temp_buffer_size - 1] = '\0'; + + // Update the name item text + if (app->variable_item_logged_out_login_username) + { + variable_item_set_current_value_text(app->variable_item_logged_out_login_username, app->login_username_logged_out); + } + + // Save the settings + save_settings(app_instance->wifi_ssid_logged_out, app_instance->wifi_password_logged_out, app_instance->login_username_logged_out, app_instance->login_username_logged_in, app_instance->login_password_logged_out, app_instance->change_password_logged_in, app_instance->is_logged_in); + + view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedOutLogin); +} + +/** + * @brief Text input callback for when the user finishes entering their password on the login (logged out) screen. + * @param context The context - FlipSocialApp object. + * @return void + */ + +void flip_social_logged_out_login_password_updated(void *context) +{ + FlipSocialApp *app = (FlipSocialApp *)context; + if (!app) + { + FURI_LOG_E(TAG, "FlipSocialApp is NULL"); + return; + } + // Store the entered password + strncpy(app->login_password_logged_out, app->login_password_logged_out_temp_buffer, app->login_password_logged_out_temp_buffer_size); + + // Store the entered password in the change password field + strncpy(app->change_password_logged_in, app->login_password_logged_out_temp_buffer, app->login_password_logged_out_temp_buffer_size); + strncpy(app->change_password_logged_in_temp_buffer, app->login_password_logged_out_temp_buffer, app->login_password_logged_out_temp_buffer_size); + + // Ensure null-termination + app->login_password_logged_out[app->login_password_logged_out_temp_buffer_size - 1] = '\0'; + + // Update the password item text + if (app->variable_item_logged_out_login_password) + { + // dont show the password on the screen (version 0.2) + // variable_item_set_current_value_text(app->variable_item_logged_out_login_password, app->login_password_logged_out); + } + + // Save the settings + save_settings(app_instance->wifi_ssid_logged_out, app_instance->wifi_password_logged_out, app_instance->login_username_logged_out, app_instance->login_username_logged_in, app_instance->login_password_logged_out, app_instance->change_password_logged_in, app_instance->is_logged_in); + + view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedOutLogin); +} + +/** + * @brief Callback when the user selects a menu item in the login (logged out) screen. + * @param context The context - FlipSocialApp object. + * @param index The index of the selected item. + * @return void + */ +void flip_social_text_input_logged_out_login_item_selected(void *context, uint32_t index) +{ + FlipSocialApp *app = (FlipSocialApp *)context; + if (!app) + { + FURI_LOG_E(TAG, "FlipSocialApp is NULL"); + return; + } + switch (index) + { + case 0: // Input Username + view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedOutLoginUsernameInput); + break; + case 1: // Input Password + view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedOutLoginPasswordInput); + break; + case 2: // Login Button + view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedOutProcessLogin); + break; + default: + FURI_LOG_E(TAG, "Unknown configuration item index"); + break; + } +} + +/** + * @brief Text input callback for when the user finishes entering their username on the register (logged out) screen. + * @param context The context - FlipSocialApp object. + * @return void + */ +void flip_social_logged_out_register_username_updated(void *context) +{ + FlipSocialApp *app = (FlipSocialApp *)context; + if (!app) + { + FURI_LOG_E(TAG, "FlipSocialApp is NULL"); + return; + } + // Store the entered name + strncpy(app->register_username_logged_out, app->register_username_logged_out_temp_buffer, app->register_username_logged_out_temp_buffer_size); + + // Ensure null-termination + app->register_username_logged_out[app->register_username_logged_out_temp_buffer_size - 1] = '\0'; + + // Update the name item text + if (app->variable_item_logged_out_register_username) + { + variable_item_set_current_value_text(app->variable_item_logged_out_register_username, app->register_username_logged_out); + } + + view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedOutRegister); +} + +/** + * @brief Text input callback for when the user finishes entering their password on the register (logged out) screen. + * @param context The context - FlipSocialApp object. + * @return void + */ +void flip_social_logged_out_register_password_updated(void *context) +{ + FlipSocialApp *app = (FlipSocialApp *)context; + if (!app) + { + FURI_LOG_E(TAG, "FlipSocialApp is NULL"); + return; + } + // Store the entered password + strncpy(app->register_password_logged_out, app->register_password_logged_out_temp_buffer, app->register_password_logged_out_temp_buffer_size); + + // Ensure null-termination + app->register_password_logged_out[app->register_password_logged_out_temp_buffer_size - 1] = '\0'; + + // Update the password item text + if (app->variable_item_logged_out_register_password) + { + variable_item_set_current_value_text(app->variable_item_logged_out_register_password, app->register_password_logged_out); + } + + view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedOutRegister); +} + +/** + * @brief Text input callback for when the user finishes entering their password 2 on the register (logged out) screen. + * @param context The context - FlipSocialApp object. + * @return void + */ +void flip_social_logged_out_register_password_2_updated(void *context) +{ + FlipSocialApp *app = (FlipSocialApp *)context; + if (!app) + { + FURI_LOG_E(TAG, "FlipSocialApp is NULL"); + return; + } + // Store the entered password + strncpy(app->register_password_2_logged_out, app->register_password_2_logged_out_temp_buffer, app->register_password_2_logged_out_temp_buffer_size); + + // Ensure null-termination + app->register_password_2_logged_out[app->register_password_2_logged_out_temp_buffer_size - 1] = '\0'; + + // Update the password item text + if (app->variable_item_logged_out_register_password_2) + { + variable_item_set_current_value_text(app->variable_item_logged_out_register_password_2, app->register_password_2_logged_out); + } + + view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedOutRegister); +} + +/** + * @brief Callback when the user selects a menu item in the register (logged out) screen. + * @param context The context - FlipSocialApp object. + * @param index The index of the selected item. + * @return void + */ +void flip_social_text_input_logged_out_register_item_selected(void *context, uint32_t index) +{ + FlipSocialApp *app = (FlipSocialApp *)context; + if (!app) + { + FURI_LOG_E(TAG, "FlipSocialApp is NULL"); + return; + } + switch (index) + { + case 0: // Input Username + view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedOutRegisterUsernameInput); + break; + case 1: // Input Password + view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedOutRegisterPasswordInput); + break; + case 2: // Input Password 2 + view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedOutRegisterPassword2Input); + break; + case 3: // Register button + view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedOutProcessRegister); + break; + default: + FURI_LOG_E(TAG, "Unknown configuration item index"); + break; + } +} + +/** + * @brief Text input callback for when the user finishes entering their SSID on the wifi settings (logged in) screen. + * @param context The context - FlipSocialApp object. + * @return void + */ +void flip_social_logged_in_wifi_settings_ssid_updated(void *context) +{ + FlipSocialApp *app = (FlipSocialApp *)context; + if (!app) + { + FURI_LOG_E(TAG, "FlipSocialApp is NULL"); + return; + } + // Store the entered SSID + strncpy(app->wifi_ssid_logged_in, app->wifi_ssid_logged_in_temp_buffer, app->wifi_ssid_logged_in_temp_buffer_size); + + // Store the entered SSID in the logged out SSID + strncpy(app->wifi_ssid_logged_out, app->wifi_ssid_logged_in, app->wifi_ssid_logged_in_temp_buffer_size); + strncpy(app->wifi_ssid_logged_out_temp_buffer, app->wifi_ssid_logged_in, app->wifi_ssid_logged_in_temp_buffer_size); + + // Ensure null-termination + app->wifi_ssid_logged_in[app->wifi_ssid_logged_in_temp_buffer_size - 1] = '\0'; + + // Update the name item text + if (app->variable_item_logged_in_wifi_settings_ssid) + { + variable_item_set_current_value_text(app->variable_item_logged_in_wifi_settings_ssid, app->wifi_ssid_logged_in); + } + + // Save the settings + save_settings(app_instance->wifi_ssid_logged_in, app_instance->wifi_password_logged_in, app_instance->login_username_logged_out, app_instance->login_username_logged_in, app_instance->login_password_logged_out, app_instance->change_password_logged_in, app_instance->is_logged_in); + + // update the wifi settings + if (strlen(app->wifi_ssid_logged_in) > 0 && strlen(app->wifi_password_logged_in) > 0) + { + if (!flipper_http_save_wifi(app->wifi_ssid_logged_in, app->wifi_password_logged_in)) + { + FURI_LOG_E(TAG, "Failed to save wifi settings via UART"); + FURI_LOG_E(TAG, "Make sure the Flipper is connected to the Wifi Dev Board"); + } + } + + view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedInSettingsWifi); +} + +/** + * @brief Text input callback for when the user finishes entering their password on the wifi settings (logged in) screen. + * @param context The context - FlipSocialApp object. + * @return void + */ +void flip_social_logged_in_wifi_settings_password_updated(void *context) +{ + FlipSocialApp *app = (FlipSocialApp *)context; + if (!app) + { + FURI_LOG_E(TAG, "FlipSocialApp is NULL"); + return; + } + // Store the entered password + strncpy(app->wifi_password_logged_in, app->wifi_password_logged_in_temp_buffer, app->wifi_password_logged_in_temp_buffer_size); + + // Store the entered password in the logged out password + strncpy(app->login_password_logged_out, app->wifi_password_logged_in, app->wifi_password_logged_in_temp_buffer_size); + strncpy(app->login_password_logged_out_temp_buffer, app->wifi_password_logged_in, app->wifi_password_logged_in_temp_buffer_size); + + // Ensure null-termination + app->wifi_password_logged_in[app->wifi_password_logged_in_temp_buffer_size - 1] = '\0'; + + // Update the password item text + if (app->variable_item_logged_in_wifi_settings_password) + { + // dont show the password on the screen (version 0.2) + // variable_item_set_current_value_text(app->variable_item_logged_in_wifi_settings_password, app->wifi_password_logged_in); + } + + // Save the settings + save_settings(app_instance->wifi_ssid_logged_in, app_instance->wifi_password_logged_in, app_instance->login_username_logged_out, app_instance->login_username_logged_in, app_instance->login_password_logged_out, app_instance->change_password_logged_in, app_instance->is_logged_in); + + // update the wifi settings + if (strlen(app->wifi_ssid_logged_in) > 0 && strlen(app->wifi_password_logged_in) > 0) + { + if (!flipper_http_save_wifi(app->wifi_ssid_logged_in, app->wifi_password_logged_in)) + { + FURI_LOG_E(TAG, "Failed to save wifi settings via UART"); + FURI_LOG_E(TAG, "Make sure the Flipper is connected to the Wifi Dev Board"); + } + } + + view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedInSettingsWifi); +} + +/** + * @brief Callback when the user selects a menu item in the wifi settings (logged in) screen. + * @param context The context - FlipSocialApp object. + * @param index The index of the selected item. + * @return void + */ +void flip_social_text_input_logged_in_wifi_settings_item_selected(void *context, uint32_t index) +{ + FlipSocialApp *app = (FlipSocialApp *)context; + if (!app) + { + FURI_LOG_E(TAG, "FlipSocialApp is NULL"); + return; + } + switch (index) + { + case 0: // Input SSID + view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedInWifiSettingsSSIDInput); + break; + case 1: // Input Password + view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedInWifiSettingsPasswordInput); + break; + default: + FURI_LOG_E(TAG, "Unknown configuration item index"); + break; + } +} + +/** + * @brief Text input callback for when the user finishes entering their message on the compose (logged in) screen for Add Text + * @param context The context - FlipSocialApp object. + * @return void + */ +void flip_social_logged_in_compose_pre_save_updated(void *context) +{ + FlipSocialApp *app = (FlipSocialApp *)context; + if (!app) + { + FURI_LOG_E(TAG, "FlipSocialApp is NULL"); + return; + } + + // check if the message is empty or if adding in the message would exceed the MAX_PRE_SAVED_MESSAGES + if (app->compose_pre_save_logged_in_temp_buffer_size == 0 || app->pre_saved_messages.count >= MAX_PRE_SAVED_MESSAGES) + { + FURI_LOG_E(TAG, "Message is empty or would exceed the maximum number of pre-saved messages"); + view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedInCompose); + return; + } + + // Store the entered message + strncpy(app->compose_pre_save_logged_in, app->compose_pre_save_logged_in_temp_buffer, app->compose_pre_save_logged_in_temp_buffer_size); + + // Ensure null-termination + app->compose_pre_save_logged_in[app->compose_pre_save_logged_in_temp_buffer_size - 1] = '\0'; + + // add the item to the submenu + submenu_reset(app->submenu_compose); + + // loop through the items and add them to the submenu + app->pre_saved_messages.messages[app->pre_saved_messages.count] = app->compose_pre_save_logged_in; + app->pre_saved_messages.count++; + + submenu_add_item(app->submenu_compose, "Add Pre-Save", FlipSocialSubmenuComposeIndexAddPreSave, flip_social_callback_submenu_choices, app); + for (uint32_t i = 0; i < app->pre_saved_messages.count; i++) + { + submenu_add_item(app->submenu_compose, app->pre_saved_messages.messages[i], FlipSocialSubemnuComposeIndexStartIndex + i, flip_social_callback_submenu_choices, app); + } + + // save playlist + save_playlist(&app->pre_saved_messages); + + view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedInCompose); +} + +/** + * @brief Text input callback for when the user finishes entering their message on the profile (logged in) screen for change password + * @param context The context - FlipSocialApp object. + * @return void + */ +void flip_social_logged_in_profile_change_password_updated(void *context) +{ + FlipSocialApp *app = (FlipSocialApp *)context; + if (!app) + { + FURI_LOG_E(TAG, "FlipSocialApp is NULL"); + return; + } + // Correct type: old_pass should be a pointer to a string (char *) + const char *old_password = app->login_password_logged_out; + + // Store the entered message + strncpy(app->change_password_logged_in, app->change_password_logged_in_temp_buffer, app->change_password_logged_in_temp_buffer_size); + + // store the entered password in the logged out password + strncpy(app->login_password_logged_out, app->change_password_logged_in, app->login_password_logged_out_temp_buffer_size); + strncpy(app->login_password_logged_out_temp_buffer, app->change_password_logged_in, app->login_password_logged_out_temp_buffer_size); + + // Ensure null-termination + app->change_password_logged_in[app->change_password_logged_in_temp_buffer_size - 1] = '\0'; + + // Update the message item text + if (app->variable_item_logged_in_profile_change_password) + { + // dont show the password on the screen (version 0.2) + // variable_item_set_current_value_text(app->variable_item_logged_in_profile_change_password, app->change_password_logged_in); + } + + // send post request to change password + char payload[256]; + snprintf(payload, sizeof(payload), "{\"username\":\"%s\",\"old_password\":\"%s\",\"new_password\":\"%s\"}", app->login_username_logged_out, old_password, app->change_password_logged_in); + char *headers = jsmn("Content-Type", "application/json"); + if (!flipper_http_post_request_with_headers("https://www.flipsocial.net/api/user/change-password/", headers, payload)) + { + FURI_LOG_E(TAG, "Failed to send post request to change password"); + FURI_LOG_E(TAG, "Make sure the Flipper is connected to the Wifi Dev Board"); + free(headers); + return; + } + free(headers); + // Save the settings + save_settings(app_instance->wifi_ssid_logged_out, app_instance->wifi_password_logged_out, app_instance->login_username_logged_out, app_instance->login_username_logged_in, app_instance->login_password_logged_out, app_instance->change_password_logged_in, app_instance->is_logged_in); + + view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedInProfile); +} + +/** + * @brief Callback when a user selects a menu item in the profile (logged in) screen. + * @param context The context - FlipSocialApp object. + * @param index The index of the selected item. + * @return void + */ +void flip_social_text_input_logged_in_profile_item_selected(void *context, uint32_t index) +{ + FlipSocialApp *app = (FlipSocialApp *)context; + if (!app) + { + FURI_LOG_E(TAG, "FlipSocialApp is NULL"); + return; + } + switch (index) + { + case 0: // Change Username + // do nothing since username cannot be changed + break; + case 1: // Change Password + view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedInChangePasswordInput); + break; + case 2: // Friends + // get friends then switch to the friends screen + if (flip_social_get_friends()) // start the async friends request + { + furi_timer_start(fhttp.get_timeout_timer, TIMEOUT_DURATION_TICKS); + } + while (fhttp.state == RECEIVING && furi_timer_is_running(fhttp.get_timeout_timer) > 0) + { + // Wait for the friends to be received + furi_delay_ms(100); + } + furi_timer_stop(fhttp.get_timeout_timer); + if (!flip_social_parse_json_friends()) // parse the JSON before switching to the friends (synchonous) + { + FURI_LOG_E(TAG, "Failed to parse the JSON friends..."); + return; // just return for now, no temporary friends yet + // show a popup message saving wifi is disconnected + } + furi_timer_stop(fhttp.get_timeout_timer); + view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedInFriendsSubmenu); + break; + default: + FURI_LOG_E(TAG, "Unknown configuration item index"); + break; + } +} + +/** + * @brief Callback when a user selects a menu item in the settings (logged in) screen. + * @param context The context - FlipSocialApp object. + * @param index The index of the selected item. + * @return void + */ +void flip_social_text_input_logged_in_settings_item_selected(void *context, uint32_t index) +{ + FlipSocialApp *app = (FlipSocialApp *)context; + if (!app) + { + FURI_LOG_E(TAG, "FlipSocialApp is NULL"); + return; + } + switch (index) + { + case 0: // About + view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedInSettingsAbout); + break; + case 1: // Wifi + view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedInSettingsWifi); + break; + default: + break; + } +} + +/** + * @brief Text input callback for when the user finishes entering their message to send to the selected user choice (user choice messages view) + * @param context The context - FlipSocialApp object. + * @return void + */ +void flip_social_logged_in_messages_user_choice_message_updated(void *context) +{ + FlipSocialApp *app = (FlipSocialApp *)context; + if (!app) + { + FURI_LOG_E(TAG, "FlipSocialApp is NULL"); + return; + } + + // check if the message is empty + if (app->message_user_choice_logged_in_temp_buffer_size == 0) + { + FURI_LOG_E(TAG, "Message is empty"); + view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedInMessagesNewMessageUserChoicesInput); + return; + } + + // Store the entered message + strncpy(app->message_user_choice_logged_in, app->message_user_choice_logged_in_temp_buffer, app->message_user_choice_logged_in_temp_buffer_size); + + // Ensure null-termination + app->message_user_choice_logged_in[app->message_user_choice_logged_in_temp_buffer_size - 1] = '\0'; + + // send post request to send message + char url[128]; + char payload[256]; + snprintf(url, sizeof(url), "https://www.flipsocial.net/api/messages/%s/post/", app->login_username_logged_in); + snprintf(payload, sizeof(payload), "{\"receiver\":\"%s\",\"content\":\"%s\"}", flip_social_explore->usernames[flip_social_explore->index], app->message_user_choice_logged_in); + char *headers = jsmn("Content-Type", "application/json"); + + if (flipper_http_post_request_with_headers(url, headers, payload)) // start the async request + { + furi_timer_start(fhttp.get_timeout_timer, TIMEOUT_DURATION_TICKS); + fhttp.state = RECEIVING; + free(headers); + } + else + { + FURI_LOG_E(TAG, "Failed to send post request to send message"); + FURI_LOG_E(TAG, "Make sure the Flipper is connected to the Wifi Dev Board"); + free(headers); + return; + } + while (fhttp.state == RECEIVING && furi_timer_is_running(fhttp.get_timeout_timer) > 0) + { + // Wait for the request to be received + furi_delay_ms(100); + } + furi_timer_stop(fhttp.get_timeout_timer); + + // add user to the message list + strncpy(flip_social_message_users->usernames[flip_social_message_users->count], flip_social_explore->usernames[flip_social_explore->index], strlen(flip_social_explore->usernames[flip_social_explore->index])); + flip_social_message_users->count++; + + // redraw submenu + flip_social_update_messages_submenu(); + view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedInMessagesSubmenu); +} + +/** + * @brief Text input callback for when the user finishes entering their message to the selected user (messages view) + * @param context The context - FlipSocialApp object. + * @return void + */ +void flip_social_logged_in_messages_new_message_updated(void *context) +{ + FlipSocialApp *app = (FlipSocialApp *)context; + if (!app) + { + FURI_LOG_E(TAG, "FlipSocialApp is NULL"); + return; + } + + // check if the message is empty + if (app->messages_new_message_logged_in_temp_buffer_size == 0) + { + FURI_LOG_E(TAG, "Message is empty"); + view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedInMessagesNewMessageInput); + return; + } + + // Store the entered message + strncpy(app->messages_new_message_logged_in, app->messages_new_message_logged_in_temp_buffer, app->messages_new_message_logged_in_temp_buffer_size); + + // Ensure null-termination + app->messages_new_message_logged_in[app->messages_new_message_logged_in_temp_buffer_size - 1] = '\0'; + + // send post request to send message + char url[128]; + char payload[256]; + snprintf(url, sizeof(url), "https://www.flipsocial.net/api/messages/%s/post/", app->login_username_logged_in); + snprintf(payload, sizeof(payload), "{\"receiver\":\"%s\",\"content\":\"%s\"}", flip_social_message_users->usernames[flip_social_message_users->index], app->messages_new_message_logged_in); + char *headers = jsmn("Content-Type", "application/json"); + + if (flipper_http_post_request_with_headers(url, headers, payload)) // start the async request + { + furi_timer_start(fhttp.get_timeout_timer, TIMEOUT_DURATION_TICKS); + fhttp.state = RECEIVING; + free(headers); + } + else + { + FURI_LOG_E(TAG, "Failed to send post request to send message"); + FURI_LOG_E(TAG, "Make sure the Flipper is connected to the Wifi Dev Board"); + free(headers); + return; + } + while (fhttp.state == RECEIVING && furi_timer_is_running(fhttp.get_timeout_timer) > 0) + { + // Wait for the request to be received + furi_delay_ms(100); + } + furi_timer_stop(fhttp.get_timeout_timer); + view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedInMessagesSubmenu); +} \ No newline at end of file diff --git a/callback/flip_social_callback.h b/callback/flip_social_callback.h new file mode 100644 index 000000000..10f7fa41a --- /dev/null +++ b/callback/flip_social_callback.h @@ -0,0 +1,256 @@ +#ifndef FLIP_SOCIAL_CALLBACK_H +#define FLIP_SOCIAL_CALLBACK_H + +#include +#include +#include +#include +#include +#include +#include + +/** + * @brief Navigation callback to go back to the submenu Logged out. + * @param context The context - unused + * @return next view id (FlipSocialViewLoggedOutSubmenu) + */ +uint32_t flip_social_callback_to_submenu_logged_out(void *context); + +/** + * @brief Navigation callback to go back to the submenu Logged in. + * @param context The context - unused + * @return next view id (FlipSocialViewLoggedInSubmenu) + */ +uint32_t flip_social_callback_to_submenu_logged_in(void *context); + +/** + * @brief Navigation callback to bring the user back to the (Logged out) Login screen + * @param context The context - unused + * @return next view id (FlipSocialViewLoggedOutLogin) + */ +uint32_t flip_social_callback_to_login_logged_out(void *context); + +/** + * @brief Navigation callback to bring the user back to the (Logged out) Register screen + * @param context The context - unused + * @return next view id (FlipSocialViewLoggedOutRegister) + */ +uint32_t flip_social_callback_to_register_logged_out(void *context); + +/** + * @brief Navigation callback to bring the user back to the (Logged out) Wifi Settings screen + * @param context The context - unused + * @return next view id (FlipSocialViewLoggedOutWifiSettings) + */ +uint32_t flip_social_callback_to_wifi_settings_logged_out(void *context); + +/** + * @brief Navigation callback to bring the user back to the (Logged in) Wifi Settings screen + * @param context The context - unused + * @return next view id (FlipSocialViewLoggedInSettingsWifi) + */ +uint32_t flip_social_callback_to_wifi_settings_logged_in(void *context); + +/** + * @brief Navigation callback to bring the user back to the (Logged in) Settings screen + * @param context The context - unused + * @return next view id (FlipSocialViewLoggedInSettingsWifi) + */ +uint32_t flip_social_callback_to_settings_logged_in(void *context); + +/** + * @brief Navigation callback to bring the user back to the (Logged in) Compose screen + * @param context The context - unused + * @return next view id (FlipSocialViewLoggedInCompose) + */ +uint32_t flip_social_callback_to_compose_logged_in(void *context); + +/** + * @brief Navigation callback to bring the user back to the (Logged in) Profile screen + * @param context The context - unused + * @return next view id (FlipSocialViewLoggedInProfile) + */ +uint32_t flip_social_callback_to_profile_logged_in(void *context); + +/** + * @brief Navigation callback to bring the user back to the Explore submenu + * @param context The context - unused + * @return next view id (FlipSocialViewLoggedInExploreSubmenu) + */ +uint32_t flip_social_callback_to_explore_logged_in(void *context); + +/** + * @brief Navigation callback to bring the user back to the Friends submenu + * @param context The context - unused + * @return next view id (FlipSocialViewLoggedInFriendsSubmenu) + */ +uint32_t flip_social_callback_to_friends_logged_in(void *context); + +/** + * @brief Navigation callback to bring the user back to the Messages submenu + * @param context The context - unused + * @return next view id (FlipSocialViewLoggedInMessagesSubmenu) + */ +uint32_t flip_social_callback_to_messages_logged_in(void *context); + +/** + * @brief Navigation callback to bring the user back to the User Choices screen + * @param context The context - unused + * @return next view id (FlipSocialViewLoggedInMessagesUserChoices) + */ +uint32_t flip_social_callback_to_messages_user_choices(void *context); + +/** + * @brief Navigation callback for exiting the application + * @param context The context - unused + * @return next view id (VIEW_NONE to exit the app) + */ +uint32_t flip_social_callback_exit_app(void *context); + +/** + * @brief Handle ALL submenu item selections. + * @param context The context - FlipSocialApp object. + * @param index The FlipSocialSubmenuIndex item that was clicked. + * @return void + */ +void flip_social_callback_submenu_choices(void *context, uint32_t index); + +/** + * @brief Text input callback for when the user finishes entering their SSID on the wifi settings (logged out) screen. + * @param context The context - FlipSocialApp object. + * @return void + */ +void flip_social_logged_out_wifi_settings_ssid_updated(void *context); + +/** + * @brief Text input callback for when the user finishes entering their password on the wifi settings (logged out) screen. + * @param context The context - FlipSocialApp object. + * @return void + */ +void flip_social_logged_out_wifi_settings_password_updated(void *context); + +/** + * @brief Callback when the user selects a menu item in the wifi settings (logged out) screen. + * @param context The context - FlipSocialApp object. + * @param index The index of the selected item. + * @return void + */ +void flip_social_text_input_logged_out_wifi_settings_item_selected(void *context, uint32_t index); + +/** + * @brief Text input callback for when the user finishes entering their username on the login (logged out) screen. + * @param context The context - FlipSocialApp object. + * @return void + */ +void flip_social_logged_out_login_username_updated(void *context); + +/** + * @brief Text input callback for when the user finishes entering their password on the login (logged out) screen. + * @param context The context - FlipSocialApp object. + * @return void + */ +void flip_social_logged_out_login_password_updated(void *context); + +/** + * @brief Callback when the user selects a menu item in the login (logged out) screen. + * @param context The context - FlipSocialApp object. + * @param index The index of the selected item. + * @return void + */ +void flip_social_text_input_logged_out_login_item_selected(void *context, uint32_t index); + +/** + * @brief Text input callback for when the user finishes entering their username on the register (logged out) screen. + * @param context The context - FlipSocialApp object. + * @return void + */ +void flip_social_logged_out_register_username_updated(void *context); + +/** + * @brief Text input callback for when the user finishes entering their password on the register (logged out) screen. + * @param context The context - FlipSocialApp object. + * @return void + */ +void flip_social_logged_out_register_password_updated(void *context); + +/** + * @brief Text input callback for when the user finishes entering their password 2 on the register (logged out) screen. + * @param context The context - FlipSocialApp object. + * @return void + */ +void flip_social_logged_out_register_password_2_updated(void *context); + +/** + * @brief Text input callback for when the user finishes entering their SSID on the wifi settings (logged in) screen. + * @param context The context - FlipSocialApp object. + * @return void + */ +void flip_social_logged_in_wifi_settings_ssid_updated(void *context); + +/** + * @brief Text input callback for when the user finishes entering their password on the wifi settings (logged in) screen. + * @param context The context - FlipSocialApp object. + * @return void + */ +void flip_social_logged_in_wifi_settings_password_updated(void *context); + +/** + * @brief Callback when the user selects a menu item in the wifi settings (logged in) screen. + * @param context The context - FlipSocialApp object. + * @param index The index of the selected item. + * @return void + */ +void flip_social_text_input_logged_in_wifi_settings_item_selected(void *context, uint32_t index); + +/** + * @brief Text input callback for when the user finishes entering their message on the compose (logged in) screen for Add Text + * @param context The context - FlipSocialApp object. + * @return void + */ +void flip_social_logged_in_compose_pre_save_updated(void *context); + +/** + * @brief Text input callback for when the user finishes entering their message on the profile (logged in) screen for change password + * @param context The context - FlipSocialApp object. + * @return void + */ +void flip_social_logged_in_profile_change_password_updated(void *context); + +/** + * @brief Callback when a user selects a menu item in the profile (logged in) screen. + * @param context The context - FlipSocialApp object. + * @param index The index of the selected item. + * @return void + */ +void flip_social_text_input_logged_in_profile_item_selected(void *context, uint32_t index); + +/** + * @brief Callback when a user selects a menu item in the settings (logged in) screen. + * @param context The context - FlipSocialApp object. + * @param index The index of the selected item. + * @return void + */ +void flip_social_text_input_logged_in_settings_item_selected(void *context, uint32_t index); + +/** + * @brief Text input callback for when the user finishes entering their message to send to the selected user choice (user choice messages view) + * @param context The context - FlipSocialApp object. + * @return void + */ +void flip_social_logged_in_messages_user_choice_message_updated(void *context); +/** + * @brief Text input callback for when the user finishes entering their message to the selected user (messages view) + * @param context The context - FlipSocialApp object. + * @return void + */ +void flip_social_logged_in_messages_new_message_updated(void *context); + +/** + * @brief Callback when the user selects a menu item in the register (logged out) screen. + * @param context The context - FlipSocialApp object. + * @param index The index of the selected item. + * @return void + */ +void flip_social_text_input_logged_out_register_item_selected(void *context, uint32_t index); + +#endif \ No newline at end of file diff --git a/draw/flip_social_draw.c b/draw/flip_social_draw.c new file mode 100644 index 000000000..27e6caefb --- /dev/null +++ b/draw/flip_social_draw.c @@ -0,0 +1,1013 @@ +#include "flip_social_draw.h" +Action action = ActionNone; +bool flip_social_board_is_active(Canvas *canvas) +{ + if (fhttp.state == INACTIVE) + { + canvas_draw_str(canvas, 0, 7, "Wifi Dev Board disconnected."); + canvas_draw_str(canvas, 0, 17, "Please connect to the board."); + canvas_draw_str(canvas, 0, 32, "If your board is connected,"); + canvas_draw_str(canvas, 0, 42, "make sure you have flashed"); + canvas_draw_str(canvas, 0, 52, "your WiFi Devboard with the"); + canvas_draw_str(canvas, 0, 62, "latest FlipperHTTP flash."); + return false; + } + return true; +} + +void flip_social_handle_error(Canvas *canvas) +{ + if (fhttp.last_response != NULL) + { + if (strstr(fhttp.last_response, "[ERROR] Not connected to Wifi. Failed to reconnect.") != NULL) + { + canvas_clear(canvas); + canvas_draw_str(canvas, 0, 10, "[ERROR] Not connected to Wifi."); + canvas_draw_str(canvas, 0, 50, "Update your WiFi settings."); + canvas_draw_str(canvas, 0, 60, "Press BACK to return."); + } + else if (strstr(fhttp.last_response, "[ERROR] Failed to connect to Wifi.") != NULL) + { + canvas_clear(canvas); + canvas_draw_str(canvas, 0, 10, "[ERROR] Not connected to Wifi."); + canvas_draw_str(canvas, 0, 50, "Update your WiFi settings."); + canvas_draw_str(canvas, 0, 60, "Press BACK to return."); + } + else + { + canvas_draw_str(canvas, 0, 42, "Failed..."); + canvas_draw_str(canvas, 0, 52, "Update your credentials."); + canvas_draw_str(canvas, 0, 62, "Press BACK to return."); + } + } + else + { + canvas_draw_str(canvas, 0, 42, "Failed..."); + canvas_draw_str(canvas, 0, 52, "Update your credentials."); + canvas_draw_str(canvas, 0, 62, "Press BACK to return."); + } +} + +void on_input(const void *event, void *ctx) +{ + UNUSED(ctx); + + InputKey key = ((InputEvent *)event)->key; + InputType type = ((InputEvent *)event)->type; + + if (type != InputTypeRelease) + { + return; + } + + switch (key) + { + case InputKeyOk: + action = ActionFlip; + break; + case InputKeyBack: + action = ActionBack; + break; + case InputKeyRight: + action = ActionNext; + break; + case InputKeyLeft: + action = ActionPrev; + break; + case InputKeyUp: + action = ActionPrev; + break; + case InputKeyDown: + action = ActionNext; + break; + default: + action = ActionNone; + break; + } +} + +// Function to draw the message on the canvas with word wrapping +void draw_user_message(Canvas *canvas, const char *user_message, int x, int y) +{ + if (user_message == NULL) + { + FURI_LOG_E(TAG, "User message is NULL."); + return; + } + + size_t msg_length = strlen(user_message); + size_t start = 0; + int line_num = 0; + char line[MAX_LINE_LENGTH + 1]; // Buffer for the current line (+1 for null terminator) + + while (start < msg_length && line_num < 4) + { + size_t remaining = msg_length - start; + size_t len = (remaining > MAX_LINE_LENGTH) ? MAX_LINE_LENGTH : remaining; + + if (remaining > MAX_LINE_LENGTH) + { + // Find the last space within the first 'len' characters + size_t last_space = len; + while (last_space > 0 && user_message[start + last_space - 1] != ' ') + { + last_space--; + } + + if (last_space > 0) + { + len = last_space; // Adjust len to the position of the last space + } + } + + // Copy the substring to 'line' and null-terminate it + memcpy(line, user_message + start, len); + line[len] = '\0'; // Ensure the string is null-terminated + + // Draw the string on the canvas + // Adjust the y-coordinate based on the line number + canvas_draw_str_aligned(canvas, x, y + line_num * 10, AlignLeft, AlignTop, line); + + // Update the start position for the next line + start += len; + + // Skip any spaces to avoid leading spaces on the next line + while (start < msg_length && user_message[start] == ' ') + { + start++; + } + + // Increment the line number + line_num++; + } +} + +void flip_social_callback_draw_compose(Canvas *canvas, void *model) +{ + UNUSED(model); + if (!canvas) + { + FURI_LOG_E(TAG, "Canvas is NULL"); + return; + } + if (!app_instance) + { + FURI_LOG_E(TAG, "FlipSocialApp is NULL"); + return; + } + + if (!flip_social_dialog_shown) + { + flip_social_dialog_shown = true; + app_instance->input_event_queue = furi_record_open(RECORD_INPUT_EVENTS); + app_instance->input_event = furi_pubsub_subscribe(app_instance->input_event_queue, on_input, NULL); + } + + draw_user_message(canvas, selected_message, 0, 2); + + canvas_draw_icon(canvas, 0, 53, &I_ButtonLeft_4x7); + canvas_draw_str_aligned(canvas, 7, 54, AlignLeft, AlignTop, "Delete"); + canvas_draw_icon(canvas, 52, 53, &I_ButtonBACK_10x8); + canvas_draw_str_aligned(canvas, 64, 54, AlignLeft, AlignTop, "Back"); + canvas_draw_icon(canvas, 100, 53, &I_ButtonRight_4x7); + canvas_draw_str_aligned(canvas, 107, 54, AlignLeft, AlignTop, "Post"); + + // handle action + switch (action) + { + case ActionNone: + break; + case ActionBack: + flip_social_dialog_stop = true; + break; + case ActionNext: + // send selected_message + if (selected_message && app_instance->login_username_logged_in) + { + // Send the selected_message + char command[256]; + snprintf(command, sizeof(command), "{\"username\":\"%s\",\"content\":\"%s\"}", + app_instance->login_username_logged_in, selected_message); + + bool success = flipper_http_post_request_with_headers( + "https://www.flipsocial.net/api/feed/post/", + "{\"Content-Type\":\"application/json\"}", + command); + + if (!success) + { + FURI_LOG_E(TAG, "Failed to send HTTP request for feed"); + fhttp.state = ISSUE; + return; + } + + fhttp.state = RECEIVING; + furi_timer_start(fhttp.get_timeout_timer, TIMEOUT_DURATION_TICKS); + } + else + { + FURI_LOG_E(TAG, "Message or username is NULL"); + return; + } + + int i = 0; + while (fhttp.state == RECEIVING && furi_timer_is_running(fhttp.get_timeout_timer) > 0) + { + // Wait for the feed to be received + furi_delay_ms(100); + + char dots_str[64] = "Receiving"; + + // Append dots to the string based on the value of i + int dot_count = i % 4; + int len = strlen(dots_str); + snprintf(dots_str + len, sizeof(dots_str) - len, "%.*s", dot_count, "...."); + + // Draw the resulting string on the canvas + canvas_draw_str(canvas, 0, 30, dots_str); + + i++; + } + flip_social_dialog_stop = true; + furi_timer_stop(fhttp.get_timeout_timer); + break; + case ActionPrev: + // delete message + app_instance->pre_saved_messages.messages[app_instance->pre_saved_messages.index] = NULL; + + for (uint32_t i = app_instance->pre_saved_messages.index; i < app_instance->pre_saved_messages.count - 1; i++) + { + app_instance->pre_saved_messages.messages[i] = app_instance->pre_saved_messages.messages[i + 1]; + } + app_instance->pre_saved_messages.count--; + + // add the item to the submenu + submenu_reset(app_instance->submenu_compose); + + submenu_add_item(app_instance->submenu_compose, "Add Pre-Save", FlipSocialSubmenuComposeIndexAddPreSave, flip_social_callback_submenu_choices, app_instance); + + for (uint32_t i = 0; i < app_instance->pre_saved_messages.count; i++) + { + submenu_add_item(app_instance->submenu_compose, app_instance->pre_saved_messages.messages[i], FlipSocialSubemnuComposeIndexStartIndex + i, flip_social_callback_submenu_choices, app_instance); + } + + // save playlist + save_playlist(&app_instance->pre_saved_messages); + + flip_social_dialog_stop = true; + break; + default: + action = ActionNone; + break; + } + + if (flip_social_dialog_stop) + { + furi_pubsub_unsubscribe(app_instance->input_event_queue, app_instance->input_event); + flip_social_dialog_shown = false; + flip_social_dialog_stop = false; + if (action == ActionNext) + { + canvas_clear(canvas); + canvas_draw_str(canvas, 0, 10, "Sent successfully!"); + canvas_draw_str(canvas, 0, 50, "Loading feed :D"); + canvas_draw_str(canvas, 0, 60, "Please wait..."); + action = ActionNone; + if (flipper_http_process_response_async(flip_social_get_feed, flip_social_parse_json_feed)) + { + view_dispatcher_switch_to_view(app_instance->view_dispatcher, FlipSocialViewLoggedInFeed); + } + else + { + // Set failure FlipSocialFeed object + if (!flip_social_temp_feed()) + { + return; + } + view_dispatcher_switch_to_view(app_instance->view_dispatcher, FlipSocialViewLoggedInFeed); + } + } + else if (action == ActionBack) + { + action = ActionNone; + view_dispatcher_switch_to_view(app_instance->view_dispatcher, FlipSocialViewLoggedInSubmenu); + } + else + { + action = ActionNone; + view_dispatcher_switch_to_view(app_instance->view_dispatcher, FlipSocialViewLoggedInCompose); + } + } +} +// function to draw the dialog canvas +void flip_social_canvas_draw_message(Canvas *canvas, char *user_username, char *user_message, bool is_flipped, bool show_prev, bool show_next, int flip_count) +{ + canvas_set_color(canvas, ColorBlack); + canvas_set_font(canvas, FontPrimary); + canvas_draw_str_aligned(canvas, 64, 5, AlignCenter, AlignCenter, user_username); + canvas_set_font(canvas, FontSecondary); + + char flip_count_str[12]; + if (flip_count == 1) + { + snprintf(flip_count_str, sizeof(flip_count_str), "%d Flip", flip_count); + canvas_draw_str_aligned(canvas, 106, 54, AlignLeft, AlignTop, flip_count_str); + } + else + { + snprintf(flip_count_str, sizeof(flip_count_str), "%d Flips", flip_count); + + if (flip_count < 10) + { + canvas_draw_str_aligned(canvas, 100, 54, AlignLeft, AlignTop, flip_count_str); + } + else if (flip_count < 100) + { + canvas_draw_str_aligned(canvas, 94, 54, AlignLeft, AlignTop, flip_count_str); + } + else + { + canvas_draw_str_aligned(canvas, 88, 54, AlignLeft, AlignTop, flip_count_str); + } + } + + draw_user_message(canvas, user_message, 0, 12); + + // combine and shift icons/labels around if not show_prev or show_next + if (show_prev && show_next && !is_flipped) + { + canvas_draw_icon(canvas, 0, 54, &I_ButtonLeft_4x7); + canvas_draw_str_aligned(canvas, 6, 54, AlignLeft, AlignTop, "Prev"); + canvas_draw_icon(canvas, 30, 54, &I_ButtonRight_4x7); + canvas_draw_str_aligned(canvas, 36, 54, AlignLeft, AlignTop, "Next"); + canvas_draw_icon(canvas, 58, 54, &I_ButtonOK_7x7); + canvas_draw_str_aligned(canvas, 67, 54, AlignLeft, AlignTop, "Flip"); + } + else if (show_prev && !show_next && !is_flipped) + { + canvas_draw_icon(canvas, 0, 54, &I_ButtonLeft_4x7); + canvas_draw_str_aligned(canvas, 6, 54, AlignLeft, AlignTop, "Prev"); + canvas_draw_icon(canvas, 28, 54, &I_ButtonOK_7x7); + canvas_draw_str_aligned(canvas, 37, 54, AlignLeft, AlignTop, "Flip"); + } + else if (!show_prev && show_next && !is_flipped) + { + canvas_draw_icon(canvas, 0, 54, &I_ButtonRight_4x7); + canvas_draw_str_aligned(canvas, 6, 54, AlignLeft, AlignTop, "Next"); + canvas_draw_icon(canvas, 28, 54, &I_ButtonOK_7x7); + canvas_draw_str_aligned(canvas, 37, 54, AlignLeft, AlignTop, "Flip"); + } + else if (show_prev && show_next && is_flipped) + { + canvas_draw_icon(canvas, 0, 54, &I_ButtonLeft_4x7); + canvas_draw_str_aligned(canvas, 6, 54, AlignLeft, AlignTop, "Prev"); + canvas_draw_icon(canvas, 28, 54, &I_ButtonRight_4x7); + canvas_draw_str_aligned(canvas, 34, 54, AlignLeft, AlignTop, "Next"); + canvas_draw_icon(canvas, 54, 54, &I_ButtonOK_7x7); + canvas_draw_str_aligned(canvas, 63, 54, AlignLeft, AlignTop, "UnFlip"); + } + else if (show_prev && !show_next && is_flipped) + { + canvas_draw_icon(canvas, 0, 54, &I_ButtonLeft_4x7); + canvas_draw_str_aligned(canvas, 6, 54, AlignLeft, AlignTop, "Prev"); + canvas_draw_icon(canvas, 28, 54, &I_ButtonOK_7x7); + canvas_draw_str_aligned(canvas, 37, 54, AlignLeft, AlignTop, "UnFlip"); + } + else if (!show_prev && show_next && is_flipped) + { + canvas_draw_icon(canvas, 0, 54, &I_ButtonRight_4x7); + canvas_draw_str_aligned(canvas, 6, 54, AlignLeft, AlignTop, "Next"); + canvas_draw_icon(canvas, 28, 54, &I_ButtonOK_7x7); + canvas_draw_str_aligned(canvas, 37, 54, AlignLeft, AlignTop, "UnFlip"); + } + else if (!show_prev && !show_next && is_flipped) + { + canvas_draw_icon(canvas, 0, 54, &I_ButtonOK_7x7); + canvas_draw_str_aligned(canvas, 9, 54, AlignLeft, AlignTop, "UnFlip"); + } + else + { + canvas_draw_icon(canvas, 0, 54, &I_ButtonOK_7x7); + canvas_draw_str_aligned(canvas, 9, 54, AlignLeft, AlignTop, "Flip"); + } +} +// Callback function to handle the feed dialog +void flip_social_callback_draw_feed(Canvas *canvas, void *model) +{ + UNUSED(model); + if (!canvas) + { + FURI_LOG_E(TAG, "Canvas is NULL"); + return; + } + if (!app_instance) + { + FURI_LOG_E(TAG, "FlipSocialApp is NULL"); + return; + } + + if (!flip_social_dialog_shown) + { + flip_social_dialog_shown = true; + app_instance->input_event_queue = furi_record_open(RECORD_INPUT_EVENTS); + app_instance->input_event = furi_pubsub_subscribe(app_instance->input_event_queue, on_input, NULL); + } + + // handle action + switch (action) + { + case ActionNone: + flip_social_canvas_draw_message(canvas, flip_social_feed->usernames[flip_social_feed->index], flip_social_feed->messages[flip_social_feed->index], flip_social_feed->is_flipped[flip_social_feed->index], flip_social_feed->index > 0, flip_social_feed->index < flip_social_feed->count - 1, flip_social_feed->flips[flip_social_feed->index]); + break; + case ActionNext: + canvas_clear(canvas); + if (flip_social_feed->index < flip_social_feed->count - 1) + { + flip_social_feed->index++; + } + flip_social_canvas_draw_message(canvas, flip_social_feed->usernames[flip_social_feed->index], flip_social_feed->messages[flip_social_feed->index], flip_social_feed->is_flipped[flip_social_feed->index], flip_social_feed->index > 0, flip_social_feed->index < flip_social_feed->count - 1, flip_social_feed->flips[flip_social_feed->index]); + action = ActionNone; + break; + case ActionPrev: + canvas_clear(canvas); + if (flip_social_feed->index > 0) + { + flip_social_feed->index--; + } + flip_social_canvas_draw_message(canvas, flip_social_feed->usernames[flip_social_feed->index], flip_social_feed->messages[flip_social_feed->index], flip_social_feed->is_flipped[flip_social_feed->index], flip_social_feed->index > 0, flip_social_feed->index < flip_social_feed->count - 1, flip_social_feed->flips[flip_social_feed->index]); + action = ActionNone; + break; + case ActionFlip: + canvas_clear(canvas); + // Moved to above the is_flipped check + if (!flip_social_feed->is_flipped[flip_social_feed->index]) + { + // increase the flip count + flip_social_feed->flips[flip_social_feed->index]++; + } + else + { + // decrease the flip count + flip_social_feed->flips[flip_social_feed->index]--; + } + // change the flip status + flip_social_feed->is_flipped[flip_social_feed->index] = !flip_social_feed->is_flipped[flip_social_feed->index]; + // send post request to flip the message + if (app_instance->login_username_logged_in == NULL) + { + FURI_LOG_E(TAG, "Username is NULL"); + return; + } + char payload[256]; + snprintf(payload, sizeof(payload), "{\"username\":\"%s\",\"post_id\":\"%u\"}", app_instance->login_username_logged_in, flip_social_feed->ids[flip_social_feed->index]); + flipper_http_post_request_with_headers("https://www.flipsocial.net/api/feed/flip/", "{\"Content-Type\":\"application/json\"}", payload); + flip_social_canvas_draw_message(canvas, flip_social_feed->usernames[flip_social_feed->index], flip_social_feed->messages[flip_social_feed->index], flip_social_feed->is_flipped[flip_social_feed->index], flip_social_feed->index > 0, flip_social_feed->index < flip_social_feed->count - 1, flip_social_feed->flips[flip_social_feed->index]); + action = ActionNone; + break; + case ActionBack: + canvas_clear(canvas); + flip_social_dialog_stop = true; + flip_social_feed->index = 0; + action = ActionNone; + break; + default: + break; + } + + if (flip_social_dialog_stop) + { + furi_pubsub_unsubscribe(app_instance->input_event_queue, app_instance->input_event); + flip_social_dialog_shown = false; + flip_social_dialog_stop = false; + action = ActionNone; + } +} +/** + * @brief Navigation callback for asynchonously handling the login process. + * @param canvas The canvas to draw on. + * @param model The model - unused + * @return void + */ +void flip_social_callback_draw_login(Canvas *canvas, void *model) +{ + UNUSED(model); + if (!canvas) + { + FURI_LOG_E(TAG, "Canvas is NULL"); + return; + } + + canvas_set_font(canvas, FontSecondary); + + if (!flip_social_board_is_active(canvas)) + { + return; + } + + canvas_draw_str(canvas, 0, 7, "Logging in..."); + + // Perform login request + if (!flip_social_sent_login_request) + { + + if (!app_instance->login_username_logged_out || !app_instance->login_password_logged_out || strlen(app_instance->login_username_logged_out) == 0 || strlen(app_instance->login_password_logged_out) == 0) + { + canvas_clear(canvas); + canvas_draw_str(canvas, 0, 10, "Please enter your credentials."); + canvas_draw_str(canvas, 0, 60, "Press BACK to return."); + return; + } + + flip_social_sent_login_request = true; + + char buffer[256]; + snprintf(buffer, sizeof(buffer), "{\"username\":\"%s\",\"password\":\"%s\"}", app_instance->login_username_logged_out, app_instance->login_password_logged_out); + flip_social_login_success = flipper_http_post_request_with_headers("https://www.flipsocial.net/api/user/login/", "{\"Content-Type\":\"application/json\"}", buffer); + if (flip_social_login_success) + { + fhttp.state = RECEIVING; + return; + } + else + { + fhttp.state = ISSUE; + return; + } + } + // handle response + if (flip_social_sent_login_request && flip_social_login_success) + { + canvas_set_font(canvas, FontSecondary); + canvas_draw_str(canvas, 0, 17, "Request Sent!"); + canvas_draw_str(canvas, 0, 32, "Awaiting reponse..."); + + if (fhttp.state == IDLE && fhttp.last_response != NULL) + { + // read response + if (strstr(fhttp.last_response, "[SUCCESS]") != NULL || strstr(fhttp.last_response, "User found") != NULL) + { + canvas_draw_str(canvas, 0, 42, "Login successful!"); + canvas_draw_str(canvas, 0, 62, "Welcome back!"); + + app_instance->is_logged_in = "true"; + + // set the logged_in_username and change_password_logged_in + if (app_instance->login_username_logged_out) + { + strcpy(app_instance->login_username_logged_in, app_instance->login_username_logged_out); + } + if (app_instance->login_password_logged_out) + { + app_instance->change_password_logged_in = app_instance->login_password_logged_out; + } + + save_settings(app_instance->wifi_ssid_logged_out, app_instance->wifi_password_logged_out, app_instance->login_username_logged_out, app_instance->login_username_logged_in, app_instance->login_password_logged_out, app_instance->change_password_logged_in, app_instance->is_logged_in); + + // send user to the logged in submenu + view_dispatcher_switch_to_view(app_instance->view_dispatcher, FlipSocialViewLoggedInSubmenu); + } + else if (strstr(fhttp.last_response, "User not found") != NULL) + { + canvas_clear(canvas); + canvas_draw_str(canvas, 0, 10, "Account not found..."); + canvas_draw_str(canvas, 0, 60, "Press BACK to return."); + } + else + { + flip_social_handle_error(canvas); + } + } + else if ((fhttp.state == ISSUE || fhttp.state == INACTIVE) && fhttp.last_response != NULL) + { + flip_social_handle_error(canvas); + } + else if (fhttp.state == IDLE && fhttp.last_response == NULL) + { + flip_social_handle_error(canvas); + } + } + else if (flip_social_sent_login_request && !flip_social_login_success) + { + canvas_clear(canvas); + canvas_draw_str(canvas, 0, 10, "Failed sending request."); + canvas_draw_str(canvas, 0, 50, "Update your WiFi settings."); + canvas_draw_str(canvas, 0, 62, "Press BACK to return."); + } +} + +/** + * @brief Navigation callback for asynchonously handling the register process. + * @param canvas The canvas to draw on. + * @param model The model - unused + * @return void + */ +void flip_social_callback_draw_register(Canvas *canvas, void *model) +{ + UNUSED(model); + if (!canvas) + { + FURI_LOG_E(TAG, "Canvas is NULL"); + return; + } + + canvas_set_font(canvas, FontSecondary); + + if (!flip_social_board_is_active(canvas)) + { + return; + } + + canvas_draw_str(canvas, 0, 7, "Registering..."); + + // Perform login request + if (!flip_social_sent_register_request) + { + // check if the username and password are valid + if (!app_instance->register_username_logged_out || !app_instance->register_password_logged_out || strlen(app_instance->register_username_logged_out) == 0 || strlen(app_instance->register_password_logged_out) == 0) + { + canvas_clear(canvas); + canvas_draw_str(canvas, 0, 10, "Please enter your credentials."); + canvas_draw_str(canvas, 0, 60, "Press BACK to return."); + return; + } + + // check if both passwords match + if (strcmp(app_instance->register_password_logged_out, app_instance->register_password_2_logged_out) != 0) + { + canvas_clear(canvas); + canvas_draw_str(canvas, 0, 10, "Passwords do not match."); + canvas_draw_str(canvas, 0, 60, "Press BACK to return."); + return; + } + + char buffer[128]; + snprintf(buffer, sizeof(buffer), "{\"username\":\"%s\",\"password\":\"%s\"}", app_instance->register_username_logged_out, app_instance->register_password_logged_out); + flip_social_register_success = flipper_http_post_request_with_headers("https://www.flipsocial.net/api/user/register/", "{\"Content-Type\":\"application/json\"}", buffer); + + flip_social_sent_register_request = true; + if (flip_social_register_success) + { + // Set the state to RECEIVING to ensure we continue to see the receiving message + fhttp.state = RECEIVING; + } + else + { + fhttp.state = ISSUE; + } + } + // handle response + if (flip_social_sent_register_request && flip_social_register_success) + { + canvas_set_font(canvas, FontSecondary); + canvas_draw_str(canvas, 0, 17, "Request Sent!"); + canvas_draw_str(canvas, 0, 32, "Awaiting reponse..."); + + if (fhttp.state == IDLE) + { + // read response + if (fhttp.last_response != NULL && (strstr(fhttp.last_response, "[SUCCESS]") != NULL || strstr(fhttp.last_response, "User created") != NULL)) + { + canvas_draw_str(canvas, 0, 42, "Registeration successful!"); + canvas_draw_str(canvas, 0, 62, "Welcome to FlipSocial!"); + + // set the login credentials + if (app_instance->login_username_logged_out) + { + app_instance->login_username_logged_out = app_instance->register_username_logged_out; + } + if (app_instance->login_password_logged_out) + { + app_instance->login_password_logged_out = app_instance->register_password_logged_out; + app_instance->change_password_logged_in = app_instance->register_password_logged_out; + } + if (app_instance->login_username_logged_in) + { + app_instance->login_username_logged_in = app_instance->register_username_logged_out; + } + + app_instance->is_logged_in = "true"; + + // save the credentials + save_settings(app_instance->wifi_ssid_logged_out, app_instance->wifi_password_logged_out, app_instance->login_username_logged_out, app_instance->login_username_logged_in, app_instance->login_password_logged_out, app_instance->change_password_logged_in, app_instance->is_logged_in); + + // send user to the logged in submenu + view_dispatcher_switch_to_view(app_instance->view_dispatcher, FlipSocialViewLoggedInSubmenu); + } + else if (strstr(fhttp.last_response, "Username or password not provided") != NULL) + { + canvas_clear(canvas); + canvas_draw_str(canvas, 0, 10, "Please enter your credentials."); + canvas_draw_str(canvas, 0, 60, "Press BACK to return."); + } + else if (strstr(fhttp.last_response, "User already exists") != NULL || strstr(fhttp.last_response, "Multiple users found") != NULL) + { + canvas_draw_str(canvas, 0, 42, "Registration failed..."); + canvas_draw_str(canvas, 0, 52, "Username already exists."); + canvas_draw_str(canvas, 0, 62, "Press BACK to return."); + } + else + { + canvas_draw_str(canvas, 0, 42, "Registration failed..."); + canvas_draw_str(canvas, 0, 52, "Update your credentials."); + canvas_draw_str(canvas, 0, 62, "Press BACK to return."); + } + } + else if (fhttp.state == ISSUE || fhttp.state == INACTIVE) + { + flip_social_handle_error(canvas); + } + } + else if (flip_social_sent_register_request && !flip_social_register_success) + { + canvas_clear(canvas); + canvas_draw_str(canvas, 0, 10, "Failed sending request."); + canvas_draw_str(canvas, 0, 50, "Update your WiFi settings."); + canvas_draw_str(canvas, 0, 62, "Press BACK to return."); + } +} + +// function to draw the dialog canvas +void flip_social_canvas_draw_explore(Canvas *canvas, char *user_username, char *content) +{ + canvas_set_color(canvas, ColorBlack); + canvas_set_font(canvas, FontPrimary); + canvas_draw_str_aligned(canvas, 64, 5, AlignCenter, AlignCenter, user_username); + canvas_set_font(canvas, FontSecondary); + + draw_user_message(canvas, content, 0, 12); + + canvas_set_font(canvas, FontSecondary); + canvas_draw_icon(canvas, 0, 53, &I_ButtonLeft_4x7); + canvas_draw_str_aligned(canvas, 9, 54, AlignLeft, AlignTop, "Remove"); + canvas_draw_icon(canvas, 98, 53, &I_ButtonRight_4x7); + canvas_draw_str_aligned(canvas, 107, 54, AlignLeft, AlignTop, "Add"); + + if (strlen(content) > 0) + { + last_explore_response = content; + } +} + +// Callback function to handle the explore dialog +void flip_social_callback_draw_explore(Canvas *canvas, void *model) +{ + UNUSED(model); + if (!canvas) + { + FURI_LOG_E(TAG, "Canvas is NULL"); + return; + } + if (!app_instance) + { + FURI_LOG_E(TAG, "FlipSocialApp is NULL"); + return; + } + + if (!flip_social_dialog_shown) + { + flip_social_dialog_shown = true; + app_instance->input_event_queue = furi_record_open(RECORD_INPUT_EVENTS); + app_instance->input_event = furi_pubsub_subscribe(app_instance->input_event_queue, on_input, NULL); + } + flip_social_canvas_draw_explore(canvas, flip_social_explore->usernames[flip_social_explore->index], last_explore_response); + + // handle action + switch (action) + { + case ActionNext: + // add friend + char add_payload[128]; + snprintf(add_payload, sizeof(add_payload), "{\"username\":\"%s\",\"friend\":\"%s\"}", app_instance->login_username_logged_in, flip_social_explore->usernames[flip_social_explore->index]); + flipper_http_post_request_with_headers("https://www.flipsocial.net/api/user/add-friend/", "{\"Content-Type\":\"application/json\"}", add_payload); + canvas_clear(canvas); + flip_social_canvas_draw_explore(canvas, flip_social_explore->usernames[flip_social_explore->index], "Added!"); + action = ActionNone; + break; + case ActionPrev: + // remove friend + char remove_payload[128]; + snprintf(remove_payload, sizeof(remove_payload), "{\"username\":\"%s\",\"friend\":\"%s\"}", app_instance->login_username_logged_in, flip_social_explore->usernames[flip_social_explore->index]); + flipper_http_post_request_with_headers("https://www.flipsocial.net/api/user/remove-friend/", "{\"Content-Type\":\"application/json\"}", remove_payload); + canvas_clear(canvas); + flip_social_canvas_draw_explore(canvas, flip_social_explore->usernames[flip_social_explore->index], "Removed!"); + action = ActionNone; + break; + case ActionBack: + canvas_clear(canvas); + flip_social_dialog_stop = true; + last_explore_response = ""; + flip_social_dialog_shown = false; + flip_social_explore->index = 0; + action = ActionNone; + break; + default: + break; + } + + if (flip_social_dialog_stop) + { + furi_pubsub_unsubscribe(app_instance->input_event_queue, app_instance->input_event); + flip_social_dialog_shown = false; + flip_social_dialog_stop = false; + action = ActionNone; + } +} + +// Callback function to handle the friends dialog +void flip_social_callback_draw_friends(Canvas *canvas, void *model) +{ + UNUSED(model); + if (!canvas) + { + FURI_LOG_E(TAG, "Canvas is NULL"); + return; + } + if (!app_instance) + { + FURI_LOG_E(TAG, "FlipSocialApp is NULL"); + return; + } + + if (!flip_social_dialog_shown) + { + flip_social_dialog_shown = true; + app_instance->input_event_queue = furi_record_open(RECORD_INPUT_EVENTS); + app_instance->input_event = furi_pubsub_subscribe(app_instance->input_event_queue, on_input, NULL); + } + flip_social_canvas_draw_explore(canvas, flip_social_friends->usernames[flip_social_friends->index], last_explore_response); + + // handle action + switch (action) + { + case ActionNext: + // add friend + char add_payload[128]; + snprintf(add_payload, sizeof(add_payload), "{\"username\":\"%s\",\"friend\":\"%s\"}", app_instance->login_username_logged_in, flip_social_friends->usernames[flip_social_friends->index]); + if (flipper_http_post_request_with_headers("https://www.flipsocial.net/api/user/add-friend/", "{\"Content-Type\":\"application/json\"}", add_payload)) + { + canvas_clear(canvas); + flip_social_canvas_draw_explore(canvas, flip_social_friends->usernames[flip_social_friends->index], "Added!"); + + // add the friend to the friends list + flip_social_friends->usernames[flip_social_friends->count] = flip_social_friends->usernames[flip_social_friends->index]; + flip_social_friends->count++; + if (!flip_social_update_friends()) + { + FURI_LOG_E(TAG, "Failed to update friends"); + } + } + action = ActionNone; + break; + case ActionPrev: + // remove friend + char remove_payload[128]; + snprintf(remove_payload, sizeof(remove_payload), "{\"username\":\"%s\",\"friend\":\"%s\"}", app_instance->login_username_logged_in, flip_social_friends->usernames[flip_social_friends->index]); + if (flipper_http_post_request_with_headers("https://www.flipsocial.net/api/user/remove-friend/", "{\"Content-Type\":\"application/json\"}", remove_payload)) + { + canvas_clear(canvas); + flip_social_canvas_draw_explore(canvas, flip_social_friends->usernames[flip_social_friends->index], "Removed!"); + + // remove the friend from the friends list + for (int i = flip_social_friends->index; i < flip_social_friends->count - 1; i++) + { + flip_social_friends->usernames[i] = flip_social_friends->usernames[i + 1]; + } + flip_social_friends->count--; + if (!flip_social_update_friends()) + { + FURI_LOG_E(TAG, "Failed to update friends"); + } + } + action = ActionNone; + break; + case ActionBack: + canvas_clear(canvas); + flip_social_dialog_stop = true; + last_explore_response = ""; + flip_social_dialog_shown = false; + flip_social_friends->index = 0; + action = ActionNone; + break; + default: + break; + } + + if (flip_social_dialog_stop) + { + furi_pubsub_unsubscribe(app_instance->input_event_queue, app_instance->input_event); + flip_social_dialog_shown = false; + flip_social_dialog_stop = false; + action = ActionNone; + } +} + +void flip_social_canvas_draw_user_message(Canvas *canvas, char *user_username, char *user_message, bool show_prev, bool show_next) +{ + canvas_set_color(canvas, ColorBlack); + canvas_set_font(canvas, FontPrimary); + canvas_draw_str_aligned(canvas, 64, 5, AlignCenter, AlignCenter, user_username); + canvas_set_font(canvas, FontSecondary); + + draw_user_message(canvas, user_message, 0, 12); + + canvas_set_font(canvas, FontSecondary); + if (show_prev) + { + canvas_draw_icon(canvas, 0, 53, &I_ButtonLeft_4x7); + canvas_draw_str_aligned(canvas, 9, 54, AlignLeft, AlignTop, "Prev"); + } + + canvas_draw_icon(canvas, 47, 53, &I_ButtonOK_7x7); + canvas_draw_str_aligned(canvas, 56, 54, AlignLeft, AlignTop, "Create"); + + if (show_next) + { + canvas_draw_icon(canvas, 98, 53, &I_ButtonRight_4x7); + canvas_draw_str_aligned(canvas, 107, 54, AlignLeft, AlignTop, "Next"); + } +} + +// Callback function to handle the messages dialog +void flip_social_callback_draw_messages(Canvas *canvas, void *model) +{ + UNUSED(model); + if (!canvas) + { + FURI_LOG_E(TAG, "Canvas is NULL"); + return; + } + if (!app_instance) + { + FURI_LOG_E(TAG, "FlipSocialApp is NULL"); + return; + } + + if (!flip_social_dialog_shown) + { + flip_social_dialog_shown = true; + app_instance->input_event_queue = furi_record_open(RECORD_INPUT_EVENTS); + app_instance->input_event = furi_pubsub_subscribe(app_instance->input_event_queue, on_input, NULL); + } + + // handle action + switch (action) + { + case ActionNone: + flip_social_canvas_draw_user_message(canvas, flip_social_messages->usernames[flip_social_messages->index], flip_social_messages->messages[flip_social_messages->index], flip_social_messages->index > 0, flip_social_messages->index < flip_social_messages->count - 1); + action = ActionNone; + break; + case ActionNext: + // view next message (if any) + canvas_clear(canvas); + if (flip_social_messages->index < flip_social_messages->count - 1) + { + flip_social_messages->index++; + } + flip_social_canvas_draw_user_message(canvas, flip_social_messages->usernames[flip_social_messages->index], flip_social_messages->messages[flip_social_messages->index], flip_social_messages->index > 0, flip_social_messages->index < flip_social_messages->count - 1); + action = ActionNone; + break; + case ActionPrev: + // view previous message (if any) + canvas_clear(canvas); + if (flip_social_messages->index > 0) + { + flip_social_messages->index--; + } + flip_social_canvas_draw_user_message(canvas, flip_social_messages->usernames[flip_social_messages->index], flip_social_messages->messages[flip_social_messages->index], flip_social_messages->index > 0, flip_social_messages->index < flip_social_messages->count - 1); + action = ActionNone; + break; + case ActionBack: + // go back to the previous view + flip_social_dialog_stop = true; + action = ActionNone; + break; + case ActionFlip: + // go to the input view + flip_social_dialog_stop = true; + flip_social_send_message = true; + action = ActionNone; + break; + default: + action = ActionNone; + break; + } + + if (flip_social_dialog_stop && flip_social_dialog_shown) + { + furi_pubsub_unsubscribe(app_instance->input_event_queue, app_instance->input_event); + flip_social_dialog_shown = false; + flip_social_dialog_stop = false; + if (flip_social_send_message) + { + FURI_LOG_I(TAG, "Switching to new message input view"); + action = ActionNone; + flip_social_send_message = false; + view_dispatcher_switch_to_view(app_instance->view_dispatcher, FlipSocialViewLoggedInMessagesNewMessageInput); + } + else + { + action = ActionNone; + view_dispatcher_switch_to_view(app_instance->view_dispatcher, FlipSocialViewLoggedInMessagesSubmenu); + } + } +} \ No newline at end of file diff --git a/draw/flip_social_draw.h b/draw/flip_social_draw.h new file mode 100644 index 000000000..3ab37873d --- /dev/null +++ b/draw/flip_social_draw.h @@ -0,0 +1,60 @@ +#ifndef FLIP_SOCIAL_DRAW_H +#define FLIP_SOCIAL_DRAW_H +#include +#include +#include +#include + +typedef enum +{ + ActionNone, + ActionBack, + ActionNext, + ActionPrev, + ActionFlip, +} Action; + +extern Action action; + +bool flip_social_board_is_active(Canvas *canvas); + +void flip_social_handle_error(Canvas *canvas); + +void on_input(const void *event, void *ctx); +// Function to draw the message on the canvas with word wrapping +void draw_user_message(Canvas *canvas, const char *user_message, int x, int y); + +void flip_social_callback_draw_compose(Canvas *canvas, void *model); +// function to draw the dialog canvas +void flip_social_canvas_draw_message(Canvas *canvas, char *user_username, char *user_message, bool is_flipped, bool show_prev, bool show_next, int flip_count); +// Callback function to handle the feed dialog +void flip_social_callback_draw_feed(Canvas *canvas, void *model); +/** + * @brief Navigation callback for asynchonously handling the login process. + * @param canvas The canvas to draw on. + * @param model The model - unused + * @return void + */ +void flip_social_callback_draw_login(Canvas *canvas, void *model); +/** + * @brief Navigation callback for asynchonously handling the register process. + * @param canvas The canvas to draw on. + * @param model The model - unused + * @return void + */ +void flip_social_callback_draw_register(Canvas *canvas, void *model); + +// function to draw the dialog canvas +void flip_social_canvas_draw_explore(Canvas *canvas, char *user_username, char *content); + +// Callback function to handle the explore dialog +void flip_social_callback_draw_explore(Canvas *canvas, void *model); + +// Callback function to handle the friends dialog +void flip_social_callback_draw_friends(Canvas *canvas, void *model); + +void flip_social_canvas_draw_user_message(Canvas *canvas, char *user_username, char *user_message, bool show_prev, bool show_next); +// Callback function to handle the messages dialog +void flip_social_callback_draw_messages(Canvas *canvas, void *model); + +#endif \ No newline at end of file diff --git a/easy_flipper/easy_flipper.c b/easy_flipper/easy_flipper.c new file mode 100644 index 000000000..6e039de87 --- /dev/null +++ b/easy_flipper/easy_flipper.c @@ -0,0 +1,570 @@ +#include + +/** + * @brief Navigation callback for exiting the application + * @param context The context - unused + * @return next view id (VIEW_NONE to exit the app) + */ +uint32_t easy_flipper_callback_exit_app(void *context) +{ + // Exit the application + if (!context) + { + FURI_LOG_E(EASY_TAG, "Context is NULL"); + return VIEW_NONE; + } + UNUSED(context); + return VIEW_NONE; // Return VIEW_NONE to exit the app +} + +/** + * @brief Initialize a buffer + * @param buffer The buffer to initialize + * @param buffer_size The size of the buffer + * @return true if successful, false otherwise + */ +bool easy_flipper_set_buffer(char **buffer, uint32_t buffer_size) +{ + if (!buffer) + { + FURI_LOG_E(EASY_TAG, "Invalid arguments provided to set_buffer"); + return false; + } + *buffer = (char *)malloc(buffer_size); + if (!*buffer) + { + FURI_LOG_E(EASY_TAG, "Failed to allocate buffer"); + return false; + } + *buffer[0] = '\0'; + return true; +} + +/** + * @brief Initialize a View object + * @param view The View object to initialize + * @param view_id The ID/Index of the view + * @param draw_callback The draw callback function (set to NULL if not needed) + * @param input_callback The input callback function (set to NULL if not needed) + * @param previous_callback The previous callback function (can be set to NULL) + * @param view_dispatcher The ViewDispatcher object + * @return true if successful, false otherwise + */ +bool easy_flipper_set_view( + View **view, + int32_t view_id, + void draw_callback(Canvas *, void *), + bool input_callback(InputEvent *, void *), + uint32_t (*previous_callback)(void *), + ViewDispatcher **view_dispatcher, + void *context) +{ + if (!view || !view_dispatcher) + { + FURI_LOG_E(EASY_TAG, "Invalid arguments provided to set_view"); + return false; + } + *view = view_alloc(); + if (!*view) + { + FURI_LOG_E(EASY_TAG, "Failed to allocate View"); + return false; + } + if (draw_callback) + { + view_set_draw_callback(*view, draw_callback); + } + if (input_callback) + { + view_set_input_callback(*view, input_callback); + } + if (context) + { + view_set_context(*view, context); + } + if (previous_callback) + { + view_set_previous_callback(*view, previous_callback); + } + view_dispatcher_add_view(*view_dispatcher, view_id, *view); + return true; +} + +/** + * @brief Initialize a ViewDispatcher object + * @param view_dispatcher The ViewDispatcher object to initialize + * @param gui The GUI object + * @param context The context to pass to the event callback + * @return true if successful, false otherwise + */ +bool easy_flipper_set_view_dispatcher(ViewDispatcher **view_dispatcher, Gui *gui, void *context) +{ + if (!view_dispatcher) + { + FURI_LOG_E(EASY_TAG, "Invalid arguments provided to set_view_dispatcher"); + return false; + } + *view_dispatcher = view_dispatcher_alloc(); + if (!*view_dispatcher) + { + FURI_LOG_E(EASY_TAG, "Failed to allocate ViewDispatcher"); + return false; + } + view_dispatcher_attach_to_gui(*view_dispatcher, gui, ViewDispatcherTypeFullscreen); + if (context) + { + view_dispatcher_set_event_callback_context(*view_dispatcher, context); + } + return true; +} + +/** + * @brief Initialize a Submenu object + * @note This does not set the items in the submenu + * @param submenu The Submenu object to initialize + * @param view_id The ID/Index of the view + * @param title The title/header of the submenu + * @param previous_callback The previous callback function (can be set to NULL) + * @param view_dispatcher The ViewDispatcher object + * @return true if successful, false otherwise + */ +bool easy_flipper_set_submenu( + Submenu **submenu, + int32_t view_id, + char *title, + uint32_t(previous_callback)(void *), + ViewDispatcher **view_dispatcher) +{ + if (!submenu) + { + FURI_LOG_E(EASY_TAG, "Invalid arguments provided to set_submenu"); + return false; + } + *submenu = submenu_alloc(); + if (!*submenu) + { + FURI_LOG_E(EASY_TAG, "Failed to allocate Submenu"); + return false; + } + if (title) + { + submenu_set_header(*submenu, title); + } + if (previous_callback) + { + view_set_previous_callback(submenu_get_view(*submenu), previous_callback); + } + view_dispatcher_add_view(*view_dispatcher, view_id, submenu_get_view(*submenu)); + return true; +} +/** + * @brief Initialize a Menu object + * @note This does not set the items in the menu + * @param menu The Menu object to initialize + * @param view_id The ID/Index of the view + * @param item_callback The item callback function + * @param previous_callback The previous callback function (can be set to NULL) + * @param view_dispatcher The ViewDispatcher object + * @return true if successful, false otherwise + */ +bool easy_flipper_set_menu( + Menu **menu, + int32_t view_id, + uint32_t(previous_callback)(void *), + ViewDispatcher **view_dispatcher) +{ + if (!menu) + { + FURI_LOG_E(EASY_TAG, "Invalid arguments provided to set_menu"); + return false; + } + *menu = menu_alloc(); + if (!*menu) + { + FURI_LOG_E(EASY_TAG, "Failed to allocate Menu"); + return false; + } + if (previous_callback) + { + view_set_previous_callback(menu_get_view(*menu), previous_callback); + } + view_dispatcher_add_view(*view_dispatcher, view_id, menu_get_view(*menu)); + return true; +} + +/** + * @brief Initialize a Widget object + * @param widget The Widget object to initialize + * @param view_id The ID/Index of the view + * @param text The text to display in the widget + * @param previous_callback The previous callback function (can be set to NULL) + * @param view_dispatcher The ViewDispatcher object + * @return true if successful, false otherwise + */ +bool easy_flipper_set_widget( + Widget **widget, + int32_t view_id, + char *text, + uint32_t(previous_callback)(void *), + ViewDispatcher **view_dispatcher) +{ + if (!widget) + { + FURI_LOG_E(EASY_TAG, "Invalid arguments provided to set_widget"); + return false; + } + *widget = widget_alloc(); + if (!*widget) + { + FURI_LOG_E(EASY_TAG, "Failed to allocate Widget"); + return false; + } + if (text) + { + widget_add_text_scroll_element(*widget, 0, 0, 128, 64, text); + } + if (previous_callback) + { + view_set_previous_callback(widget_get_view(*widget), previous_callback); + } + view_dispatcher_add_view(*view_dispatcher, view_id, widget_get_view(*widget)); + return true; +} + +/** + * @brief Initialize a VariableItemList object + * @note This does not set the items in the VariableItemList + * @param variable_item_list The VariableItemList object to initialize + * @param view_id The ID/Index of the view + * @param enter_callback The enter callback function (can be set to NULL) + * @param previous_callback The previous callback function (can be set to NULL) + * @param view_dispatcher The ViewDispatcher object + * @param context The context to pass to the enter callback (usually the app) + * @return true if successful, false otherwise + */ +bool easy_flipper_set_variable_item_list( + VariableItemList **variable_item_list, + int32_t view_id, + void (*enter_callback)(void *, uint32_t), + uint32_t(previous_callback)(void *), + ViewDispatcher **view_dispatcher, + void *context) +{ + if (!variable_item_list) + { + FURI_LOG_E(EASY_TAG, "Invalid arguments provided to set_variable_item_list"); + return false; + } + *variable_item_list = variable_item_list_alloc(); + if (!*variable_item_list) + { + FURI_LOG_E(EASY_TAG, "Failed to allocate VariableItemList"); + return false; + } + if (enter_callback) + { + variable_item_list_set_enter_callback(*variable_item_list, enter_callback, context); + } + if (previous_callback) + { + view_set_previous_callback(variable_item_list_get_view(*variable_item_list), previous_callback); + } + view_dispatcher_add_view(*view_dispatcher, view_id, variable_item_list_get_view(*variable_item_list)); + return true; +} + +/** + * @brief Initialize a TextInput object + * @param text_input The TextInput object to initialize + * @param view_id The ID/Index of the view + * @param previous_callback The previous callback function (can be set to NULL) + * @param view_dispatcher The ViewDispatcher object + * @return true if successful, false otherwise + */ +bool easy_flipper_set_text_input( + TextInput **text_input, + int32_t view_id, + char *header_text, + char *text_input_temp_buffer, + uint32_t text_input_buffer_size, + void (*result_callback)(void *), + uint32_t(previous_callback)(void *), + ViewDispatcher **view_dispatcher, + void *context) +{ + if (!text_input) + { + FURI_LOG_E(EASY_TAG, "Invalid arguments provided to set_text_input"); + return false; + } + *text_input = text_input_alloc(); + if (!*text_input) + { + FURI_LOG_E(EASY_TAG, "Failed to allocate TextInput"); + return false; + } + if (previous_callback) + { + view_set_previous_callback(text_input_get_view(*text_input), previous_callback); + } + if (header_text) + { + text_input_set_header_text(*text_input, header_text); + } + if (text_input_temp_buffer && text_input_buffer_size && result_callback) + { + text_input_set_result_callback(*text_input, result_callback, context, text_input_temp_buffer, text_input_buffer_size, false); + } + view_dispatcher_add_view(*view_dispatcher, view_id, text_input_get_view(*text_input)); + return true; +} + +/** + * @brief Initialize a UART_TextInput object + * @param uart_text_input The UART_TextInput object to initialize + * @param view_id The ID/Index of the view + * @param previous_callback The previous callback function (can be set to NULL) + * @param view_dispatcher The ViewDispatcher object + * @return true if successful, false otherwise + */ +bool easy_flipper_set_uart_text_input( + UART_TextInput **uart_text_input, + int32_t view_id, + char *header_text, + char *uart_text_input_temp_buffer, + uint32_t uart_text_input_buffer_size, + void (*result_callback)(void *), + uint32_t(previous_callback)(void *), + ViewDispatcher **view_dispatcher, + void *context) +{ + if (!uart_text_input) + { + FURI_LOG_E(EASY_TAG, "Invalid arguments provided to set_uart_text_input"); + return false; + } + *uart_text_input = uart_text_input_alloc(); + if (!*uart_text_input) + { + FURI_LOG_E(EASY_TAG, "Failed to allocate UART_TextInput"); + return false; + } + if (previous_callback) + { + view_set_previous_callback(uart_text_input_get_view(*uart_text_input), previous_callback); + } + if (header_text) + { + uart_text_input_set_header_text(*uart_text_input, header_text); + } + if (uart_text_input_temp_buffer && uart_text_input_buffer_size && result_callback) + { + uart_text_input_set_result_callback(*uart_text_input, result_callback, context, uart_text_input_temp_buffer, uart_text_input_buffer_size, false); + } + view_dispatcher_add_view(*view_dispatcher, view_id, uart_text_input_get_view(*uart_text_input)); + return true; +} + +/** + * @brief Initialize a DialogEx object + * @param dialog_ex The DialogEx object to initialize + * @param view_id The ID/Index of the view + * @param header The header of the dialog + * @param header_x The x coordinate of the header + * @param header_y The y coordinate of the header + * @param text The text of the dialog + * @param text_x The x coordinate of the dialog + * @param text_y The y coordinate of the dialog + * @param left_button_text The text of the left button + * @param right_button_text The text of the right button + * @param center_button_text The text of the center button + * @param result_callback The result callback function + * @param previous_callback The previous callback function (can be set to NULL) + * @param view_dispatcher The ViewDispatcher object + * @param context The context to pass to the result callback + * @return true if successful, false otherwise + */ +bool easy_flipper_set_dialog_ex( + DialogEx **dialog_ex, + int32_t view_id, + char *header, + uint16_t header_x, + uint16_t header_y, + char *text, + uint16_t text_x, + uint16_t text_y, + char *left_button_text, + char *right_button_text, + char *center_button_text, + void (*result_callback)(DialogExResult, void *), + uint32_t(previous_callback)(void *), + ViewDispatcher **view_dispatcher, + void *context) +{ + if (!dialog_ex) + { + FURI_LOG_E(EASY_TAG, "Invalid arguments provided to set_dialog_ex"); + return false; + } + *dialog_ex = dialog_ex_alloc(); + if (!*dialog_ex) + { + FURI_LOG_E(EASY_TAG, "Failed to allocate DialogEx"); + return false; + } + if (header) + { + dialog_ex_set_header(*dialog_ex, header, header_x, header_y, AlignLeft, AlignTop); + } + if (text) + { + dialog_ex_set_text(*dialog_ex, text, text_x, text_y, AlignLeft, AlignTop); + } + if (left_button_text) + { + dialog_ex_set_left_button_text(*dialog_ex, left_button_text); + } + if (right_button_text) + { + dialog_ex_set_right_button_text(*dialog_ex, right_button_text); + } + if (center_button_text) + { + dialog_ex_set_center_button_text(*dialog_ex, center_button_text); + } + if (result_callback) + { + dialog_ex_set_result_callback(*dialog_ex, result_callback); + } + if (previous_callback) + { + view_set_previous_callback(dialog_ex_get_view(*dialog_ex), previous_callback); + } + if (context) + { + dialog_ex_set_context(*dialog_ex, context); + } + view_dispatcher_add_view(*view_dispatcher, view_id, dialog_ex_get_view(*dialog_ex)); + return true; +} + +/** + * @brief Initialize a Popup object + * @param popup The Popup object to initialize + * @param view_id The ID/Index of the view + * @param header The header of the dialog + * @param header_x The x coordinate of the header + * @param header_y The y coordinate of the header + * @param text The text of the dialog + * @param text_x The x coordinate of the dialog + * @param text_y The y coordinate of the dialog + * @param result_callback The result callback function + * @param previous_callback The previous callback function (can be set to NULL) + * @param view_dispatcher The ViewDispatcher object + * @param context The context to pass to the result callback + * @return true if successful, false otherwise + */ +bool easy_flipper_set_popup( + Popup **popup, + int32_t view_id, + char *header, + uint16_t header_x, + uint16_t header_y, + char *text, + uint16_t text_x, + uint16_t text_y, + void (*result_callback)(void *), + uint32_t(previous_callback)(void *), + ViewDispatcher **view_dispatcher, + void *context) +{ + if (!popup) + { + FURI_LOG_E(EASY_TAG, "Invalid arguments provided to set_popup"); + return false; + } + *popup = popup_alloc(); + if (!*popup) + { + FURI_LOG_E(EASY_TAG, "Failed to allocate Popup"); + return false; + } + if (header) + { + popup_set_header(*popup, header, header_x, header_y, AlignLeft, AlignTop); + } + if (text) + { + popup_set_text(*popup, text, text_x, text_y, AlignLeft, AlignTop); + } + if (result_callback) + { + popup_set_callback(*popup, result_callback); + } + if (previous_callback) + { + view_set_previous_callback(popup_get_view(*popup), previous_callback); + } + if (context) + { + popup_set_context(*popup, context); + } + view_dispatcher_add_view(*view_dispatcher, view_id, popup_get_view(*popup)); + return true; +} + +/** + * @brief Initialize a Loading object + * @param loading The Loading object to initialize + * @param view_id The ID/Index of the view + * @param previous_callback The previous callback function (can be set to NULL) + * @param view_dispatcher The ViewDispatcher object + * @return true if successful, false otherwise + */ +bool easy_flipper_set_loading( + Loading **loading, + int32_t view_id, + uint32_t(previous_callback)(void *), + ViewDispatcher **view_dispatcher) +{ + if (!loading) + { + FURI_LOG_E(EASY_TAG, "Invalid arguments provided to set_loading"); + return false; + } + *loading = loading_alloc(); + if (!*loading) + { + FURI_LOG_E(EASY_TAG, "Failed to allocate Loading"); + return false; + } + if (previous_callback) + { + view_set_previous_callback(loading_get_view(*loading), previous_callback); + } + view_dispatcher_add_view(*view_dispatcher, view_id, loading_get_view(*loading)); + return true; +} + +/** + * @brief Set a char butter to a FuriString + * @param furi_string The FuriString object + * @param buffer The buffer to copy the string to + * @return true if successful, false otherwise + */ +bool easy_flipper_set_char_to_furi_string(FuriString **furi_string, char *buffer) +{ + if (!furi_string) + { + FURI_LOG_E(EASY_TAG, "Invalid arguments provided to set_buffer_to_furi_string"); + return false; + } + *furi_string = furi_string_alloc(); + if (!furi_string) + { + FURI_LOG_E(EASY_TAG, "Failed to allocate FuriString"); + return false; + } + furi_string_set_str(*furi_string, buffer); + return true; +} \ No newline at end of file diff --git a/easy_flipper/easy_flipper.h b/easy_flipper/easy_flipper.h new file mode 100644 index 000000000..f87d9ae1e --- /dev/null +++ b/easy_flipper/easy_flipper.h @@ -0,0 +1,262 @@ +#ifndef EASY_FLIPPER_H +#define EASY_FLIPPER_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define EASY_TAG "EasyFlipper" + +/** + * @brief Navigation callback for exiting the application + * @param context The context - unused + * @return next view id (VIEW_NONE to exit the app) + */ +uint32_t easy_flipper_callback_exit_app(void *context); +/** + * @brief Initialize a buffer + * @param buffer The buffer to initialize + * @param buffer_size The size of the buffer + * @return true if successful, false otherwise + */ +bool easy_flipper_set_buffer(char **buffer, uint32_t buffer_size); +/** + * @brief Initialize a View object + * @param view The View object to initialize + * @param view_id The ID/Index of the view + * @param draw_callback The draw callback function (set to NULL if not needed) + * @param input_callback The input callback function (set to NULL if not needed) + * @param previous_callback The previous callback function (can be set to NULL) + * @param view_dispatcher The ViewDispatcher object + * @return true if successful, false otherwise + */ +bool easy_flipper_set_view( + View **view, + int32_t view_id, + void draw_callback(Canvas *, void *), + bool input_callback(InputEvent *, void *), + uint32_t (*previous_callback)(void *), + ViewDispatcher **view_dispatcher, + void *context); + +/** + * @brief Initialize a ViewDispatcher object + * @param view_dispatcher The ViewDispatcher object to initialize + * @param gui The GUI object + * @param context The context to pass to the event callback + * @return true if successful, false otherwise + */ +bool easy_flipper_set_view_dispatcher(ViewDispatcher **view_dispatcher, Gui *gui, void *context); + +/** + * @brief Initialize a Submenu object + * @note This does not set the items in the submenu + * @param submenu The Submenu object to initialize + * @param view_id The ID/Index of the view + * @param title The title/header of the submenu + * @param previous_callback The previous callback function (can be set to NULL) + * @param view_dispatcher The ViewDispatcher object + * @return true if successful, false otherwise + */ +bool easy_flipper_set_submenu( + Submenu **submenu, + int32_t view_id, + char *title, + uint32_t(previous_callback)(void *), + ViewDispatcher **view_dispatcher); + +/** + * @brief Initialize a Menu object + * @note This does not set the items in the menu + * @param menu The Menu object to initialize + * @param view_id The ID/Index of the view + * @param item_callback The item callback function + * @param previous_callback The previous callback function (can be set to NULL) + * @param view_dispatcher The ViewDispatcher object + * @return true if successful, false otherwise + */ +bool easy_flipper_set_menu( + Menu **menu, + int32_t view_id, + uint32_t(previous_callback)(void *), + ViewDispatcher **view_dispatcher); + +/** + * @brief Initialize a Widget object + * @param widget The Widget object to initialize + * @param view_id The ID/Index of the view + * @param text The text to display in the widget + * @param previous_callback The previous callback function (can be set to NULL) + * @param view_dispatcher The ViewDispatcher object + * @return true if successful, false otherwise + */ +bool easy_flipper_set_widget( + Widget **widget, + int32_t view_id, + char *text, + uint32_t(previous_callback)(void *), + ViewDispatcher **view_dispatcher); + +/** + * @brief Initialize a VariableItemList object + * @note This does not set the items in the VariableItemList + * @param variable_item_list The VariableItemList object to initialize + * @param view_id The ID/Index of the view + * @param enter_callback The enter callback function (can be set to NULL) + * @param previous_callback The previous callback function (can be set to NULL) + * @param view_dispatcher The ViewDispatcher object + * @param context The context to pass to the enter callback (usually the app) + * @return true if successful, false otherwise + */ +bool easy_flipper_set_variable_item_list( + VariableItemList **variable_item_list, + int32_t view_id, + void (*enter_callback)(void *, uint32_t), + uint32_t(previous_callback)(void *), + ViewDispatcher **view_dispatcher, + void *context); + +/** + * @brief Initialize a TextInput object + * @param text_input The TextInput object to initialize + * @param view_id The ID/Index of the view + * @param previous_callback The previous callback function (can be set to NULL) + * @param view_dispatcher The ViewDispatcher object + * @return true if successful, false otherwise + */ +bool easy_flipper_set_text_input( + TextInput **text_input, + int32_t view_id, + char *header_text, + char *text_input_temp_buffer, + uint32_t text_input_buffer_size, + void (*result_callback)(void *), + uint32_t(previous_callback)(void *), + ViewDispatcher **view_dispatcher, + void *context); + +/** + * @brief Initialize a UART_TextInput object + * @param uart_text_input The UART_TextInput object to initialize + * @param view_id The ID/Index of the view + * @param previous_callback The previous callback function (can be set to NULL) + * @param view_dispatcher The ViewDispatcher object + * @return true if successful, false otherwise + */ +bool easy_flipper_set_uart_text_input( + UART_TextInput **uart_text_input, + int32_t view_id, + char *header_text, + char *uart_text_input_temp_buffer, + uint32_t uart_text_input_buffer_size, + void (*result_callback)(void *), + uint32_t(previous_callback)(void *), + ViewDispatcher **view_dispatcher, + void *context); + +/** + * @brief Initialize a DialogEx object + * @param dialog_ex The DialogEx object to initialize + * @param view_id The ID/Index of the view + * @param header The header of the dialog + * @param header_x The x coordinate of the header + * @param header_y The y coordinate of the header + * @param text The text of the dialog + * @param text_x The x coordinate of the dialog + * @param text_y The y coordinate of the dialog + * @param left_button_text The text of the left button + * @param right_button_text The text of the right button + * @param center_button_text The text of the center button + * @param result_callback The result callback function + * @param previous_callback The previous callback function (can be set to NULL) + * @param view_dispatcher The ViewDispatcher object + * @param context The context to pass to the result callback + * @return true if successful, false otherwise + */ +bool easy_flipper_set_dialog_ex( + DialogEx **dialog_ex, + int32_t view_id, + char *header, + uint16_t header_x, + uint16_t header_y, + char *text, + uint16_t text_x, + uint16_t text_y, + char *left_button_text, + char *right_button_text, + char *center_button_text, + void (*result_callback)(DialogExResult, void *), + uint32_t(previous_callback)(void *), + ViewDispatcher **view_dispatcher, + void *context); + +/** + * @brief Initialize a Popup object + * @param popup The Popup object to initialize + * @param view_id The ID/Index of the view + * @param header The header of the dialog + * @param header_x The x coordinate of the header + * @param header_y The y coordinate of the header + * @param text The text of the dialog + * @param text_x The x coordinate of the dialog + * @param text_y The y coordinate of the dialog + * @param result_callback The result callback function + * @param previous_callback The previous callback function (can be set to NULL) + * @param view_dispatcher The ViewDispatcher object + * @param context The context to pass to the result callback + * @return true if successful, false otherwise + */ +bool easy_flipper_set_popup( + Popup **popup, + int32_t view_id, + char *header, + uint16_t header_x, + uint16_t header_y, + char *text, + uint16_t text_x, + uint16_t text_y, + void (*result_callback)(void *), + uint32_t(previous_callback)(void *), + ViewDispatcher **view_dispatcher, + void *context); + +/** + * @brief Initialize a Loading object + * @param loading The Loading object to initialize + * @param view_id The ID/Index of the view + * @param previous_callback The previous callback function (can be set to NULL) + * @param view_dispatcher The ViewDispatcher object + * @return true if successful, false otherwise + */ +bool easy_flipper_set_loading( + Loading **loading, + int32_t view_id, + uint32_t(previous_callback)(void *), + ViewDispatcher **view_dispatcher); + +/** + * @brief Set a char butter to a FuriString + * @param furi_string The FuriString object + * @param buffer The buffer to copy the string to + * @return true if successful, false otherwise + */ +bool easy_flipper_set_char_to_furi_string(FuriString **furi_string, char *buffer); + +#endif \ No newline at end of file diff --git a/explore/flip_social_explore.c b/explore/flip_social_explore.c new file mode 100644 index 000000000..ad3737058 --- /dev/null +++ b/explore/flip_social_explore.c @@ -0,0 +1,162 @@ +#include "flip_social_explore.h" + +FlipSocialModel *flip_social_explore_alloc() +{ + // Allocate memory for each username only if not already allocated + FlipSocialModel *explore = malloc(sizeof(FlipSocialModel)); + if (explore == NULL) + { + FURI_LOG_E(TAG, "Failed to allocate memory for explore model."); + return NULL; + } + for (size_t i = 0; i < MAX_EXPLORE_USERS; i++) + { + if (explore->usernames[i] == NULL) + { + explore->usernames[i] = malloc(MAX_USER_LENGTH); + if (explore->usernames[i] == NULL) + { + FURI_LOG_E(TAG, "Failed to allocate memory for username %zu", i); + return NULL; // Return false on memory allocation failure + } + } + } + return explore; +} + +void flip_social_free_explore() +{ + if (!flip_social_explore) + { + FURI_LOG_E(TAG, "Explore model is NULL"); + return; + } + for (int i = 0; i < flip_social_explore->count; i++) + { + if (flip_social_explore->usernames[i]) + { + free(flip_social_explore->usernames[i]); + } + } +} + +// for now we're just listing the current users +// as the feed is upgraded, then we can port more to the explore view +bool flip_social_get_explore() +{ + snprintf( + fhttp.file_path, + sizeof(fhttp.file_path), + STORAGE_EXT_PATH_PREFIX "/apps_data/flip_rss/users.txt"); + + fhttp.save_received_data = true; + char *headers = jsmn("Content-Type", "application/json"); + // will return true unless the devboard is not connected + bool success = flipper_http_get_request_with_headers("https://www.flipsocial.net/api/user/users/", headers); + free(headers); + if (!success) + { + FURI_LOG_E(TAG, "Failed to send HTTP request for explore"); + fhttp.state = ISSUE; + return false; + } + fhttp.state = RECEIVING; + return true; +} + +bool flip_social_parse_json_explore() +{ + // load the received data from the saved file + FuriString *user_data = flipper_http_load_from_file(fhttp.file_path); + if (user_data == NULL) + { + FURI_LOG_E(TAG, "Failed to load received data from file."); + return false; + } + char *data_cstr = (char *)furi_string_get_cstr(user_data); + if (data_cstr == NULL) + { + FURI_LOG_E(TAG, "Failed to get C-string from FuriString."); + furi_string_free(user_data); + return false; + } + + // Allocate memory for each username only if not already allocated + flip_social_explore = flip_social_explore_alloc(); + if (flip_social_explore == NULL) + { + FURI_LOG_E(TAG, "Failed to allocate memory for explore usernames."); + furi_string_free(user_data); + free(data_cstr); + return false; + } + + // Remove newlines + char *pos = data_cstr; + while ((pos = strchr(pos, '\n')) != NULL) + { + *pos = ' '; + } + + // Initialize explore count + flip_social_explore->count = 0; + + // Extract the users array from the JSON + char *json_users = get_json_value("users", data_cstr, MAX_TOKENS); + if (json_users == NULL) + { + FURI_LOG_E(TAG, "Failed to parse users array."); + furi_string_free(user_data); + free(data_cstr); + return false; + } + + // Manual tokenization for comma-separated values + char *start = json_users + 1; // Skip the opening bracket + char *end; + while ((end = strchr(start, ',')) != NULL && flip_social_explore->count < MAX_EXPLORE_USERS) + { + *end = '\0'; // Null-terminate the current token + + // Remove quotes + if (*start == '"') + start++; + if (*(end - 1) == '"') + *(end - 1) = '\0'; + + // Copy username to pre-allocated memory + snprintf(flip_social_explore->usernames[flip_social_explore->count], MAX_USER_LENGTH, "%s", start); + flip_social_explore->count++; + start = end + 1; + } + + // Handle the last token + if (*start != '\0' && flip_social_explore->count < MAX_EXPLORE_USERS) + { + if (*start == '"') + start++; + if (*(start + strlen(start) - 1) == ']') + *(start + strlen(start) - 1) = '\0'; + if (*(start + strlen(start) - 1) == '"') + *(start + strlen(start) - 1) = '\0'; + + snprintf(flip_social_explore->usernames[flip_social_explore->count], MAX_USER_LENGTH, "%s", start); + flip_social_explore->count++; + } + + // Add submenu items for the users + submenu_reset(app_instance->submenu_explore); + submenu_set_header(app_instance->submenu_explore, "Explore"); + for (int i = 0; i < flip_social_explore->count; i++) + { + submenu_add_item(app_instance->submenu_explore, flip_social_explore->usernames[i], FlipSocialSubmenuExploreIndexStartIndex + i, flip_social_callback_submenu_choices, app_instance); + } + + // Free the json_users + free(json_users); + free(start); + free(end); + furi_string_free(user_data); + free(data_cstr); + return flip_social_explore->count > 0; +} diff --git a/explore/flip_social_explore.h b/explore/flip_social_explore.h new file mode 100644 index 000000000..aed55b2dd --- /dev/null +++ b/explore/flip_social_explore.h @@ -0,0 +1,9 @@ +#ifndef FLIP_SOCIAL_EXPLORE_H +#define FLIP_SOCIAL_EXPLORE_H +#include "flip_social.h" +#include +FlipSocialModel *flip_social_explore_alloc(); +void flip_social_free_explore(); +bool flip_social_get_explore(); +bool flip_social_parse_json_explore(); +#endif \ No newline at end of file diff --git a/feed/flip_social_feed.c b/feed/flip_social_feed.c new file mode 100644 index 000000000..4b46234bc --- /dev/null +++ b/feed/flip_social_feed.c @@ -0,0 +1,226 @@ +#include "flip_social_feed.h" + +// Set failure FlipSocialFeed object +bool flip_social_temp_feed() +{ + if (flip_social_feed == NULL) + { + flip_social_feed = malloc(sizeof(FlipSocialFeed)); + if (flip_social_feed == NULL) + { + FURI_LOG_E(TAG, "Failed to allocate memory for feed"); + return false; + } + } + for (int i = 0; i < 3; i++) + { + if (flip_social_feed->usernames[i] == NULL) + { + flip_social_feed->usernames[i] = malloc(MAX_USER_LENGTH); + if (flip_social_feed->usernames[i] == NULL) + { + FURI_LOG_E(TAG, "Failed to allocate memory for username %zu", i); + return false; + } + } + if (flip_social_feed->messages[i] == NULL) + { + flip_social_feed->messages[i] = malloc(MAX_MESSAGE_LENGTH); + if (flip_social_feed->messages[i] == NULL) + { + FURI_LOG_E(TAG, "Failed to allocate memory for message %zu", i); + return false; + } + } + } + flip_social_feed->usernames[0] = "JBlanked"; + flip_social_feed->usernames[1] = "FlipperKing"; + flip_social_feed->usernames[2] = "FlipperQueen"; + // + flip_social_feed->messages[0] = "Welcome. This is a temp message. Either the feed didn't load or there was a server error."; + flip_social_feed->messages[1] = "I am the Chosen Flipper."; + flip_social_feed->messages[2] = "No one can flip like me."; + // + flip_social_feed->is_flipped[0] = true; + flip_social_feed->is_flipped[1] = false; + flip_social_feed->is_flipped[2] = true; + // + flip_social_feed->ids[0] = 0; + flip_social_feed->ids[1] = 1; + flip_social_feed->ids[2] = 2; + // + flip_social_feed->flips[0] = 51; + flip_social_feed->flips[1] = 8; + flip_social_feed->flips[2] = 23; + // + flip_social_feed->count = 3; + flip_social_feed->index = 0; + + return true; +} + +// Allocate memory for each feed item if not already allocated +FlipSocialFeed *flip_social_feed_alloc() +{ + // Initialize the feed + FlipSocialFeed *feed = (FlipSocialFeed *)malloc(sizeof(FlipSocialFeed)); + if (!feed) + { + FURI_LOG_E(TAG, "Failed to allocate memory for feed"); + return feed; + } + for (size_t i = 0; i < MAX_FEED_ITEMS; i++) + { + if (feed->usernames[i] == NULL) + { + feed->usernames[i] = malloc(MAX_USER_LENGTH); + if (feed->usernames[i] == NULL) + { + FURI_LOG_E(TAG, "Failed to allocate memory for username %zu", i); + return NULL; + } + } + if (feed->messages[i] == NULL) + { + feed->messages[i] = malloc(MAX_MESSAGE_LENGTH); + if (feed->messages[i] == NULL) + { + FURI_LOG_E(TAG, "Failed to allocate memory for message %zu", i); + return NULL; + } + } + } + return feed; +} + +void flip_social_free_feed() +{ + if (!flip_social_feed) + { + FURI_LOG_E(TAG, "Feed model is NULL"); + return; + } + for (uint32_t i = 0; i < flip_social_feed->count; i++) + { + if (flip_social_feed->usernames[i]) + { + free(flip_social_feed->usernames[i]); + } + } +} + +bool flip_social_get_feed() +{ + // Get the feed from the server + if (app_instance->login_username_logged_out == NULL) + { + FURI_LOG_E(TAG, "Username is NULL"); + return false; + } + char command[128]; + snprintf( + fhttp.file_path, + sizeof(fhttp.file_path), + STORAGE_EXT_PATH_PREFIX "/apps_data/flip_rss/feed.txt"); + + fhttp.save_received_data = true; + char *headers = jsmn("Content-Type", "application/json"); + snprintf(command, 128, "https://www.flipsocial.net/api/feed/40/%s/extended/", app_instance->login_username_logged_out); + bool success = flipper_http_get_request_with_headers(command, headers); + free(headers); + if (!success) + { + FURI_LOG_E(TAG, "Failed to send HTTP request for feed"); + fhttp.state = ISSUE; + return false; + } + fhttp.state = RECEIVING; + return true; +} + +bool flip_social_parse_json_feed() +{ + // load the received data from the saved file + FuriString *feed_data = flipper_http_load_from_file(fhttp.file_path); + if (feed_data == NULL) + { + FURI_LOG_E(TAG, "Failed to load received data from file."); + return false; + } + char *data_cstr = (char *)furi_string_get_cstr(feed_data); + if (data_cstr == NULL) + { + FURI_LOG_E(TAG, "Failed to get C-string from FuriString."); + furi_string_free(feed_data); + return false; + } + + // Allocate memory for each feed item if not already allocated + flip_social_feed = flip_social_feed_alloc(); + if (flip_social_feed == NULL) + { + return false; + } + // Remove newlines + char *pos = data_cstr; + while ((pos = strchr(pos, '\n')) != NULL) + { + *pos = ' '; + } + + // Initialize feed count + flip_social_feed->count = 0; + + // Iterate through the feed array + for (int i = 0; i < MAX_FEED_ITEMS; i++) + { + // Parse each item in the array + char *item = get_json_array_value("feed", i, data_cstr, MAX_TOKENS); + if (item == NULL) + { + break; + } + + // Extract individual fields from the JSON object + char *username = get_json_value("username", item, MAX_TOKENS); + char *message = get_json_value("message", item, MAX_TOKENS); + char *flipped = get_json_value("flipped", item, MAX_TOKENS); + char *flips = get_json_value("flip_count", item, MAX_TOKENS); + char *id = get_json_value("id", item, MAX_TOKENS); + + if (username == NULL || message == NULL || flipped == NULL || id == NULL) + { + FURI_LOG_E(TAG, "Failed to parse item fields."); + free(item); + free(username); + free(message); + free(flipped); + free(flips); + free(id); + continue; + } + + // Safely copy strings with bounds checking + snprintf(flip_social_feed->usernames[i], MAX_USER_LENGTH, "%s", username); + snprintf(flip_social_feed->messages[i], MAX_MESSAGE_LENGTH, "%s", message); + + // Store boolean and integer values + flip_social_feed->is_flipped[i] = strstr(flipped, "true") != NULL; + flip_social_feed->ids[i] = atoi(id); + flip_social_feed->flips[i] = atoi(flips); + + flip_social_feed->count++; + + // Free allocated memory + free(item); + free(username); + free(message); + free(flipped); + free(flips); + free(id); + } + + furi_string_free(feed_data); + free(data_cstr); + return flip_social_feed->count > 0; +} diff --git a/feed/flip_social_feed.h b/feed/flip_social_feed.h new file mode 100644 index 000000000..ec597ed4c --- /dev/null +++ b/feed/flip_social_feed.h @@ -0,0 +1,11 @@ +#ifndef FLIP_SOCIAL_FEED_H +#define FLIP_SOCIAL_FEED_H +#include "flip_social.h" +#include + +bool flip_social_temp_feed(); +FlipSocialFeed *flip_social_feed_alloc(); +void flip_social_free_feed(); +bool flip_social_get_feed(); +bool flip_social_parse_json_feed(); +#endif \ No newline at end of file diff --git a/flip_social.c b/flip_social.c new file mode 100644 index 000000000..74d3c25cc --- /dev/null +++ b/flip_social.c @@ -0,0 +1,297 @@ +#include "flip_social.h" + +FlipSocialFeed *flip_social_feed = NULL; // Store the feed +FlipSocialModel *flip_social_friends = NULL; // Store the friends +FlipSocialModel2 *flip_social_message_users = NULL; // Store the users that have sent messages to the logged in user +FlipSocialModel *flip_social_explore = NULL; // Store the users to explore +FlipSocialMessage *flip_social_messages = NULL; // Store the messages between the logged in user and the selected user + +FlipSocialApp *app_instance = NULL; + +bool flip_social_sent_login_request = false; +bool flip_social_sent_register_request = false; +bool flip_social_login_success = false; +bool flip_social_register_success = false; +bool flip_social_dialog_shown = false; +bool flip_social_dialog_stop = false; +bool flip_social_send_message = false; +char *last_explore_response = NULL; +char *selected_message = NULL; + +/** + * @brief Function to free the resources used by FlipSocialApp. + * @details Cleans up all allocated resources before exiting the application. + * @param app The FlipSocialApp object to free. + * @return void + */ +void flip_social_app_free(FlipSocialApp *app) +{ + if (!app) + { + FURI_LOG_E(TAG, "FlipSocialApp is NULL"); + return; + } + if (!app->view_dispatcher) + { + FURI_LOG_E(TAG, "ViewDispatcher is NULL"); + return; + } + + // Free Submenu(s) + if (app->submenu_logged_out) + { + view_dispatcher_remove_view(app->view_dispatcher, FlipSocialViewLoggedOutSubmenu); + submenu_free(app->submenu_logged_out); + } + if (app->submenu_logged_in) + { + view_dispatcher_remove_view(app->view_dispatcher, FlipSocialViewLoggedInSubmenu); + submenu_free(app->submenu_logged_in); + } + if (app->submenu_compose) + { + view_dispatcher_remove_view(app->view_dispatcher, FlipSocialViewLoggedInCompose); + submenu_free(app->submenu_compose); + } + if (app->submenu_explore) + { + view_dispatcher_remove_view(app->view_dispatcher, FlipSocialViewLoggedInExploreSubmenu); + submenu_free(app->submenu_explore); + } + if (app->submenu_friends) + { + view_dispatcher_remove_view(app->view_dispatcher, FlipSocialViewLoggedInFriendsSubmenu); + submenu_free(app->submenu_friends); + } + if (app->submenu_messages) + { + view_dispatcher_remove_view(app->view_dispatcher, FlipSocialViewLoggedInMessagesSubmenu); + submenu_free(app->submenu_messages); + } + if (app->submenu_messages_user_choices) + { + view_dispatcher_remove_view(app->view_dispatcher, FlipSocialViewLoggedInMessagesUserChoices); + submenu_free(app->submenu_messages_user_choices); + } + + // Free Variable Item List(s) + if (app->variable_item_list_logged_out_wifi_settings) + { + view_dispatcher_remove_view(app->view_dispatcher, FlipSocialViewLoggedOutWifiSettings); + variable_item_list_free(app->variable_item_list_logged_out_wifi_settings); + } + if (app->variable_item_list_logged_out_login) + { + view_dispatcher_remove_view(app->view_dispatcher, FlipSocialViewLoggedOutLogin); + variable_item_list_free(app->variable_item_list_logged_out_login); + } + if (app->variable_item_list_logged_out_register) + { + view_dispatcher_remove_view(app->view_dispatcher, FlipSocialViewLoggedOutRegister); + variable_item_list_free(app->variable_item_list_logged_out_register); + } + if (app->variable_item_list_logged_in_profile) + { + view_dispatcher_remove_view(app->view_dispatcher, FlipSocialViewLoggedInProfile); + variable_item_list_free(app->variable_item_list_logged_in_profile); + } + if (app->variable_item_list_logged_in_settings) + { + view_dispatcher_remove_view(app->view_dispatcher, FlipSocialViewLoggedInSettings); + variable_item_list_free(app->variable_item_list_logged_in_settings); + } + if (app->variable_item_list_logged_in_settings_wifi) + { + view_dispatcher_remove_view(app->view_dispatcher, FlipSocialViewLoggedInSettingsWifi); + variable_item_list_free(app->variable_item_list_logged_in_settings_wifi); + } + + // Free Text Input(s) + if (app->text_input_logged_out_wifi_settings_ssid) + { + view_dispatcher_remove_view(app->view_dispatcher, FlipSocialViewLoggedOutWifiSettingsSSIDInput); + uart_text_input_free(app->text_input_logged_out_wifi_settings_ssid); + } + if (app->text_input_logged_out_wifi_settings_password) + { + view_dispatcher_remove_view(app->view_dispatcher, FlipSocialViewLoggedOutWifiSettingsPasswordInput); + uart_text_input_free(app->text_input_logged_out_wifi_settings_password); + } + if (app->text_input_logged_out_login_username) + { + view_dispatcher_remove_view(app->view_dispatcher, FlipSocialViewLoggedOutLoginUsernameInput); + uart_text_input_free(app->text_input_logged_out_login_username); + } + if (app->text_input_logged_out_login_password) + { + view_dispatcher_remove_view(app->view_dispatcher, FlipSocialViewLoggedOutLoginPasswordInput); + uart_text_input_free(app->text_input_logged_out_login_password); + } + if (app->text_input_logged_out_register_username) + { + view_dispatcher_remove_view(app->view_dispatcher, FlipSocialViewLoggedOutRegisterUsernameInput); + uart_text_input_free(app->text_input_logged_out_register_username); + } + if (app->text_input_logged_out_register_password) + { + view_dispatcher_remove_view(app->view_dispatcher, FlipSocialViewLoggedOutRegisterPasswordInput); + uart_text_input_free(app->text_input_logged_out_register_password); + } + if (app->text_input_logged_out_register_password_2) + { + view_dispatcher_remove_view(app->view_dispatcher, FlipSocialViewLoggedOutRegisterPassword2Input); + uart_text_input_free(app->text_input_logged_out_register_password_2); + } + if (app->text_input_logged_in_change_password) + { + view_dispatcher_remove_view(app->view_dispatcher, FlipSocialViewLoggedInChangePasswordInput); + uart_text_input_free(app->text_input_logged_in_change_password); + } + if (app->text_input_logged_in_compose_pre_save_input) + { + view_dispatcher_remove_view(app->view_dispatcher, FlipSocialViewLoggedInComposeAddPreSaveInput); + uart_text_input_free(app->text_input_logged_in_compose_pre_save_input); + } + if (app->text_input_logged_in_wifi_settings_ssid) + { + view_dispatcher_remove_view(app->view_dispatcher, FlipSocialViewLoggedInWifiSettingsSSIDInput); + uart_text_input_free(app->text_input_logged_in_wifi_settings_ssid); + } + if (app->text_input_logged_in_wifi_settings_password) + { + view_dispatcher_remove_view(app->view_dispatcher, FlipSocialViewLoggedInWifiSettingsPasswordInput); + uart_text_input_free(app->text_input_logged_in_wifi_settings_password); + } + if (app->text_input_logged_in_messages_new_message) + { + view_dispatcher_remove_view(app->view_dispatcher, FlipSocialViewLoggedInMessagesNewMessageInput); + uart_text_input_free(app->text_input_logged_in_messages_new_message); + } + if (app->text_input_logged_in_messages_new_message_user_choices) + { + view_dispatcher_remove_view(app->view_dispatcher, FlipSocialViewLoggedInMessagesNewMessageUserChoicesInput); + uart_text_input_free(app->text_input_logged_in_messages_new_message_user_choices); + } + + // Free Widget(s) + if (app->widget_logged_out_about) + { + view_dispatcher_remove_view(app->view_dispatcher, FlipSocialViewLoggedOutAbout); + widget_free(app->widget_logged_out_about); + } + if (app->widget_logged_in_about) + { + view_dispatcher_remove_view(app->view_dispatcher, FlipSocialViewLoggedInSettingsAbout); + widget_free(app->widget_logged_in_about); + } + + // Free View(s) + if (app->view_process_login) + { + view_dispatcher_remove_view(app->view_dispatcher, FlipSocialViewLoggedOutProcessLogin); + view_free(app->view_process_login); + } + if (app->view_process_register) + { + view_dispatcher_remove_view(app->view_dispatcher, FlipSocialViewLoggedOutProcessRegister); + view_free(app->view_process_register); + } + if (app->view_process_feed) + { + view_dispatcher_remove_view(app->view_dispatcher, FlipSocialViewLoggedInFeed); + view_free(app->view_process_feed); + } + if (app->view_process_compose) + { + view_dispatcher_remove_view(app->view_dispatcher, FlipSocialViewLoggedInProcessCompose); + view_free(app->view_process_compose); + } + if (app->view_process_explore) + { + view_dispatcher_remove_view(app->view_dispatcher, FlipSocialViewLoggedInExploreProccess); + view_free(app->view_process_explore); + } + if (app->view_process_friends) + { + view_dispatcher_remove_view(app->view_dispatcher, FlipSocialViewLoggedInFriendsProcess); + view_free(app->view_process_friends); + } + if (app->view_process_messages) + { + view_dispatcher_remove_view(app->view_dispatcher, FlipSocialViewLoggedInMessagesProcess); + view_free(app->view_process_messages); + } + + if (app->view_dispatcher) + view_dispatcher_free(app->view_dispatcher); + + // Free the app structure members + if (app->wifi_ssid_logged_out) + free(app->wifi_ssid_logged_out); + if (app->wifi_ssid_logged_out_temp_buffer) + free(app->wifi_ssid_logged_out_temp_buffer); + if (app->wifi_password_logged_out) + free(app->wifi_password_logged_out); + if (app->wifi_password_logged_out_temp_buffer) + free(app->wifi_password_logged_out_temp_buffer); + if (app->login_username_logged_out) + free(app->login_username_logged_out); + if (app->login_username_logged_out_temp_buffer) + free(app->login_username_logged_out_temp_buffer); + if (app->login_password_logged_out) + free(app->login_password_logged_out); + if (app->login_password_logged_out_temp_buffer) + free(app->login_password_logged_out_temp_buffer); + if (app->register_username_logged_out) + free(app->register_username_logged_out); + if (app->register_username_logged_out_temp_buffer) + free(app->register_username_logged_out_temp_buffer); + if (app->register_password_logged_out) + free(app->register_password_logged_out); + if (app->register_password_logged_out_temp_buffer) + free(app->register_password_logged_out_temp_buffer); + if (app->register_password_2_logged_out) + free(app->register_password_2_logged_out); + if (app->register_password_2_logged_out_temp_buffer) + free(app->register_password_2_logged_out_temp_buffer); + if (app->change_password_logged_in) + free(app->change_password_logged_in); + if (app->change_password_logged_in_temp_buffer) + free(app->change_password_logged_in_temp_buffer); + if (app->compose_pre_save_logged_in) + free(app->compose_pre_save_logged_in); + if (app->compose_pre_save_logged_in_temp_buffer) + free(app->compose_pre_save_logged_in_temp_buffer); + if (app->wifi_ssid_logged_in) + free(app->wifi_ssid_logged_in); + if (app->wifi_ssid_logged_in_temp_buffer) + free(app->wifi_ssid_logged_in_temp_buffer); + if (app->wifi_password_logged_in) + free(app->wifi_password_logged_in); + if (app->wifi_password_logged_in_temp_buffer) + free(app->wifi_password_logged_in_temp_buffer); + if (app->is_logged_in) + free(app->is_logged_in); + if (app->login_username_logged_in) + free(app->login_username_logged_in); + if (app->login_username_logged_in_temp_buffer) + free(app->login_username_logged_in_temp_buffer); + if (app->messages_new_message_logged_in) + free(app->messages_new_message_logged_in); + if (app->messages_new_message_logged_in_temp_buffer) + free(app->messages_new_message_logged_in_temp_buffer); + if (app->message_user_choice_logged_in) + free(app->message_user_choice_logged_in); + if (app->message_user_choice_logged_in_temp_buffer) + free(app->message_user_choice_logged_in_temp_buffer); + + if (app->input_event && app->input_event_queue) + furi_pubsub_unsubscribe(app->input_event_queue, app->input_event); + + // DeInit UART + flipper_http_deinit(); + + // Free the app structure + if (app_instance) + free(app_instance); +} \ No newline at end of file diff --git a/flip_social.h b/flip_social.h new file mode 100644 index 000000000..c92de6b89 --- /dev/null +++ b/flip_social.h @@ -0,0 +1,297 @@ +#ifndef FLIP_SOCIAL_H +#define FLIP_SOCIAL_H + +#include +#include +#include +#include +#include +#include +#define TAG "FlipSocial" + +#define MAX_PRE_SAVED_MESSAGES 25 // Maximum number of pre-saved messages +#define MAX_MESSAGE_LENGTH 100 // Maximum length of a message in the feed +#define MAX_EXPLORE_USERS 50 // Maximum number of users to explore +#define MAX_USER_LENGTH 32 // Maximum length of a username +#define MAX_FRIENDS 50 // Maximum number of friends +#define MAX_TOKENS 450 // Adjust based on expected JSON tokens +#define MAX_FEED_ITEMS 41 // Maximum number of feed items +#define MAX_LINE_LENGTH 30 +#define MAX_MESSAGE_USERS 20 // Maximum number of users to display in the submenu +#define MAX_MESSAGES 20 // Maximum number of meesages between each user + +#define SETTINGS_PATH STORAGE_EXT_PATH_PREFIX "/apps_data/flip_social/settings.bin" +#define PRE_SAVED_MESSAGES_PATH STORAGE_EXT_PATH_PREFIX "/apps_data/flip_social/pre_saved_messages.txt" + +// Define the submenu items for our Hello World application +typedef enum +{ + FlipSocialSubmenuLoggedOutIndexLogin, // click to go to the login screen + FlipSocialSubmenuLoggedOutIndexRegister, // click to go to the register screen + FlipSocialSubmenuLoggedOutIndexAbout, // click to go to the about screen + FlipSocialSubmenuLoggedOutIndexWifiSettings, // click to go to the wifi settings screen + // + FlipSocialSubmenuLoggedInIndexProfile, // click to go to the profile screen + FlipSocialSubmenuExploreIndex, // click to go to the explore + FlipSocialSubmenuLoggedInIndexFeed, // click to go to the feed screen + FlipSocialSubmenuLoggedInIndexMessages, // click to go to the messages screen + FlipSocialSubmenuLoggedInIndexCompose, // click to go to the compose screen + FlipSocialSubmenuLoggedInIndexSettings, // click to go to the settings screen + FlipSocialSubmenuLoggedInSignOutButton, // click to sign out + // + FlipSocialSubmenuLoggedInIndexMessagesNewMessage, // click to add a new message + // + FlipSocialSubmenuComposeIndexAddPreSave, // click to add a pre-saved message + FlipSocialSubemnuComposeIndexStartIndex = 100, // starting index for the first pre saved message + // + FlipSocialSubmenuExploreIndexStartIndex = 150, // starting index for the users to explore + // + FlipSocialSubmenuLoggedInIndexFriendsStart = 200, // starting index for the friends + // + FlipSocialSubmenuLoggedInIndexMessagesUsersStart = 250, // starting index for the messages + // + FlipSocialSubmenuLoggedInIndexMessagesUserChoicesIndexStart = 300, // click to select a user to message +} FlipSocialSubmenuIndex; + +// Define the ScriptPlaylist structure +typedef struct +{ + char *messages[MAX_PRE_SAVED_MESSAGES]; + size_t count; + size_t index; +} PreSavedPlaylist; + +typedef struct +{ + char *usernames[MAX_FEED_ITEMS]; + char *messages[MAX_FEED_ITEMS]; + bool is_flipped[MAX_FEED_ITEMS]; + int ids[MAX_FEED_ITEMS]; + int flips[MAX_FEED_ITEMS]; + size_t count; + size_t index; +} FlipSocialFeed; + +typedef struct +{ + char *usernames[MAX_EXPLORE_USERS]; + int count; + int index; +} FlipSocialModel; + +typedef struct +{ + char *usernames[MAX_MESSAGE_USERS]; + int count; + int index; +} FlipSocialModel2; + +typedef struct +{ + char *usernames[MAX_MESSAGES]; + char *messages[MAX_MESSAGES]; + int count; + int index; +} FlipSocialMessage; + +// Define views for our Hello World application +typedef enum +{ + FlipSocialViewLoggedOutSubmenu, // The menu if the user is not logged in + FlipSocialViewLoggedOutLogin, // The login screen + FlipSocialViewLoggedOutRegister, // The register screen + FlipSocialViewLoggedOutAbout, // The about screen + FlipSocialViewLoggedOutWifiSettings, // The wifi settings screen + // + FlipSocialViewLoggedOutLoginUsernameInput, // Text input screen for username input on login screen + FlipSocialViewLoggedOutLoginPasswordInput, // Text input screen for password input on login screen + FlipSocialViewLoggedOutRegisterUsernameInput, // Text input screen for username input on register screen + FlipSocialViewLoggedOutRegisterPasswordInput, // Text input screen for password input on register screen + FlipSocialViewLoggedOutRegisterPassword2Input, // Text input screen for password 2 input on register screen + FlipSocialViewLoggedOutWifiSettingsSSIDInput, // Text input screen for SSID input on wifi screen + FlipSocialViewLoggedOutWifiSettingsPasswordInput, // Text input screen for Password input on wifi screen + FlipSocialViewLoggedOutProcessLogin, // The screen displayed after clicking login + FlipSocialViewLoggedOutProcessRegister, // The screen displayed after clicking register + // + FlipSocialViewLoggedInSubmenu, // The menu if the user is logged in + FlipSocialViewLoggedInProfile, // The profile screen + FlipSocialViewLoggedInFeed, // The feed screen + FlipSocialViewLoggedInCompose, // The compose screen + FlipSocialViewLoggedInSettings, // The settings screen + // + FlipSocialViewLoggedInChangePasswordInput, // Text input screen for password input on change password screen + FlipSocialViewLoggedInComposeAddPreSaveInput, // Text input screen for add text input on compose screen + // + FlipSocialViewLoggedInMessagesNewMessageInput, // Text input screen for new message input on messages screen + FlipSocialViewLoggedInMessagesNewMessageUserChoicesInput, // Text input screen for new message input on messages screen + FlipSocialViewLoggedInMessagesUserChoices, // the view after clicking [New Message] - select a user to message, then direct to input view + // + FlipSocialViewLoggedInSettingsAbout, // The about screen + FlipSocialViewLoggedInSettingsWifi, // The wifi settings screen + FlipSocialViewLoggedInWifiSettingsSSIDInput, // Text input screen for SSID input on wifi screen + FlipSocialViewLoggedInWifiSettingsPasswordInput, // Text input screen for Password input on wifi screen + FlipSocialViewLoggedInProcessCompose, // The dialog view to delete or send the clicked pre-saved text + // + FlipSocialViewLoggedInSignOut, // The view after clicking the sign out button + // + FlipSocialViewLoggedInExploreSubmenu, // The view after clicking the explore button + FlipSocialViewLoggedInExploreProccess, // The view after clicking on a user in the explore screen + FlipSocialViewLoggedInFriendsSubmenu, // The view after clicking the friends button on the profile screen + FlipSocialViewLoggedInFriendsProcess, // The view after clicking on a friend in the friends screen + FlipSocialViewLoggedInMessagesSubmenu, // The view after clicking the messages button on the profile screen + FlipSocialViewLoggedInMessagesProcess, // The view after clicking on a user in the messages screen +} FlipSocialView; + +// Define the application structure +typedef struct +{ + ViewDispatcher *view_dispatcher; // Switches between our views + Submenu *submenu_logged_out; // The application submenu (logged out) + Submenu *submenu_logged_in; // The application submenu (logged in) + Submenu *submenu_compose; // The application submenu (compose) + Submenu *submenu_explore; // The application submenu (explore) + Submenu *submenu_friends; // The application submenu (friends) + Submenu *submenu_messages; // The application submenu (messages) + Submenu *submenu_messages_user_choices; // The application submenu (messages user choices) + Widget *widget_logged_out_about; // The about screen (logged out) + Widget *widget_logged_in_about; // The about screen (logged in) + + View *view_process_login; // The screen displayed after clicking login + View *view_process_register; // The screen displayed after clicking register + View *view_process_feed; // Dialog for the feed screen + View *view_process_compose; // Dialog for the compose screen (delete or send) + View *view_process_explore; // Dialog for the explore screen (view user profile - add or delete friend) + View *view_process_friends; // Dialog for the friends screen (view user profile - add or delete friend) + View *view_process_messages; // Dialog for the messages screen (next, previous, send message) + + VariableItemList *variable_item_list_logged_out_wifi_settings; // The wifi settings menu + VariableItemList *variable_item_list_logged_out_login; // The login menu + VariableItemList *variable_item_list_logged_out_register; // The register menu + // + VariableItemList *variable_item_list_logged_in_profile; // The profile menu + VariableItemList *variable_item_list_logged_in_settings; // The settings menu + VariableItemList *variable_item_list_logged_in_settings_wifi; // The wifi settings menu + + UART_TextInput *text_input_logged_out_wifi_settings_ssid; // Text input for ssid input on wifi settings screen + UART_TextInput *text_input_logged_out_wifi_settings_password; // Text input for password input on wifi settings screen + UART_TextInput *text_input_logged_out_login_username; // Text input for username input on login screen + UART_TextInput *text_input_logged_out_login_password; // Text input for password input on login screen + UART_TextInput *text_input_logged_out_register_username; // Text input for username input on register screen + UART_TextInput *text_input_logged_out_register_password; // Text input for password input on register screen + UART_TextInput *text_input_logged_out_register_password_2; // Text input for password 2 input on register screen + // + UART_TextInput *text_input_logged_in_change_password; // Text input for password input on change password screen + UART_TextInput *text_input_logged_in_compose_pre_save_input; // Text input for pre save input on compose screen + UART_TextInput *text_input_logged_in_wifi_settings_ssid; // Text input for ssid input on wifi settings screen + UART_TextInput *text_input_logged_in_wifi_settings_password; // Text input for password input on wifi settings screen + // + UART_TextInput *text_input_logged_in_messages_new_message; // Text input for new message input on messages screen + UART_TextInput *text_input_logged_in_messages_new_message_user_choices; // + + VariableItem *variable_item_logged_out_wifi_settings_ssid; // Reference to the ssid configuration item + VariableItem *variable_item_logged_out_wifi_settings_password; // Reference to the password configuration item + VariableItem *variable_item_logged_out_login_username; // Reference to the username configuration item + VariableItem *variable_item_logged_out_login_password; // Reference to the password configuration item + VariableItem *variable_item_logged_out_login_button; // Reference to the login button configuration item + VariableItem *variable_item_logged_out_register_username; // Reference to the username configuration item + VariableItem *variable_item_logged_out_register_password; // Reference to the password configuration item + VariableItem *variable_item_logged_out_register_password_2; // Reference to the password 2 configuration item + VariableItem *variable_item_logged_out_register_button; // Reference to the register button configuration item + // + VariableItem *variable_item_logged_in_profile_username; // Reference to the username configuration item + VariableItem *variable_item_logged_in_profile_change_password; // Reference to the change password configuration item + VariableItem *variable_item_logged_in_settings_about; // Reference to the about configuration item + VariableItem *variable_item_logged_in_settings_wifi; // Reference to the wifi settings configuration item + VariableItem *variable_item_logged_in_wifi_settings_ssid; // Reference to the ssid configuration item + VariableItem *variable_item_logged_in_wifi_settings_password; // Reference to the password configuration item + // + VariableItem *variable_item_logged_in_profile_friends; // Reference to the friends configuration item + // + FuriPubSub *input_event_queue; + FuriPubSubSubscription *input_event; + + PreSavedPlaylist pre_saved_messages; // Pre-saved messages for the feed screen + + char *is_logged_in; // Store the login status + uint32_t is_logged_in_size; // Size of the login status buffer + + char *login_username_logged_in; // Store the entered login username + char *login_username_logged_in_temp_buffer; // Temporary buffer for login username text input + uint32_t login_username_logged_in_temp_buffer_size; // Size of the login username temporary buffer + + char *wifi_ssid_logged_out; // Store the entered wifi ssid + char *wifi_ssid_logged_out_temp_buffer; // Temporary buffer for wifi ssid text input + uint32_t wifi_ssid_logged_out_temp_buffer_size; // Size of the wifi ssid temporary buffer + + char *wifi_password_logged_out; // Store the entered wifi password + char *wifi_password_logged_out_temp_buffer; // Temporary buffer for wifi_password text input + uint32_t wifi_password_logged_out_temp_buffer_size; // Size of the wifi_password temporary buffer + + char *login_username_logged_out; // Store the entered login username + char *login_username_logged_out_temp_buffer; // Temporary buffer for login username text input + uint32_t login_username_logged_out_temp_buffer_size; // Size of the login username temporary buffer + + char *login_password_logged_out; // Store the entered login password + char *login_password_logged_out_temp_buffer; // Temporary buffer for login password text input + uint32_t login_password_logged_out_temp_buffer_size; // Size of the login password temporary buffer + + char *register_username_logged_out; // Store the entered register username + char *register_username_logged_out_temp_buffer; // Temporary buffer for register username text input + uint32_t register_username_logged_out_temp_buffer_size; // Size of the register username temporary buffer + + char *register_password_logged_out; // Store the entered register password + char *register_password_logged_out_temp_buffer; // Temporary buffer for register password text input + uint32_t register_password_logged_out_temp_buffer_size; // Size of the register password temporary buffer + + char *register_password_2_logged_out; // Store the entered register password 2 + char *register_password_2_logged_out_temp_buffer; // Temporary buffer for register password 2 text input + uint32_t register_password_2_logged_out_temp_buffer_size; // Size of the register password 2 temporary buffer + + // + char *change_password_logged_in; // Store the entered change password + char *change_password_logged_in_temp_buffer; // Temporary buffer for change password text input + uint32_t change_password_logged_in_temp_buffer_size; // Size of the change password temporary buffer + + char *compose_pre_save_logged_in; // Store the entered add text + char *compose_pre_save_logged_in_temp_buffer; // Temporary buffer for add text text input + uint32_t compose_pre_save_logged_in_temp_buffer_size; // Size of the add text temporary buffer + + char *wifi_ssid_logged_in; // Store the entered wifi ssid + char *wifi_ssid_logged_in_temp_buffer; // Temporary buffer for wifi ssid text input + uint32_t wifi_ssid_logged_in_temp_buffer_size; // Size of the wifi ssid temporary buffer + + char *wifi_password_logged_in; // Store the entered wifi password + char *wifi_password_logged_in_temp_buffer; // Temporary buffer for wifi_password text input + uint32_t wifi_password_logged_in_temp_buffer_size; // Size of the wifi_password temporary buffer + + // + char *messages_new_message_logged_in; // Store the entered new message + char *messages_new_message_logged_in_temp_buffer; // Temporary buffer for new message text input + uint32_t messages_new_message_logged_in_temp_buffer_size; // Size of the new message temporary buffer + + char *message_user_choice_logged_in; // Store the entered message to send to the selected user + char *message_user_choice_logged_in_temp_buffer; // Temporary buffer for message to send to the selected user + uint32_t message_user_choice_logged_in_temp_buffer_size; // Size of the message to send to the selected user temporary buffer +} FlipSocialApp; + +void flip_social_app_free(FlipSocialApp *app); + +extern FlipSocialFeed *flip_social_feed; // Store the feed +extern FlipSocialModel *flip_social_friends; // Store the friends +extern FlipSocialModel2 *flip_social_message_users; // Store the users that have sent messages to the logged in user +extern FlipSocialModel *flip_social_explore; // Store the users to explore +extern FlipSocialMessage *flip_social_messages; // Store the messages between the logged in user and the selected user + +extern FlipSocialApp *app_instance; + +extern bool flip_social_sent_login_request; +extern bool flip_social_sent_register_request; +extern bool flip_social_login_success; +extern bool flip_social_register_success; +extern bool flip_social_dialog_shown; +extern bool flip_social_dialog_stop; +extern bool flip_social_send_message; +extern char *last_explore_response; +extern char *selected_message; + +#endif \ No newline at end of file diff --git a/flip_storage/flip_social_storage.c b/flip_storage/flip_social_storage.c new file mode 100644 index 000000000..ca1da586e --- /dev/null +++ b/flip_storage/flip_social_storage.c @@ -0,0 +1,371 @@ +#include "flip_social_storage.h" + +// Function to save the playlist +void save_playlist(const PreSavedPlaylist *playlist) +{ + if (!playlist) + { + FURI_LOG_E(TAG, "Playlist is NULL"); + return; + } + // Create the directory for saving settings + char directory_path[128]; + snprintf(directory_path, sizeof(directory_path), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_social"); + + // Create the directory + Storage *storage = furi_record_open(RECORD_STORAGE); + storage_common_mkdir(storage, directory_path); + + // Open the settings file + File *file = storage_file_alloc(storage); + if (!storage_file_open(file, PRE_SAVED_MESSAGES_PATH, FSAM_WRITE, FSOM_CREATE_ALWAYS)) + { + FURI_LOG_E(TAG, "Failed to open settings file for writing: %s", PRE_SAVED_MESSAGES_PATH); + storage_file_free(file); + furi_record_close(RECORD_STORAGE); + return; + } + + // Write each playlist message on a separate line + for (size_t i = 0; i < playlist->count; ++i) + { + // Add a newline character after each message + if (storage_file_write(file, playlist->messages[i], strlen(playlist->messages[i])) != strlen(playlist->messages[i])) + { + FURI_LOG_E(TAG, "Failed to write playlist message %zu", i); + } + + // Write a newline after each message + if (storage_file_write(file, "\n", 1) != 1) + { + FURI_LOG_E(TAG, "Failed to write newline after message %zu", i); + } + } + + storage_file_close(file); + storage_file_free(file); + furi_record_close(RECORD_STORAGE); +} + +// Function to load the playlist +// Function to load the playlist +bool load_playlist(PreSavedPlaylist *playlist) +{ + // Ensure playlist is not NULL + if (!playlist) + { + FURI_LOG_E(TAG, "Playlist is NULL"); + return false; + } + + // Ensure playlist->messages is not NULL and allocate memory for each message + for (size_t i = 0; i < MAX_PRE_SAVED_MESSAGES; ++i) + { + if (!playlist->messages[i]) // Check if memory is already allocated + { + playlist->messages[i] = (char *)malloc(MAX_MESSAGE_LENGTH * sizeof(char)); + if (!playlist->messages[i]) + { + FURI_LOG_E(TAG, "Failed to allocate memory for message %zu", i); + return false; // Return false on memory allocation failure + } + } + } + + // Open the storage + Storage *storage = furi_record_open(RECORD_STORAGE); + File *file = storage_file_alloc(storage); + + if (!storage_file_open(file, PRE_SAVED_MESSAGES_PATH, FSAM_READ, FSOM_OPEN_EXISTING)) + { + FURI_LOG_E(TAG, "Failed to open pre-saved messages file for reading: %s", PRE_SAVED_MESSAGES_PATH); + storage_file_free(file); + furi_record_close(RECORD_STORAGE); + return false; // Return false if the file does not exist + } + + // Initialize the playlist count + playlist->count = 0; + + // Read the file byte by byte to simulate reading lines + char ch; + size_t message_pos = 0; + bool message_started = false; + + while (storage_file_read(file, &ch, 1) == 1) // Read one character at a time + { + message_started = true; + + if (ch == '\n' || message_pos >= (MAX_MESSAGE_LENGTH - 1)) // End of line or message is too long + { + playlist->messages[playlist->count][message_pos] = '\0'; // Null-terminate the message + playlist->count++; // Move to the next message + message_pos = 0; // Reset for the next message + message_started = false; + + // Ensure the playlist count does not exceed the maximum + if (playlist->count >= MAX_PRE_SAVED_MESSAGES) + { + FURI_LOG_W(TAG, "Reached maximum playlist messages"); + break; + } + } + else + { + playlist->messages[playlist->count][message_pos++] = ch; // Add character to current message + } + } + + // Handle the case where the last message does not end with a newline + if (message_started && message_pos > 0) + { + playlist->messages[playlist->count][message_pos] = '\0'; // Null-terminate the last message + playlist->count++; // Increment the count for the last message + + // Ensure the playlist count does not exceed the maximum + if (playlist->count >= MAX_PRE_SAVED_MESSAGES) + { + FURI_LOG_W(TAG, "Reached maximum playlist messages"); + } + } + + // Close the file and storage + storage_file_close(file); + storage_file_free(file); + furi_record_close(RECORD_STORAGE); + + return true; +} +void save_settings( + const char *ssid, + const char *password, + const char *login_username_logged_out, + const char *login_username_logged_in, + const char *login_password_logged_out, + const char *change_password_logged_in, + const char *is_logged_in) +{ + // Create the directory for saving settings + char directory_path[128]; + snprintf(directory_path, sizeof(directory_path), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_social"); + + // Create the directory + Storage *storage = furi_record_open(RECORD_STORAGE); + storage_common_mkdir(storage, directory_path); + + // Open the settings file + File *file = storage_file_alloc(storage); + if (!storage_file_open(file, SETTINGS_PATH, FSAM_WRITE, FSOM_CREATE_ALWAYS)) + { + FURI_LOG_E(TAG, "Failed to open settings file for writing: %s", SETTINGS_PATH); + storage_file_free(file); + furi_record_close(RECORD_STORAGE); + return; + } + + // Save the ssid length and data + size_t ssid_length = strlen(ssid) + 1; // Include null terminator + if (storage_file_write(file, &ssid_length, sizeof(size_t)) != sizeof(size_t) || + storage_file_write(file, ssid, ssid_length) != ssid_length) + { + FURI_LOG_E(TAG, "Failed to write SSID"); + } + + // Save the password length and data + size_t password_length = strlen(password) + 1; // Include null terminator + if (storage_file_write(file, &password_length, sizeof(size_t)) != sizeof(size_t) || + storage_file_write(file, password, password_length) != password_length) + { + FURI_LOG_E(TAG, "Failed to write password"); + } + + // Save the login_username_logged_out length and data + size_t username_out_length = strlen(login_username_logged_out) + 1; // Include null terminator + if (storage_file_write(file, &username_out_length, sizeof(size_t)) != sizeof(size_t) || + storage_file_write(file, login_username_logged_out, username_out_length) != username_out_length) + { + FURI_LOG_E(TAG, "Failed to write login_username_logged_out"); + } + + // Save the login_username_logged_in length and data + size_t username_in_length = strlen(login_username_logged_in) + 1; // Include null terminator + if (storage_file_write(file, &username_in_length, sizeof(size_t)) != sizeof(size_t) || + storage_file_write(file, login_username_logged_in, username_in_length) != username_in_length) + { + FURI_LOG_E(TAG, "Failed to write login_username_logged_in"); + } + + // Save the login_password_logged_out length and data + size_t password_out_length = strlen(login_password_logged_out) + 1; // Include null terminator + if (storage_file_write(file, &password_out_length, sizeof(size_t)) != sizeof(size_t) || + storage_file_write(file, login_password_logged_out, password_out_length) != password_out_length) + { + FURI_LOG_E(TAG, "Failed to write login_password_logged_out"); + } + + // Save the change_password_logged_in length and data + size_t change_password_length = strlen(change_password_logged_in) + 1; // Include null terminator + if (storage_file_write(file, &change_password_length, sizeof(size_t)) != sizeof(size_t) || + storage_file_write(file, change_password_logged_in, change_password_length) != change_password_length) + { + FURI_LOG_E(TAG, "Failed to write change_password_logged_in"); + } + + // Save the is_logged_in length and data + size_t is_logged_in_length = strlen(is_logged_in) + 1; // Include null terminator + if (storage_file_write(file, &is_logged_in_length, sizeof(size_t)) != sizeof(size_t) || + storage_file_write(file, is_logged_in, is_logged_in_length) != is_logged_in_length) + { + FURI_LOG_E(TAG, "Failed to write is_logged_in"); + } + + storage_file_close(file); + storage_file_free(file); + furi_record_close(RECORD_STORAGE); +} + +bool load_settings( + char *ssid, + size_t ssid_size, + char *password, + size_t password_size, + char *login_username_logged_out, + size_t username_out_size, + char *login_username_logged_in, + size_t username_in_size, + char *login_password_logged_out, + size_t password_out_size, + char *change_password_logged_in, + size_t change_password_size, + char *is_logged_in, + size_t is_logged_in_size) +{ + Storage *storage = furi_record_open(RECORD_STORAGE); + File *file = storage_file_alloc(storage); + + if (!storage_file_open(file, SETTINGS_PATH, FSAM_READ, FSOM_OPEN_EXISTING)) + { + FURI_LOG_E(TAG, "Failed to open settings file for reading: %s", SETTINGS_PATH); + storage_file_free(file); + furi_record_close(RECORD_STORAGE); + return false; // Return false if the file does not exist + } + + // Load the ssid + size_t ssid_length; + if (storage_file_read(file, &ssid_length, sizeof(size_t)) != sizeof(size_t) || ssid_length > ssid_size || + storage_file_read(file, ssid, ssid_length) != ssid_length) + { + FURI_LOG_E(TAG, "Failed to read SSID"); + storage_file_close(file); + storage_file_free(file); + furi_record_close(RECORD_STORAGE); + return false; + } + else + { + ssid[ssid_length - 1] = '\0'; // Ensure null-termination + } + + // Load the password + size_t password_length; + if (storage_file_read(file, &password_length, sizeof(size_t)) != sizeof(size_t) || password_length > password_size || + storage_file_read(file, password, password_length) != password_length) + { + FURI_LOG_E(TAG, "Failed to read password"); + storage_file_close(file); + storage_file_free(file); + furi_record_close(RECORD_STORAGE); + return false; + } + else + { + password[password_length - 1] = '\0'; // Ensure null-termination + } + + // Load the login_username_logged_out + size_t username_out_length; + if (storage_file_read(file, &username_out_length, sizeof(size_t)) != sizeof(size_t) || username_out_length > username_out_size || + storage_file_read(file, login_username_logged_out, username_out_length) != username_out_length) + { + FURI_LOG_E(TAG, "Failed to read login_username_logged_out"); + // storage_file_close(file); + // storage_file_free(file); + // furi_record_close(RECORD_STORAGE); + // return false; + } + else + { + login_username_logged_out[username_out_length - 1] = '\0'; // Ensure null-termination + } + + // Load the login_username_logged_in + size_t username_in_length; + if (storage_file_read(file, &username_in_length, sizeof(size_t)) != sizeof(size_t) || username_in_length > username_in_size || + storage_file_read(file, login_username_logged_in, username_in_length) != username_in_length) + { + FURI_LOG_E(TAG, "Failed to read login_username_logged_in"); + // storage_file_close(file); + // storage_file_free(file); + // furi_record_close(RECORD_STORAGE); + // return false; + } + else + { + login_username_logged_in[username_in_length - 1] = '\0'; // Ensure null-termination + } + + // Load the login_password_logged_out + size_t password_out_length; + if (storage_file_read(file, &password_out_length, sizeof(size_t)) != sizeof(size_t) || password_out_length > password_out_size || + storage_file_read(file, login_password_logged_out, password_out_length) != password_out_length) + { + FURI_LOG_E(TAG, "Failed to read login_password_logged_out"); + // storage_file_close(file); + // storage_file_free(file); + // furi_record_close(RECORD_STORAGE); + // return false; + } + else + { + login_password_logged_out[password_out_length - 1] = '\0'; // Ensure null-termination + } + + // Load the change_password_logged_in + size_t change_password_length; + if (storage_file_read(file, &change_password_length, sizeof(size_t)) != sizeof(size_t) || change_password_length > change_password_size || + storage_file_read(file, change_password_logged_in, change_password_length) != change_password_length) + { + FURI_LOG_E(TAG, "Failed to read change_password_logged_in"); + // storage_file_close(file); + // storage_file_free(file); + // furi_record_close(RECORD_STORAGE); + // return false; + } + else + { + change_password_logged_in[change_password_length - 1] = '\0'; // Ensure null-termination + } + + // Load the is_logged_in + size_t is_logged_in_length; + if (storage_file_read(file, &is_logged_in_length, sizeof(size_t)) != sizeof(size_t) || is_logged_in_length > is_logged_in_size || + storage_file_read(file, is_logged_in, is_logged_in_length) != is_logged_in_length) + { + FURI_LOG_E(TAG, "Failed to read is_logged_in"); + // storage_file_close(file); + // storage_file_free(file); + // furi_record_close(RECORD_STORAGE); + // return false; + } + else + { + is_logged_in[is_logged_in_length - 1] = '\0'; // Ensure null-termination + } + + storage_file_close(file); + storage_file_free(file); + furi_record_close(RECORD_STORAGE); + + return true; +} diff --git a/flip_storage/flip_social_storage.h b/flip_storage/flip_social_storage.h new file mode 100644 index 000000000..9f59a2889 --- /dev/null +++ b/flip_storage/flip_social_storage.h @@ -0,0 +1,37 @@ +#ifndef FLIP_SOCIAL_STORAGE_H +#define FLIP_SOCIAL_STORAGE_H + +#include "flip_social.h" + +// Function to save the playlist +void save_playlist(const PreSavedPlaylist *playlist); + +// Function to load the playlist +bool load_playlist(PreSavedPlaylist *playlist); + +void save_settings( + const char *ssid, + const char *password, + const char *login_username_logged_out, + const char *login_username_logged_in, + const char *login_password_logged_out, + const char *change_password_logged_in, + const char *is_logged_in); + +bool load_settings( + char *ssid, + size_t ssid_size, + char *password, + size_t password_size, + char *login_username_logged_out, + size_t username_out_size, + char *login_username_logged_in, + size_t username_in_size, + char *login_password_logged_out, + size_t password_out_size, + char *change_password_logged_in, + size_t change_password_size, + char *is_logged_in, + size_t is_logged_in_size); + +#endif \ No newline at end of file diff --git a/flipper_http/flipper_http.c b/flipper_http/flipper_http.c new file mode 100644 index 000000000..7d3eca302 --- /dev/null +++ b/flipper_http/flipper_http.c @@ -0,0 +1,1425 @@ +#include +FlipperHTTP fhttp; +char rx_line_buffer[RX_LINE_BUFFER_SIZE]; +uint8_t file_buffer[FILE_BUFFER_SIZE]; +// Function to append received data to file +// make sure to initialize the file path before calling this function +bool flipper_http_append_to_file( + const void *data, + size_t data_size, + bool start_new_file, + char *file_path) +{ + Storage *storage = furi_record_open(RECORD_STORAGE); + File *file = storage_file_alloc(storage); + + if (start_new_file) + { + // Open the file in write mode + if (!storage_file_open(file, file_path, FSAM_WRITE, FSOM_CREATE_ALWAYS)) + { + FURI_LOG_E(HTTP_TAG, "Failed to open file for writing: %s", file_path); + storage_file_free(file); + furi_record_close(RECORD_STORAGE); + return false; + } + } + else + { + // Open the file in append mode + if (!storage_file_open(file, file_path, FSAM_WRITE, FSOM_OPEN_APPEND)) + { + FURI_LOG_E(HTTP_TAG, "Failed to open file for appending: %s", file_path); + storage_file_free(file); + furi_record_close(RECORD_STORAGE); + return false; + } + } + + // Write the data to the file + if (storage_file_write(file, data, data_size) != data_size) + { + FURI_LOG_E(HTTP_TAG, "Failed to append data to file"); + storage_file_close(file); + storage_file_free(file); + furi_record_close(RECORD_STORAGE); + return false; + } + + storage_file_close(file); + storage_file_free(file); + furi_record_close(RECORD_STORAGE); + return true; +} + +FuriString *flipper_http_load_from_file(char *file_path) +{ + // Open the storage record + Storage *storage = furi_record_open(RECORD_STORAGE); + if (!storage) + { + FURI_LOG_E(HTTP_TAG, "Failed to open storage record"); + return NULL; + } + + // Allocate a file handle + File *file = storage_file_alloc(storage); + if (!file) + { + FURI_LOG_E(HTTP_TAG, "Failed to allocate storage file"); + furi_record_close(RECORD_STORAGE); + return NULL; + } + + // Open the file for reading + if (!storage_file_open(file, file_path, FSAM_READ, FSOM_OPEN_EXISTING)) + { + storage_file_free(file); + furi_record_close(RECORD_STORAGE); + return NULL; // Return false if the file does not exist + } + + // Allocate a FuriString to hold the received data + FuriString *str_result = furi_string_alloc(); + if (!str_result) + { + FURI_LOG_E(HTTP_TAG, "Failed to allocate FuriString"); + storage_file_close(file); + storage_file_free(file); + furi_record_close(RECORD_STORAGE); + return NULL; + } + + // Reset the FuriString to ensure it's empty before reading + furi_string_reset(str_result); + + // Define a buffer to hold the read data + uint8_t *buffer = (uint8_t *)malloc(MAX_FILE_SHOW); + if (!buffer) + { + FURI_LOG_E(HTTP_TAG, "Failed to allocate buffer"); + furi_string_free(str_result); + storage_file_close(file); + storage_file_free(file); + furi_record_close(RECORD_STORAGE); + return NULL; + } + + // Read data into the buffer + size_t read_count = storage_file_read(file, buffer, MAX_FILE_SHOW); + if (storage_file_get_error(file) != FSE_OK) + { + FURI_LOG_E(HTTP_TAG, "Error reading from file."); + furi_string_free(str_result); + storage_file_close(file); + storage_file_free(file); + furi_record_close(RECORD_STORAGE); + return NULL; + } + + // Append each byte to the FuriString + for (size_t i = 0; i < read_count; i++) + { + furi_string_push_back(str_result, buffer[i]); + } + + // Check if there is more data beyond the maximum size + char extra_byte; + storage_file_read(file, &extra_byte, 1); + + // Clean up + storage_file_close(file); + storage_file_free(file); + furi_record_close(RECORD_STORAGE); + free(buffer); + return str_result; +} + +// UART worker thread +/** + * @brief Worker thread to handle UART data asynchronously. + * @return 0 + * @param context The context to pass to the callback. + * @note This function will handle received data asynchronously via the callback. + */ +// UART worker thread +int32_t flipper_http_worker(void *context) +{ + UNUSED(context); + size_t rx_line_pos = 0; + static size_t file_buffer_len = 0; + + while (1) + { + uint32_t events = furi_thread_flags_wait( + WorkerEvtStop | WorkerEvtRxDone, FuriFlagWaitAny, FuriWaitForever); + if (events & WorkerEvtStop) + break; + if (events & WorkerEvtRxDone) + { + // Continuously read from the stream buffer until it's empty + while (!furi_stream_buffer_is_empty(fhttp.flipper_http_stream)) + { + // Read one byte at a time + char c = 0; + size_t received = furi_stream_buffer_receive(fhttp.flipper_http_stream, &c, 1, 0); + + if (received == 0) + { + // No more data to read + break; + } + + // Append the received byte to the file if saving is enabled + if (fhttp.save_bytes) + { + // Add byte to the buffer + file_buffer[file_buffer_len++] = c; + // Write to file if buffer is full + if (file_buffer_len >= FILE_BUFFER_SIZE) + { + if (!flipper_http_append_to_file( + file_buffer, file_buffer_len, false, fhttp.file_path)) + { + FURI_LOG_E(HTTP_TAG, "Failed to append data to file"); + } + file_buffer_len = 0; + } + } + + // Handle line buffering only if callback is set (text data) + if (fhttp.handle_rx_line_cb) + { + // Handle line buffering + if (c == '\n' || rx_line_pos >= RX_LINE_BUFFER_SIZE - 1) + { + rx_line_buffer[rx_line_pos] = '\0'; // Null-terminate the line + + // Invoke the callback with the complete line + fhttp.handle_rx_line_cb(rx_line_buffer, fhttp.callback_context); + + // Reset the line buffer position + rx_line_pos = 0; + } + else + { + rx_line_buffer[rx_line_pos++] = c; // Add character to the line buffer + } + } + } + } + } + + if (fhttp.save_bytes) + { + // Write the remaining data to the file + if (file_buffer_len > 0) + { + if (!flipper_http_append_to_file(file_buffer, file_buffer_len, false, fhttp.file_path)) + { + FURI_LOG_E(HTTP_TAG, "Failed to append remaining data to file"); + } + } + } + + // remove [POST/END] and/or [GET/END] from the file + if (fhttp.save_bytes) + { + char *end = NULL; + if ((end = strstr(fhttp.file_path, "[POST/END]")) != NULL) + { + *end = '\0'; + } + else if ((end = strstr(fhttp.file_path, "[GET/END]")) != NULL) + { + *end = '\0'; + } + } + + // remove newline from the from the end of the file + if (fhttp.save_bytes) + { + char *end = NULL; + if ((end = strstr(fhttp.file_path, "\n")) != NULL) + { + *end = '\0'; + } + } + + // Reset the file buffer length + file_buffer_len = 0; + + return 0; +} +// Timer callback function +/** + * @brief Callback function for the GET timeout timer. + * @return 0 + * @param context The context to pass to the callback. + * @note This function will be called when the GET request times out. + */ +void get_timeout_timer_callback(void *context) +{ + UNUSED(context); + FURI_LOG_E(HTTP_TAG, "Timeout reached: 2 seconds without receiving the end."); + + // Reset the state + fhttp.started_receiving_get = false; + fhttp.started_receiving_post = false; + fhttp.started_receiving_put = false; + fhttp.started_receiving_delete = false; + + // Update UART state + fhttp.state = ISSUE; +} + +// UART RX Handler Callback (Interrupt Context) +/** + * @brief A private callback function to handle received data asynchronously. + * @return void + * @param handle The UART handle. + * @param event The event type. + * @param context The context to pass to the callback. + * @note This function will handle received data asynchronously via the callback. + */ +void _flipper_http_rx_callback( + FuriHalSerialHandle *handle, + FuriHalSerialRxEvent event, + void *context) +{ + UNUSED(context); + if (event == FuriHalSerialRxEventData) + { + uint8_t data = furi_hal_serial_async_rx(handle); + furi_stream_buffer_send(fhttp.flipper_http_stream, &data, 1, 0); + furi_thread_flags_set(fhttp.rx_thread_id, WorkerEvtRxDone); + } +} + +// UART initialization function +/** + * @brief Initialize UART. + * @return true if the UART was initialized successfully, false otherwise. + * @param callback The callback function to handle received data (ex. flipper_http_rx_callback). + * @param context The context to pass to the callback. + * @note The received data will be handled asynchronously via the callback. + */ +bool flipper_http_init(FlipperHTTP_Callback callback, void *context) +{ + if (!context) + { + FURI_LOG_E(HTTP_TAG, "Invalid context provided to flipper_http_init."); + return false; + } + if (!callback) + { + FURI_LOG_E(HTTP_TAG, "Invalid callback provided to flipper_http_init."); + return false; + } + fhttp.flipper_http_stream = furi_stream_buffer_alloc(RX_BUF_SIZE, 1); + if (!fhttp.flipper_http_stream) + { + FURI_LOG_E(HTTP_TAG, "Failed to allocate UART stream buffer."); + return false; + } + + fhttp.rx_thread = furi_thread_alloc(); + if (!fhttp.rx_thread) + { + FURI_LOG_E(HTTP_TAG, "Failed to allocate UART thread."); + furi_stream_buffer_free(fhttp.flipper_http_stream); + return false; + } + + furi_thread_set_name(fhttp.rx_thread, "FlipperHTTP_RxThread"); + furi_thread_set_stack_size(fhttp.rx_thread, 1024); + furi_thread_set_context(fhttp.rx_thread, &fhttp); + furi_thread_set_callback(fhttp.rx_thread, flipper_http_worker); + + fhttp.handle_rx_line_cb = callback; + fhttp.callback_context = context; + + furi_thread_start(fhttp.rx_thread); + fhttp.rx_thread_id = furi_thread_get_id(fhttp.rx_thread); + + // handle when the UART control is busy to avoid furi_check failed + if (furi_hal_serial_control_is_busy(UART_CH)) + { + FURI_LOG_E(HTTP_TAG, "UART control is busy."); + return false; + } + + fhttp.serial_handle = furi_hal_serial_control_acquire(UART_CH); + if (!fhttp.serial_handle) + { + FURI_LOG_E(HTTP_TAG, "Failed to acquire UART control - handle is NULL"); + // Cleanup resources + furi_thread_free(fhttp.rx_thread); + furi_stream_buffer_free(fhttp.flipper_http_stream); + return false; + } + + // Initialize UART with acquired handle + furi_hal_serial_init(fhttp.serial_handle, BAUDRATE); + + // Enable RX direction + furi_hal_serial_enable_direction(fhttp.serial_handle, FuriHalSerialDirectionRx); + + // Start asynchronous RX with the callback + furi_hal_serial_async_rx_start(fhttp.serial_handle, _flipper_http_rx_callback, &fhttp, false); + + // Wait for the TX to complete to ensure UART is ready + furi_hal_serial_tx_wait_complete(fhttp.serial_handle); + + // Allocate the timer for handling timeouts + fhttp.get_timeout_timer = furi_timer_alloc( + get_timeout_timer_callback, // Callback function + FuriTimerTypeOnce, // One-shot timer + &fhttp // Context passed to callback + ); + + if (!fhttp.get_timeout_timer) + { + FURI_LOG_E(HTTP_TAG, "Failed to allocate HTTP request timeout timer."); + // Cleanup resources + furi_hal_serial_async_rx_stop(fhttp.serial_handle); + furi_hal_serial_disable_direction(fhttp.serial_handle, FuriHalSerialDirectionRx); + furi_hal_serial_control_release(fhttp.serial_handle); + furi_hal_serial_deinit(fhttp.serial_handle); + furi_thread_flags_set(fhttp.rx_thread_id, WorkerEvtStop); + furi_thread_join(fhttp.rx_thread); + furi_thread_free(fhttp.rx_thread); + furi_stream_buffer_free(fhttp.flipper_http_stream); + return false; + } + + // Set the timer thread priority if needed + furi_timer_set_thread_priority(FuriTimerThreadPriorityElevated); + + fhttp.last_response = (char *)malloc(RX_BUF_SIZE); + if (!fhttp.last_response) + { + FURI_LOG_E(HTTP_TAG, "Failed to allocate memory for last_response."); + return false; + } + + // FURI_LOG_I(HTTP_TAG, "UART initialized successfully."); + return true; +} + +// Deinitialize UART +/** + * @brief Deinitialize UART. + * @return void + * @note This function will stop the asynchronous RX, release the serial handle, and free the resources. + */ +void flipper_http_deinit() +{ + if (fhttp.serial_handle == NULL) + { + FURI_LOG_E(HTTP_TAG, "UART handle is NULL. Already deinitialized?"); + return; + } + // Stop asynchronous RX + furi_hal_serial_async_rx_stop(fhttp.serial_handle); + + // Release and deinitialize the serial handle + furi_hal_serial_disable_direction(fhttp.serial_handle, FuriHalSerialDirectionRx); + furi_hal_serial_control_release(fhttp.serial_handle); + furi_hal_serial_deinit(fhttp.serial_handle); + + // Signal the worker thread to stop + furi_thread_flags_set(fhttp.rx_thread_id, WorkerEvtStop); + // Wait for the thread to finish + furi_thread_join(fhttp.rx_thread); + // Free the thread resources + furi_thread_free(fhttp.rx_thread); + + // Free the stream buffer + furi_stream_buffer_free(fhttp.flipper_http_stream); + + // Free the timer + if (fhttp.get_timeout_timer) + { + furi_timer_free(fhttp.get_timeout_timer); + fhttp.get_timeout_timer = NULL; + } + + // Free the last response + if (fhttp.last_response) + { + free(fhttp.last_response); + fhttp.last_response = NULL; + } + + // FURI_LOG_I("FlipperHTTP", "UART deinitialized successfully."); +} + +// Function to send data over UART with newline termination +/** + * @brief Send data over UART with newline termination. + * @return true if the data was sent successfully, false otherwise. + * @param data The data to send over UART. + * @note The data will be sent over UART with a newline character appended. + */ +bool flipper_http_send_data(const char *data) +{ + size_t data_length = strlen(data); + if (data_length == 0) + { + FURI_LOG_E("FlipperHTTP", "Attempted to send empty data."); + return false; + } + + // Create a buffer with data + '\n' + size_t send_length = data_length + 1; // +1 for '\n' + if (send_length > 256) + { // Ensure buffer size is sufficient + FURI_LOG_E("FlipperHTTP", "Data too long to send over FHTTP."); + return false; + } + + char send_buffer[257]; // 256 + 1 for safety + strncpy(send_buffer, data, 256); + send_buffer[data_length] = '\n'; // Append newline + send_buffer[data_length + 1] = '\0'; // Null-terminate + + if (fhttp.state == INACTIVE && ((strstr(send_buffer, "[PING]") == NULL) && + (strstr(send_buffer, "[WIFI/CONNECT]") == NULL))) + { + FURI_LOG_E("FlipperHTTP", "Cannot send data while INACTIVE."); + fhttp.last_response = "Cannot send data while INACTIVE."; + return false; + } + + fhttp.state = SENDING; + furi_hal_serial_tx(fhttp.serial_handle, (const uint8_t *)send_buffer, send_length); + + // Uncomment below line to log the data sent over UART + // FURI_LOG_I("FlipperHTTP", "Sent data over UART: %s", send_buffer); + fhttp.state = IDLE; + return true; +} + +// Function to send a PING request +/** + * @brief Send a PING request to check if the Wifi Dev Board is connected. + * @return true if the request was successful, false otherwise. + * @note The received data will be handled asynchronously via the callback. + * @note This is best used to check if the Wifi Dev Board is connected. + * @note The state will remain INACTIVE until a PONG is received. + */ +bool flipper_http_ping() +{ + const char *command = "[PING]"; + if (!flipper_http_send_data(command)) + { + FURI_LOG_E("FlipperHTTP", "Failed to send PING command."); + return false; + } + // set state as INACTIVE to be made IDLE if PONG is received + fhttp.state = INACTIVE; + // The response will be handled asynchronously via the callback + return true; +} + +// Function to list available commands +/** + * @brief Send a command to list available commands. + * @return true if the request was successful, false otherwise. + * @note The received data will be handled asynchronously via the callback. + */ +bool flipper_http_list_commands() +{ + const char *command = "[LIST]"; + if (!flipper_http_send_data(command)) + { + FURI_LOG_E("FlipperHTTP", "Failed to send LIST command."); + return false; + } + + // The response will be handled asynchronously via the callback + return true; +} + +// Function to turn on the LED +/** + * @brief Allow the LED to display while processing. + * @return true if the request was successful, false otherwise. + * @note The received data will be handled asynchronously via the callback. + */ +bool flipper_http_led_on() +{ + const char *command = "[LED/ON]"; + if (!flipper_http_send_data(command)) + { + FURI_LOG_E("FlipperHTTP", "Failed to send LED ON command."); + return false; + } + + // The response will be handled asynchronously via the callback + return true; +} + +// Function to turn off the LED +/** + * @brief Disable the LED from displaying while processing. + * @return true if the request was successful, false otherwise. + * @note The received data will be handled asynchronously via the callback. + */ +bool flipper_http_led_off() +{ + const char *command = "[LED/OFF]"; + if (!flipper_http_send_data(command)) + { + FURI_LOG_E("FlipperHTTP", "Failed to send LED OFF command."); + return false; + } + + // The response will be handled asynchronously via the callback + return true; +} + +// Function to parse JSON data +/** + * @brief Parse JSON data. + * @return true if the JSON data was parsed successfully, false otherwise. + * @param key The key to parse from the JSON data. + * @param json_data The JSON data to parse. + * @note The received data will be handled asynchronously via the callback. + */ +bool flipper_http_parse_json(const char *key, const char *json_data) +{ + if (!key || !json_data) + { + FURI_LOG_E("FlipperHTTP", "Invalid arguments provided to flipper_http_parse_json."); + return false; + } + + char buffer[256]; + int ret = + snprintf(buffer, sizeof(buffer), "[PARSE]{\"key\":\"%s\",\"json\":%s}", key, json_data); + if (ret < 0 || ret >= (int)sizeof(buffer)) + { + FURI_LOG_E("FlipperHTTP", "Failed to format JSON parse command."); + return false; + } + + if (!flipper_http_send_data(buffer)) + { + FURI_LOG_E("FlipperHTTP", "Failed to send JSON parse command."); + return false; + } + + // The response will be handled asynchronously via the callback + return true; +} + +// Function to parse JSON array data +/** + * @brief Parse JSON array data. + * @return true if the JSON array data was parsed successfully, false otherwise. + * @param key The key to parse from the JSON array data. + * @param index The index to parse from the JSON array data. + * @param json_data The JSON array data to parse. + * @note The received data will be handled asynchronously via the callback. + */ +bool flipper_http_parse_json_array(const char *key, int index, const char *json_data) +{ + if (!key || !json_data) + { + FURI_LOG_E("FlipperHTTP", "Invalid arguments provided to flipper_http_parse_json_array."); + return false; + } + + char buffer[256]; + int ret = snprintf( + buffer, + sizeof(buffer), + "[PARSE/ARRAY]{\"key\":\"%s\",\"index\":%d,\"json\":%s}", + key, + index, + json_data); + if (ret < 0 || ret >= (int)sizeof(buffer)) + { + FURI_LOG_E("FlipperHTTP", "Failed to format JSON parse array command."); + return false; + } + + if (!flipper_http_send_data(buffer)) + { + FURI_LOG_E("FlipperHTTP", "Failed to send JSON parse array command."); + return false; + } + + // The response will be handled asynchronously via the callback + return true; +} + +// Function to scan for WiFi networks +/** + * @brief Send a command to scan for WiFi networks. + * @return true if the request was successful, false otherwise. + * @note The received data will be handled asynchronously via the callback. + */ +bool flipper_http_scan_wifi() +{ + const char *command = "[WIFI/SCAN]"; + if (!flipper_http_send_data(command)) + { + FURI_LOG_E("FlipperHTTP", "Failed to send WiFi scan command."); + return false; + } + + // The response will be handled asynchronously via the callback + return true; +} + +// Function to save WiFi settings (returns true if successful) +/** + * @brief Send a command to save WiFi settings. + * @return true if the request was successful, false otherwise. + * @note The received data will be handled asynchronously via the callback. + */ +bool flipper_http_save_wifi(const char *ssid, const char *password) +{ + if (!ssid || !password) + { + FURI_LOG_E("FlipperHTTP", "Invalid arguments provided to flipper_http_save_wifi."); + return false; + } + char buffer[256]; + int ret = snprintf( + buffer, sizeof(buffer), "[WIFI/SAVE]{\"ssid\":\"%s\",\"password\":\"%s\"}", ssid, password); + if (ret < 0 || ret >= (int)sizeof(buffer)) + { + FURI_LOG_E("FlipperHTTP", "Failed to format WiFi save command."); + return false; + } + + if (!flipper_http_send_data(buffer)) + { + FURI_LOG_E("FlipperHTTP", "Failed to send WiFi save command."); + return false; + } + + // The response will be handled asynchronously via the callback + return true; +} + +// Function to get IP address of WiFi Devboard +/** + * @brief Send a command to get the IP address of the WiFi Devboard + * @return true if the request was successful, false otherwise. + * @note The received data will be handled asynchronously via the callback. + */ +bool flipper_http_ip_address() +{ + const char *command = "[IP/ADDRESS]"; + if (!flipper_http_send_data(command)) + { + FURI_LOG_E("FlipperHTTP", "Failed to send IP address command."); + return false; + } + + // The response will be handled asynchronously via the callback + return true; +} + +// Function to get IP address of the connected WiFi network +/** + * @brief Send a command to get the IP address of the connected WiFi network. + * @return true if the request was successful, false otherwise. + * @note The received data will be handled asynchronously via the callback. + */ +bool flipper_http_ip_wifi() +{ + const char *command = "[WIFI/IP]"; + if (!flipper_http_send_data(command)) + { + FURI_LOG_E("FlipperHTTP", "Failed to send WiFi IP command."); + return false; + } + + // The response will be handled asynchronously via the callback + return true; +} + +// Function to disconnect from WiFi (returns true if successful) +/** + * @brief Send a command to disconnect from WiFi. + * @return true if the request was successful, false otherwise. + * @note The received data will be handled asynchronously via the callback. + */ +bool flipper_http_disconnect_wifi() +{ + const char *command = "[WIFI/DISCONNECT]"; + if (!flipper_http_send_data(command)) + { + FURI_LOG_E("FlipperHTTP", "Failed to send WiFi disconnect command."); + return false; + } + + // The response will be handled asynchronously via the callback + return true; +} + +// Function to connect to WiFi (returns true if successful) +/** + * @brief Send a command to connect to WiFi. + * @return true if the request was successful, false otherwise. + * @note The received data will be handled asynchronously via the callback. + */ +bool flipper_http_connect_wifi() +{ + const char *command = "[WIFI/CONNECT]"; + if (!flipper_http_send_data(command)) + { + FURI_LOG_E("FlipperHTTP", "Failed to send WiFi connect command."); + return false; + } + + // The response will be handled asynchronously via the callback + return true; +} + +// Function to send a GET request +/** + * @brief Send a GET request to the specified URL. + * @return true if the request was successful, false otherwise. + * @param url The URL to send the GET request to. + * @note The received data will be handled asynchronously via the callback. + */ +bool flipper_http_get_request(const char *url) +{ + if (!url) + { + FURI_LOG_E("FlipperHTTP", "Invalid arguments provided to flipper_http_get_request."); + return false; + } + + // Prepare GET request command + char command[256]; + int ret = snprintf(command, sizeof(command), "[GET]%s", url); + if (ret < 0 || ret >= (int)sizeof(command)) + { + FURI_LOG_E("FlipperHTTP", "Failed to format GET request command."); + return false; + } + + // Send GET request via UART + if (!flipper_http_send_data(command)) + { + FURI_LOG_E("FlipperHTTP", "Failed to send GET request command."); + return false; + } + + // The response will be handled asynchronously via the callback + return true; +} +// Function to send a GET request with headers +/** + * @brief Send a GET request to the specified URL. + * @return true if the request was successful, false otherwise. + * @param url The URL to send the GET request to. + * @param headers The headers to send with the GET request. + * @note The received data will be handled asynchronously via the callback. + */ +bool flipper_http_get_request_with_headers(const char *url, const char *headers) +{ + if (!url || !headers) + { + FURI_LOG_E( + "FlipperHTTP", "Invalid arguments provided to flipper_http_get_request_with_headers."); + return false; + } + + // Prepare GET request command with headers + char command[256]; + int ret = snprintf( + command, sizeof(command), "[GET/HTTP]{\"url\":\"%s\",\"headers\":%s}", url, headers); + if (ret < 0 || ret >= (int)sizeof(command)) + { + FURI_LOG_E("FlipperHTTP", "Failed to format GET request command with headers."); + return false; + } + + // Send GET request via UART + if (!flipper_http_send_data(command)) + { + FURI_LOG_E("FlipperHTTP", "Failed to send GET request command with headers."); + return false; + } + + // The response will be handled asynchronously via the callback + return true; +} +// Function to send a GET request with headers and return bytes +/** + * @brief Send a GET request to the specified URL. + * @return true if the request was successful, false otherwise. + * @param url The URL to send the GET request to. + * @param headers The headers to send with the GET request. + * @note The received data will be handled asynchronously via the callback. + */ +bool flipper_http_get_request_bytes(const char *url, const char *headers) +{ + if (!url || !headers) + { + FURI_LOG_E("FlipperHTTP", "Invalid arguments provided to flipper_http_get_request_bytes."); + return false; + } + + // Prepare GET request command with headers + char command[256]; + int ret = snprintf( + command, sizeof(command), "[GET/BYTES]{\"url\":\"%s\",\"headers\":%s}", url, headers); + if (ret < 0 || ret >= (int)sizeof(command)) + { + FURI_LOG_E("FlipperHTTP", "Failed to format GET request command with headers."); + return false; + } + + // Send GET request via UART + if (!flipper_http_send_data(command)) + { + FURI_LOG_E("FlipperHTTP", "Failed to send GET request command with headers."); + return false; + } + + // The response will be handled asynchronously via the callback + return true; +} +// Function to send a POST request with headers +/** + * @brief Send a POST request to the specified URL. + * @return true if the request was successful, false otherwise. + * @param url The URL to send the POST request to. + * @param headers The headers to send with the POST request. + * @param data The data to send with the POST request. + * @note The received data will be handled asynchronously via the callback. + */ +bool flipper_http_post_request_with_headers( + const char *url, + const char *headers, + const char *payload) +{ + if (!url || !headers || !payload) + { + FURI_LOG_E( + "FlipperHTTP", + "Invalid arguments provided to flipper_http_post_request_with_headers."); + return false; + } + + // Prepare POST request command with headers and data + char command[256]; + int ret = snprintf( + command, + sizeof(command), + "[POST/HTTP]{\"url\":\"%s\",\"headers\":%s,\"payload\":%s}", + url, + headers, + payload); + if (ret < 0 || ret >= (int)sizeof(command)) + { + FURI_LOG_E("FlipperHTTP", "Failed to format POST request command with headers and data."); + return false; + } + + // Send POST request via UART + if (!flipper_http_send_data(command)) + { + FURI_LOG_E("FlipperHTTP", "Failed to send POST request command with headers and data."); + return false; + } + + // The response will be handled asynchronously via the callback + return true; +} +// Function to send a POST request with headers and return bytes +/** + * @brief Send a POST request to the specified URL. + * @return true if the request was successful, false otherwise. + * @param url The URL to send the POST request to. + * @param headers The headers to send with the POST request. + * @param payload The data to send with the POST request. + * @note The received data will be handled asynchronously via the callback. + */ +bool flipper_http_post_request_bytes(const char *url, const char *headers, const char *payload) +{ + if (!url || !headers || !payload) + { + FURI_LOG_E( + "FlipperHTTP", "Invalid arguments provided to flipper_http_post_request_bytes."); + return false; + } + + // Prepare POST request command with headers and data + char command[256]; + int ret = snprintf( + command, + sizeof(command), + "[POST/BYTES]{\"url\":\"%s\",\"headers\":%s,\"payload\":%s}", + url, + headers, + payload); + if (ret < 0 || ret >= (int)sizeof(command)) + { + FURI_LOG_E("FlipperHTTP", "Failed to format POST request command with headers and data."); + return false; + } + + // Send POST request via UART + if (!flipper_http_send_data(command)) + { + FURI_LOG_E("FlipperHTTP", "Failed to send POST request command with headers and data."); + return false; + } + + // The response will be handled asynchronously via the callback + return true; +} +// Function to send a PUT request with headers +/** + * @brief Send a PUT request to the specified URL. + * @return true if the request was successful, false otherwise. + * @param url The URL to send the PUT request to. + * @param headers The headers to send with the PUT request. + * @param data The data to send with the PUT request. + * @note The received data will be handled asynchronously via the callback. + */ +bool flipper_http_put_request_with_headers( + const char *url, + const char *headers, + const char *payload) +{ + if (!url || !headers || !payload) + { + FURI_LOG_E( + "FlipperHTTP", "Invalid arguments provided to flipper_http_put_request_with_headers."); + return false; + } + + // Prepare PUT request command with headers and data + char command[256]; + int ret = snprintf( + command, + sizeof(command), + "[PUT/HTTP]{\"url\":\"%s\",\"headers\":%s,\"payload\":%s}", + url, + headers, + payload); + if (ret < 0 || ret >= (int)sizeof(command)) + { + FURI_LOG_E("FlipperHTTP", "Failed to format PUT request command with headers and data."); + return false; + } + + // Send PUT request via UART + if (!flipper_http_send_data(command)) + { + FURI_LOG_E("FlipperHTTP", "Failed to send PUT request command with headers and data."); + return false; + } + + // The response will be handled asynchronously via the callback + return true; +} +// Function to send a DELETE request with headers +/** + * @brief Send a DELETE request to the specified URL. + * @return true if the request was successful, false otherwise. + * @param url The URL to send the DELETE request to. + * @param headers The headers to send with the DELETE request. + * @param data The data to send with the DELETE request. + * @note The received data will be handled asynchronously via the callback. + */ +bool flipper_http_delete_request_with_headers( + const char *url, + const char *headers, + const char *payload) +{ + if (!url || !headers || !payload) + { + FURI_LOG_E( + "FlipperHTTP", + "Invalid arguments provided to flipper_http_delete_request_with_headers."); + return false; + } + + // Prepare DELETE request command with headers and data + char command[256]; + int ret = snprintf( + command, + sizeof(command), + "[DELETE/HTTP]{\"url\":\"%s\",\"headers\":%s,\"payload\":%s}", + url, + headers, + payload); + if (ret < 0 || ret >= (int)sizeof(command)) + { + FURI_LOG_E( + "FlipperHTTP", "Failed to format DELETE request command with headers and data."); + return false; + } + + // Send DELETE request via UART + if (!flipper_http_send_data(command)) + { + FURI_LOG_E("FlipperHTTP", "Failed to send DELETE request command with headers and data."); + return false; + } + + // The response will be handled asynchronously via the callback + return true; +} +// Function to handle received data asynchronously +/** + * @brief Callback function to handle received data asynchronously. + * @return void + * @param line The received line. + * @param context The context passed to the callback. + * @note The received data will be handled asynchronously via the callback and handles the state of the UART. + */ +void flipper_http_rx_callback(const char *line, void *context) +{ + if (!line || !context) + { + FURI_LOG_E(HTTP_TAG, "Invalid arguments provided to flipper_http_rx_callback."); + return; + } + + // Trim the received line to check if it's empty + char *trimmed_line = trim(line); + if (trimmed_line != NULL && trimmed_line[0] != '\0') + { + // if the line is not [GET/END] or [POST/END] or [PUT/END] or [DELETE/END] + if (strstr(trimmed_line, "[GET/END]") == NULL && + strstr(trimmed_line, "[POST/END]") == NULL && + strstr(trimmed_line, "[PUT/END]") == NULL && + strstr(trimmed_line, "[DELETE/END]") == NULL) + { + strncpy(fhttp.last_response, trimmed_line, RX_BUF_SIZE); + } + } + free(trimmed_line); // Free the allocated memory for trimmed_line + + if (fhttp.state != INACTIVE && fhttp.state != ISSUE) + { + fhttp.state = RECEIVING; + } + + // Uncomment below line to log the data received over UART + // FURI_LOG_I(HTTP_TAG, "Received UART line: %s", line); + + // Check if we've started receiving data from a GET request + if (fhttp.started_receiving_get) + { + // Restart the timeout timer each time new data is received + furi_timer_restart(fhttp.get_timeout_timer, TIMEOUT_DURATION_TICKS); + + if (strstr(line, "[GET/END]") != NULL) + { + FURI_LOG_I(HTTP_TAG, "GET request completed."); + // Stop the timer since we've completed the GET request + furi_timer_stop(fhttp.get_timeout_timer); + fhttp.started_receiving_get = false; + fhttp.just_started_get = false; + fhttp.state = IDLE; + fhttp.save_bytes = false; + fhttp.is_bytes_request = false; + fhttp.save_received_data = false; + return; + } + + // Append the new line to the existing data + if (fhttp.save_received_data && + !flipper_http_append_to_file( + line, strlen(line), !fhttp.just_started_get, fhttp.file_path)) + { + FURI_LOG_E(HTTP_TAG, "Failed to append data to file."); + fhttp.started_receiving_get = false; + fhttp.just_started_get = false; + fhttp.state = IDLE; + return; + } + + if (!fhttp.just_started_get) + { + fhttp.just_started_get = true; + } + return; + } + + // Check if we've started receiving data from a POST request + else if (fhttp.started_receiving_post) + { + // Restart the timeout timer each time new data is received + furi_timer_restart(fhttp.get_timeout_timer, TIMEOUT_DURATION_TICKS); + + if (strstr(line, "[POST/END]") != NULL) + { + FURI_LOG_I(HTTP_TAG, "POST request completed."); + // Stop the timer since we've completed the POST request + furi_timer_stop(fhttp.get_timeout_timer); + fhttp.started_receiving_post = false; + fhttp.just_started_post = false; + fhttp.state = IDLE; + fhttp.save_bytes = false; + fhttp.is_bytes_request = false; + fhttp.save_received_data = false; + return; + } + + // Append the new line to the existing data + if (fhttp.save_received_data && + !flipper_http_append_to_file( + line, strlen(line), !fhttp.just_started_post, fhttp.file_path)) + { + FURI_LOG_E(HTTP_TAG, "Failed to append data to file."); + fhttp.started_receiving_post = false; + fhttp.just_started_post = false; + fhttp.state = IDLE; + return; + } + + if (!fhttp.just_started_post) + { + fhttp.just_started_post = true; + } + return; + } + + // Check if we've started receiving data from a PUT request + else if (fhttp.started_receiving_put) + { + // Restart the timeout timer each time new data is received + furi_timer_restart(fhttp.get_timeout_timer, TIMEOUT_DURATION_TICKS); + + if (strstr(line, "[PUT/END]") != NULL) + { + FURI_LOG_I(HTTP_TAG, "PUT request completed."); + // Stop the timer since we've completed the PUT request + furi_timer_stop(fhttp.get_timeout_timer); + fhttp.started_receiving_put = false; + fhttp.just_started_put = false; + fhttp.state = IDLE; + fhttp.save_bytes = false; + fhttp.is_bytes_request = false; + fhttp.save_received_data = false; + return; + } + + // Append the new line to the existing data + if (fhttp.save_received_data && + !flipper_http_append_to_file( + line, strlen(line), !fhttp.just_started_put, fhttp.file_path)) + { + FURI_LOG_E(HTTP_TAG, "Failed to append data to file."); + fhttp.started_receiving_put = false; + fhttp.just_started_put = false; + fhttp.state = IDLE; + return; + } + + if (!fhttp.just_started_put) + { + fhttp.just_started_put = true; + } + return; + } + + // Check if we've started receiving data from a DELETE request + else if (fhttp.started_receiving_delete) + { + // Restart the timeout timer each time new data is received + furi_timer_restart(fhttp.get_timeout_timer, TIMEOUT_DURATION_TICKS); + + if (strstr(line, "[DELETE/END]") != NULL) + { + FURI_LOG_I(HTTP_TAG, "DELETE request completed."); + // Stop the timer since we've completed the DELETE request + furi_timer_stop(fhttp.get_timeout_timer); + fhttp.started_receiving_delete = false; + fhttp.just_started_delete = false; + fhttp.state = IDLE; + fhttp.save_bytes = false; + fhttp.is_bytes_request = false; + fhttp.save_received_data = false; + return; + } + + // Append the new line to the existing data + if (fhttp.save_received_data && + !flipper_http_append_to_file( + line, strlen(line), !fhttp.just_started_delete, fhttp.file_path)) + { + FURI_LOG_E(HTTP_TAG, "Failed to append data to file."); + fhttp.started_receiving_delete = false; + fhttp.just_started_delete = false; + fhttp.state = IDLE; + return; + } + + if (!fhttp.just_started_delete) + { + fhttp.just_started_delete = true; + } + return; + } + + // Handle different types of responses + if (strstr(line, "[SUCCESS]") != NULL || strstr(line, "[CONNECTED]") != NULL) + { + FURI_LOG_I(HTTP_TAG, "Operation succeeded."); + } + else if (strstr(line, "[INFO]") != NULL) + { + FURI_LOG_I(HTTP_TAG, "Received info: %s", line); + + if (fhttp.state == INACTIVE && strstr(line, "[INFO] Already connected to Wifi.") != NULL) + { + fhttp.state = IDLE; + } + } + else if (strstr(line, "[GET/SUCCESS]") != NULL) + { + FURI_LOG_I(HTTP_TAG, "GET request succeeded."); + fhttp.started_receiving_get = true; + furi_timer_start(fhttp.get_timeout_timer, TIMEOUT_DURATION_TICKS); + fhttp.state = RECEIVING; + // for GET request, save data only if it's a bytes request + fhttp.save_bytes = fhttp.is_bytes_request; + return; + } + else if (strstr(line, "[POST/SUCCESS]") != NULL) + { + FURI_LOG_I(HTTP_TAG, "POST request succeeded."); + fhttp.started_receiving_post = true; + furi_timer_start(fhttp.get_timeout_timer, TIMEOUT_DURATION_TICKS); + fhttp.state = RECEIVING; + // for POST request, save data only if it's a bytes request + fhttp.save_bytes = fhttp.is_bytes_request; + return; + } + else if (strstr(line, "[PUT/SUCCESS]") != NULL) + { + FURI_LOG_I(HTTP_TAG, "PUT request succeeded."); + fhttp.started_receiving_put = true; + furi_timer_start(fhttp.get_timeout_timer, TIMEOUT_DURATION_TICKS); + fhttp.state = RECEIVING; + return; + } + else if (strstr(line, "[DELETE/SUCCESS]") != NULL) + { + FURI_LOG_I(HTTP_TAG, "DELETE request succeeded."); + fhttp.started_receiving_delete = true; + furi_timer_start(fhttp.get_timeout_timer, TIMEOUT_DURATION_TICKS); + fhttp.state = RECEIVING; + return; + } + else if (strstr(line, "[DISCONNECTED]") != NULL) + { + FURI_LOG_I(HTTP_TAG, "WiFi disconnected successfully."); + } + else if (strstr(line, "[ERROR]") != NULL) + { + FURI_LOG_E(HTTP_TAG, "Received error: %s", line); + fhttp.state = ISSUE; + return; + } + else if (strstr(line, "[PONG]") != NULL) + { + FURI_LOG_I(HTTP_TAG, "Received PONG response: Wifi Dev Board is still alive."); + + // send command to connect to WiFi + if (fhttp.state == INACTIVE) + { + fhttp.state = IDLE; + return; + } + } + + if (fhttp.state == INACTIVE && strstr(line, "[PONG]") != NULL) + { + fhttp.state = IDLE; + } + else if (fhttp.state == INACTIVE && strstr(line, "[PONG]") == NULL) + { + fhttp.state = INACTIVE; + } + else + { + fhttp.state = IDLE; + } +} + +// Function to trim leading and trailing spaces and newlines from a constant string +char *trim(const char *str) +{ + const char *end; + char *trimmed_str; + size_t len; + + // Trim leading space + while (isspace((unsigned char)*str)) + str++; + + // All spaces? + if (*str == 0) + return strdup(""); // Return an empty string if all spaces + + // Trim trailing space + end = str + strlen(str) - 1; + while (end > str && isspace((unsigned char)*end)) + end--; + + // Set length for the trimmed string + len = end - str + 1; + + // Allocate space for the trimmed string and null terminator + trimmed_str = (char *)malloc(len + 1); + if (trimmed_str == NULL) + { + return NULL; // Handle memory allocation failure + } + + // Copy the trimmed part of the string into trimmed_str + strncpy(trimmed_str, str, len); + trimmed_str[len] = '\0'; // Null terminate the string + + return trimmed_str; +} + +/** + * @brief Process requests and parse JSON data asynchronously + * @param http_request The function to send the request + * @param parse_json The function to parse the JSON + * @return true if successful, false otherwise + */ +bool flipper_http_process_response_async(bool (*http_request)(void), bool (*parse_json)(void)) +{ + if (http_request()) // start the async request + { + furi_timer_start(fhttp.get_timeout_timer, TIMEOUT_DURATION_TICKS); + fhttp.state = RECEIVING; + } + else + { + FURI_LOG_E(HTTP_TAG, "Failed to send request"); + return false; + } + while (fhttp.state == RECEIVING && furi_timer_is_running(fhttp.get_timeout_timer) > 0) + { + // Wait for the request to be received + furi_delay_ms(100); + } + furi_timer_stop(fhttp.get_timeout_timer); + if (!parse_json()) // parse the JSON before switching to the view (synchonous) + { + FURI_LOG_E(HTTP_TAG, "Failed to parse the JSON..."); + return false; + } + return true; +} \ No newline at end of file diff --git a/flipper_http/flipper_http.h b/flipper_http/flipper_http.h new file mode 100644 index 000000000..835b7e8eb --- /dev/null +++ b/flipper_http/flipper_http.h @@ -0,0 +1,363 @@ +// flipper_http.h +#ifndef FLIPPER_HTTP_H +#define FLIPPER_HTTP_H + +#include +#include +#include +#include +#include + +// STORAGE_EXT_PATH_PREFIX is defined in the Furi SDK as /ext + +#define HTTP_TAG "FlipSocial" // change this to your app name +#define http_tag "flip_social" // change this to your app id +#define UART_CH (FuriHalSerialIdUsart) // UART channel +#define TIMEOUT_DURATION_TICKS (5 * 1000) // 5 seconds +#define BAUDRATE (115200) // UART baudrate +#define RX_BUF_SIZE 1024 // UART RX buffer size +#define RX_LINE_BUFFER_SIZE 8192 // UART RX line buffer size (increase for large responses) +#define MAX_FILE_SHOW 8192 // Maximum data from file to show +#define FILE_BUFFER_SIZE 512 // File buffer size + +// Forward declaration for callback +typedef void (*FlipperHTTP_Callback)(const char *line, void *context); + +// State variable to track the UART state +typedef enum +{ + INACTIVE, // Inactive state + IDLE, // Default state + RECEIVING, // Receiving data + SENDING, // Sending data + ISSUE, // Issue with connection +} SerialState; + +// Event Flags for UART Worker Thread +typedef enum +{ + WorkerEvtStop = (1 << 0), + WorkerEvtRxDone = (1 << 1), +} WorkerEvtFlags; + +// FlipperHTTP Structure +typedef struct +{ + FuriStreamBuffer *flipper_http_stream; // Stream buffer for UART communication + FuriHalSerialHandle *serial_handle; // Serial handle for UART communication + FuriThread *rx_thread; // Worker thread for UART + FuriThreadId rx_thread_id; // Worker thread ID + FlipperHTTP_Callback handle_rx_line_cb; // Callback for received lines + void *callback_context; // Context for the callback + SerialState state; // State of the UART + + // variable to store the last received data from the UART + char *last_response; + char file_path[256]; // Path to save the received data + + // Timer-related members + FuriTimer *get_timeout_timer; // Timer for HTTP request timeout + + bool started_receiving_get; // Indicates if a GET request has started + bool just_started_get; // Indicates if GET data reception has just started + + bool started_receiving_post; // Indicates if a POST request has started + bool just_started_post; // Indicates if POST data reception has just started + + bool started_receiving_put; // Indicates if a PUT request has started + bool just_started_put; // Indicates if PUT data reception has just started + + bool started_receiving_delete; // Indicates if a DELETE request has started + bool just_started_delete; // Indicates if DELETE data reception has just started + + // Buffer to hold the raw bytes received from the UART + uint8_t *received_bytes; + size_t received_bytes_len; // Length of the received bytes + bool is_bytes_request; // Flag to indicate if the request is for bytes + bool save_bytes; // Flag to save the received data to a file + bool save_received_data; // Flag to save the received data to a file +} FlipperHTTP; + +extern FlipperHTTP fhttp; +// Global static array for the line buffer +extern char rx_line_buffer[RX_LINE_BUFFER_SIZE]; +extern uint8_t file_buffer[FILE_BUFFER_SIZE]; + +// fhttp.last_response holds the last received data from the UART + +// Function to append received data to file +// make sure to initialize the file path before calling this function +bool flipper_http_append_to_file( + const void *data, + size_t data_size, + bool start_new_file, + char *file_path); + +FuriString *flipper_http_load_from_file(char *file_path); + +// UART worker thread +/** + * @brief Worker thread to handle UART data asynchronously. + * @return 0 + * @param context The context to pass to the callback. + * @note This function will handle received data asynchronously via the callback. + */ +// UART worker thread +int32_t flipper_http_worker(void *context); + +// Timer callback function +/** + * @brief Callback function for the GET timeout timer. + * @return 0 + * @param context The context to pass to the callback. + * @note This function will be called when the GET request times out. + */ +void get_timeout_timer_callback(void *context); + +// UART RX Handler Callback (Interrupt Context) +/** + * @brief A private callback function to handle received data asynchronously. + * @return void + * @param handle The UART handle. + * @param event The event type. + * @param context The context to pass to the callback. + * @note This function will handle received data asynchronously via the callback. + */ +void _flipper_http_rx_callback( + FuriHalSerialHandle *handle, + FuriHalSerialRxEvent event, + void *context); + +// UART initialization function +/** + * @brief Initialize UART. + * @return true if the UART was initialized successfully, false otherwise. + * @param callback The callback function to handle received data (ex. flipper_http_rx_callback). + * @param context The context to pass to the callback. + * @note The received data will be handled asynchronously via the callback. + */ +bool flipper_http_init(FlipperHTTP_Callback callback, void *context); + +// Deinitialize UART +/** + * @brief Deinitialize UART. + * @return void + * @note This function will stop the asynchronous RX, release the serial handle, and free the resources. + */ +void flipper_http_deinit(); + +// Function to send data over UART with newline termination +/** + * @brief Send data over UART with newline termination. + * @return true if the data was sent successfully, false otherwise. + * @param data The data to send over UART. + * @note The data will be sent over UART with a newline character appended. + */ +bool flipper_http_send_data(const char *data); + +// Function to send a PING request +/** + * @brief Send a PING request to check if the Wifi Dev Board is connected. + * @return true if the request was successful, false otherwise. + * @note The received data will be handled asynchronously via the callback. + * @note This is best used to check if the Wifi Dev Board is connected. + * @note The state will remain INACTIVE until a PONG is received. + */ +bool flipper_http_ping(); + +// Function to list available commands +/** + * @brief Send a command to list available commands. + * @return true if the request was successful, false otherwise. + * @note The received data will be handled asynchronously via the callback. + */ +bool flipper_http_list_commands(); + +// Function to turn on the LED +/** + * @brief Allow the LED to display while processing. + * @return true if the request was successful, false otherwise. + * @note The received data will be handled asynchronously via the callback. + */ +bool flipper_http_led_on(); + +// Function to turn off the LED +/** + * @brief Disable the LED from displaying while processing. + * @return true if the request was successful, false otherwise. + * @note The received data will be handled asynchronously via the callback. + */ +bool flipper_http_led_off(); + +// Function to parse JSON data +/** + * @brief Parse JSON data. + * @return true if the JSON data was parsed successfully, false otherwise. + * @param key The key to parse from the JSON data. + * @param json_data The JSON data to parse. + * @note The received data will be handled asynchronously via the callback. + */ +bool flipper_http_parse_json(const char *key, const char *json_data); + +// Function to parse JSON array data +/** + * @brief Parse JSON array data. + * @return true if the JSON array data was parsed successfully, false otherwise. + * @param key The key to parse from the JSON array data. + * @param index The index to parse from the JSON array data. + * @param json_data The JSON array data to parse. + * @note The received data will be handled asynchronously via the callback. + */ +bool flipper_http_parse_json_array(const char *key, int index, const char *json_data); + +// Function to scan for WiFi networks +/** + * @brief Send a command to scan for WiFi networks. + * @return true if the request was successful, false otherwise. + * @note The received data will be handled asynchronously via the callback. + */ +bool flipper_http_scan_wifi(); + +// Function to save WiFi settings (returns true if successful) +/** + * @brief Send a command to save WiFi settings. + * @return true if the request was successful, false otherwise. + * @note The received data will be handled asynchronously via the callback. + */ +bool flipper_http_save_wifi(const char *ssid, const char *password); + +// Function to get IP address of WiFi Devboard +/** + * @brief Send a command to get the IP address of the WiFi Devboard + * @return true if the request was successful, false otherwise. + * @note The received data will be handled asynchronously via the callback. + */ +bool flipper_http_ip_address(); + +// Function to get IP address of the connected WiFi network +/** + * @brief Send a command to get the IP address of the connected WiFi network. + * @return true if the request was successful, false otherwise. + * @note The received data will be handled asynchronously via the callback. + */ +bool flipper_http_ip_wifi(); + +// Function to disconnect from WiFi (returns true if successful) +/** + * @brief Send a command to disconnect from WiFi. + * @return true if the request was successful, false otherwise. + * @note The received data will be handled asynchronously via the callback. + */ +bool flipper_http_disconnect_wifi(); + +// Function to connect to WiFi (returns true if successful) +/** + * @brief Send a command to connect to WiFi. + * @return true if the request was successful, false otherwise. + * @note The received data will be handled asynchronously via the callback. + */ +bool flipper_http_connect_wifi(); + +// Function to send a GET request +/** + * @brief Send a GET request to the specified URL. + * @return true if the request was successful, false otherwise. + * @param url The URL to send the GET request to. + * @note The received data will be handled asynchronously via the callback. + */ +bool flipper_http_get_request(const char *url); + +// Function to send a GET request with headers +/** + * @brief Send a GET request to the specified URL. + * @return true if the request was successful, false otherwise. + * @param url The URL to send the GET request to. + * @param headers The headers to send with the GET request. + * @note The received data will be handled asynchronously via the callback. + */ +bool flipper_http_get_request_with_headers(const char *url, const char *headers); + +// Function to send a GET request with headers and return bytes +/** + * @brief Send a GET request to the specified URL. + * @return true if the request was successful, false otherwise. + * @param url The URL to send the GET request to. + * @param headers The headers to send with the GET request. + * @note The received data will be handled asynchronously via the callback. + */ +bool flipper_http_get_request_bytes(const char *url, const char *headers); + +// Function to send a POST request with headers +/** + * @brief Send a POST request to the specified URL. + * @return true if the request was successful, false otherwise. + * @param url The URL to send the POST request to. + * @param headers The headers to send with the POST request. + * @param data The data to send with the POST request. + * @note The received data will be handled asynchronously via the callback. + */ +bool flipper_http_post_request_with_headers( + const char *url, + const char *headers, + const char *payload); + +// Function to send a POST request with headers and return bytes +/** + * @brief Send a POST request to the specified URL. + * @return true if the request was successful, false otherwise. + * @param url The URL to send the POST request to. + * @param headers The headers to send with the POST request. + * @param payload The data to send with the POST request. + * @note The received data will be handled asynchronously via the callback. + */ +bool flipper_http_post_request_bytes(const char *url, const char *headers, const char *payload); + +// Function to send a PUT request with headers +/** + * @brief Send a PUT request to the specified URL. + * @return true if the request was successful, false otherwise. + * @param url The URL to send the PUT request to. + * @param headers The headers to send with the PUT request. + * @param data The data to send with the PUT request. + * @note The received data will be handled asynchronously via the callback. + */ +bool flipper_http_put_request_with_headers( + const char *url, + const char *headers, + const char *payload); + +// Function to send a DELETE request with headers +/** + * @brief Send a DELETE request to the specified URL. + * @return true if the request was successful, false otherwise. + * @param url The URL to send the DELETE request to. + * @param headers The headers to send with the DELETE request. + * @param data The data to send with the DELETE request. + * @note The received data will be handled asynchronously via the callback. + */ +bool flipper_http_delete_request_with_headers( + const char *url, + const char *headers, + const char *payload); + +// Function to handle received data asynchronously +/** + * @brief Callback function to handle received data asynchronously. + * @return void + * @param line The received line. + * @param context The context passed to the callback. + * @note The received data will be handled asynchronously via the callback and handles the state of the UART. + */ +void flipper_http_rx_callback(const char *line, void *context); + +// Function to trim leading and trailing spaces and newlines from a constant string +char *trim(const char *str); +/** + * @brief Process requests and parse JSON data asynchronously + * @param http_request The function to send the request + * @param parse_json The function to parse the JSON + * @return true if successful, false otherwise + */ +bool flipper_http_process_response_async(bool (*http_request)(void), bool (*parse_json)(void)); + +#endif // FLIPPER_HTTP_H diff --git a/friends/flip_social_friends.c b/friends/flip_social_friends.c new file mode 100644 index 000000000..a9b76f005 --- /dev/null +++ b/friends/flip_social_friends.c @@ -0,0 +1,181 @@ +#include "flip_social_friends.h" + +FlipSocialModel *flip_social_friends_alloc() +{ + // Allocate memory for each username only if not already allocated + FlipSocialModel *friends = malloc(sizeof(FlipSocialModel)); + for (size_t i = 0; i < MAX_FRIENDS; i++) + { + if (friends->usernames[i] == NULL) + { + friends->usernames[i] = malloc(MAX_USER_LENGTH); + if (friends->usernames[i] == NULL) + { + FURI_LOG_E(TAG, "Failed to allocate memory for username %zu", i); + return NULL; // Return false on memory allocation failure + } + } + } + return friends; +} + +void flip_social_free_friends() +{ + if (!flip_social_friends) + { + FURI_LOG_E(TAG, "Friends model is NULL"); + return; + } + for (int i = 0; i < flip_social_friends->count; i++) + { + if (flip_social_friends->usernames[i]) + { + free(flip_social_friends->usernames[i]); + } + } +} + +// for now we're just listing the current users +// as the feed is upgraded, then we can port more to the friends view +bool flip_social_get_friends() +{ + // will return true unless the devboard is not connected + char url[100]; + snprintf( + fhttp.file_path, + sizeof(fhttp.file_path), + STORAGE_EXT_PATH_PREFIX "/apps_data/flip_rss/friends.txt"); + + fhttp.save_received_data = true; + char *headers = jsmn("Content-Type", "application/json"); + snprintf(url, 100, "https://www.flipsocial.net/api/user/friends/%s/", app_instance->login_username_logged_in); + bool success = flipper_http_get_request_with_headers(url, headers); + free(headers); + if (!success) + { + FURI_LOG_E(TAG, "Failed to send HTTP request for friends"); + return false; + } + fhttp.state = RECEIVING; + return true; +} + +bool flip_social_update_friends() +{ + if (!app_instance->submenu_friends) + { + FURI_LOG_E(TAG, "Friends submenu is NULL"); + return false; + } + if (!flip_social_friends) + { + FURI_LOG_E(TAG, "Friends model is NULL"); + return false; + } + // Add submenu items for the users + submenu_reset(app_instance->submenu_friends); + submenu_set_header(app_instance->submenu_friends, "Friends"); + for (int i = 0; i < flip_social_friends->count; i++) + { + submenu_add_item(app_instance->submenu_friends, flip_social_friends->usernames[i], FlipSocialSubmenuLoggedInIndexFriendsStart + i, flip_social_callback_submenu_choices, app_instance); + } + return true; +} + +bool flip_social_parse_json_friends() +{ + // load the received data from the saved file + FuriString *friend_data = flipper_http_load_from_file(fhttp.file_path); + if (friend_data == NULL) + { + FURI_LOG_E(TAG, "Failed to load received data from file."); + return false; + } + char *data_cstr = (char *)furi_string_get_cstr(friend_data); + if (data_cstr == NULL) + { + FURI_LOG_E(TAG, "Failed to get C-string from FuriString."); + furi_string_free(friend_data); + return false; + } + + // Allocate memory for each username only if not already allocated + flip_social_friends = flip_social_friends_alloc(); + if (flip_social_friends == NULL) + { + FURI_LOG_E(TAG, "Failed to allocate memory for friends usernames."); + furi_string_free(friend_data); + free(data_cstr); + return false; + } + + // Remove newlines + char *pos = data_cstr; + while ((pos = strchr(pos, '\n')) != NULL) + { + *pos = ' '; + } + + // Initialize friends count + flip_social_friends->count = 0; + + // Extract the users array from the JSON + char *json_users = get_json_value("friends", data_cstr, MAX_TOKENS); + if (json_users == NULL) + { + FURI_LOG_E(TAG, "Failed to parse friends array."); + furi_string_free(friend_data); + free(data_cstr); + return false; + } + + // Manual tokenization for comma-separated values + char *start = json_users + 1; // Skip the opening bracket + char *end; + while ((end = strchr(start, ',')) != NULL && flip_social_friends->count < MAX_FRIENDS) + { + *end = '\0'; // Null-terminate the current token + + // Remove quotes + if (*start == '"') + start++; + if (*(end - 1) == '"') + *(end - 1) = '\0'; + + // Copy username to pre-allocated memory + snprintf(flip_social_friends->usernames[flip_social_friends->count], MAX_USER_LENGTH, "%s", start); + flip_social_friends->count++; + start = end + 1; + } + + // Handle the last token + if (*start != '\0' && flip_social_friends->count < MAX_FRIENDS) + { + if (*start == '"') + start++; + if (*(start + strlen(start) - 1) == ']') + *(start + strlen(start) - 1) = '\0'; + if (*(start + strlen(start) - 1) == '"') + *(start + strlen(start) - 1) = '\0'; + + snprintf(flip_social_friends->usernames[flip_social_friends->count], MAX_USER_LENGTH, "%s", start); + flip_social_friends->count++; + } + + // Add submenu items for the friends + if (!flip_social_update_friends()) + { + FURI_LOG_E(TAG, "Failed to update friends submenu"); + furi_string_free(friend_data); + free(data_cstr); + return false; + } + + // Free the json_users + free(json_users); + free(start); + free(end); + furi_string_free(friend_data); + free(data_cstr); + return true; +} diff --git a/friends/flip_social_friends.h b/friends/flip_social_friends.h new file mode 100644 index 000000000..0f4e436f7 --- /dev/null +++ b/friends/flip_social_friends.h @@ -0,0 +1,11 @@ +#ifndef FLIP_SOCIAL_FRIENDS_H +#define FLIP_SOCIAL_FRIENDS_H +#include "flip_social.h" +#include + +FlipSocialModel *flip_social_friends_alloc(); +void flip_social_free_friends(); +bool flip_social_get_friends(); +bool flip_social_update_friends(); +bool flip_social_parse_json_friends(); +#endif \ No newline at end of file diff --git a/jsmn/jsmn.c b/jsmn/jsmn.c new file mode 100644 index 000000000..b85ab83b5 --- /dev/null +++ b/jsmn/jsmn.c @@ -0,0 +1,747 @@ +/* + * MIT License + * + * Copyright (c) 2010 Serge Zaitsev + * + * [License text continues...] + */ + +#include +#include +#include + +/** + * Allocates a fresh unused token from the token pool. + */ +static jsmntok_t *jsmn_alloc_token(jsmn_parser *parser, jsmntok_t *tokens, + const size_t num_tokens) +{ + jsmntok_t *tok; + + if (parser->toknext >= num_tokens) + { + return NULL; + } + tok = &tokens[parser->toknext++]; + tok->start = tok->end = -1; + tok->size = 0; +#ifdef JSMN_PARENT_LINKS + tok->parent = -1; +#endif + return tok; +} + +/** + * Fills token type and boundaries. + */ +static void jsmn_fill_token(jsmntok_t *token, const jsmntype_t type, + const int start, const int end) +{ + token->type = type; + token->start = start; + token->end = end; + token->size = 0; +} + +/** + * Fills next available token with JSON primitive. + */ +static int jsmn_parse_primitive(jsmn_parser *parser, const char *js, + const size_t len, jsmntok_t *tokens, + const size_t num_tokens) +{ + jsmntok_t *token; + int start; + + start = parser->pos; + + for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) + { + switch (js[parser->pos]) + { +#ifndef JSMN_STRICT + /* In strict mode primitive must be followed by "," or "}" or "]" */ + case ':': +#endif + case '\t': + case '\r': + case '\n': + case ' ': + case ',': + case ']': + case '}': + goto found; + default: + /* to quiet a warning from gcc*/ + break; + } + if (js[parser->pos] < 32 || js[parser->pos] >= 127) + { + parser->pos = start; + return JSMN_ERROR_INVAL; + } + } +#ifdef JSMN_STRICT + /* In strict mode primitive must be followed by a comma/object/array */ + parser->pos = start; + return JSMN_ERROR_PART; +#endif + +found: + if (tokens == NULL) + { + parser->pos--; + return 0; + } + token = jsmn_alloc_token(parser, tokens, num_tokens); + if (token == NULL) + { + parser->pos = start; + return JSMN_ERROR_NOMEM; + } + jsmn_fill_token(token, JSMN_PRIMITIVE, start, parser->pos); +#ifdef JSMN_PARENT_LINKS + token->parent = parser->toksuper; +#endif + parser->pos--; + return 0; +} + +/** + * Fills next token with JSON string. + */ +static int jsmn_parse_string(jsmn_parser *parser, const char *js, + const size_t len, jsmntok_t *tokens, + const size_t num_tokens) +{ + jsmntok_t *token; + + int start = parser->pos; + + /* Skip starting quote */ + parser->pos++; + + for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) + { + char c = js[parser->pos]; + + /* Quote: end of string */ + if (c == '\"') + { + if (tokens == NULL) + { + return 0; + } + token = jsmn_alloc_token(parser, tokens, num_tokens); + if (token == NULL) + { + parser->pos = start; + return JSMN_ERROR_NOMEM; + } + jsmn_fill_token(token, JSMN_STRING, start + 1, parser->pos); +#ifdef JSMN_PARENT_LINKS + token->parent = parser->toksuper; +#endif + return 0; + } + + /* Backslash: Quoted symbol expected */ + if (c == '\\' && parser->pos + 1 < len) + { + int i; + parser->pos++; + switch (js[parser->pos]) + { + /* Allowed escaped symbols */ + case '\"': + case '/': + case '\\': + case 'b': + case 'f': + case 'r': + case 'n': + case 't': + break; + /* Allows escaped symbol \uXXXX */ + case 'u': + parser->pos++; + for (i = 0; i < 4 && parser->pos < len && js[parser->pos] != '\0'; i++) + { + /* If it isn't a hex character we have an error */ + if (!((js[parser->pos] >= 48 && js[parser->pos] <= 57) || /* 0-9 */ + (js[parser->pos] >= 65 && js[parser->pos] <= 70) || /* A-F */ + (js[parser->pos] >= 97 && js[parser->pos] <= 102))) + { /* a-f */ + parser->pos = start; + return JSMN_ERROR_INVAL; + } + parser->pos++; + } + parser->pos--; + break; + /* Unexpected symbol */ + default: + parser->pos = start; + return JSMN_ERROR_INVAL; + } + } + } + parser->pos = start; + return JSMN_ERROR_PART; +} + +/** + * Create JSON parser over an array of tokens + */ +void jsmn_init(jsmn_parser *parser) +{ + parser->pos = 0; + parser->toknext = 0; + parser->toksuper = -1; +} + +/** + * Parse JSON string and fill tokens. + */ +int jsmn_parse(jsmn_parser *parser, const char *js, const size_t len, + jsmntok_t *tokens, const unsigned int num_tokens) +{ + int r; + int i; + jsmntok_t *token; + int count = parser->toknext; + + for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) + { + char c; + jsmntype_t type; + + c = js[parser->pos]; + switch (c) + { + case '{': + case '[': + count++; + if (tokens == NULL) + { + break; + } + token = jsmn_alloc_token(parser, tokens, num_tokens); + if (token == NULL) + { + return JSMN_ERROR_NOMEM; + } + if (parser->toksuper != -1) + { + jsmntok_t *t = &tokens[parser->toksuper]; +#ifdef JSMN_STRICT + /* In strict mode an object or array can't become a key */ + if (t->type == JSMN_OBJECT) + { + return JSMN_ERROR_INVAL; + } +#endif + t->size++; +#ifdef JSMN_PARENT_LINKS + token->parent = parser->toksuper; +#endif + } + token->type = (c == '{' ? JSMN_OBJECT : JSMN_ARRAY); + token->start = parser->pos; + parser->toksuper = parser->toknext - 1; + break; + case '}': + case ']': + if (tokens == NULL) + { + break; + } + type = (c == '}' ? JSMN_OBJECT : JSMN_ARRAY); +#ifdef JSMN_PARENT_LINKS + if (parser->toknext < 1) + { + return JSMN_ERROR_INVAL; + } + token = &tokens[parser->toknext - 1]; + for (;;) + { + if (token->start != -1 && token->end == -1) + { + if (token->type != type) + { + return JSMN_ERROR_INVAL; + } + token->end = parser->pos + 1; + parser->toksuper = token->parent; + break; + } + if (token->parent == -1) + { + if (token->type != type || parser->toksuper == -1) + { + return JSMN_ERROR_INVAL; + } + break; + } + token = &tokens[token->parent]; + } +#else + for (i = parser->toknext - 1; i >= 0; i--) + { + token = &tokens[i]; + if (token->start != -1 && token->end == -1) + { + if (token->type != type) + { + return JSMN_ERROR_INVAL; + } + parser->toksuper = -1; + token->end = parser->pos + 1; + break; + } + } + /* Error if unmatched closing bracket */ + if (i == -1) + { + return JSMN_ERROR_INVAL; + } + for (; i >= 0; i--) + { + token = &tokens[i]; + if (token->start != -1 && token->end == -1) + { + parser->toksuper = i; + break; + } + } +#endif + break; + case '\"': + r = jsmn_parse_string(parser, js, len, tokens, num_tokens); + if (r < 0) + { + return r; + } + count++; + if (parser->toksuper != -1 && tokens != NULL) + { + tokens[parser->toksuper].size++; + } + break; + case '\t': + case '\r': + case '\n': + case ' ': + break; + case ':': + parser->toksuper = parser->toknext - 1; + break; + case ',': + if (tokens != NULL && parser->toksuper != -1 && + tokens[parser->toksuper].type != JSMN_ARRAY && + tokens[parser->toksuper].type != JSMN_OBJECT) + { +#ifdef JSMN_PARENT_LINKS + parser->toksuper = tokens[parser->toksuper].parent; +#else + for (i = parser->toknext - 1; i >= 0; i--) + { + if (tokens[i].type == JSMN_ARRAY || tokens[i].type == JSMN_OBJECT) + { + if (tokens[i].start != -1 && tokens[i].end == -1) + { + parser->toksuper = i; + break; + } + } + } +#endif + } + break; +#ifdef JSMN_STRICT + /* In strict mode primitives are: numbers and booleans */ + case '-': + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case 't': + case 'f': + case 'n': + /* And they must not be keys of the object */ + if (tokens != NULL && parser->toksuper != -1) + { + const jsmntok_t *t = &tokens[parser->toksuper]; + if (t->type == JSMN_OBJECT || + (t->type == JSMN_STRING && t->size != 0)) + { + return JSMN_ERROR_INVAL; + } + } +#else + /* In non-strict mode every unquoted value is a primitive */ + default: +#endif + r = jsmn_parse_primitive(parser, js, len, tokens, num_tokens); + if (r < 0) + { + return r; + } + count++; + if (parser->toksuper != -1 && tokens != NULL) + { + tokens[parser->toksuper].size++; + } + break; + +#ifdef JSMN_STRICT + /* Unexpected char in strict mode */ + default: + return JSMN_ERROR_INVAL; +#endif + } + } + + if (tokens != NULL) + { + for (i = parser->toknext - 1; i >= 0; i--) + { + /* Unmatched opened object or array */ + if (tokens[i].start != -1 && tokens[i].end == -1) + { + return JSMN_ERROR_PART; + } + } + } + + return count; +} + +// Helper function to create a JSON object +char *jsmn(const char *key, const char *value) +{ + int length = strlen(key) + strlen(value) + 8; // Calculate required length + char *result = (char *)malloc(length * sizeof(char)); // Allocate memory + if (result == NULL) + { + return NULL; // Handle memory allocation failure + } + snprintf(result, length, "{\"%s\":\"%s\"}", key, value); + return result; // Caller is responsible for freeing this memory +} + +// Helper function to compare JSON keys +int jsoneq(const char *json, jsmntok_t *tok, const char *s) +{ + if (tok->type == JSMN_STRING && (int)strlen(s) == tok->end - tok->start && + strncmp(json + tok->start, s, tok->end - tok->start) == 0) + { + return 0; + } + return -1; +} + +// Return the value of the key in the JSON data +char *get_json_value(char *key, char *json_data, uint32_t max_tokens) +{ + // Parse the JSON feed + if (json_data != NULL) + { + jsmn_parser parser; + jsmn_init(&parser); + + // Allocate tokens array on the heap + jsmntok_t *tokens = malloc(sizeof(jsmntok_t) * max_tokens); + if (tokens == NULL) + { + FURI_LOG_E("JSMM.H", "Failed to allocate memory for JSON tokens."); + return NULL; + } + + int ret = jsmn_parse(&parser, json_data, strlen(json_data), tokens, max_tokens); + if (ret < 0) + { + // Handle parsing errors + FURI_LOG_E("JSMM.H", "Failed to parse JSON: %d", ret); + free(tokens); + return NULL; + } + + // Ensure that the root element is an object + if (ret < 1 || tokens[0].type != JSMN_OBJECT) + { + FURI_LOG_E("JSMM.H", "Root element is not an object."); + free(tokens); + return NULL; + } + + // Loop through the tokens to find the key + for (int i = 1; i < ret; i++) + { + if (jsoneq(json_data, &tokens[i], key) == 0) + { + // We found the key. Now, return the associated value. + int length = tokens[i + 1].end - tokens[i + 1].start; + char *value = malloc(length + 1); + if (value == NULL) + { + FURI_LOG_E("JSMM.H", "Failed to allocate memory for value."); + free(tokens); + return NULL; + } + strncpy(value, json_data + tokens[i + 1].start, length); + value[length] = '\0'; // Null-terminate the string + + free(tokens); // Free the token array + return value; // Return the extracted value + } + } + + // Free the token array if key was not found + free(tokens); + } + else + { + FURI_LOG_E("JSMM.H", "JSON data is NULL"); + } + FURI_LOG_E("JSMM.H", "Failed to find the key in the JSON."); + return NULL; // Return NULL if something goes wrong +} + +// Revised get_json_array_value function +char *get_json_array_value(char *key, uint32_t index, char *json_data, uint32_t max_tokens) +{ + // Retrieve the array string for the given key + char *array_str = get_json_value(key, json_data, max_tokens); + if (array_str == NULL) + { + FURI_LOG_E("JSMM.H", "Failed to get array for key: %s", key); + return NULL; + } + + // Initialize the JSON parser + jsmn_parser parser; + jsmn_init(&parser); + + // Allocate memory for JSON tokens + jsmntok_t *tokens = malloc(sizeof(jsmntok_t) * max_tokens); + if (tokens == NULL) + { + FURI_LOG_E("JSMM.H", "Failed to allocate memory for JSON tokens."); + free(array_str); + return NULL; + } + + // Parse the JSON array + int ret = jsmn_parse(&parser, array_str, strlen(array_str), tokens, max_tokens); + if (ret < 0) + { + FURI_LOG_E("JSMM.H", "Failed to parse JSON array: %d", ret); + free(tokens); + free(array_str); + return NULL; + } + + // Ensure the root element is an array + if (ret < 1 || tokens[0].type != JSMN_ARRAY) + { + FURI_LOG_E("JSMM.H", "Value for key '%s' is not an array.", key); + free(tokens); + free(array_str); + return NULL; + } + + // Check if the index is within bounds + if (index >= (uint32_t)tokens[0].size) + { + FURI_LOG_E("JSMM.H", "Index %lu out of bounds for array with size %d.", (unsigned long)index, tokens[0].size); + free(tokens); + free(array_str); + return NULL; + } + + // Locate the token corresponding to the desired array element + int current_token = 1; // Start after the array token + for (uint32_t i = 0; i < index; i++) + { + if (tokens[current_token].type == JSMN_OBJECT) + { + // For objects, skip all key-value pairs + current_token += 1 + 2 * tokens[current_token].size; + } + else if (tokens[current_token].type == JSMN_ARRAY) + { + // For nested arrays, skip all elements + current_token += 1 + tokens[current_token].size; + } + else + { + // For primitive types, simply move to the next token + current_token += 1; + } + + // Safety check to prevent out-of-bounds + if (current_token >= ret) + { + FURI_LOG_E("JSMM.H", "Unexpected end of tokens while traversing array."); + free(tokens); + free(array_str); + return NULL; + } + } + + // Extract the array element + jsmntok_t element = tokens[current_token]; + int length = element.end - element.start; + char *value = malloc(length + 1); + if (value == NULL) + { + FURI_LOG_E("JSMM.H", "Failed to allocate memory for array element."); + free(tokens); + free(array_str); + return NULL; + } + + // Copy the element value to a new string + strncpy(value, array_str + element.start, length); + value[length] = '\0'; // Null-terminate the string + + // Clean up + free(tokens); + free(array_str); + + return value; +} + +// Revised get_json_array_values function with correct token skipping +char **get_json_array_values(char *key, char *json_data, uint32_t max_tokens, int *num_values) +{ + // Retrieve the array string for the given key + char *array_str = get_json_value(key, json_data, max_tokens); + if (array_str == NULL) + { + FURI_LOG_E("JSMM.H", "Failed to get array for key: %s", key); + return NULL; + } + + // Initialize the JSON parser + jsmn_parser parser; + jsmn_init(&parser); + + // Allocate memory for JSON tokens + jsmntok_t *tokens = malloc(sizeof(jsmntok_t) * max_tokens); // Allocate on the heap + if (tokens == NULL) + { + FURI_LOG_E("JSMM.H", "Failed to allocate memory for JSON tokens."); + free(array_str); + return NULL; + } + + // Parse the JSON array + int ret = jsmn_parse(&parser, array_str, strlen(array_str), tokens, max_tokens); + if (ret < 0) + { + FURI_LOG_E("JSMM.H", "Failed to parse JSON array: %d", ret); + free(tokens); + free(array_str); + return NULL; + } + + // Ensure the root element is an array + if (tokens[0].type != JSMN_ARRAY) + { + FURI_LOG_E("JSMM.H", "Value for key '%s' is not an array.", key); + free(tokens); + free(array_str); + return NULL; + } + + // Allocate memory for the array of values (maximum possible) + int array_size = tokens[0].size; + char **values = malloc(array_size * sizeof(char *)); + if (values == NULL) + { + FURI_LOG_E("JSMM.H", "Failed to allocate memory for array of values."); + free(tokens); + free(array_str); + return NULL; + } + + int actual_num_values = 0; + + // Traverse the array and extract all object values + int current_token = 1; // Start after the array token + for (int i = 0; i < array_size; i++) + { + if (current_token >= ret) + { + FURI_LOG_E("JSMM.H", "Unexpected end of tokens while traversing array."); + break; + } + + jsmntok_t element = tokens[current_token]; + + if (element.type != JSMN_OBJECT) + { + FURI_LOG_E("JSMM.H", "Array element %d is not an object, skipping.", i); + // Skip this element + current_token += 1; + continue; + } + + int length = element.end - element.start; + + // Allocate a new string for the value and copy the data + char *value = malloc(length + 1); + if (value == NULL) + { + FURI_LOG_E("JSMM.H", "Failed to allocate memory for array element."); + for (int j = 0; j < actual_num_values; j++) + { + free(values[j]); + } + free(values); + free(tokens); + free(array_str); + return NULL; + } + + strncpy(value, array_str + element.start, length); + value[length] = '\0'; // Null-terminate the string + + values[actual_num_values] = value; + actual_num_values++; + + // Skip all tokens related to this object to avoid misparsing + current_token += 1 + (2 * element.size); // Each key-value pair consumes two tokens + } + + *num_values = actual_num_values; + + // Reallocate the values array to actual_num_values if necessary + if (actual_num_values < array_size) + { + char **reduced_values = realloc(values, actual_num_values * sizeof(char *)); + if (reduced_values != NULL) + { + values = reduced_values; + } + + // Free the remaining values + for (int i = actual_num_values; i < array_size; i++) + { + free(values[i]); + } + } + + // Clean up + free(tokens); + free(array_str); + return values; +} diff --git a/jsmn/jsmn.h b/jsmn/jsmn.h new file mode 100644 index 000000000..74cdccf95 --- /dev/null +++ b/jsmn/jsmn.h @@ -0,0 +1,132 @@ +/* + * MIT License + * + * Copyright (c) 2010 Serge Zaitsev + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * [License text continues...] + */ + +#ifndef JSMN_H +#define JSMN_H + +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + +#ifdef JSMN_STATIC +#define JSMN_API static +#else +#define JSMN_API extern +#endif + + /** + * JSON type identifier. Basic types are: + * o Object + * o Array + * o String + * o Other primitive: number, boolean (true/false) or null + */ + typedef enum + { + JSMN_UNDEFINED = 0, + JSMN_OBJECT = 1 << 0, + JSMN_ARRAY = 1 << 1, + JSMN_STRING = 1 << 2, + JSMN_PRIMITIVE = 1 << 3 + } jsmntype_t; + + enum jsmnerr + { + /* Not enough tokens were provided */ + JSMN_ERROR_NOMEM = -1, + /* Invalid character inside JSON string */ + JSMN_ERROR_INVAL = -2, + /* The string is not a full JSON packet, more bytes expected */ + JSMN_ERROR_PART = -3 + }; + + /** + * JSON token description. + * type type (object, array, string etc.) + * start start position in JSON data string + * end end position in JSON data string + */ + typedef struct + { + jsmntype_t type; + int start; + int end; + int size; +#ifdef JSMN_PARENT_LINKS + int parent; +#endif + } jsmntok_t; + + /** + * JSON parser. Contains an array of token blocks available. Also stores + * the string being parsed now and current position in that string. + */ + typedef struct + { + unsigned int pos; /* offset in the JSON string */ + unsigned int toknext; /* next token to allocate */ + int toksuper; /* superior token node, e.g. parent object or array */ + } jsmn_parser; + + /** + * Create JSON parser over an array of tokens + */ + JSMN_API void jsmn_init(jsmn_parser *parser); + + /** + * Run JSON parser. It parses a JSON data string into and array of tokens, each + * describing a single JSON object. + */ + JSMN_API int jsmn_parse(jsmn_parser *parser, const char *js, const size_t len, + jsmntok_t *tokens, const unsigned int num_tokens); + +#ifndef JSMN_HEADER +/* Implementation has been moved to jsmn.c */ +#endif /* JSMN_HEADER */ + +#ifdef __cplusplus +} +#endif + +#endif /* JSMN_H */ + +/* Custom Helper Functions */ +#ifndef JB_JSMN_EDIT +#define JB_JSMN_EDIT +/* Added in by JBlanked on 2024-10-16 for use in Flipper Zero SDK*/ + +#include +#include +#include +#include +#include + +// Helper function to create a JSON object +char *jsmn(const char *key, const char *value); +// Helper function to compare JSON keys +int jsoneq(const char *json, jsmntok_t *tok, const char *s); + +// Return the value of the key in the JSON data +char *get_json_value(char *key, char *json_data, uint32_t max_tokens); + +// Revised get_json_array_value function +char *get_json_array_value(char *key, uint32_t index, char *json_data, uint32_t max_tokens); + +// Revised get_json_array_values function with correct token skipping +char **get_json_array_values(char *key, char *json_data, uint32_t max_tokens, int *num_values); +#endif /* JB_JSMN_EDIT */ diff --git a/messages/flip_social_messages.c b/messages/flip_social_messages.c new file mode 100644 index 000000000..6ab78e2a8 --- /dev/null +++ b/messages/flip_social_messages.c @@ -0,0 +1,454 @@ +#include "flip_social_messages.h" + +FlipSocialModel2 *flip_social_messages_alloc() +{ + // Allocate memory for each username only if not already allocated + FlipSocialModel2 *users = malloc(sizeof(FlipSocialModel2)); + if (users == NULL) + { + FURI_LOG_E(TAG, "Failed to allocate memory for message users"); + return NULL; + } + for (size_t i = 0; i < MAX_MESSAGE_USERS; i++) + { + if (users->usernames[i] == NULL) + { + users->usernames[i] = malloc(MAX_USER_LENGTH); + if (users->usernames[i] == NULL) + { + FURI_LOG_E(TAG, "Failed to allocate memory for username %zu", i); + return NULL; // Return false on memory allocation failure + } + } + } + return users; +} + +FlipSocialMessage *flip_social_user_messages_alloc() +{ + // Allocate memory for each username only if not already allocated + FlipSocialMessage *messages = malloc(sizeof(FlipSocialMessage)); + if (messages == NULL) + { + FURI_LOG_E(TAG, "Failed to allocate memory for messages"); + return NULL; + } + for (size_t i = 0; i < MAX_MESSAGE_USERS; i++) + { + if (messages->usernames[i] == NULL) + { + messages->usernames[i] = malloc(MAX_USER_LENGTH); + if (messages->usernames[i] == NULL) + { + FURI_LOG_E(TAG, "Failed to allocate memory for username %zu", i); + return NULL; // Return false on memory allocation failure + } + } + if (messages->messages[i] == NULL) + { + messages->messages[i] = malloc(MAX_MESSAGE_LENGTH); + if (messages->messages[i] == NULL) + { + FURI_LOG_E(TAG, "Failed to allocate memory for message %zu", i); + return NULL; // Return false on memory allocation failure + } + } + } + return messages; +} + +void flip_social_free_message_users() +{ + if (flip_social_message_users == NULL) + { + FURI_LOG_E(TAG, "Message users model is NULL"); + return; + } + for (int i = 0; i < flip_social_message_users->count; i++) + { + if (flip_social_message_users->usernames[i]) + { + free(flip_social_message_users->usernames[i]); + } + } +} + +void flip_social_free_messages() +{ + if (flip_social_messages == NULL) + { + FURI_LOG_E(TAG, "Messages model is NULL"); + return; + } + for (int i = 0; i < flip_social_messages->count; i++) + { + if (flip_social_messages->usernames[i]) + { + free(flip_social_messages->usernames[i]); + } + if (flip_social_messages->messages[i]) + { + free(flip_social_messages->messages[i]); + } + } +} + +bool flip_social_update_messages_submenu() +{ + if (app_instance->submenu_messages == NULL) + { + FURI_LOG_E(TAG, "Submenu is NULL"); + return false; + } + if (flip_social_message_users == NULL) + { + FURI_LOG_E(TAG, "Message users model is NULL"); + return false; + } + submenu_reset(app_instance->submenu_messages); + submenu_set_header(app_instance->submenu_messages, "Messages"); + submenu_add_item(app_instance->submenu_messages, "[New Message]", FlipSocialSubmenuLoggedInIndexMessagesNewMessage, flip_social_callback_submenu_choices, app_instance); + for (int i = 0; i < flip_social_message_users->count; i++) + { + submenu_add_item(app_instance->submenu_messages, flip_social_message_users->usernames[i], FlipSocialSubmenuLoggedInIndexMessagesUsersStart + i, flip_social_callback_submenu_choices, app_instance); + } + return true; +} + +bool flip_social_update_submenu_user_choices() +{ + if (app_instance->submenu_messages_user_choices == NULL) + { + FURI_LOG_E(TAG, "Submenu is NULL"); + return false; + } + if (flip_social_explore == NULL) + { + FURI_LOG_E(TAG, "Explore model is NULL"); + return false; + } + submenu_reset(app_instance->submenu_messages_user_choices); + submenu_set_header(app_instance->submenu_messages_user_choices, "Users"); + for (int i = 0; i < flip_social_explore->count; i++) + { + submenu_add_item(app_instance->submenu_messages_user_choices, flip_social_explore->usernames[i], FlipSocialSubmenuLoggedInIndexMessagesUserChoicesIndexStart + i, flip_social_callback_submenu_choices, app_instance); + } + return true; +} + +// Get all the users that have sent messages to the logged in user +bool flip_social_get_message_users() +{ + if (app_instance->login_username_logged_out == NULL) + { + FURI_LOG_E(TAG, "Username is NULL"); + return false; + } + char command[128]; + snprintf( + fhttp.file_path, + sizeof(fhttp.file_path), + STORAGE_EXT_PATH_PREFIX "/apps_data/flip_rss/message_users.txt"); + + fhttp.save_received_data = true; + char *headers = jsmn("Content-Type", "application/json"); + snprintf(command, 128, "https://www.flipsocial.net/api/messages/%s/get/list/", app_instance->login_username_logged_out); + bool success = flipper_http_get_request_with_headers(command, headers); + free(headers); + if (!success) + { + FURI_LOG_E(TAG, "Failed to send HTTP request for messages"); + fhttp.state = ISSUE; + return false; + } + fhttp.state = RECEIVING; + return true; +} + +// Get all the messages between the logged in user and the selected user +bool flip_social_get_messages_with_user() +{ + if (app_instance->login_username_logged_out == NULL) + { + FURI_LOG_E(TAG, "Username is NULL"); + return false; + } + char command[128]; + snprintf( + fhttp.file_path, + sizeof(fhttp.file_path), + STORAGE_EXT_PATH_PREFIX "/apps_data/flip_rss/messages.txt"); + + fhttp.save_received_data = true; + char *headers = jsmn("Content-Type", "application/json"); + snprintf(command, 128, "https://www.flipsocial.net/api/messages/%s/get/%s/", app_instance->login_username_logged_out, flip_social_message_users->usernames[flip_social_message_users->index]); + bool success = flipper_http_get_request_with_headers(command, headers); + free(headers); + if (!success) + { + FURI_LOG_E(TAG, "Failed to send HTTP request for messages"); + fhttp.state = ISSUE; + return false; + } + fhttp.state = RECEIVING; + return true; +} + +// Parse the users that have sent messages to the logged-in user +bool flip_social_parse_json_message_users() +{ + // load the received data from the saved file + FuriString *message_data = flipper_http_load_from_file(fhttp.file_path); + if (message_data == NULL) + { + FURI_LOG_E(TAG, "Failed to load received data from file."); + return false; + } + char *data_cstr = (char *)furi_string_get_cstr(message_data); + if (data_cstr == NULL) + { + FURI_LOG_E(TAG, "Failed to get C-string from FuriString."); + furi_string_free(message_data); + return false; + } + + // Allocate memory for each username only if not already allocated + flip_social_message_users = flip_social_messages_alloc(); + if (flip_social_message_users == NULL) + { + FURI_LOG_E(TAG, "Failed to allocate memory for message users."); + furi_string_free(message_data); + free(data_cstr); + return false; + } + + // Remove newlines + char *pos = data_cstr; + while ((pos = strchr(pos, '\n')) != NULL) + { + *pos = ' '; + } + + // Initialize message users count + flip_social_message_users->count = 0; + + // Extract the users array from the JSON + char *json_users = get_json_value("users", data_cstr, MAX_TOKENS); + if (json_users == NULL) + { + FURI_LOG_E(TAG, "Failed to parse users array."); + furi_string_free(message_data); + free(data_cstr); + return false; + } + + // Manual tokenization for comma-separated values + char *start = json_users + 1; // Skip the opening bracket + char *end; + while ((end = strchr(start, ',')) != NULL && flip_social_message_users->count < MAX_MESSAGE_USERS) + { + *end = '\0'; // Null-terminate the current token + + // Remove quotes + if (*start == '"') + start++; + if (*(end - 1) == '"') + *(end - 1) = '\0'; + + // Copy username to pre-allocated memory + snprintf(flip_social_message_users->usernames[flip_social_message_users->count], MAX_USER_LENGTH, "%s", start); + flip_social_message_users->count++; + start = end + 1; + } + + // Handle the last token + if (*start != '\0' && flip_social_message_users->count < MAX_MESSAGE_USERS) + { + if (*start == '"') + start++; + if (*(start + strlen(start) - 1) == ']') + *(start + strlen(start) - 1) = '\0'; + if (*(start + strlen(start) - 1) == '"') + *(start + strlen(start) - 1) = '\0'; + + snprintf(flip_social_message_users->usernames[flip_social_message_users->count], MAX_USER_LENGTH, "%s", start); + flip_social_message_users->count++; + } + + // Add submenu items for the users + flip_social_update_messages_submenu(); + + // Free the JSON data + free(json_users); + free(start); + free(end); + furi_string_free(message_data); + free(data_cstr); + return true; +} + +// Parse the users that the logged in user can message +bool flip_social_parse_json_message_user_choices() +{ + // load the received data from the saved file + FuriString *user_data = flipper_http_load_from_file(fhttp.file_path); + if (user_data == NULL) + { + FURI_LOG_E(TAG, "Failed to load received data from file."); + return false; + } + char *data_cstr = (char *)furi_string_get_cstr(user_data); + if (data_cstr == NULL) + { + FURI_LOG_E(TAG, "Failed to get C-string from FuriString."); + furi_string_free(user_data); + return false; + } + + // Allocate memory for each username only if not already allocated + flip_social_explore = flip_social_explore_alloc(); + if (flip_social_explore == NULL) + { + FURI_LOG_E(TAG, "Failed to allocate memory for explore usernames."); + furi_string_free(user_data); + free(data_cstr); + return false; + } + + // Remove newlines + char *pos = data_cstr; + while ((pos = strchr(pos, '\n')) != NULL) + { + *pos = ' '; + } + + // Initialize explore count + flip_social_explore->count = 0; + + // Extract the users array from the JSON + char *json_users = get_json_value("users", data_cstr, MAX_TOKENS); + if (json_users == NULL) + { + FURI_LOG_E(TAG, "Failed to parse users array."); + furi_string_free(user_data); + free(data_cstr); + return false; + } + + // Manual tokenization for comma-separated values + char *start = json_users + 1; // Skip the opening bracket + char *end; + while ((end = strchr(start, ',')) != NULL && flip_social_explore->count < MAX_EXPLORE_USERS) + { + *end = '\0'; // Null-terminate the current token + + // Remove quotes + if (*start == '"') + start++; + if (*(end - 1) == '"') + *(end - 1) = '\0'; + + // Copy username to pre-allocated memory + snprintf(flip_social_explore->usernames[flip_social_explore->count], MAX_USER_LENGTH, "%s", start); + flip_social_explore->count++; + start = end + 1; + } + + // Handle the last token + if (*start != '\0' && flip_social_explore->count < MAX_EXPLORE_USERS) + { + if (*start == '"') + start++; + if (*(start + strlen(start) - 1) == ']') + *(start + strlen(start) - 1) = '\0'; + if (*(start + strlen(start) - 1) == '"') + *(start + strlen(start) - 1) = '\0'; + + snprintf(flip_social_explore->usernames[flip_social_explore->count], MAX_USER_LENGTH, "%s", start); + flip_social_explore->count++; + } + + // Add submenu items for the users + flip_social_update_submenu_user_choices(); + + // Free the JSON data + free(json_users); + free(start); + free(end); + furi_string_free(user_data); + free(data_cstr); + return true; +} + +// parse messages between the logged in user and the selected user +bool flip_social_parse_json_messages() +{ + // load the received data from the saved file + FuriString *message_data = flipper_http_load_from_file(fhttp.file_path); + if (message_data == NULL) + { + FURI_LOG_E(TAG, "Failed to load received data from file."); + return false; + } + char *data_cstr = (char *)furi_string_get_cstr(message_data); + if (data_cstr == NULL) + { + FURI_LOG_E(TAG, "Failed to get C-string from FuriString."); + furi_string_free(message_data); + return false; + } + + // Allocate memory for each message only if not already allocated + flip_social_messages = flip_social_user_messages_alloc(); + if (!flip_social_messages) + { + FURI_LOG_E(TAG, "Failed to allocate memory for messages."); + furi_string_free(message_data); + free(data_cstr); + return false; + } + + // Remove newlines + char *pos = data_cstr; + while ((pos = strchr(pos, '\n')) != NULL) + { + *pos = ' '; + } + + // Initialize messages count + flip_social_messages->count = 0; + + // Iterate through the messages array + for (int i = 0; i < MAX_MESSAGES; i++) + { + // Parse each item in the array + char *item = get_json_array_value("conversations", i, data_cstr, MAX_TOKENS); + if (item == NULL) + { + break; + } + + // Extract individual fields from the JSON object + char *sender = get_json_value("sender", item, MAX_TOKENS); + char *content = get_json_value("content", item, MAX_TOKENS); + + if (sender == NULL || content == NULL) + { + FURI_LOG_E(TAG, "Failed to parse item fields."); + free(item); + continue; + } + + // Store parsed values in pre-allocated memory + snprintf(flip_social_messages->usernames[i], MAX_USER_LENGTH, "%s", sender); + snprintf(flip_social_messages->messages[i], MAX_MESSAGE_LENGTH, "%s", content); + flip_social_messages->count++; + + free(item); + free(sender); + free(content); + } + furi_string_free(message_data); + free(data_cstr); + return true; +} \ No newline at end of file diff --git a/messages/flip_social_messages.h b/messages/flip_social_messages.h new file mode 100644 index 000000000..9e5ce89c3 --- /dev/null +++ b/messages/flip_social_messages.h @@ -0,0 +1,27 @@ +#ifndef FLIP_SOCIAL_MESSAGES_H +#define FLIP_SOCIAL_MESSAGES_H + +#include "flip_social.h" +#include +#include + +FlipSocialModel2 *flip_social_messages_alloc(); +FlipSocialMessage *flip_social_user_messages_alloc(); +void flip_social_free_message_users(); +void flip_social_free_messages(); +bool flip_social_update_messages_submenu(); +bool flip_social_update_submenu_user_choices(); +// Get all the users that have sent messages to the logged in user +bool flip_social_get_message_users(); +// Get all the messages between the logged in user and the selected user +bool flip_social_get_messages_with_user(); +// Parse the users that have sent messages to the logged-in user +bool flip_social_parse_json_message_users(); + +// Parse the users that the logged in user can message +bool flip_social_parse_json_message_user_choices(); + +// parse messages between the logged in user and the selected user +bool flip_social_parse_json_messages(); + +#endif \ No newline at end of file diff --git a/text_input/uart_text_input.c b/text_input/uart_text_input.c new file mode 100644 index 000000000..572167cc4 --- /dev/null +++ b/text_input/uart_text_input.c @@ -0,0 +1,784 @@ +// from https://github.com/xMasterX/all-the-plugins/blob/dev/base_pack/uart_terminal/uart_text_input.c +// all credits to xMasterX for the code +#include "uart_text_input.h" +#include +#include "flip_social_icons.h" +#include + +struct UART_TextInput +{ + View *view; + FuriTimer *timer; +}; + +typedef struct +{ + const char text; + const uint8_t x; + const uint8_t y; +} UART_TextInputKey; + +typedef struct +{ + const char *header; + char *text_buffer; + size_t text_buffer_size; + bool clear_default_text; + + UART_TextInputCallback callback; + void *callback_context; + + uint8_t selected_row; + uint8_t selected_column; + + UART_TextInputValidatorCallback validator_callback; + void *validator_callback_context; + FuriString *validator_text; + bool valadator_message_visible; +} UART_TextInputModel; + +static const uint8_t keyboard_origin_x = 1; +static const uint8_t keyboard_origin_y = 29; +static const uint8_t keyboard_row_count = 4; + +#define mode_AT "Send AT command to UART" + +#define ENTER_KEY '\r' +#define BACKSPACE_KEY '\b' + +static const UART_TextInputKey keyboard_keys_row_1[] = { + {'{', 1, 0}, + {'(', 9, 0}, + {'[', 17, 0}, + {'|', 25, 0}, + {'@', 33, 0}, + {'&', 41, 0}, + {'#', 49, 0}, + {';', 57, 0}, + {'^', 65, 0}, + {'*', 73, 0}, + {'`', 81, 0}, + {'"', 89, 0}, + {'~', 97, 0}, + {'\'', 105, 0}, + {'.', 113, 0}, + {'/', 120, 0}, +}; + +static const UART_TextInputKey keyboard_keys_row_2[] = { + {'q', 1, 10}, + {'w', 9, 10}, + {'e', 17, 10}, + {'r', 25, 10}, + {'t', 33, 10}, + {'y', 41, 10}, + {'u', 49, 10}, + {'i', 57, 10}, + {'o', 65, 10}, + {'p', 73, 10}, + {'0', 81, 10}, + {'1', 89, 10}, + {'2', 97, 10}, + {'3', 105, 10}, + {'=', 113, 10}, + {'-', 120, 10}, +}; + +static const UART_TextInputKey keyboard_keys_row_3[] = { + {'a', 1, 21}, + {'s', 9, 21}, + {'d', 18, 21}, + {'f', 25, 21}, + {'g', 33, 21}, + {'h', 41, 21}, + {'j', 49, 21}, + {'k', 57, 21}, + {'l', 65, 21}, + {BACKSPACE_KEY, 72, 13}, + {'4', 89, 21}, + {'5', 97, 21}, + {'6', 105, 21}, + {'$', 113, 21}, + {'%', 120, 21}, + +}; + +static const UART_TextInputKey keyboard_keys_row_4[] = { + {'z', 1, 33}, + {'x', 9, 33}, + {'c', 18, 33}, + {'v', 25, 33}, + {'b', 33, 33}, + {'n', 41, 33}, + {'m', 49, 33}, + {'_', 57, 33}, + {ENTER_KEY, 64, 24}, + {'7', 89, 33}, + {'8', 97, 33}, + {'9', 105, 33}, + {'!', 113, 33}, + {'+', 120, 33}, +}; + +static uint8_t get_row_size(uint8_t row_index) +{ + uint8_t row_size = 0; + + switch (row_index + 1) + { + case 1: + row_size = sizeof(keyboard_keys_row_1) / sizeof(UART_TextInputKey); + break; + case 2: + row_size = sizeof(keyboard_keys_row_2) / sizeof(UART_TextInputKey); + break; + case 3: + row_size = sizeof(keyboard_keys_row_3) / sizeof(UART_TextInputKey); + break; + case 4: + row_size = sizeof(keyboard_keys_row_4) / sizeof(UART_TextInputKey); + break; + } + + return row_size; +} + +static const UART_TextInputKey *get_row(uint8_t row_index) +{ + const UART_TextInputKey *row = NULL; + + switch (row_index + 1) + { + case 1: + row = keyboard_keys_row_1; + break; + case 2: + row = keyboard_keys_row_2; + break; + case 3: + row = keyboard_keys_row_3; + break; + case 4: + row = keyboard_keys_row_4; + break; + } + + return row; +} + +static char get_selected_char(UART_TextInputModel *model) +{ + return get_row(model->selected_row)[model->selected_column].text; +} + +static bool char_is_lowercase(char letter) +{ + return (letter >= 0x61 && letter <= 0x7A); +} + +static bool char_is_uppercase(char letter) +{ + return (letter >= 0x41 && letter <= 0x5A); +} + +static char char_to_lowercase(const char letter) +{ + switch (letter) + { + case ' ': + return 0x5f; + break; + case ')': + return 0x28; + break; + case '}': + return 0x7b; + break; + case ']': + return 0x5b; + break; + case '\\': + return 0x2f; + break; + case ':': + return 0x3b; + break; + case ',': + return 0x2e; + break; + case '?': + return 0x21; + break; + case '>': + return 0x3c; + break; + } + if (char_is_uppercase(letter)) + { + return (letter + 0x20); + } + else + { + return letter; + } +} + +static char char_to_uppercase(const char letter) +{ + switch (letter) + { + case '_': + return 0x20; + break; + case '(': + return 0x29; + break; + case '{': + return 0x7d; + break; + case '[': + return 0x5d; + break; + case '/': + return 0x5c; + break; + case ';': + return 0x3a; + break; + case '.': + return 0x2c; + break; + case '!': + return 0x3f; + break; + case '<': + return 0x3e; + break; + } + if (char_is_lowercase(letter)) + { + return (letter - 0x20); + } + else + { + return letter; + } +} + +static void uart_text_input_backspace_cb(UART_TextInputModel *model) +{ + uint8_t text_length = model->clear_default_text ? 1 : strlen(model->text_buffer); + if (text_length > 0) + { + model->text_buffer[text_length - 1] = 0; + } +} + +static void uart_text_input_view_draw_callback(Canvas *canvas, void *_model) +{ + UART_TextInputModel *model = _model; + // uint8_t text_length = model->text_buffer ? strlen(model->text_buffer) : 0; + uint8_t needed_string_width = canvas_width(canvas) - 8; + uint8_t start_pos = 4; + + const char *text = model->text_buffer; + + canvas_clear(canvas); + canvas_set_color(canvas, ColorBlack); + + canvas_draw_str(canvas, 2, 7, model->header); + elements_slightly_rounded_frame(canvas, 1, 8, 126, 12); + + if (canvas_string_width(canvas, text) > needed_string_width) + { + canvas_draw_str(canvas, start_pos, 17, "..."); + start_pos += 6; + needed_string_width -= 8; + } + + while (text != 0 && canvas_string_width(canvas, text) > needed_string_width) + { + text++; + } + + if (model->clear_default_text) + { + elements_slightly_rounded_box( + canvas, start_pos - 1, 14, canvas_string_width(canvas, text) + 2, 10); + canvas_set_color(canvas, ColorWhite); + } + else + { + canvas_draw_str(canvas, start_pos + canvas_string_width(canvas, text) + 1, 18, "|"); + canvas_draw_str(canvas, start_pos + canvas_string_width(canvas, text) + 2, 18, "|"); + } + canvas_draw_str(canvas, start_pos, 17, text); + + canvas_set_font(canvas, FontKeyboard); + + for (uint8_t row = 0; row <= keyboard_row_count; row++) + { + const uint8_t column_count = get_row_size(row); + const UART_TextInputKey *keys = get_row(row); + + for (size_t column = 0; column < column_count; column++) + { + if (keys[column].text == ENTER_KEY) + { + canvas_set_color(canvas, ColorBlack); + if (model->selected_row == row && model->selected_column == column) + { + canvas_draw_icon( + canvas, + keyboard_origin_x + keys[column].x, + keyboard_origin_y + keys[column].y, + &I_KeySaveSelected_24x11); + } + else + { + canvas_draw_icon( + canvas, + keyboard_origin_x + keys[column].x, + keyboard_origin_y + keys[column].y, + &I_KeySave_24x11); + } + } + else if (keys[column].text == BACKSPACE_KEY) + { + canvas_set_color(canvas, ColorBlack); + if (model->selected_row == row && model->selected_column == column) + { + canvas_draw_icon( + canvas, + keyboard_origin_x + keys[column].x, + keyboard_origin_y + keys[column].y, + &I_KeyBackspaceSelected_16x9); + } + else + { + canvas_draw_icon( + canvas, + keyboard_origin_x + keys[column].x, + keyboard_origin_y + keys[column].y, + &I_KeyBackspace_16x9); + } + } + else + { + if (model->selected_row == row && model->selected_column == column) + { + canvas_set_color(canvas, ColorBlack); + canvas_draw_box( + canvas, + keyboard_origin_x + keys[column].x - 1, + keyboard_origin_y + keys[column].y - 8, + 7, + 10); + canvas_set_color(canvas, ColorWhite); + } + else + { + canvas_set_color(canvas, ColorBlack); + } + if (0 == strcmp(model->header, mode_AT)) + { + canvas_draw_glyph( + canvas, + keyboard_origin_x + keys[column].x, + keyboard_origin_y + keys[column].y, + char_to_uppercase(keys[column].text)); + } + else + { + canvas_draw_glyph( + canvas, + keyboard_origin_x + keys[column].x, + keyboard_origin_y + keys[column].y, + keys[column].text); + } + } + } + } + if (model->valadator_message_visible) + { + canvas_set_font(canvas, FontSecondary); + canvas_set_color(canvas, ColorWhite); + canvas_draw_box(canvas, 8, 10, 110, 48); + canvas_set_color(canvas, ColorBlack); + canvas_draw_icon(canvas, 10, 14, &I_WarningDolphin_45x42); + canvas_draw_rframe(canvas, 8, 8, 112, 50, 3); + canvas_draw_rframe(canvas, 9, 9, 110, 48, 2); + elements_multiline_text(canvas, 62, 20, furi_string_get_cstr(model->validator_text)); + canvas_set_font(canvas, FontKeyboard); + } +} + +static void +uart_text_input_handle_up(UART_TextInput *uart_text_input, UART_TextInputModel *model) +{ + UNUSED(uart_text_input); + if (model->selected_row > 0) + { + model->selected_row--; + if (model->selected_column > get_row_size(model->selected_row) - 6) + { + model->selected_column = model->selected_column + 1; + } + } +} + +static void +uart_text_input_handle_down(UART_TextInput *uart_text_input, UART_TextInputModel *model) +{ + UNUSED(uart_text_input); + if (model->selected_row < keyboard_row_count - 1) + { + model->selected_row++; + if (model->selected_column > get_row_size(model->selected_row) - 4) + { + model->selected_column = model->selected_column - 1; + } + } +} + +static void +uart_text_input_handle_left(UART_TextInput *uart_text_input, UART_TextInputModel *model) +{ + UNUSED(uart_text_input); + if (model->selected_column > 0) + { + model->selected_column--; + } + else + { + model->selected_column = get_row_size(model->selected_row) - 1; + } +} + +static void +uart_text_input_handle_right(UART_TextInput *uart_text_input, UART_TextInputModel *model) +{ + UNUSED(uart_text_input); + if (model->selected_column < get_row_size(model->selected_row) - 1) + { + model->selected_column++; + } + else + { + model->selected_column = 0; + } +} + +static void uart_text_input_handle_ok( + UART_TextInput *uart_text_input, + UART_TextInputModel *model, + bool shift) +{ + char selected = get_selected_char(model); + uint8_t text_length = strlen(model->text_buffer); + + if (0 == strcmp(model->header, mode_AT)) + { + selected = char_to_uppercase(selected); + } + + if (shift) + { + if (0 == strcmp(model->header, mode_AT)) + { + selected = char_to_lowercase(selected); + } + else + { + selected = char_to_uppercase(selected); + } + } + + if (selected == ENTER_KEY) + { + if (model->validator_callback && + (!model->validator_callback( + model->text_buffer, model->validator_text, model->validator_callback_context))) + { + model->valadator_message_visible = true; + furi_timer_start(uart_text_input->timer, furi_kernel_get_tick_frequency() * 4); + } + else if (model->callback != 0 && text_length > 0) + { + model->callback(model->callback_context); + } + } + else if (selected == BACKSPACE_KEY) + { + uart_text_input_backspace_cb(model); + } + else + { + if (model->clear_default_text) + { + text_length = 0; + } + if (text_length < (model->text_buffer_size - 1)) + { + model->text_buffer[text_length] = selected; + model->text_buffer[text_length + 1] = 0; + } + } + model->clear_default_text = false; +} + +static bool uart_text_input_view_input_callback(InputEvent *event, void *context) +{ + UART_TextInput *uart_text_input = context; + furi_assert(uart_text_input); + + bool consumed = false; + + // Acquire model + UART_TextInputModel *model = view_get_model(uart_text_input->view); + + if ((!(event->type == InputTypePress) && !(event->type == InputTypeRelease)) && + model->valadator_message_visible) + { + model->valadator_message_visible = false; + consumed = true; + } + else if (event->type == InputTypeShort) + { + consumed = true; + switch (event->key) + { + case InputKeyUp: + uart_text_input_handle_up(uart_text_input, model); + break; + case InputKeyDown: + uart_text_input_handle_down(uart_text_input, model); + break; + case InputKeyLeft: + uart_text_input_handle_left(uart_text_input, model); + break; + case InputKeyRight: + uart_text_input_handle_right(uart_text_input, model); + break; + case InputKeyOk: + uart_text_input_handle_ok(uart_text_input, model, false); + break; + default: + consumed = false; + break; + } + } + else if (event->type == InputTypeLong) + { + consumed = true; + switch (event->key) + { + case InputKeyUp: + uart_text_input_handle_up(uart_text_input, model); + break; + case InputKeyDown: + uart_text_input_handle_down(uart_text_input, model); + break; + case InputKeyLeft: + uart_text_input_handle_left(uart_text_input, model); + break; + case InputKeyRight: + uart_text_input_handle_right(uart_text_input, model); + break; + case InputKeyOk: + uart_text_input_handle_ok(uart_text_input, model, true); + break; + case InputKeyBack: + uart_text_input_backspace_cb(model); + break; + default: + consumed = false; + break; + } + } + else if (event->type == InputTypeRepeat) + { + consumed = true; + switch (event->key) + { + case InputKeyUp: + uart_text_input_handle_up(uart_text_input, model); + break; + case InputKeyDown: + uart_text_input_handle_down(uart_text_input, model); + break; + case InputKeyLeft: + uart_text_input_handle_left(uart_text_input, model); + break; + case InputKeyRight: + uart_text_input_handle_right(uart_text_input, model); + break; + case InputKeyBack: + uart_text_input_backspace_cb(model); + break; + default: + consumed = false; + break; + } + } + + // Commit model + view_commit_model(uart_text_input->view, consumed); + + return consumed; +} + +void uart_text_input_timer_callback(void *context) +{ + furi_assert(context); + UART_TextInput *uart_text_input = context; + + with_view_model( + uart_text_input->view, + UART_TextInputModel * model, + { model->valadator_message_visible = false; }, + true); +} + +UART_TextInput *uart_text_input_alloc() +{ + UART_TextInput *uart_text_input = malloc(sizeof(UART_TextInput)); + uart_text_input->view = view_alloc(); + view_set_context(uart_text_input->view, uart_text_input); + view_allocate_model(uart_text_input->view, ViewModelTypeLocking, sizeof(UART_TextInputModel)); + view_set_draw_callback(uart_text_input->view, uart_text_input_view_draw_callback); + view_set_input_callback(uart_text_input->view, uart_text_input_view_input_callback); + + uart_text_input->timer = + furi_timer_alloc(uart_text_input_timer_callback, FuriTimerTypeOnce, uart_text_input); + + with_view_model( + uart_text_input->view, + UART_TextInputModel * model, + { model->validator_text = furi_string_alloc(); }, + false); + + uart_text_input_reset(uart_text_input); + + return uart_text_input; +} + +void uart_text_input_free(UART_TextInput *uart_text_input) +{ + furi_assert(uart_text_input); + with_view_model( + uart_text_input->view, + UART_TextInputModel * model, + { furi_string_free(model->validator_text); }, + false); + + // Send stop command + furi_timer_stop(uart_text_input->timer); + // Release allocated memory + furi_timer_free(uart_text_input->timer); + + view_free(uart_text_input->view); + + free(uart_text_input); +} + +void uart_text_input_reset(UART_TextInput *uart_text_input) +{ + furi_assert(uart_text_input); + with_view_model( + uart_text_input->view, + UART_TextInputModel * model, + { + model->text_buffer_size = 0; + model->header = ""; + model->selected_row = 0; + model->selected_column = 0; + model->clear_default_text = false; + model->text_buffer = NULL; + model->text_buffer_size = 0; + model->callback = NULL; + model->callback_context = NULL; + model->validator_callback = NULL; + model->validator_callback_context = NULL; + furi_string_reset(model->validator_text); + model->valadator_message_visible = false; + }, + true); +} + +View *uart_text_input_get_view(UART_TextInput *uart_text_input) +{ + furi_assert(uart_text_input); + return uart_text_input->view; +} + +void uart_text_input_set_result_callback( + UART_TextInput *uart_text_input, + UART_TextInputCallback callback, + void *callback_context, + char *text_buffer, + size_t text_buffer_size, + bool clear_default_text) +{ + with_view_model( + uart_text_input->view, + UART_TextInputModel * model, + { + model->callback = callback; + model->callback_context = callback_context; + model->text_buffer = text_buffer; + model->text_buffer_size = text_buffer_size; + model->clear_default_text = clear_default_text; + if (text_buffer && text_buffer[0] != '\0') + { + // Set focus on Save + model->selected_row = 2; + model->selected_column = 8; + } + }, + true); +} + +void uart_text_input_set_validator( + UART_TextInput *uart_text_input, + UART_TextInputValidatorCallback callback, + void *callback_context) +{ + with_view_model( + uart_text_input->view, + UART_TextInputModel * model, + { + model->validator_callback = callback; + model->validator_callback_context = callback_context; + }, + true); +} + +UART_TextInputValidatorCallback +uart_text_input_get_validator_callback(UART_TextInput *uart_text_input) +{ + UART_TextInputValidatorCallback validator_callback = NULL; + with_view_model( + uart_text_input->view, + UART_TextInputModel * model, + { validator_callback = model->validator_callback; }, + false); + return validator_callback; +} + +void *uart_text_input_get_validator_callback_context(UART_TextInput *uart_text_input) +{ + void *validator_callback_context = NULL; + with_view_model( + uart_text_input->view, + UART_TextInputModel * model, + { validator_callback_context = model->validator_callback_context; }, + false); + return validator_callback_context; +} + +void uart_text_input_set_header_text(UART_TextInput *uart_text_input, const char *text) +{ + with_view_model( + uart_text_input->view, UART_TextInputModel * model, { model->header = text; }, true); +} diff --git a/text_input/uart_text_input.h b/text_input/uart_text_input.h new file mode 100644 index 000000000..319a3f628 --- /dev/null +++ b/text_input/uart_text_input.h @@ -0,0 +1,83 @@ +#pragma once +// from https://github.com/xMasterX/all-the-plugins/blob/dev/base_pack/uart_terminal/uart_text_input.c +// all credits to xMasterX for the code +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + + /** Text input anonymous structure */ + typedef struct UART_TextInput UART_TextInput; + typedef void (*UART_TextInputCallback)(void *context); + typedef bool (*UART_TextInputValidatorCallback)(const char *text, FuriString *error, void *context); + + /** Allocate and initialize text input + * + * This text input is used to enter string + * + * @return UART_TextInput instance + */ + UART_TextInput *uart_text_input_alloc(); + + /** Deinitialize and free text input + * + * @param uart_text_input UART_TextInput instance + */ + void uart_text_input_free(UART_TextInput *uart_text_input); + + /** Clean text input view Note: this function does not free memory + * + * @param uart_text_input Text input instance + */ + void uart_text_input_reset(UART_TextInput *uart_text_input); + + /** Get text input view + * + * @param uart_text_input UART_TextInput instance + * + * @return View instance that can be used for embedding + */ + View *uart_text_input_get_view(UART_TextInput *uart_text_input); + + /** Set text input result callback + * + * @param uart_text_input UART_TextInput instance + * @param callback callback fn + * @param callback_context callback context + * @param text_buffer pointer to YOUR text buffer, that we going + * to modify + * @param text_buffer_size YOUR text buffer size in bytes. Max string + * length will be text_buffer_size-1. + * @param clear_default_text clear text from text_buffer on first OK + * event + */ + void uart_text_input_set_result_callback( + UART_TextInput *uart_text_input, + UART_TextInputCallback callback, + void *callback_context, + char *text_buffer, + size_t text_buffer_size, + bool clear_default_text); + + void uart_text_input_set_validator( + UART_TextInput *uart_text_input, + UART_TextInputValidatorCallback callback, + void *callback_context); + + UART_TextInputValidatorCallback + uart_text_input_get_validator_callback(UART_TextInput *uart_text_input); + + void *uart_text_input_get_validator_callback_context(UART_TextInput *uart_text_input); + + /** Set text input header text + * + * @param uart_text_input UART_TextInput instance + * @param text text to be shown + */ + void uart_text_input_set_header_text(UART_TextInput *uart_text_input, const char *text); + +#ifdef __cplusplus +} +#endif From ea5fcd33f10864bdd1b47ba4aceadc82a670ff82 Mon Sep 17 00:00:00 2001 From: jblanked <82678820+jblanked@users.noreply.github.com> Date: Wed, 6 Nov 2024 20:50:47 -0500 Subject: [PATCH 2/3] FlipSocial - v0.6 --- app.png | Bin 198 -> 0 bytes easy_flipper.h | 596 -------------------- flip_social_callback.h | 1062 ----------------------------------- flip_social_draw.h | 1008 --------------------------------- flip_social_e.h | 310 ----------- flip_social_explore.h | 141 ----- flip_social_feed.h | 209 ------- flip_social_free.h | 294 ---------- flip_social_friends.h | 158 ------ flip_social_i.h | 542 ------------------ flip_social_messages.h | 396 ------------- flip_social_storage.h | 381 ------------- flipper_http.h | 1197 ---------------------------------------- jsmn.h | 865 ----------------------------- uart_text_input.h | 803 --------------------------- 15 files changed, 7962 deletions(-) delete mode 100644 app.png delete mode 100644 easy_flipper.h delete mode 100644 flip_social_callback.h delete mode 100644 flip_social_draw.h delete mode 100644 flip_social_e.h delete mode 100644 flip_social_explore.h delete mode 100644 flip_social_feed.h delete mode 100644 flip_social_free.h delete mode 100644 flip_social_friends.h delete mode 100644 flip_social_i.h delete mode 100644 flip_social_messages.h delete mode 100644 flip_social_storage.h delete mode 100644 flipper_http.h delete mode 100644 jsmn.h delete mode 100644 uart_text_input.h diff --git a/app.png b/app.png deleted file mode 100644 index f13be734c01aa48f6632722db7177e5277171042..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 198 zcmeAS@N?(olHy`uVBq!ia0vp^AT}2VGmzZ%#=aj&SqAuoxW0e?e%-ot=H}+-&!6AD zd-vVDcOO4~%+1X$Cnu+^t-WN)l70L3y?XWP`t|D~A|l1b#j|G33JVL{yLWG4|M60w zAs(JCjv*Y^WluT^9Z=w4ImpV)k#*(I|BZpaOhTLWtUB5}*cWWRwsOY{(`wD)n;VU| yXBVEFa>M#b*amr-^xdlZKlZ5QU--6UnSBj2Q}m>lS#yDAGI+ZBxvX -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#define EASY_TAG "EasyFlipper" - -/** - * @brief Navigation callback for exiting the application - * @param context The context - unused - * @return next view id (VIEW_NONE to exit the app) - */ -uint32_t easy_flipper_callback_exit_app(void *context) -{ - // Exit the application - if (!context) - { - FURI_LOG_E(EASY_TAG, "Context is NULL"); - return VIEW_NONE; - } - UNUSED(context); - return VIEW_NONE; // Return VIEW_NONE to exit the app -} - -/** - * @brief Initialize a buffer - * @param buffer The buffer to initialize - * @param buffer_size The size of the buffer - * @return true if successful, false otherwise - */ -bool easy_flipper_set_buffer(char **buffer, uint32_t buffer_size) -{ - if (!buffer) - { - FURI_LOG_E(EASY_TAG, "Invalid arguments provided to set_buffer"); - return false; - } - *buffer = (char *)malloc(buffer_size); - if (!*buffer) - { - FURI_LOG_E(EASY_TAG, "Failed to allocate buffer"); - return false; - } - *buffer[0] = '\0'; - return true; -} - -/** - * @brief Initialize a View object - * @param view The View object to initialize - * @param view_id The ID/Index of the view - * @param draw_callback The draw callback function (set to NULL if not needed) - * @param input_callback The input callback function (set to NULL if not needed) - * @param previous_callback The previous callback function (can be set to NULL) - * @param view_dispatcher The ViewDispatcher object - * @return true if successful, false otherwise - */ -bool easy_flipper_set_view( - View **view, - int32_t view_id, - void draw_callback(Canvas *, void *), - bool input_callback(InputEvent *, void *), - uint32_t (*previous_callback)(void *), - ViewDispatcher **view_dispatcher, - void *context) -{ - if (!view || !view_dispatcher) - { - FURI_LOG_E(EASY_TAG, "Invalid arguments provided to set_view"); - return false; - } - *view = view_alloc(); - if (!*view) - { - FURI_LOG_E(EASY_TAG, "Failed to allocate View"); - return false; - } - if (draw_callback) - { - view_set_draw_callback(*view, draw_callback); - } - if (input_callback) - { - view_set_input_callback(*view, input_callback); - } - if (context) - { - view_set_context(*view, context); - } - if (previous_callback) - { - view_set_previous_callback(*view, previous_callback); - } - view_dispatcher_add_view(*view_dispatcher, view_id, *view); - return true; -} - -/** - * @brief Initialize a ViewDispatcher object - * @param view_dispatcher The ViewDispatcher object to initialize - * @param gui The GUI object - * @param context The context to pass to the event callback - * @return true if successful, false otherwise - */ -bool easy_flipper_set_view_dispatcher(ViewDispatcher **view_dispatcher, Gui *gui, void *context) -{ - if (!view_dispatcher) - { - FURI_LOG_E(EASY_TAG, "Invalid arguments provided to set_view_dispatcher"); - return false; - } - *view_dispatcher = view_dispatcher_alloc(); - if (!*view_dispatcher) - { - FURI_LOG_E(EASY_TAG, "Failed to allocate ViewDispatcher"); - return false; - } - view_dispatcher_attach_to_gui(*view_dispatcher, gui, ViewDispatcherTypeFullscreen); - if (context) - { - view_dispatcher_set_event_callback_context(*view_dispatcher, context); - } - return true; -} - -/** - * @brief Initialize a Submenu object - * @note This does not set the items in the submenu - * @param submenu The Submenu object to initialize - * @param view_id The ID/Index of the view - * @param title The title/header of the submenu - * @param previous_callback The previous callback function (can be set to NULL) - * @param view_dispatcher The ViewDispatcher object - * @return true if successful, false otherwise - */ -bool easy_flipper_set_submenu( - Submenu **submenu, - int32_t view_id, - char *title, - uint32_t(previous_callback)(void *), - ViewDispatcher **view_dispatcher) -{ - if (!submenu) - { - FURI_LOG_E(EASY_TAG, "Invalid arguments provided to set_submenu"); - return false; - } - *submenu = submenu_alloc(); - if (!*submenu) - { - FURI_LOG_E(EASY_TAG, "Failed to allocate Submenu"); - return false; - } - if (title) - { - submenu_set_header(*submenu, title); - } - if (previous_callback) - { - view_set_previous_callback(submenu_get_view(*submenu), previous_callback); - } - view_dispatcher_add_view(*view_dispatcher, view_id, submenu_get_view(*submenu)); - return true; -} -/** - * @brief Initialize a Menu object - * @note This does not set the items in the menu - * @param menu The Menu object to initialize - * @param view_id The ID/Index of the view - * @param item_callback The item callback function - * @param previous_callback The previous callback function (can be set to NULL) - * @param view_dispatcher The ViewDispatcher object - * @return true if successful, false otherwise - */ -bool easy_flipper_set_menu( - Menu **menu, - int32_t view_id, - uint32_t(previous_callback)(void *), - ViewDispatcher **view_dispatcher) -{ - if (!menu) - { - FURI_LOG_E(EASY_TAG, "Invalid arguments provided to set_menu"); - return false; - } - *menu = menu_alloc(); - if (!*menu) - { - FURI_LOG_E(EASY_TAG, "Failed to allocate Menu"); - return false; - } - if (previous_callback) - { - view_set_previous_callback(menu_get_view(*menu), previous_callback); - } - view_dispatcher_add_view(*view_dispatcher, view_id, menu_get_view(*menu)); - return true; -} - -/** - * @brief Initialize a Widget object - * @param widget The Widget object to initialize - * @param view_id The ID/Index of the view - * @param text The text to display in the widget - * @param previous_callback The previous callback function (can be set to NULL) - * @param view_dispatcher The ViewDispatcher object - * @return true if successful, false otherwise - */ -bool easy_flipper_set_widget( - Widget **widget, - int32_t view_id, - char *text, - uint32_t(previous_callback)(void *), - ViewDispatcher **view_dispatcher) -{ - if (!widget) - { - FURI_LOG_E(EASY_TAG, "Invalid arguments provided to set_widget"); - return false; - } - *widget = widget_alloc(); - if (!*widget) - { - FURI_LOG_E(EASY_TAG, "Failed to allocate Widget"); - return false; - } - if (text) - { - widget_add_text_scroll_element(*widget, 0, 0, 128, 64, text); - } - if (previous_callback) - { - view_set_previous_callback(widget_get_view(*widget), previous_callback); - } - view_dispatcher_add_view(*view_dispatcher, view_id, widget_get_view(*widget)); - return true; -} - -/** - * @brief Initialize a VariableItemList object - * @note This does not set the items in the VariableItemList - * @param variable_item_list The VariableItemList object to initialize - * @param view_id The ID/Index of the view - * @param enter_callback The enter callback function (can be set to NULL) - * @param previous_callback The previous callback function (can be set to NULL) - * @param view_dispatcher The ViewDispatcher object - * @param context The context to pass to the enter callback (usually the app) - * @return true if successful, false otherwise - */ -bool easy_flipper_set_variable_item_list( - VariableItemList **variable_item_list, - int32_t view_id, - void (*enter_callback)(void *, uint32_t), - uint32_t(previous_callback)(void *), - ViewDispatcher **view_dispatcher, - void *context) -{ - if (!variable_item_list) - { - FURI_LOG_E(EASY_TAG, "Invalid arguments provided to set_variable_item_list"); - return false; - } - *variable_item_list = variable_item_list_alloc(); - if (!*variable_item_list) - { - FURI_LOG_E(EASY_TAG, "Failed to allocate VariableItemList"); - return false; - } - if (enter_callback) - { - variable_item_list_set_enter_callback(*variable_item_list, enter_callback, context); - } - if (previous_callback) - { - view_set_previous_callback(variable_item_list_get_view(*variable_item_list), previous_callback); - } - view_dispatcher_add_view(*view_dispatcher, view_id, variable_item_list_get_view(*variable_item_list)); - return true; -} - -/** - * @brief Initialize a TextInput object - * @param text_input The TextInput object to initialize - * @param view_id The ID/Index of the view - * @param previous_callback The previous callback function (can be set to NULL) - * @param view_dispatcher The ViewDispatcher object - * @return true if successful, false otherwise - */ -bool easy_flipper_set_text_input( - TextInput **text_input, - int32_t view_id, - char *header_text, - char *text_input_temp_buffer, - uint32_t text_input_buffer_size, - void (*result_callback)(void *), - uint32_t(previous_callback)(void *), - ViewDispatcher **view_dispatcher, - void *context) -{ - if (!text_input) - { - FURI_LOG_E(EASY_TAG, "Invalid arguments provided to set_text_input"); - return false; - } - *text_input = text_input_alloc(); - if (!*text_input) - { - FURI_LOG_E(EASY_TAG, "Failed to allocate TextInput"); - return false; - } - if (previous_callback) - { - view_set_previous_callback(text_input_get_view(*text_input), previous_callback); - } - if (header_text) - { - text_input_set_header_text(*text_input, header_text); - } - if (text_input_temp_buffer && text_input_buffer_size && result_callback) - { - text_input_set_result_callback(*text_input, result_callback, context, text_input_temp_buffer, text_input_buffer_size, false); - } - view_dispatcher_add_view(*view_dispatcher, view_id, text_input_get_view(*text_input)); - return true; -} - -/** - * @brief Initialize a UART_TextInput object - * @param uart_text_input The UART_TextInput object to initialize - * @param view_id The ID/Index of the view - * @param previous_callback The previous callback function (can be set to NULL) - * @param view_dispatcher The ViewDispatcher object - * @return true if successful, false otherwise - */ -bool easy_flipper_set_uart_text_input( - UART_TextInput **uart_text_input, - int32_t view_id, - char *header_text, - char *uart_text_input_temp_buffer, - uint32_t uart_text_input_buffer_size, - void (*result_callback)(void *), - uint32_t(previous_callback)(void *), - ViewDispatcher **view_dispatcher, - void *context) -{ - if (!uart_text_input) - { - FURI_LOG_E(EASY_TAG, "Invalid arguments provided to set_uart_text_input"); - return false; - } - *uart_text_input = uart_text_input_alloc(); - if (!*uart_text_input) - { - FURI_LOG_E(EASY_TAG, "Failed to allocate UART_TextInput"); - return false; - } - if (previous_callback) - { - view_set_previous_callback(uart_text_input_get_view(*uart_text_input), previous_callback); - } - if (header_text) - { - uart_text_input_set_header_text(*uart_text_input, header_text); - } - if (uart_text_input_temp_buffer && uart_text_input_buffer_size && result_callback) - { - uart_text_input_set_result_callback(*uart_text_input, result_callback, context, uart_text_input_temp_buffer, uart_text_input_buffer_size, false); - } - view_dispatcher_add_view(*view_dispatcher, view_id, uart_text_input_get_view(*uart_text_input)); - return true; -} - -/** - * @brief Initialize a DialogEx object - * @param dialog_ex The DialogEx object to initialize - * @param view_id The ID/Index of the view - * @param header The header of the dialog - * @param header_x The x coordinate of the header - * @param header_y The y coordinate of the header - * @param text The text of the dialog - * @param text_x The x coordinate of the dialog - * @param text_y The y coordinate of the dialog - * @param left_button_text The text of the left button - * @param right_button_text The text of the right button - * @param center_button_text The text of the center button - * @param result_callback The result callback function - * @param previous_callback The previous callback function (can be set to NULL) - * @param view_dispatcher The ViewDispatcher object - * @param context The context to pass to the result callback - * @return true if successful, false otherwise - */ -bool easy_flipper_set_dialog_ex( - DialogEx **dialog_ex, - int32_t view_id, - char *header, - uint16_t header_x, - uint16_t header_y, - char *text, - uint16_t text_x, - uint16_t text_y, - char *left_button_text, - char *right_button_text, - char *center_button_text, - void (*result_callback)(DialogExResult, void *), - uint32_t(previous_callback)(void *), - ViewDispatcher **view_dispatcher, - void *context) -{ - if (!dialog_ex) - { - FURI_LOG_E(EASY_TAG, "Invalid arguments provided to set_dialog_ex"); - return false; - } - *dialog_ex = dialog_ex_alloc(); - if (!*dialog_ex) - { - FURI_LOG_E(EASY_TAG, "Failed to allocate DialogEx"); - return false; - } - if (header) - { - dialog_ex_set_header(*dialog_ex, header, header_x, header_y, AlignLeft, AlignTop); - } - if (text) - { - dialog_ex_set_text(*dialog_ex, text, text_x, text_y, AlignLeft, AlignTop); - } - if (left_button_text) - { - dialog_ex_set_left_button_text(*dialog_ex, left_button_text); - } - if (right_button_text) - { - dialog_ex_set_right_button_text(*dialog_ex, right_button_text); - } - if (center_button_text) - { - dialog_ex_set_center_button_text(*dialog_ex, center_button_text); - } - if (result_callback) - { - dialog_ex_set_result_callback(*dialog_ex, result_callback); - } - if (previous_callback) - { - view_set_previous_callback(dialog_ex_get_view(*dialog_ex), previous_callback); - } - if (context) - { - dialog_ex_set_context(*dialog_ex, context); - } - view_dispatcher_add_view(*view_dispatcher, view_id, dialog_ex_get_view(*dialog_ex)); - return true; -} - -/** - * @brief Initialize a Popup object - * @param popup The Popup object to initialize - * @param view_id The ID/Index of the view - * @param header The header of the dialog - * @param header_x The x coordinate of the header - * @param header_y The y coordinate of the header - * @param text The text of the dialog - * @param text_x The x coordinate of the dialog - * @param text_y The y coordinate of the dialog - * @param result_callback The result callback function - * @param previous_callback The previous callback function (can be set to NULL) - * @param view_dispatcher The ViewDispatcher object - * @param context The context to pass to the result callback - * @return true if successful, false otherwise - */ -bool easy_flipper_set_popup( - Popup **popup, - int32_t view_id, - char *header, - uint16_t header_x, - uint16_t header_y, - char *text, - uint16_t text_x, - uint16_t text_y, - void (*result_callback)(void *), - uint32_t(previous_callback)(void *), - ViewDispatcher **view_dispatcher, - void *context) -{ - if (!popup) - { - FURI_LOG_E(EASY_TAG, "Invalid arguments provided to set_popup"); - return false; - } - *popup = popup_alloc(); - if (!*popup) - { - FURI_LOG_E(EASY_TAG, "Failed to allocate Popup"); - return false; - } - if (header) - { - popup_set_header(*popup, header, header_x, header_y, AlignLeft, AlignTop); - } - if (text) - { - popup_set_text(*popup, text, text_x, text_y, AlignLeft, AlignTop); - } - if (result_callback) - { - popup_set_callback(*popup, result_callback); - } - if (previous_callback) - { - view_set_previous_callback(popup_get_view(*popup), previous_callback); - } - if (context) - { - popup_set_context(*popup, context); - } - view_dispatcher_add_view(*view_dispatcher, view_id, popup_get_view(*popup)); - return true; -} - -/** - * @brief Initialize a Loading object - * @param loading The Loading object to initialize - * @param view_id The ID/Index of the view - * @param previous_callback The previous callback function (can be set to NULL) - * @param view_dispatcher The ViewDispatcher object - * @return true if successful, false otherwise - */ -bool easy_flipper_set_loading( - Loading **loading, - int32_t view_id, - uint32_t(previous_callback)(void *), - ViewDispatcher **view_dispatcher) -{ - if (!loading) - { - FURI_LOG_E(EASY_TAG, "Invalid arguments provided to set_loading"); - return false; - } - *loading = loading_alloc(); - if (!*loading) - { - FURI_LOG_E(EASY_TAG, "Failed to allocate Loading"); - return false; - } - if (previous_callback) - { - view_set_previous_callback(loading_get_view(*loading), previous_callback); - } - view_dispatcher_add_view(*view_dispatcher, view_id, loading_get_view(*loading)); - return true; -} - -/** - * @brief Set a char butter to a FuriString - * @param furi_string The FuriString object - * @param buffer The buffer to copy the string to - * @return true if successful, false otherwise - */ -bool easy_flipper_set_char_to_furi_string(FuriString **furi_string, char *buffer) -{ - if (!furi_string) - { - FURI_LOG_E(EASY_TAG, "Invalid arguments provided to set_buffer_to_furi_string"); - return false; - } - *furi_string = furi_string_alloc(); - if (!furi_string) - { - FURI_LOG_E(EASY_TAG, "Failed to allocate FuriString"); - return false; - } - furi_string_set_str(*furi_string, buffer); - return true; -} - -#endif // EASY_FLIPPER_H \ No newline at end of file diff --git a/flip_social_callback.h b/flip_social_callback.h deleted file mode 100644 index 98de83b6c..000000000 --- a/flip_social_callback.h +++ /dev/null @@ -1,1062 +0,0 @@ -// flip_social_callback.h -#ifndef FLIP_SOCIAL_CALLBACK_H -#define FLIP_SOCIAL_CALLBACK_H - -/** - * @brief Navigation callback to go back to the submenu Logged out. - * @param context The context - unused - * @return next view id (FlipSocialViewLoggedOutSubmenu) - */ -static uint32_t flip_social_callback_to_submenu_logged_out(void *context) -{ - UNUSED(context); - return FlipSocialViewLoggedOutSubmenu; -} - -/** - * @brief Navigation callback to go back to the submenu Logged in. - * @param context The context - unused - * @return next view id (FlipSocialViewLoggedInSubmenu) - */ -static uint32_t flip_social_callback_to_submenu_logged_in(void *context) -{ - UNUSED(context); - flip_social_free_explore(); - flip_social_free_feed(); - flip_social_free_friends(); - flip_social_free_message_users(); - flip_social_free_messages(); - return FlipSocialViewLoggedInSubmenu; -} - -/** - * @brief Navigation callback to bring the user back to the (Logged out) Login screen - * @param context The context - unused - * @return next view id (FlipSocialViewLoggedOutLogin) - */ -static uint32_t flip_social_callback_to_login_logged_out(void *context) -{ - UNUSED(context); - flip_social_sent_login_request = false; - flip_social_login_success = false; - return FlipSocialViewLoggedOutLogin; -} - -/** - * @brief Navigation callback to bring the user back to the (Logged out) Register screen - * @param context The context - unused - * @return next view id (FlipSocialViewLoggedOutRegister) - */ -static uint32_t flip_social_callback_to_register_logged_out(void *context) -{ - UNUSED(context); - flip_social_sent_register_request = false; - flip_social_register_success = false; - return FlipSocialViewLoggedOutRegister; -} - -/** - * @brief Navigation callback to bring the user back to the (Logged out) Wifi Settings screen - * @param context The context - unused - * @return next view id (FlipSocialViewLoggedOutWifiSettings) - */ -static uint32_t flip_social_callback_to_wifi_settings_logged_out(void *context) -{ - UNUSED(context); - return FlipSocialViewLoggedOutWifiSettings; -} - -/** - * @brief Navigation callback to bring the user back to the (Logged in) Wifi Settings screen - * @param context The context - unused - * @return next view id (FlipSocialViewLoggedInSettingsWifi) - */ -static uint32_t flip_social_callback_to_wifi_settings_logged_in(void *context) -{ - UNUSED(context); - return FlipSocialViewLoggedInSettingsWifi; -} - -/** - * @brief Navigation callback to bring the user back to the (Logged in) Settings screen - * @param context The context - unused - * @return next view id (FlipSocialViewLoggedInSettingsWifi) - */ -static uint32_t flip_social_callback_to_settings_logged_in(void *context) -{ - UNUSED(context); - return FlipSocialViewLoggedInSettings; -} - -/** - * @brief Navigation callback to bring the user back to the (Logged in) Compose screen - * @param context The context - unused - * @return next view id (FlipSocialViewLoggedInCompose) - */ -static uint32_t flip_social_callback_to_compose_logged_in(void *context) -{ - UNUSED(context); - return FlipSocialViewLoggedInCompose; -} - -/** - * @brief Navigation callback to bring the user back to the (Logged in) Profile screen - * @param context The context - unused - * @return next view id (FlipSocialViewLoggedInProfile) - */ -static uint32_t flip_social_callback_to_profile_logged_in(void *context) -{ - UNUSED(context); - return FlipSocialViewLoggedInProfile; -} - -/** - * @brief Navigation callback to bring the user back to the Explore submenu - * @param context The context - unused - * @return next view id (FlipSocialViewLoggedInExploreSubmenu) - */ -static uint32_t flip_social_callback_to_explore_logged_in(void *context) -{ - UNUSED(context); - flip_social_dialog_stop = true; - last_explore_response = ""; - flip_social_dialog_shown = false; - flip_social_explore->index = 0; - action = ActionNone; - return FlipSocialViewLoggedInExploreSubmenu; -} - -/** - * @brief Navigation callback to bring the user back to the Friends submenu - * @param context The context - unused - * @return next view id (FlipSocialViewLoggedInFriendsSubmenu) - */ -static uint32_t flip_social_callback_to_friends_logged_in(void *context) -{ - UNUSED(context); - flip_social_dialog_stop = true; - last_explore_response = ""; - flip_social_dialog_shown = false; - flip_social_friends->index = 0; - action = ActionNone; - return FlipSocialViewLoggedInFriendsSubmenu; -} - -/** - * @brief Navigation callback to bring the user back to the Messages submenu - * @param context The context - unused - * @return next view id (FlipSocialViewLoggedInMessagesSubmenu) - */ -static uint32_t flip_social_callback_to_messages_logged_in(void *context) -{ - UNUSED(context); - return FlipSocialViewLoggedInMessagesSubmenu; -} - -/** - * @brief Navigation callback to bring the user back to the User Choices screen - * @param context The context - unused - * @return next view id (FlipSocialViewLoggedInMessagesUserChoices) - */ -static uint32_t flip_social_callback_to_messages_user_choices(void *context) -{ - UNUSED(context); - return FlipSocialViewLoggedInMessagesUserChoices; -} - -/** - * @brief Navigation callback for exiting the application - * @param context The context - unused - * @return next view id (VIEW_NONE to exit the app) - */ -static uint32_t flip_social_callback_exit_app(void *context) -{ - // Exit the application - if (!context) - { - FURI_LOG_E(TAG, "Context is NULL"); - return VIEW_NONE; - } - UNUSED(context); - return VIEW_NONE; // Return VIEW_NONE to exit the app -} - -/** - * @brief Handle ALL submenu item selections. - * @param context The context - FlipSocialApp object. - * @param index The FlipSocialSubmenuIndex item that was clicked. - * @return void - */ -static void flip_social_callback_submenu_choices(void *context, uint32_t index) -{ - FlipSocialApp *app = (FlipSocialApp *)context; - if (!app) - { - FURI_LOG_E(TAG, "FlipSocialApp is NULL"); - return; - } - switch (index) - { - case FlipSocialSubmenuLoggedOutIndexLogin: - flip_social_sent_login_request = false; - flip_social_login_success = false; - view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedOutLogin); - break; - case FlipSocialSubmenuLoggedOutIndexRegister: - flip_social_sent_register_request = false; - flip_social_register_success = false; - view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedOutRegister); - break; - case FlipSocialSubmenuLoggedOutIndexAbout: - view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedOutAbout); - break; - case FlipSocialSubmenuLoggedOutIndexWifiSettings: - view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedOutWifiSettings); - break; - case FlipSocialSubmenuLoggedInIndexProfile: - view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedInProfile); - break; - case FlipSocialSubmenuLoggedInIndexMessages: - if (flipper_http_process_response_async(flip_social_get_message_users, flip_social_parse_json_message_users)) - { - view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedInMessagesSubmenu); - } - break; - case FlipSocialSubmenuLoggedInIndexMessagesNewMessage: - if (flipper_http_process_response_async(flip_social_get_explore, flip_social_parse_json_message_user_choices)) - { - view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedInMessagesUserChoices); - } - break; - case FlipSocialSubmenuLoggedInIndexFeed: - if (flipper_http_process_response_async(flip_social_get_feed, flip_social_parse_json_feed)) - { - view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedInFeed); - } - else - { - // Set failure FlipSocialFeed object - if (!flip_social_temp_feed()) - { - return; - } - view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedInFeed); - } - break; - case FlipSocialSubmenuExploreIndex: - if (flipper_http_process_response_async(flip_social_get_explore, flip_social_parse_json_explore)) - { - view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedInExploreSubmenu); - } - break; - case FlipSocialSubmenuLoggedInIndexCompose: - view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedInCompose); - break; - case FlipSocialSubmenuLoggedInIndexSettings: - view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedInSettings); - break; - case FlipSocialSubmenuLoggedInSignOutButton: - app->is_logged_in = "false"; - - save_settings(app_instance->wifi_ssid_logged_out, app_instance->wifi_password_logged_out, app_instance->login_username_logged_out, app_instance->login_username_logged_in, app_instance->login_password_logged_out, app_instance->change_password_logged_in, app_instance->is_logged_in); - - view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedOutSubmenu); - break; - case FlipSocialSubmenuComposeIndexAddPreSave: - view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedInComposeAddPreSaveInput); - break; - default: - // Handle the pre-saved message selection (has a max of 25 items) - if (index >= FlipSocialSubemnuComposeIndexStartIndex && index < FlipSocialSubemnuComposeIndexStartIndex + MAX_PRE_SAVED_MESSAGES) - { - flip_social_feed->index = index - FlipSocialSubemnuComposeIndexStartIndex; - view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedInProcessCompose); - } - - // Handle the explore selection - else if (index >= FlipSocialSubmenuExploreIndexStartIndex && index < FlipSocialSubmenuExploreIndexStartIndex + MAX_EXPLORE_USERS) - { - flip_social_explore->index = index - FlipSocialSubmenuExploreIndexStartIndex; - view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedInExploreProccess); - } - - // handle the friends selection - else if (index >= FlipSocialSubmenuLoggedInIndexFriendsStart && index < FlipSocialSubmenuLoggedInIndexFriendsStart + MAX_FRIENDS) - { - flip_social_friends->index = index - FlipSocialSubmenuLoggedInIndexFriendsStart; - view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedInFriendsProcess); - } - - // handle the messages selection - else if (index >= FlipSocialSubmenuLoggedInIndexMessagesUsersStart && index < FlipSocialSubmenuLoggedInIndexMessagesUsersStart + MAX_MESSAGE_USERS) - { - flip_social_message_users->index = index - FlipSocialSubmenuLoggedInIndexMessagesUsersStart; - if (flipper_http_process_response_async(flip_social_get_messages_with_user, flip_social_parse_json_messages)) - { - view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedInMessagesProcess); - } - } - - // handle the messages user choices selection - else if (index >= FlipSocialSubmenuLoggedInIndexMessagesUserChoicesIndexStart && index < FlipSocialSubmenuLoggedInIndexMessagesUserChoicesIndexStart + MAX_EXPLORE_USERS) - { - flip_social_explore->index = index - FlipSocialSubmenuLoggedInIndexMessagesUserChoicesIndexStart; - view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedInMessagesNewMessageUserChoicesInput); - } - else - { - FURI_LOG_E(TAG, "Unknown submenu index"); - } - - break; - } -} - -/** - * @brief Text input callback for when the user finishes entering their SSID on the wifi settings (logged out) screen. - * @param context The context - FlipSocialApp object. - * @return void - */ -static void flip_social_logged_out_wifi_settings_ssid_updated(void *context) -{ - FlipSocialApp *app = (FlipSocialApp *)context; - if (!app) - { - FURI_LOG_E(TAG, "FlipSocialApp is NULL"); - return; - } - // Store the entered name - strncpy(app->wifi_ssid_logged_out, app->wifi_ssid_logged_out_temp_buffer, app->wifi_ssid_logged_out_temp_buffer_size); - - // Store the entered name in the logged in name field - strncpy(app->wifi_ssid_logged_in, app->wifi_ssid_logged_out_temp_buffer, app->wifi_ssid_logged_out_temp_buffer_size); - strncpy(app->wifi_ssid_logged_in_temp_buffer, app->wifi_ssid_logged_out_temp_buffer, app->wifi_ssid_logged_out_temp_buffer_size); - - // Ensure null-termination - app->wifi_ssid_logged_out[app->wifi_ssid_logged_out_temp_buffer_size - 1] = '\0'; - - // Update the name item text - if (app->variable_item_logged_out_wifi_settings_ssid) - { - variable_item_set_current_value_text(app->variable_item_logged_out_wifi_settings_ssid, app->wifi_ssid_logged_out); - } - - // update the wifi settings - if (!flipper_http_save_wifi(app->wifi_ssid_logged_out, app->wifi_password_logged_out)) - { - FURI_LOG_E(TAG, "Failed to save wifi settings via UART"); - FURI_LOG_E(TAG, "Make sure the Flipper is connected to the Wifi Dev Board"); - } - - // Save the settings - save_settings(app_instance->wifi_ssid_logged_out, app_instance->wifi_password_logged_out, app_instance->login_username_logged_out, app_instance->login_username_logged_in, app_instance->login_password_logged_out, app_instance->change_password_logged_in, app_instance->is_logged_in); - - view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedOutWifiSettings); -} - -/** - * @brief Text input callback for when the user finishes entering their password on the wifi settings (logged out) screen. - * @param context The context - FlipSocialApp object. - * @return void - */ -static void flip_social_logged_out_wifi_settings_password_updated(void *context) -{ - FlipSocialApp *app = (FlipSocialApp *)context; - if (!app) - { - FURI_LOG_E(TAG, "FlipSocialApp is NULL"); - return; - } - // Store the entered WiFi password - strncpy(app->wifi_password_logged_out, app->wifi_password_logged_out_temp_buffer, app->wifi_password_logged_out_temp_buffer_size); - - // Store the entered WiFi password in the logged in password field - strncpy(app->wifi_password_logged_in, app->wifi_password_logged_out_temp_buffer, app->wifi_password_logged_out_temp_buffer_size); - strncpy(app->wifi_password_logged_in_temp_buffer, app->wifi_password_logged_out_temp_buffer, app->wifi_password_logged_out_temp_buffer_size); - - // Ensure null-termination - app->wifi_password_logged_out[app->wifi_password_logged_out_temp_buffer_size - 1] = '\0'; - - // Update the password item text - if (app->variable_item_logged_out_wifi_settings_password) - { - variable_item_set_current_value_text(app->variable_item_logged_out_wifi_settings_password, app->wifi_password_logged_out); - } - - // update the wifi settings - if (!flipper_http_save_wifi(app->wifi_ssid_logged_out, app->wifi_password_logged_out)) - { - FURI_LOG_E(TAG, "Failed to save wifi settings via UART"); - FURI_LOG_E(TAG, "Make sure the Flipper is connected to the Wifi Dev Board"); - } - - // Save the settings - save_settings(app_instance->wifi_ssid_logged_out, app_instance->wifi_password_logged_out, app_instance->login_username_logged_out, app_instance->login_username_logged_in, app_instance->login_password_logged_out, app_instance->change_password_logged_in, app_instance->is_logged_in); - - view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedOutWifiSettings); -} - -/** - * @brief Callback when the user selects a menu item in the wifi settings (logged out) screen. - * @param context The context - FlipSocialApp object. - * @param index The index of the selected item. - * @return void - */ -static void flip_social_text_input_logged_out_wifi_settings_item_selected(void *context, uint32_t index) -{ - FlipSocialApp *app = (FlipSocialApp *)context; - if (!app) - { - FURI_LOG_E(TAG, "FlipSocialApp is NULL"); - return; - } - switch (index) - { - case 0: // Input SSID - view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedOutWifiSettingsSSIDInput); - break; - case 1: // Input Password - view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedOutWifiSettingsPasswordInput); - break; - default: - FURI_LOG_E(TAG, "Unknown configuration item index"); - break; - } -} - -/** - * @brief Text input callback for when the user finishes entering their username on the login (logged out) screen. - * @param context The context - FlipSocialApp object. - * @return void - */ -static void flip_social_logged_out_login_username_updated(void *context) -{ - FlipSocialApp *app = (FlipSocialApp *)context; - if (!app) - { - FURI_LOG_E(TAG, "FlipSocialApp is NULL"); - return; - } - // Store the entered name - strncpy(app->login_username_logged_out, app->login_username_logged_out_temp_buffer, app->login_username_logged_out_temp_buffer_size); - - // Store the entered name in the logged in username field - strncpy(app->login_username_logged_in, app->login_username_logged_out_temp_buffer, app->login_username_logged_out_temp_buffer_size); - strncpy(app->login_username_logged_in_temp_buffer, app->login_username_logged_out_temp_buffer, app->login_username_logged_out_temp_buffer_size); - - // Ensure null-termination - app->login_username_logged_out[app->login_username_logged_out_temp_buffer_size - 1] = '\0'; - - // Update the name item text - if (app->variable_item_logged_out_login_username) - { - variable_item_set_current_value_text(app->variable_item_logged_out_login_username, app->login_username_logged_out); - } - - // Save the settings - save_settings(app_instance->wifi_ssid_logged_out, app_instance->wifi_password_logged_out, app_instance->login_username_logged_out, app_instance->login_username_logged_in, app_instance->login_password_logged_out, app_instance->change_password_logged_in, app_instance->is_logged_in); - - view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedOutLogin); -} - -/** - * @brief Text input callback for when the user finishes entering their password on the login (logged out) screen. - * @param context The context - FlipSocialApp object. - * @return void - */ - -static void flip_social_logged_out_login_password_updated(void *context) -{ - FlipSocialApp *app = (FlipSocialApp *)context; - if (!app) - { - FURI_LOG_E(TAG, "FlipSocialApp is NULL"); - return; - } - // Store the entered password - strncpy(app->login_password_logged_out, app->login_password_logged_out_temp_buffer, app->login_password_logged_out_temp_buffer_size); - - // Store the entered password in the change password field - strncpy(app->change_password_logged_in, app->login_password_logged_out_temp_buffer, app->login_password_logged_out_temp_buffer_size); - strncpy(app->change_password_logged_in_temp_buffer, app->login_password_logged_out_temp_buffer, app->login_password_logged_out_temp_buffer_size); - - // Ensure null-termination - app->login_password_logged_out[app->login_password_logged_out_temp_buffer_size - 1] = '\0'; - - // Update the password item text - if (app->variable_item_logged_out_login_password) - { - // dont show the password on the screen (version 0.2) - // variable_item_set_current_value_text(app->variable_item_logged_out_login_password, app->login_password_logged_out); - } - - // Save the settings - save_settings(app_instance->wifi_ssid_logged_out, app_instance->wifi_password_logged_out, app_instance->login_username_logged_out, app_instance->login_username_logged_in, app_instance->login_password_logged_out, app_instance->change_password_logged_in, app_instance->is_logged_in); - - view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedOutLogin); -} - -/** - * @brief Callback when the user selects a menu item in the login (logged out) screen. - * @param context The context - FlipSocialApp object. - * @param index The index of the selected item. - * @return void - */ -static void flip_social_text_input_logged_out_login_item_selected(void *context, uint32_t index) -{ - FlipSocialApp *app = (FlipSocialApp *)context; - if (!app) - { - FURI_LOG_E(TAG, "FlipSocialApp is NULL"); - return; - } - switch (index) - { - case 0: // Input Username - view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedOutLoginUsernameInput); - break; - case 1: // Input Password - view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedOutLoginPasswordInput); - break; - case 2: // Login Button - view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedOutProcessLogin); - break; - default: - FURI_LOG_E(TAG, "Unknown configuration item index"); - break; - } -} - -/** - * @brief Text input callback for when the user finishes entering their username on the register (logged out) screen. - * @param context The context - FlipSocialApp object. - * @return void - */ -static void flip_social_logged_out_register_username_updated(void *context) -{ - FlipSocialApp *app = (FlipSocialApp *)context; - if (!app) - { - FURI_LOG_E(TAG, "FlipSocialApp is NULL"); - return; - } - // Store the entered name - strncpy(app->register_username_logged_out, app->register_username_logged_out_temp_buffer, app->register_username_logged_out_temp_buffer_size); - - // Ensure null-termination - app->register_username_logged_out[app->register_username_logged_out_temp_buffer_size - 1] = '\0'; - - // Update the name item text - if (app->variable_item_logged_out_register_username) - { - variable_item_set_current_value_text(app->variable_item_logged_out_register_username, app->register_username_logged_out); - } - - view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedOutRegister); -} - -/** - * @brief Text input callback for when the user finishes entering their password on the register (logged out) screen. - * @param context The context - FlipSocialApp object. - * @return void - */ -static void flip_social_logged_out_register_password_updated(void *context) -{ - FlipSocialApp *app = (FlipSocialApp *)context; - if (!app) - { - FURI_LOG_E(TAG, "FlipSocialApp is NULL"); - return; - } - // Store the entered password - strncpy(app->register_password_logged_out, app->register_password_logged_out_temp_buffer, app->register_password_logged_out_temp_buffer_size); - - // Ensure null-termination - app->register_password_logged_out[app->register_password_logged_out_temp_buffer_size - 1] = '\0'; - - // Update the password item text - if (app->variable_item_logged_out_register_password) - { - variable_item_set_current_value_text(app->variable_item_logged_out_register_password, app->register_password_logged_out); - } - - view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedOutRegister); -} - -/** - * @brief Text input callback for when the user finishes entering their password 2 on the register (logged out) screen. - * @param context The context - FlipSocialApp object. - * @return void - */ -static void flip_social_logged_out_register_password_2_updated(void *context) -{ - FlipSocialApp *app = (FlipSocialApp *)context; - if (!app) - { - FURI_LOG_E(TAG, "FlipSocialApp is NULL"); - return; - } - // Store the entered password - strncpy(app->register_password_2_logged_out, app->register_password_2_logged_out_temp_buffer, app->register_password_2_logged_out_temp_buffer_size); - - // Ensure null-termination - app->register_password_2_logged_out[app->register_password_2_logged_out_temp_buffer_size - 1] = '\0'; - - // Update the password item text - if (app->variable_item_logged_out_register_password_2) - { - variable_item_set_current_value_text(app->variable_item_logged_out_register_password_2, app->register_password_2_logged_out); - } - - view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedOutRegister); -} - -/** - * @brief Callback when the user selects a menu item in the register (logged out) screen. - * @param context The context - FlipSocialApp object. - * @param index The index of the selected item. - * @return void - */ -static void flip_social_text_input_logged_out_register_item_selected(void *context, uint32_t index) -{ - FlipSocialApp *app = (FlipSocialApp *)context; - if (!app) - { - FURI_LOG_E(TAG, "FlipSocialApp is NULL"); - return; - } - switch (index) - { - case 0: // Input Username - view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedOutRegisterUsernameInput); - break; - case 1: // Input Password - view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedOutRegisterPasswordInput); - break; - case 2: // Input Password 2 - view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedOutRegisterPassword2Input); - break; - case 3: // Register button - view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedOutProcessRegister); - break; - default: - FURI_LOG_E(TAG, "Unknown configuration item index"); - break; - } -} - -/** - * @brief Text input callback for when the user finishes entering their SSID on the wifi settings (logged in) screen. - * @param context The context - FlipSocialApp object. - * @return void - */ -static void flip_social_logged_in_wifi_settings_ssid_updated(void *context) -{ - FlipSocialApp *app = (FlipSocialApp *)context; - if (!app) - { - FURI_LOG_E(TAG, "FlipSocialApp is NULL"); - return; - } - // Store the entered SSID - strncpy(app->wifi_ssid_logged_in, app->wifi_ssid_logged_in_temp_buffer, app->wifi_ssid_logged_in_temp_buffer_size); - - // Store the entered SSID in the logged out SSID - strncpy(app->wifi_ssid_logged_out, app->wifi_ssid_logged_in, app->wifi_ssid_logged_in_temp_buffer_size); - strncpy(app->wifi_ssid_logged_out_temp_buffer, app->wifi_ssid_logged_in, app->wifi_ssid_logged_in_temp_buffer_size); - - // Ensure null-termination - app->wifi_ssid_logged_in[app->wifi_ssid_logged_in_temp_buffer_size - 1] = '\0'; - - // Update the name item text - if (app->variable_item_logged_in_wifi_settings_ssid) - { - variable_item_set_current_value_text(app->variable_item_logged_in_wifi_settings_ssid, app->wifi_ssid_logged_in); - } - - // Save the settings - save_settings(app_instance->wifi_ssid_logged_in, app_instance->wifi_password_logged_in, app_instance->login_username_logged_out, app_instance->login_username_logged_in, app_instance->login_password_logged_out, app_instance->change_password_logged_in, app_instance->is_logged_in); - - // update the wifi settings - if (strlen(app->wifi_ssid_logged_in) > 0 && strlen(app->wifi_password_logged_in) > 0) - { - if (!flipper_http_save_wifi(app->wifi_ssid_logged_in, app->wifi_password_logged_in)) - { - FURI_LOG_E(TAG, "Failed to save wifi settings via UART"); - FURI_LOG_E(TAG, "Make sure the Flipper is connected to the Wifi Dev Board"); - } - } - - view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedInSettingsWifi); -} - -/** - * @brief Text input callback for when the user finishes entering their password on the wifi settings (logged in) screen. - * @param context The context - FlipSocialApp object. - * @return void - */ -static void flip_social_logged_in_wifi_settings_password_updated(void *context) -{ - FlipSocialApp *app = (FlipSocialApp *)context; - if (!app) - { - FURI_LOG_E(TAG, "FlipSocialApp is NULL"); - return; - } - // Store the entered password - strncpy(app->wifi_password_logged_in, app->wifi_password_logged_in_temp_buffer, app->wifi_password_logged_in_temp_buffer_size); - - // Store the entered password in the logged out password - strncpy(app->login_password_logged_out, app->wifi_password_logged_in, app->wifi_password_logged_in_temp_buffer_size); - strncpy(app->login_password_logged_out_temp_buffer, app->wifi_password_logged_in, app->wifi_password_logged_in_temp_buffer_size); - - // Ensure null-termination - app->wifi_password_logged_in[app->wifi_password_logged_in_temp_buffer_size - 1] = '\0'; - - // Update the password item text - if (app->variable_item_logged_in_wifi_settings_password) - { - // dont show the password on the screen (version 0.2) - // variable_item_set_current_value_text(app->variable_item_logged_in_wifi_settings_password, app->wifi_password_logged_in); - } - - // Save the settings - save_settings(app_instance->wifi_ssid_logged_in, app_instance->wifi_password_logged_in, app_instance->login_username_logged_out, app_instance->login_username_logged_in, app_instance->login_password_logged_out, app_instance->change_password_logged_in, app_instance->is_logged_in); - - // update the wifi settings - if (strlen(app->wifi_ssid_logged_in) > 0 && strlen(app->wifi_password_logged_in) > 0) - { - if (!flipper_http_save_wifi(app->wifi_ssid_logged_in, app->wifi_password_logged_in)) - { - FURI_LOG_E(TAG, "Failed to save wifi settings via UART"); - FURI_LOG_E(TAG, "Make sure the Flipper is connected to the Wifi Dev Board"); - } - } - - view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedInSettingsWifi); -} - -/** - * @brief Callback when the user selects a menu item in the wifi settings (logged in) screen. - * @param context The context - FlipSocialApp object. - * @param index The index of the selected item. - * @return void - */ -static void flip_social_text_input_logged_in_wifi_settings_item_selected(void *context, uint32_t index) -{ - FlipSocialApp *app = (FlipSocialApp *)context; - if (!app) - { - FURI_LOG_E(TAG, "FlipSocialApp is NULL"); - return; - } - switch (index) - { - case 0: // Input SSID - view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedInWifiSettingsSSIDInput); - break; - case 1: // Input Password - view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedInWifiSettingsPasswordInput); - break; - default: - FURI_LOG_E(TAG, "Unknown configuration item index"); - break; - } -} - -/** - * @brief Text input callback for when the user finishes entering their message on the compose (logged in) screen for Add Text - * @param context The context - FlipSocialApp object. - * @return void - */ -static void flip_social_logged_in_compose_pre_save_updated(void *context) -{ - FlipSocialApp *app = (FlipSocialApp *)context; - if (!app) - { - FURI_LOG_E(TAG, "FlipSocialApp is NULL"); - return; - } - - // check if the message is empty or if adding in the message would exceed the MAX_PRE_SAVED_MESSAGES - if (app->compose_pre_save_logged_in_temp_buffer_size == 0 || app->pre_saved_messages.count >= MAX_PRE_SAVED_MESSAGES) - { - FURI_LOG_E(TAG, "Message is empty or would exceed the maximum number of pre-saved messages"); - view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedInCompose); - return; - } - - // Store the entered message - strncpy(app->compose_pre_save_logged_in, app->compose_pre_save_logged_in_temp_buffer, app->compose_pre_save_logged_in_temp_buffer_size); - - // Ensure null-termination - app->compose_pre_save_logged_in[app->compose_pre_save_logged_in_temp_buffer_size - 1] = '\0'; - - // add the item to the submenu - submenu_reset(app->submenu_compose); - - // loop through the items and add them to the submenu - app->pre_saved_messages.messages[app->pre_saved_messages.count] = app->compose_pre_save_logged_in; - app->pre_saved_messages.count++; - - submenu_add_item(app->submenu_compose, "Add Pre-Save", FlipSocialSubmenuComposeIndexAddPreSave, flip_social_callback_submenu_choices, app); - for (uint32_t i = 0; i < app->pre_saved_messages.count; i++) - { - submenu_add_item(app->submenu_compose, app->pre_saved_messages.messages[i], FlipSocialSubemnuComposeIndexStartIndex + i, flip_social_callback_submenu_choices, app); - } - - // save playlist - save_playlist(&app->pre_saved_messages); - - view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedInCompose); -} - -/** - * @brief Text input callback for when the user finishes entering their message on the profile (logged in) screen for change password - * @param context The context - FlipSocialApp object. - * @return void - */ -static void flip_social_logged_in_profile_change_password_updated(void *context) -{ - FlipSocialApp *app = (FlipSocialApp *)context; - if (!app) - { - FURI_LOG_E(TAG, "FlipSocialApp is NULL"); - return; - } - // Correct type: old_pass should be a pointer to a string (char *) - const char *old_password = app->login_password_logged_out; - - // Store the entered message - strncpy(app->change_password_logged_in, app->change_password_logged_in_temp_buffer, app->change_password_logged_in_temp_buffer_size); - - // store the entered password in the logged out password - strncpy(app->login_password_logged_out, app->change_password_logged_in, app->login_password_logged_out_temp_buffer_size); - strncpy(app->login_password_logged_out_temp_buffer, app->change_password_logged_in, app->login_password_logged_out_temp_buffer_size); - - // Ensure null-termination - app->change_password_logged_in[app->change_password_logged_in_temp_buffer_size - 1] = '\0'; - - // Update the message item text - if (app->variable_item_logged_in_profile_change_password) - { - // dont show the password on the screen (version 0.2) - // variable_item_set_current_value_text(app->variable_item_logged_in_profile_change_password, app->change_password_logged_in); - } - - // send post request to change password - char payload[256]; - snprintf(payload, sizeof(payload), "{\"username\":\"%s\",\"old_password\":\"%s\",\"new_password\":\"%s\"}", app->login_username_logged_out, old_password, app->change_password_logged_in); - char *headers = jsmn("Content-Type", "application/json"); - if (!flipper_http_post_request_with_headers("https://www.flipsocial.net/api/user/change-password/", headers, payload)) - { - FURI_LOG_E(TAG, "Failed to send post request to change password"); - FURI_LOG_E(TAG, "Make sure the Flipper is connected to the Wifi Dev Board"); - free(headers); - return; - } - free(headers); - // Save the settings - save_settings(app_instance->wifi_ssid_logged_out, app_instance->wifi_password_logged_out, app_instance->login_username_logged_out, app_instance->login_username_logged_in, app_instance->login_password_logged_out, app_instance->change_password_logged_in, app_instance->is_logged_in); - - view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedInProfile); -} - -/** - * @brief Callback when a user selects a menu item in the profile (logged in) screen. - * @param context The context - FlipSocialApp object. - * @param index The index of the selected item. - * @return void - */ -static void flip_social_text_input_logged_in_profile_item_selected(void *context, uint32_t index) -{ - FlipSocialApp *app = (FlipSocialApp *)context; - if (!app) - { - FURI_LOG_E(TAG, "FlipSocialApp is NULL"); - return; - } - switch (index) - { - case 0: // Change Username - // do nothing since username cannot be changed - break; - case 1: // Change Password - view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedInChangePasswordInput); - break; - case 2: // Friends - // get friends then switch to the friends screen - if (flip_social_get_friends()) // start the async friends request - { - furi_timer_start(fhttp.get_timeout_timer, TIMEOUT_DURATION_TICKS); - } - while (fhttp.state == RECEIVING && furi_timer_is_running(fhttp.get_timeout_timer) > 0) - { - // Wait for the friends to be received - furi_delay_ms(100); - } - furi_timer_stop(fhttp.get_timeout_timer); - if (!flip_social_parse_json_friends()) // parse the JSON before switching to the friends (synchonous) - { - FURI_LOG_E(TAG, "Failed to parse the JSON friends..."); - return; // just return for now, no temporary friends yet - // show a popup message saving wifi is disconnected - } - furi_timer_stop(fhttp.get_timeout_timer); - view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedInFriendsSubmenu); - break; - default: - FURI_LOG_E(TAG, "Unknown configuration item index"); - break; - } -} - -/** - * @brief Callback when a user selects a menu item in the settings (logged in) screen. - * @param context The context - FlipSocialApp object. - * @param index The index of the selected item. - * @return void - */ -static void flip_social_text_input_logged_in_settings_item_selected(void *context, uint32_t index) -{ - FlipSocialApp *app = (FlipSocialApp *)context; - if (!app) - { - FURI_LOG_E(TAG, "FlipSocialApp is NULL"); - return; - } - switch (index) - { - case 0: // About - view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedInSettingsAbout); - break; - case 1: // Wifi - view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedInSettingsWifi); - break; - default: - break; - } -} - -/** - * @brief Text input callback for when the user finishes entering their message to send to the selected user choice (user choice messages view) - * @param context The context - FlipSocialApp object. - * @return void - */ -static void flip_social_logged_in_messages_user_choice_message_updated(void *context) -{ - FlipSocialApp *app = (FlipSocialApp *)context; - if (!app) - { - FURI_LOG_E(TAG, "FlipSocialApp is NULL"); - return; - } - - // check if the message is empty - if (app->message_user_choice_logged_in_temp_buffer_size == 0) - { - FURI_LOG_E(TAG, "Message is empty"); - view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedInMessagesNewMessageUserChoicesInput); - return; - } - - // Store the entered message - strncpy(app->message_user_choice_logged_in, app->message_user_choice_logged_in_temp_buffer, app->message_user_choice_logged_in_temp_buffer_size); - - // Ensure null-termination - app->message_user_choice_logged_in[app->message_user_choice_logged_in_temp_buffer_size - 1] = '\0'; - - // send post request to send message - char url[128]; - char payload[256]; - snprintf(url, sizeof(url), "https://www.flipsocial.net/api/messages/%s/post/", app->login_username_logged_in); - snprintf(payload, sizeof(payload), "{\"receiver\":\"%s\",\"content\":\"%s\"}", flip_social_explore->usernames[flip_social_explore->index], app->message_user_choice_logged_in); - char *headers = jsmn("Content-Type", "application/json"); - - if (flipper_http_post_request_with_headers(url, headers, payload)) // start the async request - { - furi_timer_start(fhttp.get_timeout_timer, TIMEOUT_DURATION_TICKS); - fhttp.state = RECEIVING; - free(headers); - } - else - { - FURI_LOG_E(TAG, "Failed to send post request to send message"); - FURI_LOG_E(TAG, "Make sure the Flipper is connected to the Wifi Dev Board"); - free(headers); - return; - } - while (fhttp.state == RECEIVING && furi_timer_is_running(fhttp.get_timeout_timer) > 0) - { - // Wait for the request to be received - furi_delay_ms(100); - } - furi_timer_stop(fhttp.get_timeout_timer); - - // add user to the message list - strncpy(flip_social_message_users->usernames[flip_social_message_users->count], flip_social_explore->usernames[flip_social_explore->index], strlen(flip_social_explore->usernames[flip_social_explore->index])); - flip_social_message_users->count++; - - // redraw submenu - flip_social_update_messages_submenu(); - view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedInMessagesSubmenu); -} - -/** - * @brief Text input callback for when the user finishes entering their message to the selected user (messages view) - * @param context The context - FlipSocialApp object. - * @return void - */ -static void flip_social_logged_in_messages_new_message_updated(void *context) -{ - FlipSocialApp *app = (FlipSocialApp *)context; - if (!app) - { - FURI_LOG_E(TAG, "FlipSocialApp is NULL"); - return; - } - - // check if the message is empty - if (app->messages_new_message_logged_in_temp_buffer_size == 0) - { - FURI_LOG_E(TAG, "Message is empty"); - view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedInMessagesNewMessageInput); - return; - } - - // Store the entered message - strncpy(app->messages_new_message_logged_in, app->messages_new_message_logged_in_temp_buffer, app->messages_new_message_logged_in_temp_buffer_size); - - // Ensure null-termination - app->messages_new_message_logged_in[app->messages_new_message_logged_in_temp_buffer_size - 1] = '\0'; - - // send post request to send message - char url[128]; - char payload[256]; - snprintf(url, sizeof(url), "https://www.flipsocial.net/api/messages/%s/post/", app->login_username_logged_in); - snprintf(payload, sizeof(payload), "{\"receiver\":\"%s\",\"content\":\"%s\"}", flip_social_message_users->usernames[flip_social_message_users->index], app->messages_new_message_logged_in); - char *headers = jsmn("Content-Type", "application/json"); - - if (flipper_http_post_request_with_headers(url, headers, payload)) // start the async request - { - furi_timer_start(fhttp.get_timeout_timer, TIMEOUT_DURATION_TICKS); - fhttp.state = RECEIVING; - free(headers); - } - else - { - FURI_LOG_E(TAG, "Failed to send post request to send message"); - FURI_LOG_E(TAG, "Make sure the Flipper is connected to the Wifi Dev Board"); - free(headers); - return; - } - while (fhttp.state == RECEIVING && furi_timer_is_running(fhttp.get_timeout_timer) > 0) - { - // Wait for the request to be received - furi_delay_ms(100); - } - furi_timer_stop(fhttp.get_timeout_timer); - view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedInMessagesSubmenu); -} - -#endif // FLIP_SOCIAL_CALLBACK_H \ No newline at end of file diff --git a/flip_social_draw.h b/flip_social_draw.h deleted file mode 100644 index 49a21d6e6..000000000 --- a/flip_social_draw.h +++ /dev/null @@ -1,1008 +0,0 @@ -#ifndef FLIP_SOCIAL_DRAW_H -#define FLIP_SOCIAL_DRAW_H - -bool flip_social_sent_login_request = false; -bool flip_social_sent_register_request = false; -bool flip_social_login_success = false; -bool flip_social_register_success = false; -bool flip_social_dialog_shown = false; -bool flip_social_dialog_stop = false; -char *last_explore_response = ""; -static bool flip_social_update_friends(); - -bool flip_social_board_is_active(Canvas *canvas) -{ - if (fhttp.state == INACTIVE) - { - canvas_draw_str(canvas, 0, 7, "Wifi Dev Board disconnected."); - canvas_draw_str(canvas, 0, 17, "Please connect to the board."); - canvas_draw_str(canvas, 0, 32, "If your board is connected,"); - canvas_draw_str(canvas, 0, 42, "make sure you have flashed"); - canvas_draw_str(canvas, 0, 52, "your WiFi Devboard with the"); - canvas_draw_str(canvas, 0, 62, "latest FlipperHTTP flash."); - return false; - } - return true; -} - -void flip_social_handle_error(Canvas *canvas) -{ - if (fhttp.received_data != NULL) - { - if (strstr(fhttp.received_data, "[ERROR] Not connected to Wifi. Failed to reconnect.") != NULL) - { - canvas_clear(canvas); - canvas_draw_str(canvas, 0, 10, "[ERROR] Not connected to Wifi."); - canvas_draw_str(canvas, 0, 50, "Update your WiFi settings."); - canvas_draw_str(canvas, 0, 60, "Press BACK to return."); - } - else if (strstr(fhttp.received_data, "[ERROR] Failed to connect to Wifi.") != NULL) - { - canvas_clear(canvas); - canvas_draw_str(canvas, 0, 10, "[ERROR] Not connected to Wifi."); - canvas_draw_str(canvas, 0, 50, "Update your WiFi settings."); - canvas_draw_str(canvas, 0, 60, "Press BACK to return."); - } - else - { - canvas_draw_str(canvas, 0, 42, "Failed..."); - canvas_draw_str(canvas, 0, 52, "Update your credentials."); - canvas_draw_str(canvas, 0, 62, "Press BACK to return."); - } - } - else - { - canvas_draw_str(canvas, 0, 42, "Failed..."); - canvas_draw_str(canvas, 0, 52, "Update your credentials."); - canvas_draw_str(canvas, 0, 62, "Press BACK to return."); - } -} - -static void on_input(const void *event, void *ctx) -{ - UNUSED(ctx); - - InputKey key = ((InputEvent *)event)->key; - InputType type = ((InputEvent *)event)->type; - - if (type != InputTypeRelease) - { - return; - } - - switch (key) - { - case InputKeyOk: - action = ActionFlip; - break; - case InputKeyBack: - action = ActionBack; - break; - case InputKeyRight: - action = ActionNext; - break; - case InputKeyLeft: - action = ActionPrev; - break; - case InputKeyUp: - action = ActionPrev; - break; - case InputKeyDown: - action = ActionNext; - break; - default: - action = ActionNone; - break; - } -} - -// Function to draw the message on the canvas with word wrapping -void draw_user_message(Canvas *canvas, const char *user_message, int x, int y) -{ - if (user_message == NULL) - { - FURI_LOG_E(TAG, "User message is NULL."); - return; - } - - size_t msg_length = strlen(user_message); - size_t start = 0; - int line_num = 0; - char line[MAX_LINE_LENGTH + 1]; // Buffer for the current line (+1 for null terminator) - - while (start < msg_length && line_num < 4) - { - size_t remaining = msg_length - start; - size_t len = (remaining > MAX_LINE_LENGTH) ? MAX_LINE_LENGTH : remaining; - - if (remaining > MAX_LINE_LENGTH) - { - // Find the last space within the first 'len' characters - size_t last_space = len; - while (last_space > 0 && user_message[start + last_space - 1] != ' ') - { - last_space--; - } - - if (last_space > 0) - { - len = last_space; // Adjust len to the position of the last space - } - } - - // Copy the substring to 'line' and null-terminate it - memcpy(line, user_message + start, len); - line[len] = '\0'; // Ensure the string is null-terminated - - // Draw the string on the canvas - // Adjust the y-coordinate based on the line number - canvas_draw_str_aligned(canvas, x, y + line_num * 10, AlignLeft, AlignTop, line); - - // Update the start position for the next line - start += len; - - // Skip any spaces to avoid leading spaces on the next line - while (start < msg_length && user_message[start] == ' ') - { - start++; - } - - // Increment the line number - line_num++; - } -} - -static void flip_social_callback_draw_compose(Canvas *canvas, void *model) -{ - UNUSED(model); - if (!canvas) - { - FURI_LOG_E(TAG, "Canvas is NULL"); - return; - } - if (!app_instance) - { - FURI_LOG_E(TAG, "FlipSocialApp is NULL"); - return; - } - - char *message = app_instance->pre_saved_messages.messages[flip_social_feed->index]; - - if (!flip_social_dialog_shown) - { - flip_social_dialog_shown = true; - app_instance->input_event_queue = furi_record_open(RECORD_INPUT_EVENTS); - app_instance->input_event = furi_pubsub_subscribe(app_instance->input_event_queue, on_input, NULL); - } - - draw_user_message(canvas, message, 0, 2); - - canvas_draw_icon(canvas, 0, 53, &I_ButtonLeft_4x7); - canvas_draw_str_aligned(canvas, 7, 54, AlignLeft, AlignTop, "Delete"); - canvas_draw_icon(canvas, 52, 53, &I_ButtonBACK_10x8); - canvas_draw_str_aligned(canvas, 64, 54, AlignLeft, AlignTop, "Back"); - canvas_draw_icon(canvas, 100, 53, &I_ButtonRight_4x7); - canvas_draw_str_aligned(canvas, 107, 54, AlignLeft, AlignTop, "Post"); - - // handle action - switch (action) - { - case ActionNone: - break; - case ActionBack: - flip_social_dialog_stop = true; - break; - case ActionNext: - // send message - if (message && app_instance->login_username_logged_in) - { - // Send the message - char command[128]; - snprintf(command, sizeof(command), "{\"username\":\"%s\",\"content\":\"%s\"}", - app_instance->login_username_logged_in, message); - - bool success = flipper_http_post_request_with_headers( - "https://www.flipsocial.net/api/feed/post/", - "{\"Content-Type\":\"application/json\"}", - command); - - if (!success) - { - FURI_LOG_E(TAG, "Failed to send HTTP request for feed"); - furi_check(success); // Log the error with furi_check - return; // Exit early to avoid further errors - } - - fhttp.state = RECEIVING; - furi_timer_start(fhttp.get_timeout_timer, TIMEOUT_DURATION_TICKS); - } - else - { - FURI_LOG_E(TAG, "Message or username is NULL"); - furi_check(false); // Log as an error and return - return; - } - - int i = 0; - while (fhttp.state == RECEIVING && furi_timer_is_running(fhttp.get_timeout_timer) > 0) - { - // Wait for the feed to be received - furi_delay_ms(100); - - char dots_str[64] = "Receiving"; - - // Append dots to the string based on the value of i - int dot_count = i % 4; - int len = strlen(dots_str); - snprintf(dots_str + len, sizeof(dots_str) - len, "%.*s", dot_count, "...."); - - // Draw the resulting string on the canvas - canvas_draw_str(canvas, 0, 30, dots_str); - - i++; - } - flip_social_dialog_stop = true; - furi_timer_stop(fhttp.get_timeout_timer); - break; - case ActionPrev: - // delete message - app_instance->pre_saved_messages.messages[flip_social_feed->index] = NULL; - - for (uint32_t i = flip_social_feed->index; i < app_instance->pre_saved_messages.count - 1; i++) - { - app_instance->pre_saved_messages.messages[i] = app_instance->pre_saved_messages.messages[i + 1]; - } - app_instance->pre_saved_messages.count--; - - // add the item to the submenu - submenu_reset(app_instance->submenu_compose); - - submenu_add_item(app_instance->submenu_compose, "Add Pre-Save", FlipSocialSubmenuComposeIndexAddPreSave, flip_social_callback_submenu_choices, app_instance); - - for (uint32_t i = 0; i < app_instance->pre_saved_messages.count; i++) - { - submenu_add_item(app_instance->submenu_compose, app_instance->pre_saved_messages.messages[i], FlipSocialSubemnuComposeIndexStartIndex + i, flip_social_callback_submenu_choices, app_instance); - } - - // save playlist - save_playlist(&app_instance->pre_saved_messages); - - flip_social_dialog_stop = true; - break; - default: - action = ActionNone; - break; - } - - if (flip_social_dialog_stop) - { - furi_pubsub_unsubscribe(app_instance->input_event_queue, app_instance->input_event); - flip_social_dialog_shown = false; - flip_social_dialog_stop = false; - if (action == ActionNext) - { - action = ActionNone; - view_dispatcher_switch_to_view(app_instance->view_dispatcher, FlipSocialViewLoggedInFeed); - } - else if (action == ActionBack) - { - action = ActionNone; - view_dispatcher_switch_to_view(app_instance->view_dispatcher, FlipSocialViewLoggedInSubmenu); - } - else - { - action = ActionNone; - view_dispatcher_switch_to_view(app_instance->view_dispatcher, FlipSocialViewLoggedInCompose); - } - } -} -// function to draw the dialog canvas -static void flip_social_canvas_draw_message(Canvas *canvas, char *user_username, char *user_message, bool is_flipped, bool show_prev, bool show_next, int flip_count) -{ - canvas_set_color(canvas, ColorBlack); - canvas_set_font(canvas, FontPrimary); - canvas_draw_str_aligned(canvas, 64, 5, AlignCenter, AlignCenter, user_username); - canvas_set_font(canvas, FontSecondary); - - char flip_count_str[12]; - if (flip_count == 1) - { - snprintf(flip_count_str, sizeof(flip_count_str), "%d Flip", flip_count); - canvas_draw_str_aligned(canvas, 106, 54, AlignLeft, AlignTop, flip_count_str); - } - else - { - snprintf(flip_count_str, sizeof(flip_count_str), "%d Flips", flip_count); - - if (flip_count < 10) - { - canvas_draw_str_aligned(canvas, 100, 54, AlignLeft, AlignTop, flip_count_str); - } - else if (flip_count < 100) - { - canvas_draw_str_aligned(canvas, 94, 54, AlignLeft, AlignTop, flip_count_str); - } - else - { - canvas_draw_str_aligned(canvas, 88, 54, AlignLeft, AlignTop, flip_count_str); - } - } - - draw_user_message(canvas, user_message, 0, 12); - - // combine and shift icons/labels around if not show_prev or show_next - if (show_prev && show_next && !is_flipped) - { - canvas_draw_icon(canvas, 0, 54, &I_ButtonLeft_4x7); - canvas_draw_str_aligned(canvas, 6, 54, AlignLeft, AlignTop, "Prev"); - canvas_draw_icon(canvas, 30, 54, &I_ButtonRight_4x7); - canvas_draw_str_aligned(canvas, 36, 54, AlignLeft, AlignTop, "Next"); - canvas_draw_icon(canvas, 58, 54, &I_ButtonOK_7x7); - canvas_draw_str_aligned(canvas, 67, 54, AlignLeft, AlignTop, "Flip"); - } - else if (show_prev && !show_next && !is_flipped) - { - canvas_draw_icon(canvas, 0, 54, &I_ButtonLeft_4x7); - canvas_draw_str_aligned(canvas, 6, 54, AlignLeft, AlignTop, "Prev"); - canvas_draw_icon(canvas, 28, 54, &I_ButtonOK_7x7); - canvas_draw_str_aligned(canvas, 37, 54, AlignLeft, AlignTop, "Flip"); - } - else if (!show_prev && show_next && !is_flipped) - { - canvas_draw_icon(canvas, 0, 54, &I_ButtonRight_4x7); - canvas_draw_str_aligned(canvas, 6, 54, AlignLeft, AlignTop, "Next"); - canvas_draw_icon(canvas, 28, 54, &I_ButtonOK_7x7); - canvas_draw_str_aligned(canvas, 37, 54, AlignLeft, AlignTop, "Flip"); - } - else if (show_prev && show_next && is_flipped) - { - canvas_draw_icon(canvas, 0, 54, &I_ButtonLeft_4x7); - canvas_draw_str_aligned(canvas, 6, 54, AlignLeft, AlignTop, "Prev"); - canvas_draw_icon(canvas, 28, 54, &I_ButtonRight_4x7); - canvas_draw_str_aligned(canvas, 34, 54, AlignLeft, AlignTop, "Next"); - canvas_draw_icon(canvas, 54, 54, &I_ButtonOK_7x7); - canvas_draw_str_aligned(canvas, 63, 54, AlignLeft, AlignTop, "UnFlip"); - } - else if (show_prev && !show_next && is_flipped) - { - canvas_draw_icon(canvas, 0, 54, &I_ButtonLeft_4x7); - canvas_draw_str_aligned(canvas, 6, 54, AlignLeft, AlignTop, "Prev"); - canvas_draw_icon(canvas, 28, 54, &I_ButtonOK_7x7); - canvas_draw_str_aligned(canvas, 37, 54, AlignLeft, AlignTop, "UnFlip"); - } - else if (!show_prev && show_next && is_flipped) - { - canvas_draw_icon(canvas, 0, 54, &I_ButtonRight_4x7); - canvas_draw_str_aligned(canvas, 6, 54, AlignLeft, AlignTop, "Next"); - canvas_draw_icon(canvas, 28, 54, &I_ButtonOK_7x7); - canvas_draw_str_aligned(canvas, 37, 54, AlignLeft, AlignTop, "UnFlip"); - } - else if (!show_prev && !show_next && is_flipped) - { - canvas_draw_icon(canvas, 0, 54, &I_ButtonOK_7x7); - canvas_draw_str_aligned(canvas, 9, 54, AlignLeft, AlignTop, "UnFlip"); - } - else - { - canvas_draw_icon(canvas, 0, 54, &I_ButtonOK_7x7); - canvas_draw_str_aligned(canvas, 9, 54, AlignLeft, AlignTop, "Flip"); - } -} -// Callback function to handle the feed dialog -static void flip_social_callback_draw_feed(Canvas *canvas, void *model) -{ - UNUSED(model); - if (!canvas) - { - FURI_LOG_E(TAG, "Canvas is NULL"); - return; - } - if (!app_instance) - { - FURI_LOG_E(TAG, "FlipSocialApp is NULL"); - return; - } - - if (!flip_social_dialog_shown) - { - flip_social_dialog_shown = true; - app_instance->input_event_queue = furi_record_open(RECORD_INPUT_EVENTS); - app_instance->input_event = furi_pubsub_subscribe(app_instance->input_event_queue, on_input, NULL); - } - - // handle action - switch (action) - { - case ActionNone: - flip_social_canvas_draw_message(canvas, flip_social_feed->usernames[flip_social_feed->index], flip_social_feed->messages[flip_social_feed->index], flip_social_feed->is_flipped[flip_social_feed->index], flip_social_feed->index > 0, flip_social_feed->index < flip_social_feed->count - 1, flip_social_feed->flips[flip_social_feed->index]); - break; - case ActionNext: - canvas_clear(canvas); - if (flip_social_feed->index < flip_social_feed->count - 1) - { - flip_social_feed->index++; - } - flip_social_canvas_draw_message(canvas, flip_social_feed->usernames[flip_social_feed->index], flip_social_feed->messages[flip_social_feed->index], flip_social_feed->is_flipped[flip_social_feed->index], flip_social_feed->index > 0, flip_social_feed->index < flip_social_feed->count - 1, flip_social_feed->flips[flip_social_feed->index]); - action = ActionNone; - break; - case ActionPrev: - canvas_clear(canvas); - if (flip_social_feed->index > 0) - { - flip_social_feed->index--; - } - flip_social_canvas_draw_message(canvas, flip_social_feed->usernames[flip_social_feed->index], flip_social_feed->messages[flip_social_feed->index], flip_social_feed->is_flipped[flip_social_feed->index], flip_social_feed->index > 0, flip_social_feed->index < flip_social_feed->count - 1, flip_social_feed->flips[flip_social_feed->index]); - action = ActionNone; - break; - case ActionFlip: - canvas_clear(canvas); - // Moved to above the is_flipped check - if (!flip_social_feed->is_flipped[flip_social_feed->index]) - { - // increase the flip count - flip_social_feed->flips[flip_social_feed->index]++; - } - else - { - // decrease the flip count - flip_social_feed->flips[flip_social_feed->index]--; - } - // change the flip status - flip_social_feed->is_flipped[flip_social_feed->index] = !flip_social_feed->is_flipped[flip_social_feed->index]; - // send post request to flip the message - if (app_instance->login_username_logged_in == NULL) - { - FURI_LOG_E(TAG, "Username is NULL"); - return; - } - char payload[256]; - snprintf(payload, sizeof(payload), "{\"username\":\"%s\",\"post_id\":\"%u\"}", app_instance->login_username_logged_in, flip_social_feed->ids[flip_social_feed->index]); - flipper_http_post_request_with_headers("https://www.flipsocial.net/api/feed/flip/", "{\"Content-Type\":\"application/json\"}", payload); - flip_social_canvas_draw_message(canvas, flip_social_feed->usernames[flip_social_feed->index], flip_social_feed->messages[flip_social_feed->index], flip_social_feed->is_flipped[flip_social_feed->index], flip_social_feed->index > 0, flip_social_feed->index < flip_social_feed->count - 1, flip_social_feed->flips[flip_social_feed->index]); - action = ActionNone; - break; - case ActionBack: - canvas_clear(canvas); - flip_social_dialog_stop = true; - flip_social_feed->index = 0; - action = ActionNone; - break; - default: - break; - } - - if (flip_social_dialog_stop) - { - furi_pubsub_unsubscribe(app_instance->input_event_queue, app_instance->input_event); - flip_social_dialog_shown = false; - flip_social_dialog_stop = false; - action = ActionNone; - } -} -/** - * @brief Navigation callback for asynchonously handling the login process. - * @param canvas The canvas to draw on. - * @param model The model - unused - * @return void - */ -static void flip_social_callback_draw_login(Canvas *canvas, void *model) -{ - UNUSED(model); - if (!canvas) - { - FURI_LOG_E(TAG, "Canvas is NULL"); - return; - } - - canvas_set_font(canvas, FontSecondary); - - if (!flip_social_board_is_active(canvas)) - { - return; - } - - canvas_draw_str(canvas, 0, 7, "Logging in..."); - - // Perform login request - if (!flip_social_sent_login_request) - { - - if (!app_instance->login_username_logged_out || !app_instance->login_password_logged_out || strlen(app_instance->login_username_logged_out) == 0 || strlen(app_instance->login_password_logged_out) == 0) - { - canvas_clear(canvas); - canvas_draw_str(canvas, 0, 10, "Please enter your credentials."); - canvas_draw_str(canvas, 0, 60, "Press BACK to return."); - return; - } - - flip_social_sent_login_request = true; - - char buffer[256]; - snprintf(buffer, sizeof(buffer), "{\"username\":\"%s\",\"password\":\"%s\"}", app_instance->login_username_logged_out, app_instance->login_password_logged_out); - flip_social_login_success = flipper_http_post_request_with_headers("https://www.flipsocial.net/api/user/login/", "{\"Content-Type\":\"application/json\"}", buffer); - if (flip_social_login_success) - { - fhttp.state = RECEIVING; - return; - } - else - { - fhttp.state = ISSUE; - return; - } - } - // handle response - if (flip_social_sent_login_request && flip_social_login_success) - { - canvas_set_font(canvas, FontSecondary); - canvas_draw_str(canvas, 0, 17, "Request Sent!"); - canvas_draw_str(canvas, 0, 32, "Awaiting reponse..."); - - if (fhttp.state == IDLE && fhttp.received_data != NULL) - { - // read response - if (strstr(fhttp.received_data, "[SUCCESS]") != NULL || strstr(fhttp.received_data, "User found") != NULL) - { - canvas_draw_str(canvas, 0, 42, "Login successful!"); - canvas_draw_str(canvas, 0, 62, "Welcome back!"); - - app_instance->is_logged_in = "true"; - - // set the logged_in_username and change_password_logged_in - if (app_instance->login_username_logged_out) - { - strcpy(app_instance->login_username_logged_in, app_instance->login_username_logged_out); - } - if (app_instance->login_password_logged_out) - { - app_instance->change_password_logged_in = app_instance->login_password_logged_out; - } - - save_settings(app_instance->wifi_ssid_logged_out, app_instance->wifi_password_logged_out, app_instance->login_username_logged_out, app_instance->login_username_logged_in, app_instance->login_password_logged_out, app_instance->change_password_logged_in, app_instance->is_logged_in); - - // send user to the logged in submenu - view_dispatcher_switch_to_view(app_instance->view_dispatcher, FlipSocialViewLoggedInSubmenu); - } - else if (strstr(fhttp.received_data, "User not found") != NULL) - { - canvas_clear(canvas); - canvas_draw_str(canvas, 0, 10, "Account not found..."); - canvas_draw_str(canvas, 0, 60, "Press BACK to return."); - } - else - { - flip_social_handle_error(canvas); - } - } - else if ((fhttp.state == ISSUE || fhttp.state == INACTIVE) && fhttp.received_data != NULL) - { - flip_social_handle_error(canvas); - } - else if (fhttp.state == IDLE && fhttp.received_data == NULL) - { - flip_social_handle_error(canvas); - } - } - else if (flip_social_sent_login_request && !flip_social_login_success) - { - canvas_clear(canvas); - canvas_draw_str(canvas, 0, 10, "Failed sending request."); - canvas_draw_str(canvas, 0, 50, "Update your WiFi settings."); - canvas_draw_str(canvas, 0, 62, "Press BACK to return."); - } -} - -/** - * @brief Navigation callback for asynchonously handling the register process. - * @param canvas The canvas to draw on. - * @param model The model - unused - * @return void - */ -static void flip_social_callback_draw_register(Canvas *canvas, void *model) -{ - UNUSED(model); - if (!canvas) - { - FURI_LOG_E(TAG, "Canvas is NULL"); - return; - } - - canvas_set_font(canvas, FontSecondary); - - if (!flip_social_board_is_active(canvas)) - { - return; - } - - canvas_draw_str(canvas, 0, 7, "Registering..."); - - // Perform login request - if (!flip_social_sent_register_request) - { - // check if the username and password are valid - if (!app_instance->register_username_logged_out || !app_instance->register_password_logged_out || strlen(app_instance->register_username_logged_out) == 0 || strlen(app_instance->register_password_logged_out) == 0) - { - canvas_clear(canvas); - canvas_draw_str(canvas, 0, 10, "Please enter your credentials."); - canvas_draw_str(canvas, 0, 60, "Press BACK to return."); - return; - } - - // check if both passwords match - if (strcmp(app_instance->register_password_logged_out, app_instance->register_password_2_logged_out) != 0) - { - canvas_clear(canvas); - canvas_draw_str(canvas, 0, 10, "Passwords do not match."); - canvas_draw_str(canvas, 0, 60, "Press BACK to return."); - return; - } - - char buffer[128]; - snprintf(buffer, sizeof(buffer), "{\"username\":\"%s\",\"password\":\"%s\"}", app_instance->register_username_logged_out, app_instance->register_password_logged_out); - flip_social_register_success = flipper_http_post_request_with_headers("https://www.flipsocial.net/api/user/register/", "{\"Content-Type\":\"application/json\"}", buffer); - - flip_social_sent_register_request = true; - if (flip_social_register_success) - { - // Set the state to RECEIVING to ensure we continue to see the receiving message - fhttp.state = RECEIVING; - } - else - { - fhttp.state = ISSUE; - } - } - // handle response - if (flip_social_sent_register_request && flip_social_register_success) - { - canvas_set_font(canvas, FontSecondary); - canvas_draw_str(canvas, 0, 17, "Request Sent!"); - canvas_draw_str(canvas, 0, 32, "Awaiting reponse..."); - - if (fhttp.state == IDLE) - { - // read response - if (fhttp.received_data != NULL && (strstr(fhttp.received_data, "[SUCCESS]") != NULL || strstr(fhttp.received_data, "User created") != NULL)) - { - canvas_draw_str(canvas, 0, 42, "Registeration successful!"); - canvas_draw_str(canvas, 0, 62, "Welcome to FlipSocial!"); - - // set the login credentials - if (app_instance->login_username_logged_out) - { - app_instance->login_username_logged_out = app_instance->register_username_logged_out; - } - if (app_instance->login_password_logged_out) - { - app_instance->login_password_logged_out = app_instance->register_password_logged_out; - app_instance->change_password_logged_in = app_instance->register_password_logged_out; - } - if (app_instance->login_username_logged_in) - { - app_instance->login_username_logged_in = app_instance->register_username_logged_out; - } - - app_instance->is_logged_in = "true"; - - // save the credentials - save_settings(app_instance->wifi_ssid_logged_out, app_instance->wifi_password_logged_out, app_instance->login_username_logged_out, app_instance->login_username_logged_in, app_instance->login_password_logged_out, app_instance->change_password_logged_in, app_instance->is_logged_in); - - // send user to the logged in submenu - view_dispatcher_switch_to_view(app_instance->view_dispatcher, FlipSocialViewLoggedInSubmenu); - } - else if (strstr(fhttp.received_data, "Username or password not provided") != NULL) - { - canvas_clear(canvas); - canvas_draw_str(canvas, 0, 10, "Please enter your credentials."); - canvas_draw_str(canvas, 0, 60, "Press BACK to return."); - } - else if (strstr(fhttp.received_data, "User already exists") != NULL || strstr(fhttp.received_data, "Multiple users found") != NULL) - { - canvas_draw_str(canvas, 0, 42, "Registration failed..."); - canvas_draw_str(canvas, 0, 52, "Username already exists."); - canvas_draw_str(canvas, 0, 62, "Press BACK to return."); - } - else - { - canvas_draw_str(canvas, 0, 42, "Registration failed..."); - canvas_draw_str(canvas, 0, 52, "Update your credentials."); - canvas_draw_str(canvas, 0, 62, "Press BACK to return."); - } - } - else if (fhttp.state == ISSUE || fhttp.state == INACTIVE) - { - flip_social_handle_error(canvas); - } - } - else if (flip_social_sent_register_request && !flip_social_register_success) - { - canvas_clear(canvas); - canvas_draw_str(canvas, 0, 10, "Failed sending request."); - canvas_draw_str(canvas, 0, 50, "Update your WiFi settings."); - canvas_draw_str(canvas, 0, 62, "Press BACK to return."); - } -} - -// function to draw the dialog canvas -static void flip_social_canvas_draw_explore(Canvas *canvas, char *user_username, char *content) -{ - canvas_set_color(canvas, ColorBlack); - canvas_set_font(canvas, FontPrimary); - canvas_draw_str_aligned(canvas, 64, 5, AlignCenter, AlignCenter, user_username); - canvas_set_font(canvas, FontSecondary); - - draw_user_message(canvas, content, 0, 12); - - canvas_set_font(canvas, FontSecondary); - canvas_draw_icon(canvas, 0, 53, &I_ButtonLeft_4x7); - canvas_draw_str_aligned(canvas, 9, 54, AlignLeft, AlignTop, "Remove"); - canvas_draw_icon(canvas, 98, 53, &I_ButtonRight_4x7); - canvas_draw_str_aligned(canvas, 107, 54, AlignLeft, AlignTop, "Add"); - - if (strlen(content) > 0) - { - last_explore_response = content; - } -} - -// Callback function to handle the explore dialog -static void flip_social_callback_draw_explore(Canvas *canvas, void *model) -{ - UNUSED(model); - if (!canvas) - { - FURI_LOG_E(TAG, "Canvas is NULL"); - return; - } - if (!app_instance) - { - FURI_LOG_E(TAG, "FlipSocialApp is NULL"); - return; - } - - if (!flip_social_dialog_shown) - { - flip_social_dialog_shown = true; - app_instance->input_event_queue = furi_record_open(RECORD_INPUT_EVENTS); - app_instance->input_event = furi_pubsub_subscribe(app_instance->input_event_queue, on_input, NULL); - } - flip_social_canvas_draw_explore(canvas, flip_social_explore->usernames[flip_social_explore->index], last_explore_response); - - // handle action - switch (action) - { - case ActionNext: - // add friend - char add_payload[128]; - snprintf(add_payload, sizeof(add_payload), "{\"username\":\"%s\",\"friend\":\"%s\"}", app_instance->login_username_logged_in, flip_social_explore->usernames[flip_social_explore->index]); - flipper_http_post_request_with_headers("https://www.flipsocial.net/api/user/add-friend/", "{\"Content-Type\":\"application/json\"}", add_payload); - canvas_clear(canvas); - flip_social_canvas_draw_explore(canvas, flip_social_explore->usernames[flip_social_explore->index], "Added!"); - action = ActionNone; - break; - case ActionPrev: - // remove friend - char remove_payload[128]; - snprintf(remove_payload, sizeof(remove_payload), "{\"username\":\"%s\",\"friend\":\"%s\"}", app_instance->login_username_logged_in, flip_social_explore->usernames[flip_social_explore->index]); - flipper_http_post_request_with_headers("https://www.flipsocial.net/api/user/remove-friend/", "{\"Content-Type\":\"application/json\"}", remove_payload); - canvas_clear(canvas); - flip_social_canvas_draw_explore(canvas, flip_social_explore->usernames[flip_social_explore->index], "Removed!"); - action = ActionNone; - break; - case ActionBack: - canvas_clear(canvas); - flip_social_dialog_stop = true; - last_explore_response = ""; - flip_social_dialog_shown = false; - flip_social_explore->index = 0; - action = ActionNone; - break; - default: - break; - } - - if (flip_social_dialog_stop) - { - furi_pubsub_unsubscribe(app_instance->input_event_queue, app_instance->input_event); - flip_social_dialog_shown = false; - flip_social_dialog_stop = false; - action = ActionNone; - } -} - -// Callback function to handle the friends dialog -static void flip_social_callback_draw_friends(Canvas *canvas, void *model) -{ - UNUSED(model); - if (!canvas) - { - FURI_LOG_E(TAG, "Canvas is NULL"); - return; - } - if (!app_instance) - { - FURI_LOG_E(TAG, "FlipSocialApp is NULL"); - return; - } - - if (!flip_social_dialog_shown) - { - flip_social_dialog_shown = true; - app_instance->input_event_queue = furi_record_open(RECORD_INPUT_EVENTS); - app_instance->input_event = furi_pubsub_subscribe(app_instance->input_event_queue, on_input, NULL); - } - flip_social_canvas_draw_explore(canvas, flip_social_friends->usernames[flip_social_friends->index], last_explore_response); - - // handle action - switch (action) - { - case ActionNext: - // add friend - char add_payload[128]; - snprintf(add_payload, sizeof(add_payload), "{\"username\":\"%s\",\"friend\":\"%s\"}", app_instance->login_username_logged_in, flip_social_friends->usernames[flip_social_friends->index]); - if (flipper_http_post_request_with_headers("https://www.flipsocial.net/api/user/add-friend/", "{\"Content-Type\":\"application/json\"}", add_payload)) - { - canvas_clear(canvas); - flip_social_canvas_draw_explore(canvas, flip_social_friends->usernames[flip_social_friends->index], "Added!"); - - // add the friend to the friends list - flip_social_friends->usernames[flip_social_friends->count] = flip_social_friends->usernames[flip_social_friends->index]; - flip_social_friends->count++; - if (!flip_social_update_friends()) - { - FURI_LOG_E(TAG, "Failed to update friends"); - } - } - action = ActionNone; - break; - case ActionPrev: - // remove friend - char remove_payload[128]; - snprintf(remove_payload, sizeof(remove_payload), "{\"username\":\"%s\",\"friend\":\"%s\"}", app_instance->login_username_logged_in, flip_social_friends->usernames[flip_social_friends->index]); - if (flipper_http_post_request_with_headers("https://www.flipsocial.net/api/user/remove-friend/", "{\"Content-Type\":\"application/json\"}", remove_payload)) - { - canvas_clear(canvas); - flip_social_canvas_draw_explore(canvas, flip_social_friends->usernames[flip_social_friends->index], "Removed!"); - - // remove the friend from the friends list - for (int i = flip_social_friends->index; i < flip_social_friends->count - 1; i++) - { - flip_social_friends->usernames[i] = flip_social_friends->usernames[i + 1]; - } - flip_social_friends->count--; - if (!flip_social_update_friends()) - { - FURI_LOG_E(TAG, "Failed to update friends"); - } - } - action = ActionNone; - break; - case ActionBack: - canvas_clear(canvas); - flip_social_dialog_stop = true; - last_explore_response = ""; - flip_social_dialog_shown = false; - flip_social_friends->index = 0; - action = ActionNone; - break; - default: - break; - } - - if (flip_social_dialog_stop) - { - furi_pubsub_unsubscribe(app_instance->input_event_queue, app_instance->input_event); - flip_social_dialog_shown = false; - flip_social_dialog_stop = false; - action = ActionNone; - } -} - -static void flip_social_canvas_draw_user_message(Canvas *canvas, char *user_username, char *user_message, bool show_prev, bool show_next) -{ - canvas_set_color(canvas, ColorBlack); - canvas_set_font(canvas, FontPrimary); - canvas_draw_str_aligned(canvas, 64, 5, AlignCenter, AlignCenter, user_username); - canvas_set_font(canvas, FontSecondary); - - draw_user_message(canvas, user_message, 0, 12); - - canvas_set_font(canvas, FontSecondary); - if (show_prev) - { - canvas_draw_icon(canvas, 0, 53, &I_ButtonLeft_4x7); - canvas_draw_str_aligned(canvas, 9, 54, AlignLeft, AlignTop, "Prev"); - } - - canvas_draw_icon(canvas, 47, 53, &I_ButtonOK_7x7); - canvas_draw_str_aligned(canvas, 56, 54, AlignLeft, AlignTop, "Create"); - - if (show_next) - { - canvas_draw_icon(canvas, 98, 53, &I_ButtonRight_4x7); - canvas_draw_str_aligned(canvas, 107, 54, AlignLeft, AlignTop, "Next"); - } -} - -// Callback function to handle the messages dialog -static void flip_social_callback_draw_messages(Canvas *canvas, void *model) -{ - UNUSED(model); - if (!canvas) - { - FURI_LOG_E(TAG, "Canvas is NULL"); - return; - } - if (!app_instance) - { - FURI_LOG_E(TAG, "FlipSocialApp is NULL"); - return; - } - - if (!flip_social_dialog_shown) - { - flip_social_dialog_shown = true; - app_instance->input_event_queue = furi_record_open(RECORD_INPUT_EVENTS); - app_instance->input_event = furi_pubsub_subscribe(app_instance->input_event_queue, on_input, NULL); - } - - // handle action - switch (action) - { - case ActionNone: - flip_social_canvas_draw_user_message(canvas, flip_social_messages->usernames[flip_social_messages->index], flip_social_messages->messages[flip_social_messages->index], flip_social_messages->index > 0, flip_social_messages->index < flip_social_messages->count - 1); - action = ActionNone; - break; - case ActionNext: - // view next message (if any) - canvas_clear(canvas); - if (flip_social_messages->index < flip_social_messages->count - 1) - { - flip_social_messages->index++; - } - flip_social_canvas_draw_user_message(canvas, flip_social_messages->usernames[flip_social_messages->index], flip_social_messages->messages[flip_social_messages->index], flip_social_messages->index > 0, flip_social_messages->index < flip_social_messages->count - 1); - action = ActionNone; - break; - case ActionPrev: - // view previous message (if any) - canvas_clear(canvas); - if (flip_social_messages->index > 0) - { - flip_social_messages->index--; - } - flip_social_canvas_draw_user_message(canvas, flip_social_messages->usernames[flip_social_messages->index], flip_social_messages->messages[flip_social_messages->index], flip_social_messages->index > 0, flip_social_messages->index < flip_social_messages->count - 1); - action = ActionNone; - break; - case ActionBack: - // go back to the previous view - flip_social_dialog_stop = true; - action = ActionNone; - break; - case ActionFlip: - // go to the input view - flip_social_dialog_stop = true; - break; - default: - action = ActionNone; - break; - } - - if (flip_social_dialog_stop) - { - furi_pubsub_unsubscribe(app_instance->input_event_queue, app_instance->input_event); - flip_social_dialog_shown = false; - flip_social_dialog_stop = false; - if (action == ActionFlip) - { - action = ActionNone; - view_dispatcher_switch_to_view(app_instance->view_dispatcher, FlipSocialViewLoggedInMessagesNewMessageInput); - } - else - { - action = ActionNone; - view_dispatcher_switch_to_view(app_instance->view_dispatcher, FlipSocialViewLoggedInMessagesSubmenu); - } - } -} - -#endif // FLIP_SOCIAL_DRAW_H \ No newline at end of file diff --git a/flip_social_e.h b/flip_social_e.h deleted file mode 100644 index 2300418ba..000000000 --- a/flip_social_e.h +++ /dev/null @@ -1,310 +0,0 @@ -// flip_social_e.h -#ifndef FLIP_SOCIAL_E -#define FLIP_SOCIAL_E -#include -#include -#include -#include -#include -#include -#define TAG "FlipSocial" - -#define MAX_PRE_SAVED_MESSAGES 25 // Maximum number of pre-saved messages -#define MAX_MESSAGE_LENGTH 100 // Maximum length of a message in the feed -#define MAX_EXPLORE_USERS 50 // Maximum number of users to explore -#define MAX_USER_LENGTH 32 // Maximum length of a username -#define MAX_FRIENDS 50 // Maximum number of friends -#define MAX_TOKENS 450 // Adjust based on expected JSON tokens -#define MAX_FEED_ITEMS 41 // Maximum number of feed items -#define MAX_LINE_LENGTH 30 -#define MAX_MESSAGE_USERS 20 // Maximum number of users to display in the submenu -#define MAX_MESSAGES 20 // Maximum number of meesages between each user - -// Define the submenu items for our Hello World application -typedef enum -{ - FlipSocialSubmenuLoggedOutIndexLogin, // click to go to the login screen - FlipSocialSubmenuLoggedOutIndexRegister, // click to go to the register screen - FlipSocialSubmenuLoggedOutIndexAbout, // click to go to the about screen - FlipSocialSubmenuLoggedOutIndexWifiSettings, // click to go to the wifi settings screen - // - FlipSocialSubmenuLoggedInIndexProfile, // click to go to the profile screen - FlipSocialSubmenuExploreIndex, // click to go to the explore - FlipSocialSubmenuLoggedInIndexFeed, // click to go to the feed screen - FlipSocialSubmenuLoggedInIndexMessages, // click to go to the messages screen - FlipSocialSubmenuLoggedInIndexCompose, // click to go to the compose screen - FlipSocialSubmenuLoggedInIndexSettings, // click to go to the settings screen - FlipSocialSubmenuLoggedInSignOutButton, // click to sign out - // - FlipSocialSubmenuLoggedInIndexMessagesNewMessage, // click to add a new message - // - FlipSocialSubmenuComposeIndexAddPreSave, // click to add a pre-saved message - FlipSocialSubemnuComposeIndexStartIndex = 100, // starting index for the first pre saved message - // - FlipSocialSubmenuExploreIndexStartIndex = 150, // starting index for the users to explore - // - FlipSocialSubmenuLoggedInIndexFriendsStart = 200, // starting index for the friends - // - FlipSocialSubmenuLoggedInIndexMessagesUsersStart = 250, // starting index for the messages - // - FlipSocialSubmenuLoggedInIndexMessagesUserChoicesIndexStart = 300, // click to select a user to message -} FlipSocialSubmenuIndex; - -typedef enum -{ - ActionNone, - ActionBack, - ActionNext, - ActionPrev, - ActionFlip, -} Action; - -static Action action = ActionNone; - -// Define the ScriptPlaylist structure -typedef struct -{ - char *messages[MAX_PRE_SAVED_MESSAGES]; - size_t count; -} PreSavedPlaylist; - -typedef struct -{ - char *usernames[MAX_FEED_ITEMS]; - char *messages[MAX_FEED_ITEMS]; - bool is_flipped[MAX_FEED_ITEMS]; - int ids[MAX_FEED_ITEMS]; - int flips[MAX_FEED_ITEMS]; - size_t count; - size_t index; -} FlipSocialFeed; - -typedef struct -{ - char *usernames[MAX_EXPLORE_USERS]; - int count; - int index; -} FlipSocialModel; - -typedef struct -{ - char *usernames[MAX_MESSAGE_USERS]; - int count; - int index; -} FlipSocialModel2; - -typedef struct -{ - char *usernames[MAX_MESSAGES]; - char *messages[MAX_MESSAGES]; - int count; - int index; -} FlipSocialMessage; - -// Define views for our Hello World application -typedef enum -{ - FlipSocialViewLoggedOutSubmenu, // The menu if the user is not logged in - FlipSocialViewLoggedOutLogin, // The login screen - FlipSocialViewLoggedOutRegister, // The register screen - FlipSocialViewLoggedOutAbout, // The about screen - FlipSocialViewLoggedOutWifiSettings, // The wifi settings screen - // - FlipSocialViewLoggedOutLoginUsernameInput, // Text input screen for username input on login screen - FlipSocialViewLoggedOutLoginPasswordInput, // Text input screen for password input on login screen - FlipSocialViewLoggedOutRegisterUsernameInput, // Text input screen for username input on register screen - FlipSocialViewLoggedOutRegisterPasswordInput, // Text input screen for password input on register screen - FlipSocialViewLoggedOutRegisterPassword2Input, // Text input screen for password 2 input on register screen - FlipSocialViewLoggedOutWifiSettingsSSIDInput, // Text input screen for SSID input on wifi screen - FlipSocialViewLoggedOutWifiSettingsPasswordInput, // Text input screen for Password input on wifi screen - FlipSocialViewLoggedOutProcessLogin, // The screen displayed after clicking login - FlipSocialViewLoggedOutProcessRegister, // The screen displayed after clicking register - // - FlipSocialViewLoggedInSubmenu, // The menu if the user is logged in - FlipSocialViewLoggedInProfile, // The profile screen - FlipSocialViewLoggedInFeed, // The feed screen - FlipSocialViewLoggedInCompose, // The compose screen - FlipSocialViewLoggedInSettings, // The settings screen - // - FlipSocialViewLoggedInChangePasswordInput, // Text input screen for password input on change password screen - FlipSocialViewLoggedInComposeAddPreSaveInput, // Text input screen for add text input on compose screen - // - FlipSocialViewLoggedInMessagesNewMessageInput, // Text input screen for new message input on messages screen - FlipSocialViewLoggedInMessagesNewMessageUserChoicesInput, // Text input screen for new message input on messages screen - FlipSocialViewLoggedInMessagesUserChoices, // the view after clicking [New Message] - select a user to message, then direct to input view - // - FlipSocialViewLoggedInSettingsAbout, // The about screen - FlipSocialViewLoggedInSettingsWifi, // The wifi settings screen - FlipSocialViewLoggedInWifiSettingsSSIDInput, // Text input screen for SSID input on wifi screen - FlipSocialViewLoggedInWifiSettingsPasswordInput, // Text input screen for Password input on wifi screen - FlipSocialViewLoggedInProcessCompose, // The dialog view to delete or send the clicked pre-saved text - // - FlipSocialViewLoggedInSignOut, // The view after clicking the sign out button - // - FlipSocialViewLoggedInExploreSubmenu, // The view after clicking the explore button - FlipSocialViewLoggedInExploreProccess, // The view after clicking on a user in the explore screen - FlipSocialViewLoggedInFriendsSubmenu, // The view after clicking the friends button on the profile screen - FlipSocialViewLoggedInFriendsProcess, // The view after clicking on a friend in the friends screen - FlipSocialViewLoggedInMessagesSubmenu, // The view after clicking the messages button on the profile screen - FlipSocialViewLoggedInMessagesProcess, // The view after clicking on a user in the messages screen -} FlipSocialView; - -// Define the application structure -typedef struct -{ - ViewDispatcher *view_dispatcher; // Switches between our views - Submenu *submenu_logged_out; // The application submenu (logged out) - Submenu *submenu_logged_in; // The application submenu (logged in) - Submenu *submenu_compose; // The application submenu (compose) - Submenu *submenu_explore; // The application submenu (explore) - Submenu *submenu_friends; // The application submenu (friends) - Submenu *submenu_messages; // The application submenu (messages) - Submenu *submenu_messages_user_choices; // The application submenu (messages user choices) - Widget *widget_logged_out_about; // The about screen (logged out) - Widget *widget_logged_in_about; // The about screen (logged in) - - View *view_process_login; // The screen displayed after clicking login - View *view_process_register; // The screen displayed after clicking register - View *view_process_feed; // Dialog for the feed screen - View *view_process_compose; // Dialog for the compose screen (delete or send) - View *view_process_explore; // Dialog for the explore screen (view user profile - add or delete friend) - View *view_process_friends; // Dialog for the friends screen (view user profile - add or delete friend) - View *view_process_messages; // Dialog for the messages screen (next, previous, send message) - - VariableItemList *variable_item_list_logged_out_wifi_settings; // The wifi settings menu - VariableItemList *variable_item_list_logged_out_login; // The login menu - VariableItemList *variable_item_list_logged_out_register; // The register menu - // - VariableItemList *variable_item_list_logged_in_profile; // The profile menu - VariableItemList *variable_item_list_logged_in_settings; // The settings menu - VariableItemList *variable_item_list_logged_in_settings_wifi; // The wifi settings menu - - UART_TextInput *text_input_logged_out_wifi_settings_ssid; // Text input for ssid input on wifi settings screen - UART_TextInput *text_input_logged_out_wifi_settings_password; // Text input for password input on wifi settings screen - UART_TextInput *text_input_logged_out_login_username; // Text input for username input on login screen - UART_TextInput *text_input_logged_out_login_password; // Text input for password input on login screen - UART_TextInput *text_input_logged_out_register_username; // Text input for username input on register screen - UART_TextInput *text_input_logged_out_register_password; // Text input for password input on register screen - UART_TextInput *text_input_logged_out_register_password_2; // Text input for password 2 input on register screen - // - UART_TextInput *text_input_logged_in_change_password; // Text input for password input on change password screen - UART_TextInput *text_input_logged_in_compose_pre_save_input; // Text input for pre save input on compose screen - UART_TextInput *text_input_logged_in_wifi_settings_ssid; // Text input for ssid input on wifi settings screen - UART_TextInput *text_input_logged_in_wifi_settings_password; // Text input for password input on wifi settings screen - // - UART_TextInput *text_input_logged_in_messages_new_message; // Text input for new message input on messages screen - UART_TextInput *text_input_logged_in_messages_new_message_user_choices; // - - VariableItem *variable_item_logged_out_wifi_settings_ssid; // Reference to the ssid configuration item - VariableItem *variable_item_logged_out_wifi_settings_password; // Reference to the password configuration item - VariableItem *variable_item_logged_out_login_username; // Reference to the username configuration item - VariableItem *variable_item_logged_out_login_password; // Reference to the password configuration item - VariableItem *variable_item_logged_out_login_button; // Reference to the login button configuration item - VariableItem *variable_item_logged_out_register_username; // Reference to the username configuration item - VariableItem *variable_item_logged_out_register_password; // Reference to the password configuration item - VariableItem *variable_item_logged_out_register_password_2; // Reference to the password 2 configuration item - VariableItem *variable_item_logged_out_register_button; // Reference to the register button configuration item - // - VariableItem *variable_item_logged_in_profile_username; // Reference to the username configuration item - VariableItem *variable_item_logged_in_profile_change_password; // Reference to the change password configuration item - VariableItem *variable_item_logged_in_settings_about; // Reference to the about configuration item - VariableItem *variable_item_logged_in_settings_wifi; // Reference to the wifi settings configuration item - VariableItem *variable_item_logged_in_wifi_settings_ssid; // Reference to the ssid configuration item - VariableItem *variable_item_logged_in_wifi_settings_password; // Reference to the password configuration item - // - VariableItem *variable_item_logged_in_profile_friends; // Reference to the friends configuration item - // - FuriPubSub *input_event_queue; - FuriPubSubSubscription *input_event; - - PreSavedPlaylist pre_saved_messages; // Pre-saved messages for the feed screen - - char *is_logged_in; // Store the login status - uint32_t is_logged_in_size; // Size of the login status buffer - - char *login_username_logged_in; // Store the entered login username - char *login_username_logged_in_temp_buffer; // Temporary buffer for login username text input - uint32_t login_username_logged_in_temp_buffer_size; // Size of the login username temporary buffer - - char *wifi_ssid_logged_out; // Store the entered wifi ssid - char *wifi_ssid_logged_out_temp_buffer; // Temporary buffer for wifi ssid text input - uint32_t wifi_ssid_logged_out_temp_buffer_size; // Size of the wifi ssid temporary buffer - - char *wifi_password_logged_out; // Store the entered wifi password - char *wifi_password_logged_out_temp_buffer; // Temporary buffer for wifi_password text input - uint32_t wifi_password_logged_out_temp_buffer_size; // Size of the wifi_password temporary buffer - - char *login_username_logged_out; // Store the entered login username - char *login_username_logged_out_temp_buffer; // Temporary buffer for login username text input - uint32_t login_username_logged_out_temp_buffer_size; // Size of the login username temporary buffer - - char *login_password_logged_out; // Store the entered login password - char *login_password_logged_out_temp_buffer; // Temporary buffer for login password text input - uint32_t login_password_logged_out_temp_buffer_size; // Size of the login password temporary buffer - - char *register_username_logged_out; // Store the entered register username - char *register_username_logged_out_temp_buffer; // Temporary buffer for register username text input - uint32_t register_username_logged_out_temp_buffer_size; // Size of the register username temporary buffer - - char *register_password_logged_out; // Store the entered register password - char *register_password_logged_out_temp_buffer; // Temporary buffer for register password text input - uint32_t register_password_logged_out_temp_buffer_size; // Size of the register password temporary buffer - - char *register_password_2_logged_out; // Store the entered register password 2 - char *register_password_2_logged_out_temp_buffer; // Temporary buffer for register password 2 text input - uint32_t register_password_2_logged_out_temp_buffer_size; // Size of the register password 2 temporary buffer - - // - char *change_password_logged_in; // Store the entered change password - char *change_password_logged_in_temp_buffer; // Temporary buffer for change password text input - uint32_t change_password_logged_in_temp_buffer_size; // Size of the change password temporary buffer - - char *compose_pre_save_logged_in; // Store the entered add text - char *compose_pre_save_logged_in_temp_buffer; // Temporary buffer for add text text input - uint32_t compose_pre_save_logged_in_temp_buffer_size; // Size of the add text temporary buffer - - char *wifi_ssid_logged_in; // Store the entered wifi ssid - char *wifi_ssid_logged_in_temp_buffer; // Temporary buffer for wifi ssid text input - uint32_t wifi_ssid_logged_in_temp_buffer_size; // Size of the wifi ssid temporary buffer - - char *wifi_password_logged_in; // Store the entered wifi password - char *wifi_password_logged_in_temp_buffer; // Temporary buffer for wifi_password text input - uint32_t wifi_password_logged_in_temp_buffer_size; // Size of the wifi_password temporary buffer - - // - char *messages_new_message_logged_in; // Store the entered new message - char *messages_new_message_logged_in_temp_buffer; // Temporary buffer for new message text input - uint32_t messages_new_message_logged_in_temp_buffer_size; // Size of the new message temporary buffer - - char *message_user_choice_logged_in; // Store the entered message to send to the selected user - char *message_user_choice_logged_in_temp_buffer; // Temporary buffer for message to send to the selected user - uint32_t message_user_choice_logged_in_temp_buffer_size; // Size of the message to send to the selected user temporary buffer -} FlipSocialApp; - -static FlipSocialFeed *flip_social_feed = NULL; // Store the feed -static FlipSocialModel *flip_social_friends = NULL; // Store the friends -static FlipSocialModel2 *flip_social_message_users = NULL; // Store the users that have sent messages to the logged in user -static FlipSocialModel *flip_social_explore = NULL; // Store the users to explore -static FlipSocialMessage *flip_social_messages = NULL; // Store the messages between the logged in user and the selected user - -// include strndup (otherwise NULL pointer dereference) -char *strndup(const char *s, size_t n) -{ - char *result; - size_t len = strlen(s); - - if (n < len) - len = n; - - result = (char *)malloc(len + 1); - if (!result) - return NULL; - - result[len] = '\0'; - return (char *)memcpy(result, s, len); -} - -static FlipSocialApp *app_instance = NULL; -static void flip_social_logged_in_compose_pre_save_updated(void *context); -static void flip_social_callback_submenu_choices(void *context, uint32_t index); -#endif \ No newline at end of file diff --git a/flip_social_explore.h b/flip_social_explore.h deleted file mode 100644 index 74b826e35..000000000 --- a/flip_social_explore.h +++ /dev/null @@ -1,141 +0,0 @@ -#ifndef FLIP_SOCIAL_EXPLORE_H -#define FLIP_SOCIAL_EXPLORE_H - -static FlipSocialModel *flip_social_explore_alloc() -{ - // Allocate memory for each username only if not already allocated - FlipSocialModel *explore = malloc(sizeof(FlipSocialModel)); - if (explore == NULL) - { - FURI_LOG_E(TAG, "Failed to allocate memory for explore model."); - return NULL; - } - for (size_t i = 0; i < MAX_EXPLORE_USERS; i++) - { - if (explore->usernames[i] == NULL) - { - explore->usernames[i] = malloc(MAX_USER_LENGTH); - if (explore->usernames[i] == NULL) - { - FURI_LOG_E(TAG, "Failed to allocate memory for username %zu", i); - return NULL; // Return false on memory allocation failure - } - } - } - return explore; -} - -static void flip_social_free_explore() -{ - if (!flip_social_explore) - { - FURI_LOG_E(TAG, "Explore model is NULL"); - return; - } - for (int i = 0; i < flip_social_explore->count; i++) - { - free(flip_social_explore->usernames[i]); - } -} - -// for now we're just listing the current users -// as the feed is upgraded, then we can port more to the explore view -static bool flip_social_get_explore() -{ - // will return true unless the devboard is not connected - bool success = flipper_http_get_request_with_headers("https://www.flipsocial.net/api/user/users/", jsmn("Content-Type", "application/json")); - if (!success) - { - FURI_LOG_E(TAG, "Failed to send HTTP request for explore"); - return false; - } - fhttp.state = RECEIVING; - return true; -} - -static bool flip_social_parse_json_explore() -{ - if (fhttp.received_data == NULL) - { - FURI_LOG_E(TAG, "No data received."); - return false; - } - - // Allocate memory for each username only if not already allocated - flip_social_explore = flip_social_explore_alloc(); - if (flip_social_explore == NULL) - { - FURI_LOG_E(TAG, "Failed to allocate memory for explore usernames."); - return false; - } - - // Remove newlines - char *pos = fhttp.received_data; - while ((pos = strchr(pos, '\n')) != NULL) - { - *pos = ' '; - } - - // Initialize explore count - flip_social_explore->count = 0; - - // Extract the users array from the JSON - char *json_users = get_json_value("users", fhttp.received_data, MAX_TOKENS); - if (json_users == NULL) - { - FURI_LOG_E(TAG, "Failed to parse users array."); - return false; - } - - // Manual tokenization for comma-separated values - char *start = json_users + 1; // Skip the opening bracket - char *end; - while ((end = strchr(start, ',')) != NULL && flip_social_explore->count < MAX_EXPLORE_USERS) - { - *end = '\0'; // Null-terminate the current token - - // Remove quotes - if (*start == '"') - start++; - if (*(end - 1) == '"') - *(end - 1) = '\0'; - - // Copy username to pre-allocated memory - strncpy(flip_social_explore->usernames[flip_social_explore->count], start, MAX_USER_LENGTH - 1); - flip_social_explore->usernames[flip_social_explore->count][MAX_USER_LENGTH - 1] = '\0'; // Ensure null termination - flip_social_explore->count++; - start = end + 1; - } - - // Handle the last token - if (*start != '\0' && flip_social_explore->count < MAX_EXPLORE_USERS) - { - if (*start == '"') - start++; - if (*(start + strlen(start) - 1) == ']') - *(start + strlen(start) - 1) = '\0'; - if (*(start + strlen(start) - 1) == '"') - *(start + strlen(start) - 1) = '\0'; - - strncpy(flip_social_explore->usernames[flip_social_explore->count], start, MAX_USER_LENGTH - 1); - flip_social_explore->usernames[flip_social_explore->count][MAX_USER_LENGTH - 1] = '\0'; // Ensure null termination - flip_social_explore->count++; - } - - // Add submenu items for the users - submenu_reset(app_instance->submenu_explore); - submenu_set_header(app_instance->submenu_explore, "Explore"); - for (int i = 0; i < flip_social_explore->count; i++) - { - submenu_add_item(app_instance->submenu_explore, flip_social_explore->usernames[i], FlipSocialSubmenuExploreIndexStartIndex + i, flip_social_callback_submenu_choices, app_instance); - } - - // Free the json_users - free(json_users); - free(start); - free(end); - - return true; -} - -#endif // FLIP_SOCIAL_EXPLORE_H \ No newline at end of file diff --git a/flip_social_feed.h b/flip_social_feed.h deleted file mode 100644 index 43c3489c8..000000000 --- a/flip_social_feed.h +++ /dev/null @@ -1,209 +0,0 @@ -#ifndef FLIP_SOCIAL_FEED_H -#define FLIP_SOCIAL_FEED_H - -// Set failure FlipSocialFeed object -static bool flip_social_temp_feed() -{ - if (flip_social_feed == NULL) - { - flip_social_feed = malloc(sizeof(FlipSocialFeed)); - if (flip_social_feed == NULL) - { - FURI_LOG_E(TAG, "Failed to allocate memory for feed"); - return false; - } - } - for (int i = 0; i < 3; i++) - { - if (flip_social_feed->usernames[i] == NULL) - { - flip_social_feed->usernames[i] = malloc(MAX_USER_LENGTH); - if (flip_social_feed->usernames[i] == NULL) - { - FURI_LOG_E(TAG, "Failed to allocate memory for username %zu", i); - return false; - } - } - if (flip_social_feed->messages[i] == NULL) - { - flip_social_feed->messages[i] = malloc(MAX_MESSAGE_LENGTH); - if (flip_social_feed->messages[i] == NULL) - { - FURI_LOG_E(TAG, "Failed to allocate memory for message %zu", i); - return false; - } - } - } - flip_social_feed->usernames[0] = "JBlanked"; - flip_social_feed->usernames[1] = "FlipperKing"; - flip_social_feed->usernames[2] = "FlipperQueen"; - // - flip_social_feed->messages[0] = "Welcome. This is a temp message. Either the feed didn't load or there was a server error."; - flip_social_feed->messages[1] = "I am the Chosen Flipper."; - flip_social_feed->messages[2] = "No one can flip like me."; - // - flip_social_feed->is_flipped[0] = true; - flip_social_feed->is_flipped[1] = false; - flip_social_feed->is_flipped[2] = true; - // - flip_social_feed->ids[0] = 0; - flip_social_feed->ids[1] = 1; - flip_social_feed->ids[2] = 2; - // - flip_social_feed->flips[0] = 51; - flip_social_feed->flips[1] = 8; - flip_social_feed->flips[2] = 23; - // - flip_social_feed->count = 3; - flip_social_feed->index = 0; - - return true; -} - -// Allocate memory for each feed item if not already allocated -static FlipSocialFeed *flip_social_feed_alloc() -{ - // Initialize the feed - FlipSocialFeed *feed = (FlipSocialFeed *)malloc(sizeof(FlipSocialFeed)); - if (!feed) - { - FURI_LOG_E(TAG, "Failed to allocate memory for feed"); - return feed; - } - for (size_t i = 0; i < MAX_FEED_ITEMS; i++) - { - if (feed->usernames[i] == NULL) - { - feed->usernames[i] = malloc(MAX_USER_LENGTH); - if (feed->usernames[i] == NULL) - { - FURI_LOG_E(TAG, "Failed to allocate memory for username %zu", i); - return NULL; - } - } - if (feed->messages[i] == NULL) - { - feed->messages[i] = malloc(MAX_MESSAGE_LENGTH); - if (feed->messages[i] == NULL) - { - FURI_LOG_E(TAG, "Failed to allocate memory for message %zu", i); - return NULL; - } - } - } - return feed; -} - -static void flip_social_free_feed() -{ - if (!flip_social_feed) - { - FURI_LOG_E(TAG, "Feed model is NULL"); - return; - } - for (uint32_t i = 0; i < flip_social_feed->count; i++) - { - free(flip_social_feed->usernames[i]); - } -} - -static bool flip_social_get_feed() -{ - // Get the feed from the server - if (app_instance->login_username_logged_out == NULL) - { - FURI_LOG_E(TAG, "Username is NULL"); - return false; - } - char command[128]; - snprintf(command, 128, "https://www.flipsocial.net/api/feed/40/%s/extended/", app_instance->login_username_logged_out); - bool success = flipper_http_get_request_with_headers(command, jsmn("Content-Type", "application/json")); - if (!success) - { - FURI_LOG_E(TAG, "Failed to send HTTP request for feed"); - return false; - } - fhttp.state = RECEIVING; - return true; -} - -static bool flip_social_parse_json_feed() -{ - if (fhttp.received_data == NULL) - { - FURI_LOG_E(TAG, "No data received."); - return false; - } - - // Allocate memory for each feed item if not already allocated - flip_social_feed = flip_social_feed_alloc(); - if (flip_social_feed == NULL) - { - return false; - } - // Remove newlines - char *pos = fhttp.received_data; - while ((pos = strchr(pos, '\n')) != NULL) - { - *pos = ' '; - } - - // Initialize feed count - flip_social_feed->count = 0; - - // Iterate through the feed array - for (int i = 0; i < MAX_FEED_ITEMS; i++) - { - // Parse each item in the array - char *item = get_json_array_value("feed", i, fhttp.received_data, MAX_TOKENS); - if (item == NULL) - { - break; - } - - // Extract individual fields from the JSON object - char *username = get_json_value("username", item, MAX_TOKENS); - char *message = get_json_value("message", item, MAX_TOKENS); - char *flipped = get_json_value("flipped", item, MAX_TOKENS); - char *flips = get_json_value("flip_count", item, MAX_TOKENS); - char *id = get_json_value("id", item, MAX_TOKENS); - - if (username == NULL || message == NULL || flipped == NULL || id == NULL) - { - FURI_LOG_E(TAG, "Failed to parse item fields."); - free(item); - free(username); - free(message); - free(flipped); - free(flips); - free(id); - continue; - } - - // Safely copy strings with bounds checking - strncpy(flip_social_feed->usernames[i], username, MAX_USER_LENGTH - 1); - flip_social_feed->usernames[i][MAX_USER_LENGTH - 1] = '\0'; - - strncpy(flip_social_feed->messages[i], message, MAX_MESSAGE_LENGTH - 1); - flip_social_feed->messages[i][MAX_MESSAGE_LENGTH - 1] = '\0'; - - // Store boolean and integer values - flip_social_feed->is_flipped[i] = strstr(flipped, "true") != NULL; - flip_social_feed->ids[i] = atoi(id); - flip_social_feed->flips[i] = atoi(flips); - - flip_social_feed->count++; - - // Free allocated memory - free(item); - free(username); - free(message); - free(flipped); - free(flips); - free(id); - } - - return flip_social_feed->count > 0; -} - -#endif // FLIP_SOCIAL_FEED_H \ No newline at end of file diff --git a/flip_social_free.h b/flip_social_free.h deleted file mode 100644 index 4f445430e..000000000 --- a/flip_social_free.h +++ /dev/null @@ -1,294 +0,0 @@ -// flip_social_free.h -#ifndef FLIP_SOCIAL_FREE_H -#define FLIP_SOCIAL_FREE_H - -/** - * @brief Function to free the resources used by FlipSocialApp. - * @details Cleans up all allocated resources before exiting the application. - * @param app The FlipSocialApp object to free. - * @return void - */ -static void flip_social_app_free(FlipSocialApp *app) -{ - if (!app) - { - FURI_LOG_E(TAG, "FlipSocialApp is NULL"); - return; - } - if (!app->view_dispatcher) - { - FURI_LOG_E(TAG, "ViewDispatcher is NULL"); - return; - } - - // Free Submenu(s) - if (app->submenu_logged_out) - { - view_dispatcher_remove_view(app->view_dispatcher, FlipSocialViewLoggedOutSubmenu); - submenu_free(app->submenu_logged_out); - } - if (app->submenu_logged_in) - { - view_dispatcher_remove_view(app->view_dispatcher, FlipSocialViewLoggedInSubmenu); - submenu_free(app->submenu_logged_in); - } - if (app->submenu_compose) - { - view_dispatcher_remove_view(app->view_dispatcher, FlipSocialViewLoggedInCompose); - submenu_free(app->submenu_compose); - } - if (app->submenu_explore) - { - view_dispatcher_remove_view(app->view_dispatcher, FlipSocialViewLoggedInExploreSubmenu); - submenu_free(app->submenu_explore); - } - if (app->submenu_friends) - { - view_dispatcher_remove_view(app->view_dispatcher, FlipSocialViewLoggedInFriendsSubmenu); - submenu_free(app->submenu_friends); - } - if (app->submenu_messages) - { - view_dispatcher_remove_view(app->view_dispatcher, FlipSocialViewLoggedInMessagesSubmenu); - submenu_free(app->submenu_messages); - } - if (app->submenu_messages_user_choices) - { - view_dispatcher_remove_view(app->view_dispatcher, FlipSocialViewLoggedInMessagesUserChoices); - submenu_free(app->submenu_messages_user_choices); - } - - // Free Variable Item List(s) - if (app->variable_item_list_logged_out_wifi_settings) - { - view_dispatcher_remove_view(app->view_dispatcher, FlipSocialViewLoggedOutWifiSettings); - variable_item_list_free(app->variable_item_list_logged_out_wifi_settings); - } - if (app->variable_item_list_logged_out_login) - { - view_dispatcher_remove_view(app->view_dispatcher, FlipSocialViewLoggedOutLogin); - variable_item_list_free(app->variable_item_list_logged_out_login); - } - if (app->variable_item_list_logged_out_register) - { - view_dispatcher_remove_view(app->view_dispatcher, FlipSocialViewLoggedOutRegister); - variable_item_list_free(app->variable_item_list_logged_out_register); - } - if (app->variable_item_list_logged_in_profile) - { - view_dispatcher_remove_view(app->view_dispatcher, FlipSocialViewLoggedInProfile); - variable_item_list_free(app->variable_item_list_logged_in_profile); - } - if (app->variable_item_list_logged_in_settings) - { - view_dispatcher_remove_view(app->view_dispatcher, FlipSocialViewLoggedInSettings); - variable_item_list_free(app->variable_item_list_logged_in_settings); - } - if (app->variable_item_list_logged_in_settings_wifi) - { - view_dispatcher_remove_view(app->view_dispatcher, FlipSocialViewLoggedInSettingsWifi); - variable_item_list_free(app->variable_item_list_logged_in_settings_wifi); - } - - // Free Text Input(s) - if (app->text_input_logged_out_wifi_settings_ssid) - { - view_dispatcher_remove_view(app->view_dispatcher, FlipSocialViewLoggedOutWifiSettingsSSIDInput); - uart_text_input_free(app->text_input_logged_out_wifi_settings_ssid); - } - if (app->text_input_logged_out_wifi_settings_password) - { - view_dispatcher_remove_view(app->view_dispatcher, FlipSocialViewLoggedOutWifiSettingsPasswordInput); - uart_text_input_free(app->text_input_logged_out_wifi_settings_password); - } - if (app->text_input_logged_out_login_username) - { - view_dispatcher_remove_view(app->view_dispatcher, FlipSocialViewLoggedOutLoginUsernameInput); - uart_text_input_free(app->text_input_logged_out_login_username); - } - if (app->text_input_logged_out_login_password) - { - view_dispatcher_remove_view(app->view_dispatcher, FlipSocialViewLoggedOutLoginPasswordInput); - uart_text_input_free(app->text_input_logged_out_login_password); - } - if (app->text_input_logged_out_register_username) - { - view_dispatcher_remove_view(app->view_dispatcher, FlipSocialViewLoggedOutRegisterUsernameInput); - uart_text_input_free(app->text_input_logged_out_register_username); - } - if (app->text_input_logged_out_register_password) - { - view_dispatcher_remove_view(app->view_dispatcher, FlipSocialViewLoggedOutRegisterPasswordInput); - uart_text_input_free(app->text_input_logged_out_register_password); - } - if (app->text_input_logged_out_register_password_2) - { - view_dispatcher_remove_view(app->view_dispatcher, FlipSocialViewLoggedOutRegisterPassword2Input); - uart_text_input_free(app->text_input_logged_out_register_password_2); - } - if (app->text_input_logged_in_change_password) - { - view_dispatcher_remove_view(app->view_dispatcher, FlipSocialViewLoggedInChangePasswordInput); - uart_text_input_free(app->text_input_logged_in_change_password); - } - if (app->text_input_logged_in_compose_pre_save_input) - { - view_dispatcher_remove_view(app->view_dispatcher, FlipSocialViewLoggedInComposeAddPreSaveInput); - uart_text_input_free(app->text_input_logged_in_compose_pre_save_input); - } - if (app->text_input_logged_in_wifi_settings_ssid) - { - view_dispatcher_remove_view(app->view_dispatcher, FlipSocialViewLoggedInWifiSettingsSSIDInput); - uart_text_input_free(app->text_input_logged_in_wifi_settings_ssid); - } - if (app->text_input_logged_in_wifi_settings_password) - { - view_dispatcher_remove_view(app->view_dispatcher, FlipSocialViewLoggedInWifiSettingsPasswordInput); - uart_text_input_free(app->text_input_logged_in_wifi_settings_password); - } - if (app->text_input_logged_in_messages_new_message) - { - view_dispatcher_remove_view(app->view_dispatcher, FlipSocialViewLoggedInMessagesNewMessageInput); - uart_text_input_free(app->text_input_logged_in_messages_new_message); - } - if (app->text_input_logged_in_messages_new_message_user_choices) - { - view_dispatcher_remove_view(app->view_dispatcher, FlipSocialViewLoggedInMessagesNewMessageUserChoicesInput); - uart_text_input_free(app->text_input_logged_in_messages_new_message_user_choices); - } - - // Free Widget(s) - if (app->widget_logged_out_about) - { - view_dispatcher_remove_view(app->view_dispatcher, FlipSocialViewLoggedOutAbout); - widget_free(app->widget_logged_out_about); - } - if (app->widget_logged_in_about) - { - view_dispatcher_remove_view(app->view_dispatcher, FlipSocialViewLoggedInSettingsAbout); - widget_free(app->widget_logged_in_about); - } - - // Free View(s) - if (app->view_process_login) - { - view_dispatcher_remove_view(app->view_dispatcher, FlipSocialViewLoggedOutProcessLogin); - view_free(app->view_process_login); - } - if (app->view_process_register) - { - view_dispatcher_remove_view(app->view_dispatcher, FlipSocialViewLoggedOutProcessRegister); - view_free(app->view_process_register); - } - if (app->view_process_feed) - { - view_dispatcher_remove_view(app->view_dispatcher, FlipSocialViewLoggedInFeed); - view_free(app->view_process_feed); - } - if (app->view_process_compose) - { - view_dispatcher_remove_view(app->view_dispatcher, FlipSocialViewLoggedInProcessCompose); - view_free(app->view_process_compose); - } - if (app->view_process_explore) - { - view_dispatcher_remove_view(app->view_dispatcher, FlipSocialViewLoggedInExploreProccess); - view_free(app->view_process_explore); - } - if (app->view_process_friends) - { - view_dispatcher_remove_view(app->view_dispatcher, FlipSocialViewLoggedInFriendsProcess); - view_free(app->view_process_friends); - } - if (app->view_process_messages) - { - view_dispatcher_remove_view(app->view_dispatcher, FlipSocialViewLoggedInMessagesProcess); - view_free(app->view_process_messages); - } - - if (app->view_dispatcher) - view_dispatcher_free(app->view_dispatcher); - - // Free the app structure members - if (app->wifi_ssid_logged_out) - free(app->wifi_ssid_logged_out); - if (app->wifi_ssid_logged_out_temp_buffer) - free(app->wifi_ssid_logged_out_temp_buffer); - if (app->wifi_password_logged_out) - free(app->wifi_password_logged_out); - if (app->wifi_password_logged_out_temp_buffer) - free(app->wifi_password_logged_out_temp_buffer); - if (app->login_username_logged_out) - free(app->login_username_logged_out); - if (app->login_username_logged_out_temp_buffer) - free(app->login_username_logged_out_temp_buffer); - if (app->login_password_logged_out) - free(app->login_password_logged_out); - if (app->login_password_logged_out_temp_buffer) - free(app->login_password_logged_out_temp_buffer); - if (app->register_username_logged_out) - free(app->register_username_logged_out); - if (app->register_username_logged_out_temp_buffer) - free(app->register_username_logged_out_temp_buffer); - if (app->register_password_logged_out) - free(app->register_password_logged_out); - if (app->register_password_logged_out_temp_buffer) - free(app->register_password_logged_out_temp_buffer); - if (app->register_password_2_logged_out) - free(app->register_password_2_logged_out); - if (app->register_password_2_logged_out_temp_buffer) - free(app->register_password_2_logged_out_temp_buffer); - if (app->change_password_logged_in) - free(app->change_password_logged_in); - if (app->change_password_logged_in_temp_buffer) - free(app->change_password_logged_in_temp_buffer); - if (app->compose_pre_save_logged_in) - free(app->compose_pre_save_logged_in); - if (app->compose_pre_save_logged_in_temp_buffer) - free(app->compose_pre_save_logged_in_temp_buffer); - if (app->wifi_ssid_logged_in) - free(app->wifi_ssid_logged_in); - if (app->wifi_ssid_logged_in_temp_buffer) - free(app->wifi_ssid_logged_in_temp_buffer); - if (app->wifi_password_logged_in) - free(app->wifi_password_logged_in); - if (app->wifi_password_logged_in_temp_buffer) - free(app->wifi_password_logged_in_temp_buffer); - if (app->is_logged_in) - free(app->is_logged_in); - if (app->login_username_logged_in) - free(app->login_username_logged_in); - if (app->login_username_logged_in_temp_buffer) - free(app->login_username_logged_in_temp_buffer); - if (app->messages_new_message_logged_in) - free(app->messages_new_message_logged_in); - if (app->messages_new_message_logged_in_temp_buffer) - free(app->messages_new_message_logged_in_temp_buffer); - if (app->message_user_choice_logged_in) - free(app->message_user_choice_logged_in); - if (app->message_user_choice_logged_in_temp_buffer) - free(app->message_user_choice_logged_in_temp_buffer); - - if (app->input_event && app->input_event_queue) - furi_pubsub_unsubscribe(app->input_event_queue, app->input_event); - - // free received_data - if (fhttp.received_data) - free(fhttp.received_data); - - // free playlist and explore page - flip_social_free_explore(); - flip_social_free_feed(); - flip_social_free_friends(); - flip_social_free_message_users(); - flip_social_free_messages(); - - // DeInit UART - flipper_http_deinit(); - - // Free the app structure - if (app_instance) - free(app_instance); -} - -#endif // FLIP_SOCIAL_FREE_H \ No newline at end of file diff --git a/flip_social_friends.h b/flip_social_friends.h deleted file mode 100644 index 13390ada7..000000000 --- a/flip_social_friends.h +++ /dev/null @@ -1,158 +0,0 @@ -#ifndef FLIP_SOCIAL_FRIENDS -#define FLIP_SOCIAL_FRIENDS - -static FlipSocialModel *flip_social_friends_alloc() -{ - // Allocate memory for each username only if not already allocated - FlipSocialModel *friends = malloc(sizeof(FlipSocialModel)); - for (size_t i = 0; i < MAX_FRIENDS; i++) - { - if (friends->usernames[i] == NULL) - { - friends->usernames[i] = malloc(MAX_USER_LENGTH); - if (friends->usernames[i] == NULL) - { - FURI_LOG_E(TAG, "Failed to allocate memory for username %zu", i); - return NULL; // Return false on memory allocation failure - } - } - } - return friends; -} - -static void flip_social_free_friends() -{ - if (!flip_social_friends) - { - FURI_LOG_E(TAG, "Friends model is NULL"); - return; - } - for (int i = 0; i < flip_social_friends->count; i++) - { - free(flip_social_friends->usernames[i]); - } -} - -// for now we're just listing the current users -// as the feed is upgraded, then we can port more to the friends view -static bool flip_social_get_friends() -{ - // will return true unless the devboard is not connected - char url[100]; - snprintf(url, 100, "https://www.flipsocial.net/api/user/friends/%s/", app_instance->login_username_logged_in); - bool success = flipper_http_get_request_with_headers(url, jsmn("Content-Type", "application/json")); - if (!success) - { - FURI_LOG_E(TAG, "Failed to send HTTP request for friends"); - return false; - } - fhttp.state = RECEIVING; - return true; -} - -static bool flip_social_update_friends() -{ - if (!app_instance->submenu_friends) - { - FURI_LOG_E(TAG, "Friends submenu is NULL"); - return false; - } - if (!flip_social_friends) - { - FURI_LOG_E(TAG, "Friends model is NULL"); - return false; - } - // Add submenu items for the users - submenu_reset(app_instance->submenu_friends); - submenu_set_header(app_instance->submenu_friends, "Friends"); - for (int i = 0; i < flip_social_friends->count; i++) - { - submenu_add_item(app_instance->submenu_friends, flip_social_friends->usernames[i], FlipSocialSubmenuLoggedInIndexFriendsStart + i, flip_social_callback_submenu_choices, app_instance); - } - return true; -} - -static bool flip_social_parse_json_friends() -{ - if (fhttp.received_data == NULL) - { - FURI_LOG_E(TAG, "No data received."); - return false; - } - - // Allocate memory for each username only if not already allocated - flip_social_friends = flip_social_friends_alloc(); - if (flip_social_friends == NULL) - { - FURI_LOG_E(TAG, "Failed to allocate memory for friends usernames."); - return false; - } - - // Remove newlines - char *pos = fhttp.received_data; - while ((pos = strchr(pos, '\n')) != NULL) - { - *pos = ' '; - } - - // Initialize friends count - flip_social_friends->count = 0; - - // Extract the users array from the JSON - char *json_users = get_json_value("friends", fhttp.received_data, MAX_TOKENS); - if (json_users == NULL) - { - FURI_LOG_E(TAG, "Failed to parse friends array."); - return false; - } - - // Manual tokenization for comma-separated values - char *start = json_users + 1; // Skip the opening bracket - char *end; - while ((end = strchr(start, ',')) != NULL && flip_social_friends->count < MAX_FRIENDS) - { - *end = '\0'; // Null-terminate the current token - - // Remove quotes - if (*start == '"') - start++; - if (*(end - 1) == '"') - *(end - 1) = '\0'; - - // Copy username to pre-allocated memory - strncpy(flip_social_friends->usernames[flip_social_friends->count], start, MAX_USER_LENGTH - 1); - flip_social_friends->usernames[flip_social_friends->count][MAX_USER_LENGTH - 1] = '\0'; // Ensure null termination - flip_social_friends->count++; - start = end + 1; - } - - // Handle the last token - if (*start != '\0' && flip_social_friends->count < MAX_FRIENDS) - { - if (*start == '"') - start++; - if (*(start + strlen(start) - 1) == ']') - *(start + strlen(start) - 1) = '\0'; - if (*(start + strlen(start) - 1) == '"') - *(start + strlen(start) - 1) = '\0'; - - strncpy(flip_social_friends->usernames[flip_social_friends->count], start, MAX_USER_LENGTH - 1); - flip_social_friends->usernames[flip_social_friends->count][MAX_USER_LENGTH - 1] = '\0'; // Ensure null termination - flip_social_friends->count++; - } - - // Add submenu items for the friends - if (!flip_social_update_friends()) - { - FURI_LOG_E(TAG, "Failed to update friends submenu"); - return false; - } - - // Free the json_users - free(json_users); - free(start); - free(end); - - return true; -} -#endif // FLIP_SOCIAL_FRIENDS \ No newline at end of file diff --git a/flip_social_i.h b/flip_social_i.h deleted file mode 100644 index 7641a3e58..000000000 --- a/flip_social_i.h +++ /dev/null @@ -1,542 +0,0 @@ -// flip_social.i.h -#ifndef FLIP_SOCIAL_I -#define FLIP_SOCIAL_I - -/** - * @brief Function to allocate resources for the FlipSocialApp. - * @details Initializes all components and views of the application. - * @return Pointer to the initialized FlipSocialApp, or NULL on failure. - */ -static FlipSocialApp *flip_social_app_alloc() -{ - // Initiailize the app - FlipSocialApp *app = (FlipSocialApp *)malloc(sizeof(FlipSocialApp)); - - // Initialize gui - Gui *gui = furi_record_open(RECORD_GUI); - - // Initialize UART - if (!flipper_http_init(flipper_http_rx_callback, app)) - { - FURI_LOG_E(TAG, "Failed to initialize UART"); - return NULL; - } - - // Allocate ViewDispatcher - if (!easy_flipper_set_view_dispatcher(&app->view_dispatcher, gui, app)) - { - return NULL; - } - - // Allocate the text input buffers - app->wifi_ssid_logged_out_temp_buffer_size = MAX_USER_LENGTH; - app->wifi_password_logged_out_temp_buffer_size = MAX_USER_LENGTH; - app->login_username_logged_out_temp_buffer_size = MAX_USER_LENGTH; - app->login_password_logged_out_temp_buffer_size = MAX_USER_LENGTH; - app->register_username_logged_out_temp_buffer_size = MAX_USER_LENGTH; - app->register_password_logged_out_temp_buffer_size = MAX_USER_LENGTH; - app->register_password_2_logged_out_temp_buffer_size = MAX_USER_LENGTH; - app->change_password_logged_in_temp_buffer_size = MAX_USER_LENGTH; - app->compose_pre_save_logged_in_temp_buffer_size = 100; - app->wifi_ssid_logged_in_temp_buffer_size = MAX_USER_LENGTH; - app->wifi_password_logged_in_temp_buffer_size = MAX_USER_LENGTH; - app->is_logged_in_size = 8; - app->login_username_logged_in_temp_buffer_size = MAX_USER_LENGTH; - app->messages_new_message_logged_in_temp_buffer_size = 100; - app->message_user_choice_logged_in_temp_buffer_size = 100; - if (!easy_flipper_set_buffer(&app->wifi_ssid_logged_out_temp_buffer, app->wifi_ssid_logged_out_temp_buffer_size)) - { - return NULL; - } - if (!easy_flipper_set_buffer(&app->wifi_password_logged_out_temp_buffer, app->wifi_password_logged_out_temp_buffer_size)) - { - return NULL; - } - if (!easy_flipper_set_buffer(&app->login_username_logged_out_temp_buffer, app->login_username_logged_out_temp_buffer_size)) - { - return NULL; - } - if (!easy_flipper_set_buffer(&app->login_password_logged_out_temp_buffer, app->login_password_logged_out_temp_buffer_size)) - { - return NULL; - } - if (!easy_flipper_set_buffer(&app->register_username_logged_out_temp_buffer, app->register_username_logged_out_temp_buffer_size)) - { - return NULL; - } - if (!easy_flipper_set_buffer(&app->register_password_logged_out_temp_buffer, app->register_password_logged_out_temp_buffer_size)) - { - return NULL; - } - if (!easy_flipper_set_buffer(&app->register_password_2_logged_out_temp_buffer, app->register_password_2_logged_out_temp_buffer_size)) - { - return NULL; - } - if (!easy_flipper_set_buffer(&app->change_password_logged_in_temp_buffer, app->change_password_logged_in_temp_buffer_size)) - { - return NULL; - } - if (!easy_flipper_set_buffer(&app->compose_pre_save_logged_in_temp_buffer, app->compose_pre_save_logged_in_temp_buffer_size)) - { - return NULL; - } - if (!easy_flipper_set_buffer(&app->wifi_ssid_logged_in_temp_buffer, app->wifi_ssid_logged_in_temp_buffer_size)) - { - return NULL; - } - if (!easy_flipper_set_buffer(&app->wifi_password_logged_in_temp_buffer, app->wifi_password_logged_in_temp_buffer_size)) - { - return NULL; - } - if (!easy_flipper_set_buffer(&app->is_logged_in, app->is_logged_in_size)) - { - return NULL; - } - if (!easy_flipper_set_buffer(&app->login_username_logged_in_temp_buffer, app->login_username_logged_in_temp_buffer_size)) - { - return NULL; - } - - if (!easy_flipper_set_buffer(&app->wifi_ssid_logged_out, app->wifi_ssid_logged_out_temp_buffer_size)) - { - return NULL; - } - if (!easy_flipper_set_buffer(&app->wifi_password_logged_out, app->wifi_password_logged_out_temp_buffer_size)) - { - return NULL; - } - if (!easy_flipper_set_buffer(&app->login_username_logged_out, app->login_username_logged_out_temp_buffer_size)) - { - return NULL; - } - if (!easy_flipper_set_buffer(&app->login_password_logged_out, app->login_password_logged_out_temp_buffer_size)) - { - return NULL; - } - if (!easy_flipper_set_buffer(&app->register_username_logged_out, app->register_username_logged_out_temp_buffer_size)) - { - return NULL; - } - if (!easy_flipper_set_buffer(&app->register_password_logged_out, app->register_password_logged_out_temp_buffer_size)) - { - return NULL; - } - if (!easy_flipper_set_buffer(&app->register_password_2_logged_out, app->register_password_2_logged_out_temp_buffer_size)) - { - return NULL; - } - if (!easy_flipper_set_buffer(&app->change_password_logged_in, app->change_password_logged_in_temp_buffer_size)) - { - return NULL; - } - if (!easy_flipper_set_buffer(&app->compose_pre_save_logged_in, app->compose_pre_save_logged_in_temp_buffer_size)) - { - return NULL; - } - if (!easy_flipper_set_buffer(&app->wifi_ssid_logged_in, app->wifi_ssid_logged_in_temp_buffer_size)) - { - return NULL; - } - if (!easy_flipper_set_buffer(&app->wifi_password_logged_in, app->wifi_password_logged_in_temp_buffer_size)) - { - return NULL; - } - if (!easy_flipper_set_buffer(&app->login_username_logged_in, app->login_username_logged_in_temp_buffer_size)) - { - return NULL; - } - // - if (!easy_flipper_set_buffer(&app->messages_new_message_logged_in, app->messages_new_message_logged_in_temp_buffer_size)) - { - return NULL; - } - if (!easy_flipper_set_buffer(&app->messages_new_message_logged_in_temp_buffer, app->messages_new_message_logged_in_temp_buffer_size)) - { - return NULL; - } - if (!easy_flipper_set_buffer(&app->message_user_choice_logged_in, app->message_user_choice_logged_in_temp_buffer_size)) - { - return NULL; - } - if (!easy_flipper_set_buffer(&app->message_user_choice_logged_in_temp_buffer, app->message_user_choice_logged_in_temp_buffer_size)) - { - return NULL; - } - - // Allocate Submenu(s) - if (!easy_flipper_set_submenu(&app->submenu_logged_out, FlipSocialViewLoggedOutSubmenu, "FlipSocial v0.5", flip_social_callback_exit_app, &app->view_dispatcher)) - { - return NULL; - } - if (!easy_flipper_set_submenu(&app->submenu_logged_in, FlipSocialViewLoggedInSubmenu, "FlipSocial v0.5", flip_social_callback_exit_app, &app->view_dispatcher)) - { - return NULL; - } - if (!easy_flipper_set_submenu(&app->submenu_compose, FlipSocialViewLoggedInCompose, "Create A Post", flip_social_callback_to_submenu_logged_in, &app->view_dispatcher)) - { - return NULL; - } - if (!easy_flipper_set_submenu(&app->submenu_explore, FlipSocialViewLoggedInExploreSubmenu, "Explore", flip_social_callback_to_submenu_logged_in, &app->view_dispatcher)) - { - return NULL; - } - if (!easy_flipper_set_submenu(&app->submenu_friends, FlipSocialViewLoggedInFriendsSubmenu, "Friends", flip_social_callback_to_profile_logged_in, &app->view_dispatcher)) - { - return NULL; - } - if (!easy_flipper_set_submenu(&app->submenu_messages, FlipSocialViewLoggedInMessagesSubmenu, "Messages", flip_social_callback_to_submenu_logged_in, &app->view_dispatcher)) - { - return NULL; - } - if (!easy_flipper_set_submenu(&app->submenu_messages_user_choices, FlipSocialViewLoggedInMessagesUserChoices, "Users", flip_social_callback_to_messages_logged_in, &app->view_dispatcher)) - { - return NULL; - } - - submenu_add_item(app->submenu_logged_out, "Login", FlipSocialSubmenuLoggedOutIndexLogin, flip_social_callback_submenu_choices, app); - submenu_add_item(app->submenu_logged_out, "Register", FlipSocialSubmenuLoggedOutIndexRegister, flip_social_callback_submenu_choices, app); - submenu_add_item(app->submenu_logged_out, "About", FlipSocialSubmenuLoggedOutIndexAbout, flip_social_callback_submenu_choices, app); - submenu_add_item(app->submenu_logged_out, "Settings", FlipSocialSubmenuLoggedOutIndexWifiSettings, flip_social_callback_submenu_choices, app); - // - submenu_add_item(app->submenu_logged_in, "Explore", FlipSocialSubmenuExploreIndex, flip_social_callback_submenu_choices, app); - submenu_add_item(app->submenu_logged_in, "Feed", FlipSocialSubmenuLoggedInIndexFeed, flip_social_callback_submenu_choices, app); - submenu_add_item(app->submenu_logged_in, "Post", FlipSocialSubmenuLoggedInIndexCompose, flip_social_callback_submenu_choices, app); - submenu_add_item(app->submenu_logged_in, "Messages", FlipSocialSubmenuLoggedInIndexMessages, flip_social_callback_submenu_choices, app); - submenu_add_item(app->submenu_logged_in, "Profile", FlipSocialSubmenuLoggedInIndexProfile, flip_social_callback_submenu_choices, app); - submenu_add_item(app->submenu_logged_in, "Settings", FlipSocialSubmenuLoggedInIndexSettings, flip_social_callback_submenu_choices, app); - submenu_add_item(app->submenu_logged_in, "Sign Out", FlipSocialSubmenuLoggedInSignOutButton, flip_social_callback_submenu_choices, app); - // - submenu_add_item(app->submenu_compose, "Add Pre-Save", FlipSocialSubmenuComposeIndexAddPreSave, flip_social_callback_submenu_choices, app); - // - - // Allocate View(s) - if (!easy_flipper_set_view(&app->view_process_login, FlipSocialViewLoggedOutProcessLogin, flip_social_callback_draw_login, NULL, flip_social_callback_to_login_logged_out, &app->view_dispatcher, app)) - { - return NULL; - } - if (!easy_flipper_set_view(&app->view_process_register, FlipSocialViewLoggedOutProcessRegister, flip_social_callback_draw_register, NULL, flip_social_callback_to_register_logged_out, &app->view_dispatcher, app)) - { - return NULL; - } - if (!easy_flipper_set_view(&app->view_process_feed, FlipSocialViewLoggedInFeed, flip_social_callback_draw_feed, NULL, flip_social_callback_to_submenu_logged_in, &app->view_dispatcher, app)) - { - return NULL; - } - if (!easy_flipper_set_view(&app->view_process_compose, FlipSocialViewLoggedInProcessCompose, flip_social_callback_draw_compose, NULL, flip_social_callback_to_compose_logged_in, &app->view_dispatcher, app)) - { - return NULL; - } - if (!easy_flipper_set_view(&app->view_process_explore, FlipSocialViewLoggedInExploreProccess, flip_social_callback_draw_explore, NULL, flip_social_callback_to_explore_logged_in, &app->view_dispatcher, app)) - { - return NULL; - } - if (!easy_flipper_set_view(&app->view_process_friends, FlipSocialViewLoggedInFriendsProcess, flip_social_callback_draw_friends, NULL, flip_social_callback_to_friends_logged_in, &app->view_dispatcher, app)) - { - return NULL; - } - if (!easy_flipper_set_view(&app->view_process_messages, FlipSocialViewLoggedInMessagesProcess, flip_social_callback_draw_messages, NULL, flip_social_callback_to_messages_logged_in, &app->view_dispatcher, app)) - { - return NULL; - } - - // Setup Variable Item List(s) - if (!easy_flipper_set_variable_item_list(&app->variable_item_list_logged_out_wifi_settings, FlipSocialViewLoggedOutWifiSettings, flip_social_text_input_logged_out_wifi_settings_item_selected, flip_social_callback_to_submenu_logged_out, &app->view_dispatcher, app)) - { - return NULL; - } - if (!easy_flipper_set_variable_item_list(&app->variable_item_list_logged_out_login, FlipSocialViewLoggedOutLogin, flip_social_text_input_logged_out_login_item_selected, flip_social_callback_to_submenu_logged_out, &app->view_dispatcher, app)) - { - return NULL; - } - if (!easy_flipper_set_variable_item_list(&app->variable_item_list_logged_out_register, FlipSocialViewLoggedOutRegister, flip_social_text_input_logged_out_register_item_selected, flip_social_callback_to_submenu_logged_out, &app->view_dispatcher, app)) - { - return NULL; - } - if (!easy_flipper_set_variable_item_list(&app->variable_item_list_logged_in_profile, FlipSocialViewLoggedInProfile, flip_social_text_input_logged_in_profile_item_selected, flip_social_callback_to_submenu_logged_in, &app->view_dispatcher, app)) - { - return NULL; - } - if (!easy_flipper_set_variable_item_list(&app->variable_item_list_logged_in_settings, FlipSocialViewLoggedInSettings, flip_social_text_input_logged_in_settings_item_selected, flip_social_callback_to_submenu_logged_in, &app->view_dispatcher, app)) - { - return NULL; - } - if (!easy_flipper_set_variable_item_list(&app->variable_item_list_logged_in_settings_wifi, FlipSocialViewLoggedInSettingsWifi, flip_social_text_input_logged_in_wifi_settings_item_selected, flip_social_callback_to_settings_logged_in, &app->view_dispatcher, app)) - { - return NULL; - } - - app->variable_item_logged_out_wifi_settings_ssid = variable_item_list_add(app->variable_item_list_logged_out_wifi_settings, "SSID", 1, NULL, NULL); - app->variable_item_logged_out_wifi_settings_password = variable_item_list_add(app->variable_item_list_logged_out_wifi_settings, "Password", 1, NULL, NULL); - // - app->variable_item_logged_out_login_username = variable_item_list_add(app->variable_item_list_logged_out_login, "Username", 1, NULL, NULL); - app->variable_item_logged_out_login_password = variable_item_list_add(app->variable_item_list_logged_out_login, "Password", 1, NULL, NULL); - app->variable_item_logged_out_login_button = variable_item_list_add(app->variable_item_list_logged_out_login, "Login", 0, NULL, NULL); - // - app->variable_item_logged_out_register_username = variable_item_list_add(app->variable_item_list_logged_out_register, "Username", 1, NULL, NULL); - app->variable_item_logged_out_register_password = variable_item_list_add(app->variable_item_list_logged_out_register, "Password", 1, NULL, NULL); - app->variable_item_logged_out_register_password_2 = variable_item_list_add(app->variable_item_list_logged_out_register, "Confirm Password", 1, NULL, NULL); - app->variable_item_logged_out_register_button = variable_item_list_add(app->variable_item_list_logged_out_register, "Register", 0, NULL, NULL); - // - app->variable_item_logged_in_profile_username = variable_item_list_add(app->variable_item_list_logged_in_profile, "Username", 0, NULL, NULL); - app->variable_item_logged_in_profile_change_password = variable_item_list_add(app->variable_item_list_logged_in_profile, "Change Password", 0, NULL, NULL); - app->variable_item_logged_in_profile_friends = variable_item_list_add(app->variable_item_list_logged_in_profile, "Friends", 0, NULL, NULL); - // - app->variable_item_logged_in_settings_about = variable_item_list_add(app->variable_item_list_logged_in_settings, "About", 0, NULL, NULL); - app->variable_item_logged_in_settings_wifi = variable_item_list_add(app->variable_item_list_logged_in_settings, "WiFi", 0, NULL, NULL); - // - app->variable_item_logged_in_wifi_settings_ssid = variable_item_list_add(app->variable_item_list_logged_in_settings_wifi, "SSID", 1, NULL, NULL); - app->variable_item_logged_in_wifi_settings_password = variable_item_list_add(app->variable_item_list_logged_in_settings_wifi, "Password", 1, NULL, NULL); - - // Setup Text Input(s) - if (!easy_flipper_set_uart_text_input(&app->text_input_logged_out_wifi_settings_ssid, FlipSocialViewLoggedOutWifiSettingsSSIDInput, "Enter SSID", app->wifi_ssid_logged_out_temp_buffer, app->wifi_ssid_logged_out_temp_buffer_size, flip_social_logged_out_wifi_settings_ssid_updated, flip_social_callback_to_wifi_settings_logged_out, &app->view_dispatcher, app)) - { - return NULL; - } - if (!easy_flipper_set_uart_text_input(&app->text_input_logged_out_wifi_settings_password, FlipSocialViewLoggedOutWifiSettingsPasswordInput, "Enter Password", app->wifi_password_logged_out_temp_buffer, app->wifi_password_logged_out_temp_buffer_size, flip_social_logged_out_wifi_settings_password_updated, flip_social_callback_to_wifi_settings_logged_out, &app->view_dispatcher, app)) - { - return NULL; - } - if (!easy_flipper_set_uart_text_input(&app->text_input_logged_out_login_username, FlipSocialViewLoggedOutLoginUsernameInput, "Enter Username", app->login_username_logged_out_temp_buffer, app->login_username_logged_out_temp_buffer_size, flip_social_logged_out_login_username_updated, flip_social_callback_to_login_logged_out, &app->view_dispatcher, app)) - { - return NULL; - } - if (!easy_flipper_set_uart_text_input(&app->text_input_logged_out_login_password, FlipSocialViewLoggedOutLoginPasswordInput, "Enter Password", app->login_password_logged_out_temp_buffer, app->login_password_logged_out_temp_buffer_size, flip_social_logged_out_login_password_updated, flip_social_callback_to_login_logged_out, &app->view_dispatcher, app)) - { - return NULL; - } - if (!easy_flipper_set_uart_text_input(&app->text_input_logged_out_register_username, FlipSocialViewLoggedOutRegisterUsernameInput, "Enter Username", app->register_username_logged_out_temp_buffer, app->register_username_logged_out_temp_buffer_size, flip_social_logged_out_register_username_updated, flip_social_callback_to_register_logged_out, &app->view_dispatcher, app)) - { - return NULL; - } - if (!easy_flipper_set_uart_text_input(&app->text_input_logged_out_register_password, FlipSocialViewLoggedOutRegisterPasswordInput, "Enter Password", app->register_password_logged_out_temp_buffer, app->register_password_logged_out_temp_buffer_size, flip_social_logged_out_register_password_updated, flip_social_callback_to_register_logged_out, &app->view_dispatcher, app)) - { - return NULL; - } - if (!easy_flipper_set_uart_text_input(&app->text_input_logged_out_register_password_2, FlipSocialViewLoggedOutRegisterPassword2Input, "Confirm Password", app->register_password_2_logged_out_temp_buffer, app->register_password_2_logged_out_temp_buffer_size, flip_social_logged_out_register_password_2_updated, flip_social_callback_to_register_logged_out, &app->view_dispatcher, app)) - { - return NULL; - } - // - if (!easy_flipper_set_uart_text_input(&app->text_input_logged_in_change_password, FlipSocialViewLoggedInChangePasswordInput, "Enter New Password", app->change_password_logged_in_temp_buffer, app->change_password_logged_in_temp_buffer_size, flip_social_logged_in_profile_change_password_updated, flip_social_callback_to_profile_logged_in, &app->view_dispatcher, app)) - { - return NULL; - } - if (!easy_flipper_set_uart_text_input(&app->text_input_logged_in_compose_pre_save_input, FlipSocialViewLoggedInComposeAddPreSaveInput, "Enter Pre-Save Message", app->compose_pre_save_logged_in_temp_buffer, app->compose_pre_save_logged_in_temp_buffer_size, flip_social_logged_in_compose_pre_save_updated, flip_social_callback_to_compose_logged_in, &app->view_dispatcher, app)) - { - return NULL; - } - if (!easy_flipper_set_uart_text_input(&app->text_input_logged_in_wifi_settings_ssid, FlipSocialViewLoggedInWifiSettingsSSIDInput, "Enter SSID", app->wifi_ssid_logged_in_temp_buffer, app->wifi_ssid_logged_in_temp_buffer_size, flip_social_logged_in_wifi_settings_ssid_updated, flip_social_callback_to_wifi_settings_logged_in, &app->view_dispatcher, app)) - { - return NULL; - } - if (!easy_flipper_set_uart_text_input(&app->text_input_logged_in_wifi_settings_password, FlipSocialViewLoggedInWifiSettingsPasswordInput, "Enter Password", app->wifi_password_logged_in_temp_buffer, app->wifi_password_logged_in_temp_buffer_size, flip_social_logged_in_wifi_settings_password_updated, flip_social_callback_to_wifi_settings_logged_in, &app->view_dispatcher, app)) - { - return NULL; - } - // - if (!easy_flipper_set_uart_text_input(&app->text_input_logged_in_messages_new_message, FlipSocialViewLoggedInMessagesNewMessageInput, "Enter Message", app->messages_new_message_logged_in_temp_buffer, app->messages_new_message_logged_in_temp_buffer_size, flip_social_logged_in_messages_new_message_updated, flip_social_callback_to_messages_logged_in, &app->view_dispatcher, app)) - { - return NULL; - } - if (!easy_flipper_set_uart_text_input(&app->text_input_logged_in_messages_new_message_user_choices, FlipSocialViewLoggedInMessagesNewMessageUserChoicesInput, "Enter Message", app->message_user_choice_logged_in_temp_buffer, app->message_user_choice_logged_in_temp_buffer_size, flip_social_logged_in_messages_user_choice_message_updated, flip_social_callback_to_messages_user_choices, &app->view_dispatcher, app)) - { - return NULL; - } - - // Setup About(s) - if (!easy_flipper_set_widget(&app->widget_logged_out_about, FlipSocialViewLoggedOutAbout, "Welcome to FlipSocial\n---\nThe social media app for\nFlipper Zero, created by\nJBlanked: www.flipsocial.net\n---\nPress BACK to return.", flip_social_callback_to_submenu_logged_out, &app->view_dispatcher)) - { - return NULL; - } - if (!easy_flipper_set_widget(&app->widget_logged_in_about, FlipSocialViewLoggedInSettingsAbout, "Welcome to FlipSocial\n---\nThe social media app for\nFlipper Zero, created by\nJBlanked: www.flipsocial.net\n---\nPress BACK to return.", flip_social_callback_to_settings_logged_in, &app->view_dispatcher)) - { - return NULL; - } - - // load the playlist - if (load_playlist(&app->pre_saved_messages)) - { - // Update the playlist submenu - for (uint32_t i = 0; i < app->pre_saved_messages.count; i++) - { - submenu_add_item(app->submenu_compose, app->pre_saved_messages.messages[i], FlipSocialSubemnuComposeIndexStartIndex + i, flip_social_callback_submenu_choices, app); - } - } - - // Load the settings - if (!load_settings(app->wifi_ssid_logged_out, - app->wifi_ssid_logged_out_temp_buffer_size, - app->wifi_password_logged_out, - app->wifi_password_logged_out_temp_buffer_size, - app->login_username_logged_out, - app->login_username_logged_out_temp_buffer_size, - app->login_username_logged_in, - app->login_username_logged_in_temp_buffer_size, - app->login_password_logged_out, - app->login_password_logged_out_temp_buffer_size, - app->change_password_logged_in, - app->change_password_logged_in_temp_buffer_size, - app->is_logged_in, - app->is_logged_in_size)) - - { - FURI_LOG_E(TAG, "Failed to load settings"); - - if (app->is_logged_in == NULL) - { - app->is_logged_in = (char *)malloc(app->is_logged_in_size); - app->is_logged_in = "false"; - } - app_instance = app; - view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedOutSubmenu); - } - else - { - // Copy items into their temp buffers with safety checks - if (app->wifi_ssid_logged_out && app->wifi_ssid_logged_out_temp_buffer) - { - strncpy(app->wifi_ssid_logged_out_temp_buffer, app->wifi_ssid_logged_out, app->wifi_ssid_logged_out_temp_buffer_size - 1); - app->wifi_ssid_logged_out_temp_buffer[app->wifi_ssid_logged_out_temp_buffer_size - 1] = '\0'; - } - if (app->wifi_password_logged_out && app->wifi_password_logged_out_temp_buffer) - { - strncpy(app->wifi_password_logged_out_temp_buffer, app->wifi_password_logged_out, app->wifi_password_logged_out_temp_buffer_size - 1); - app->wifi_password_logged_out_temp_buffer[app->wifi_password_logged_out_temp_buffer_size - 1] = '\0'; - } - if (app->login_username_logged_out && app->login_username_logged_out_temp_buffer) - { - strncpy(app->login_username_logged_out_temp_buffer, app->login_username_logged_out, app->login_username_logged_out_temp_buffer_size - 1); - app->login_username_logged_out_temp_buffer[app->login_username_logged_out_temp_buffer_size - 1] = '\0'; - } - if (app->login_password_logged_out && app->login_password_logged_out_temp_buffer) - { - strncpy(app->login_password_logged_out_temp_buffer, app->login_password_logged_out, app->login_password_logged_out_temp_buffer_size - 1); - app->login_password_logged_out_temp_buffer[app->login_password_logged_out_temp_buffer_size - 1] = '\0'; - } - if (app->register_username_logged_out && app->register_username_logged_out_temp_buffer) - { - strncpy(app->register_username_logged_out_temp_buffer, app->register_username_logged_out, app->register_username_logged_out_temp_buffer_size - 1); - app->register_username_logged_out_temp_buffer[app->register_username_logged_out_temp_buffer_size - 1] = '\0'; - } - if (app->register_password_logged_out && app->register_password_logged_out_temp_buffer) - { - strncpy(app->register_password_logged_out_temp_buffer, app->register_password_logged_out, app->register_password_logged_out_temp_buffer_size - 1); - app->register_password_logged_out_temp_buffer[app->register_password_logged_out_temp_buffer_size - 1] = '\0'; - } - if (app->register_password_2_logged_out && app->register_password_2_logged_out_temp_buffer) - { - strncpy(app->register_password_2_logged_out_temp_buffer, app->register_password_2_logged_out, app->register_password_2_logged_out_temp_buffer_size - 1); - app->register_password_2_logged_out_temp_buffer[app->register_password_2_logged_out_temp_buffer_size - 1] = '\0'; - } - if (app->change_password_logged_in && app->change_password_logged_in_temp_buffer) - { - strncpy(app->change_password_logged_in_temp_buffer, app->change_password_logged_in, app->change_password_logged_in_temp_buffer_size - 1); - app->change_password_logged_in_temp_buffer[app->change_password_logged_in_temp_buffer_size - 1] = '\0'; - } - if (app->compose_pre_save_logged_in && app->compose_pre_save_logged_in_temp_buffer) - { - strncpy(app->compose_pre_save_logged_in_temp_buffer, app->compose_pre_save_logged_in, app->compose_pre_save_logged_in_temp_buffer_size - 1); - app->compose_pre_save_logged_in_temp_buffer[app->compose_pre_save_logged_in_temp_buffer_size - 1] = '\0'; - } - if (app->wifi_ssid_logged_in && app->wifi_ssid_logged_in_temp_buffer) - { - strncpy(app->wifi_ssid_logged_in_temp_buffer, app->wifi_ssid_logged_in, app->wifi_ssid_logged_in_temp_buffer_size - 1); - app->wifi_ssid_logged_in_temp_buffer[app->wifi_ssid_logged_in_temp_buffer_size - 1] = '\0'; - } - if (app->wifi_password_logged_in && app->wifi_password_logged_in_temp_buffer) - { - strncpy(app->wifi_password_logged_in_temp_buffer, app->wifi_password_logged_in, app->wifi_password_logged_in_temp_buffer_size - 1); - app->wifi_password_logged_in_temp_buffer[app->wifi_password_logged_in_temp_buffer_size - 1] = '\0'; - } - if (app->login_username_logged_in && app->login_username_logged_in_temp_buffer) - { - strncpy(app->login_username_logged_in_temp_buffer, app->login_username_logged_in, app->login_username_logged_in_temp_buffer_size - 1); - app->login_username_logged_in_temp_buffer[app->login_username_logged_in_temp_buffer_size - 1] = '\0'; - } - - // if login username is empty but logged out isnt, copy it over - if (strlen(app->login_username_logged_out) > 0 && strlen(app->login_username_logged_in) == 0) - { - strncpy(app->login_username_logged_in, app->login_username_logged_out, app->login_username_logged_in_temp_buffer_size - 1); - strncpy(app->login_username_logged_in_temp_buffer, app->login_username_logged_out, app->login_username_logged_in_temp_buffer_size - 1); - app->login_username_logged_in[app->login_username_logged_in_temp_buffer_size - 1] = '\0'; - app->login_username_logged_in_temp_buffer[app->login_username_logged_in_temp_buffer_size - 1] = '\0'; - } - // logout username is empty but logged in isnt, copy it over - if (strlen(app->login_username_logged_in) > 0 && strlen(app->login_username_logged_out) == 0) - { - strncpy(app->login_username_logged_out, app->login_username_logged_in, app->login_username_logged_in_temp_buffer_size - 1); - strncpy(app->login_username_logged_out_temp_buffer, app->login_username_logged_in, app->login_username_logged_in_temp_buffer_size - 1); - app->login_username_logged_out[app->login_username_logged_in_temp_buffer_size - 1] = '\0'; - app->login_username_logged_out_temp_buffer[app->login_username_logged_in_temp_buffer_size - 1] = '\0'; - } - // if login password is empty but logged out isnt, copy it over - if (strlen(app->login_password_logged_out) > 0 && strlen(app->change_password_logged_in) == 0) - { - strncpy(app->change_password_logged_in, app->login_password_logged_out, app->login_password_logged_out_temp_buffer_size - 1); - strncpy(app->change_password_logged_in_temp_buffer, app->login_password_logged_out, app->login_password_logged_out_temp_buffer_size - 1); - app->change_password_logged_in[app->login_password_logged_out_temp_buffer_size - 1] = '\0'; - app->change_password_logged_in_temp_buffer[app->login_password_logged_out_temp_buffer_size - 1] = '\0'; - } - // if logout password is empty but logged in isnt, copy it over - if (strlen(app->change_password_logged_in) > 0 && strlen(app->login_password_logged_out) == 0) - { - strncpy(app->login_password_logged_out, app->change_password_logged_in, app->login_password_logged_out_temp_buffer_size - 1); - strncpy(app->login_password_logged_out_temp_buffer, app->change_password_logged_in, app->login_password_logged_out_temp_buffer_size - 1); - app->login_password_logged_out[app->login_password_logged_out_temp_buffer_size - 1] = '\0'; - app->login_password_logged_out_temp_buffer[app->login_password_logged_out_temp_buffer_size - 1] = '\0'; - } - // if wifi password is empty but logged out isnt, copy it over - if (strlen(app->wifi_password_logged_out) > 0 && strlen(app->wifi_password_logged_in) == 0) - { - strncpy(app->wifi_password_logged_in, app->wifi_password_logged_out, app->wifi_password_logged_in_temp_buffer_size - 1); - strncpy(app->wifi_password_logged_in_temp_buffer, app->wifi_password_logged_out, app->wifi_password_logged_in_temp_buffer_size - 1); - app->wifi_password_logged_in[app->wifi_password_logged_in_temp_buffer_size - 1] = '\0'; - app->wifi_password_logged_in_temp_buffer[app->wifi_password_logged_in_temp_buffer_size - 1] = '\0'; - } - // ifi wifi password is empty but logged in isnt, copy it over - if (strlen(app->wifi_password_logged_in) > 0 && strlen(app->wifi_password_logged_out) == 0) - { - strncpy(app->wifi_password_logged_out, app->wifi_password_logged_in, app->wifi_password_logged_in_temp_buffer_size - 1); - strncpy(app->wifi_password_logged_out_temp_buffer, app->wifi_password_logged_in, app->wifi_password_logged_in_temp_buffer_size - 1); - app->wifi_password_logged_out[app->wifi_password_logged_in_temp_buffer_size - 1] = '\0'; - app->wifi_password_logged_out_temp_buffer[app->wifi_password_logged_in_temp_buffer_size - 1] = '\0'; - } - // if wifi ssid is empty but logged out isnt, copy it over - if (strlen(app->wifi_ssid_logged_out) > 0 && strlen(app->wifi_ssid_logged_in) == 0) - { - strncpy(app->wifi_ssid_logged_in, app->wifi_ssid_logged_out, app->wifi_ssid_logged_in_temp_buffer_size - 1); - strncpy(app->wifi_ssid_logged_in_temp_buffer, app->wifi_ssid_logged_out, app->wifi_ssid_logged_in_temp_buffer_size - 1); - app->wifi_ssid_logged_in[app->wifi_ssid_logged_in_temp_buffer_size - 1] = '\0'; - app->wifi_ssid_logged_in_temp_buffer[app->wifi_ssid_logged_in_temp_buffer_size - 1] = '\0'; - } - // if wifi ssid is empty but logged in isnt, copy it over - if (strlen(app->wifi_ssid_logged_in) > 0 && strlen(app->wifi_ssid_logged_out) == 0) - { - strncpy(app->wifi_ssid_logged_out, app->wifi_ssid_logged_in, app->wifi_ssid_logged_in_temp_buffer_size - 1); - strncpy(app->wifi_ssid_logged_out_temp_buffer, app->wifi_ssid_logged_in, app->wifi_ssid_logged_in_temp_buffer_size - 1); - app->wifi_ssid_logged_out[app->wifi_ssid_logged_in_temp_buffer_size - 1] = '\0'; - app->wifi_ssid_logged_out_temp_buffer[app->wifi_ssid_logged_in_temp_buffer_size - 1] = '\0'; - } - - // set variable item text (ommit the passwords) - variable_item_set_current_value_text(app->variable_item_logged_in_wifi_settings_ssid, app->wifi_ssid_logged_in); - variable_item_set_current_value_text(app->variable_item_logged_out_wifi_settings_ssid, app->wifi_ssid_logged_out); - variable_item_set_current_value_text(app->variable_item_logged_out_login_username, app->login_username_logged_out); - variable_item_set_current_value_text(app->variable_item_logged_in_profile_username, app->login_username_logged_in); - // - - if (app->is_logged_in != NULL && strcmp(app->is_logged_in, "true") == 0) - { - view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedInSubmenu); - } - else - { - view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedOutSubmenu); - } - } - - return app; -} - -#endif // FLIP_SOCIAL_I \ No newline at end of file diff --git a/flip_social_messages.h b/flip_social_messages.h deleted file mode 100644 index 2d3017ff5..000000000 --- a/flip_social_messages.h +++ /dev/null @@ -1,396 +0,0 @@ -#ifndef FLIP_SOCIAL_MESSAGES_H -#define FLIP_SOCIAL_MESSAGES_H - -static FlipSocialModel2 *flip_social_messages_alloc() -{ - // Allocate memory for each username only if not already allocated - FlipSocialModel2 *users = malloc(sizeof(FlipSocialModel2)); - if (users == NULL) - { - FURI_LOG_E(TAG, "Failed to allocate memory for message users"); - return NULL; - } - for (size_t i = 0; i < MAX_MESSAGE_USERS; i++) - { - if (users->usernames[i] == NULL) - { - users->usernames[i] = malloc(MAX_USER_LENGTH); - if (users->usernames[i] == NULL) - { - FURI_LOG_E(TAG, "Failed to allocate memory for username %zu", i); - return NULL; // Return false on memory allocation failure - } - } - } - return users; -} - -static FlipSocialMessage *flip_social_user_messages_alloc() -{ - // Allocate memory for each username only if not already allocated - FlipSocialMessage *messages = malloc(sizeof(FlipSocialMessage)); - if (messages == NULL) - { - FURI_LOG_E(TAG, "Failed to allocate memory for messages"); - return NULL; - } - for (size_t i = 0; i < MAX_MESSAGE_USERS; i++) - { - if (messages->usernames[i] == NULL) - { - messages->usernames[i] = malloc(MAX_USER_LENGTH); - if (messages->usernames[i] == NULL) - { - FURI_LOG_E(TAG, "Failed to allocate memory for username %zu", i); - return NULL; // Return false on memory allocation failure - } - } - if (messages->messages[i] == NULL) - { - messages->messages[i] = malloc(MAX_MESSAGE_LENGTH); - if (messages->messages[i] == NULL) - { - FURI_LOG_E(TAG, "Failed to allocate memory for message %zu", i); - return NULL; // Return false on memory allocation failure - } - } - } - return messages; -} - -static void flip_social_free_message_users() -{ - if (flip_social_message_users == NULL) - { - FURI_LOG_E(TAG, "Message users model is NULL"); - return; - } - for (int i = 0; i < flip_social_message_users->count; i++) - { - free(flip_social_message_users->usernames[i]); - } -} - -static void flip_social_free_messages() -{ - if (flip_social_messages == NULL) - { - FURI_LOG_E(TAG, "Messages model is NULL"); - return; - } - for (int i = 0; i < flip_social_messages->count; i++) - { - free(flip_social_messages->usernames[i]); - free(flip_social_messages->messages[i]); - } -} - -static bool flip_social_update_messages_submenu() -{ - if (app_instance->submenu_messages == NULL) - { - FURI_LOG_E(TAG, "Submenu is NULL"); - return false; - } - if (flip_social_message_users == NULL) - { - FURI_LOG_E(TAG, "Message users model is NULL"); - return false; - } - submenu_reset(app_instance->submenu_messages); - submenu_set_header(app_instance->submenu_messages, "Messages"); - submenu_add_item(app_instance->submenu_messages, "[New Message]", FlipSocialSubmenuLoggedInIndexMessagesNewMessage, flip_social_callback_submenu_choices, app_instance); - for (int i = 0; i < flip_social_message_users->count; i++) - { - submenu_add_item(app_instance->submenu_messages, flip_social_message_users->usernames[i], FlipSocialSubmenuLoggedInIndexMessagesUsersStart + i, flip_social_callback_submenu_choices, app_instance); - } - return true; -} - -static bool flip_social_update_submenu_user_choices() -{ - if (app_instance->submenu_messages_user_choices == NULL) - { - FURI_LOG_E(TAG, "Submenu is NULL"); - return false; - } - if (flip_social_explore == NULL) - { - FURI_LOG_E(TAG, "Explore model is NULL"); - return false; - } - submenu_reset(app_instance->submenu_messages_user_choices); - submenu_set_header(app_instance->submenu_messages_user_choices, "Users"); - for (int i = 0; i < flip_social_explore->count; i++) - { - submenu_add_item(app_instance->submenu_messages_user_choices, flip_social_explore->usernames[i], FlipSocialSubmenuLoggedInIndexMessagesUserChoicesIndexStart + i, flip_social_callback_submenu_choices, app_instance); - } - return true; -} - -// Get all the users that have sent messages to the logged in user -static bool flip_social_get_message_users() -{ - if (app_instance->login_username_logged_out == NULL) - { - FURI_LOG_E(TAG, "Username is NULL"); - return false; - } - char command[128]; - snprintf(command, 128, "https://www.flipsocial.net/api/messages/%s/get/list/", app_instance->login_username_logged_out); - bool success = flipper_http_get_request_with_headers(command, "{\"Content-Type\":\"application/json\"}"); - if (!success) - { - FURI_LOG_E(TAG, "Failed to send HTTP request for messages"); - return false; - } - fhttp.state = RECEIVING; - return true; -} - -// Get all the messages between the logged in user and the selected user -static bool flip_social_get_messages_with_user() -{ - if (app_instance->login_username_logged_out == NULL) - { - FURI_LOG_E(TAG, "Username is NULL"); - return false; - } - char command[128]; - snprintf(command, 128, "https://www.flipsocial.net/api/messages/%s/get/%s/", app_instance->login_username_logged_out, flip_social_message_users->usernames[flip_social_message_users->index]); - bool success = flipper_http_get_request_with_headers(command, "{\"Content-Type\":\"application/json\"}"); - if (!success) - { - FURI_LOG_E(TAG, "Failed to send HTTP request for messages"); - return false; - } - fhttp.state = RECEIVING; - return true; -} - -// Parse the users that have sent messages to the logged-in user -static bool flip_social_parse_json_message_users() -{ - if (fhttp.received_data == NULL) - { - FURI_LOG_E(TAG, "No data received."); - return false; - } - - // Allocate memory for each username only if not already allocated - flip_social_message_users = flip_social_messages_alloc(); - if (flip_social_message_users == NULL) - { - FURI_LOG_E(TAG, "Failed to allocate memory for message users."); - return false; - } - - // Remove newlines - char *pos = fhttp.received_data; - while ((pos = strchr(pos, '\n')) != NULL) - { - *pos = ' '; - } - - // Initialize message users count - flip_social_message_users->count = 0; - - // Extract the users array from the JSON - char *json_users = get_json_value("users", fhttp.received_data, MAX_TOKENS); - if (json_users == NULL) - { - FURI_LOG_E(TAG, "Failed to parse users array."); - return false; - } - - // Manual tokenization for comma-separated values - char *start = json_users + 1; // Skip the opening bracket - char *end; - while ((end = strchr(start, ',')) != NULL && flip_social_message_users->count < MAX_MESSAGE_USERS) - { - *end = '\0'; // Null-terminate the current token - - // Remove quotes - if (*start == '"') - start++; - if (*(end - 1) == '"') - *(end - 1) = '\0'; - - // Copy username to pre-allocated memory - strncpy(flip_social_message_users->usernames[flip_social_message_users->count], start, MAX_USER_LENGTH - 1); - flip_social_message_users->usernames[flip_social_message_users->count][MAX_USER_LENGTH - 1] = '\0'; // Ensure null termination - flip_social_message_users->count++; - start = end + 1; - } - - // Handle the last token - if (*start != '\0' && flip_social_message_users->count < MAX_MESSAGE_USERS) - { - if (*start == '"') - start++; - if (*(start + strlen(start) - 1) == ']') - *(start + strlen(start) - 1) = '\0'; - if (*(start + strlen(start) - 1) == '"') - *(start + strlen(start) - 1) = '\0'; - - strncpy(flip_social_message_users->usernames[flip_social_message_users->count], start, MAX_USER_LENGTH - 1); - flip_social_message_users->usernames[flip_social_message_users->count][MAX_USER_LENGTH - 1] = '\0'; // Ensure null termination - flip_social_message_users->count++; - } - - // Add submenu items for the users - flip_social_update_messages_submenu(); - - // Free the JSON data - free(json_users); - free(start); - free(end); - - return true; -} - -// Parse the users that the logged in user can message -static bool flip_social_parse_json_message_user_choices() -{ - if (fhttp.received_data == NULL) - { - FURI_LOG_E(TAG, "No data received."); - return false; - } - - // Allocate memory for each username only if not already allocated - flip_social_explore = flip_social_explore_alloc(); - if (flip_social_explore == NULL) - { - FURI_LOG_E(TAG, "Failed to allocate memory for explore usernames."); - return false; - } - - // Remove newlines - char *pos = fhttp.received_data; - while ((pos = strchr(pos, '\n')) != NULL) - { - *pos = ' '; - } - - // Initialize explore count - flip_social_explore->count = 0; - - // Extract the users array from the JSON - char *json_users = get_json_value("users", fhttp.received_data, MAX_TOKENS); - if (json_users == NULL) - { - FURI_LOG_E(TAG, "Failed to parse users array."); - return false; - } - - // Manual tokenization for comma-separated values - char *start = json_users + 1; // Skip the opening bracket - char *end; - while ((end = strchr(start, ',')) != NULL && flip_social_explore->count < MAX_EXPLORE_USERS) - { - *end = '\0'; // Null-terminate the current token - - // Remove quotes - if (*start == '"') - start++; - if (*(end - 1) == '"') - *(end - 1) = '\0'; - - // Copy username to pre-allocated memory - strncpy(flip_social_explore->usernames[flip_social_explore->count], start, MAX_USER_LENGTH - 1); - flip_social_explore->usernames[flip_social_explore->count][MAX_USER_LENGTH - 1] = '\0'; // Ensure null termination - flip_social_explore->count++; - start = end + 1; - } - - // Handle the last token - if (*start != '\0' && flip_social_explore->count < MAX_EXPLORE_USERS) - { - if (*start == '"') - start++; - if (*(start + strlen(start) - 1) == ']') - *(start + strlen(start) - 1) = '\0'; - if (*(start + strlen(start) - 1) == '"') - *(start + strlen(start) - 1) = '\0'; - - strncpy(flip_social_explore->usernames[flip_social_explore->count], start, MAX_USER_LENGTH - 1); - flip_social_explore->usernames[flip_social_explore->count][MAX_USER_LENGTH - 1] = '\0'; // Ensure null termination - flip_social_explore->count++; - } - - // Add submenu items for the users - flip_social_update_submenu_user_choices(); - - // Free the JSON data - free(json_users); - free(start); - free(end); - - return true; -} - -// parse messages between the logged in user and the selected user -static bool flip_social_parse_json_messages() -{ - if (fhttp.received_data == NULL) - { - FURI_LOG_E(TAG, "No data received."); - return false; - } - - // Allocate memory for each message only if not already allocated - flip_social_messages = flip_social_user_messages_alloc(); - if (!flip_social_messages) - { - FURI_LOG_E(TAG, "Failed to allocate memory for messages."); - return false; - } - - // Remove newlines - char *pos = fhttp.received_data; - while ((pos = strchr(pos, '\n')) != NULL) - { - *pos = ' '; - } - - // Initialize messages count - flip_social_messages->count = 0; - - // Iterate through the messages array - for (int i = 0; i < MAX_MESSAGES; i++) - { - // Parse each item in the array - char *item = get_json_array_value("conversations", i, fhttp.received_data, MAX_TOKENS); - if (item == NULL) - { - break; - } - - // Extract individual fields from the JSON object - char *sender = get_json_value("sender", item, MAX_TOKENS); - char *content = get_json_value("content", item, MAX_TOKENS); - - if (sender == NULL || content == NULL) - { - FURI_LOG_E(TAG, "Failed to parse item fields."); - free(item); - continue; - } - - // Store parsed values - strncpy(flip_social_messages->usernames[i], sender, MAX_USER_LENGTH - 1); - flip_social_messages->usernames[i][MAX_USER_LENGTH - 1] = '\0'; - strncpy(flip_social_messages->messages[i], content, MAX_MESSAGE_LENGTH - 1); - flip_social_messages->messages[i][MAX_MESSAGE_LENGTH - 1] = '\0'; - flip_social_messages->count++; - - free(item); - free(sender); - free(content); - } - - return true; -} - -#endif // FLIP_SOCIAL_MESSAGES_H \ No newline at end of file diff --git a/flip_social_storage.h b/flip_social_storage.h deleted file mode 100644 index da7a122d5..000000000 --- a/flip_social_storage.h +++ /dev/null @@ -1,381 +0,0 @@ -// flip_social_storage.h -#ifndef FLIP_SOCIAL_STORAGE_H -#define FLIP_SOCIAL_STORAGE_H - -#include -#include - -#define SETTINGS_PATH STORAGE_EXT_PATH_PREFIX "/apps_data/flip_social/settings.bin" -#define PRE_SAVED_MESSAGES_PATH STORAGE_EXT_PATH_PREFIX "/apps_data/flip_social/pre_saved_messages.txt" - -// Function to save the playlist -void save_playlist(const PreSavedPlaylist *playlist) -{ - if (!playlist) - { - FURI_LOG_E(TAG, "Playlist is NULL"); - return; - } - // Create the directory for saving settings - char directory_path[128]; - snprintf(directory_path, sizeof(directory_path), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_social"); - - // Create the directory - Storage *storage = furi_record_open(RECORD_STORAGE); - storage_common_mkdir(storage, directory_path); - - // Open the settings file - File *file = storage_file_alloc(storage); - if (!storage_file_open(file, PRE_SAVED_MESSAGES_PATH, FSAM_WRITE, FSOM_CREATE_ALWAYS)) - { - FURI_LOG_E(TAG, "Failed to open settings file for writing: %s", PRE_SAVED_MESSAGES_PATH); - storage_file_free(file); - furi_record_close(RECORD_STORAGE); - return; - } - - // Write each playlist message on a separate line - for (size_t i = 0; i < playlist->count; ++i) - { - // Add a newline character after each message - if (storage_file_write(file, playlist->messages[i], strlen(playlist->messages[i])) != strlen(playlist->messages[i])) - { - FURI_LOG_E(TAG, "Failed to write playlist message %zu", i); - } - - // Write a newline after each message - if (storage_file_write(file, "\n", 1) != 1) - { - FURI_LOG_E(TAG, "Failed to write newline after message %zu", i); - } - } - - storage_file_close(file); - storage_file_free(file); - furi_record_close(RECORD_STORAGE); -} - -// Function to load the playlist -// Function to load the playlist -bool load_playlist(PreSavedPlaylist *playlist) -{ - // Ensure playlist is not NULL - if (!playlist) - { - FURI_LOG_E(TAG, "Playlist is NULL"); - return false; - } - - // Ensure playlist->messages is not NULL and allocate memory for each message - for (size_t i = 0; i < MAX_PRE_SAVED_MESSAGES; ++i) - { - if (!playlist->messages[i]) // Check if memory is already allocated - { - playlist->messages[i] = (char *)malloc(MAX_MESSAGE_LENGTH * sizeof(char)); - if (!playlist->messages[i]) - { - FURI_LOG_E(TAG, "Failed to allocate memory for message %zu", i); - return false; // Return false on memory allocation failure - } - } - } - - // Open the storage - Storage *storage = furi_record_open(RECORD_STORAGE); - File *file = storage_file_alloc(storage); - - if (!storage_file_open(file, PRE_SAVED_MESSAGES_PATH, FSAM_READ, FSOM_OPEN_EXISTING)) - { - FURI_LOG_E(TAG, "Failed to open pre-saved messages file for reading: %s", PRE_SAVED_MESSAGES_PATH); - storage_file_free(file); - furi_record_close(RECORD_STORAGE); - return false; // Return false if the file does not exist - } - - // Initialize the playlist count - playlist->count = 0; - - // Read the file byte by byte to simulate reading lines - char ch; - size_t message_pos = 0; - bool message_started = false; - - while (storage_file_read(file, &ch, 1) == 1) // Read one character at a time - { - message_started = true; - - if (ch == '\n' || message_pos >= (MAX_MESSAGE_LENGTH - 1)) // End of line or message is too long - { - playlist->messages[playlist->count][message_pos] = '\0'; // Null-terminate the message - playlist->count++; // Move to the next message - message_pos = 0; // Reset for the next message - message_started = false; - - // Ensure the playlist count does not exceed the maximum - if (playlist->count >= MAX_PRE_SAVED_MESSAGES) - { - FURI_LOG_W(TAG, "Reached maximum playlist messages"); - break; - } - } - else - { - playlist->messages[playlist->count][message_pos++] = ch; // Add character to current message - } - } - - // Handle the case where the last message does not end with a newline - if (message_started && message_pos > 0) - { - playlist->messages[playlist->count][message_pos] = '\0'; // Null-terminate the last message - playlist->count++; // Increment the count for the last message - - // Ensure the playlist count does not exceed the maximum - if (playlist->count >= MAX_PRE_SAVED_MESSAGES) - { - FURI_LOG_W(TAG, "Reached maximum playlist messages"); - } - } - - // Close the file and storage - storage_file_close(file); - storage_file_free(file); - furi_record_close(RECORD_STORAGE); - - return true; -} -static void save_settings( - const char *ssid, - const char *password, - const char *login_username_logged_out, - const char *login_username_logged_in, - const char *login_password_logged_out, - const char *change_password_logged_in, - const char *is_logged_in) -{ - // Create the directory for saving settings - char directory_path[128]; - snprintf(directory_path, sizeof(directory_path), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_social"); - - // Create the directory - Storage *storage = furi_record_open(RECORD_STORAGE); - storage_common_mkdir(storage, directory_path); - - // Open the settings file - File *file = storage_file_alloc(storage); - if (!storage_file_open(file, SETTINGS_PATH, FSAM_WRITE, FSOM_CREATE_ALWAYS)) - { - FURI_LOG_E(TAG, "Failed to open settings file for writing: %s", SETTINGS_PATH); - storage_file_free(file); - furi_record_close(RECORD_STORAGE); - return; - } - - // Save the ssid length and data - size_t ssid_length = strlen(ssid) + 1; // Include null terminator - if (storage_file_write(file, &ssid_length, sizeof(size_t)) != sizeof(size_t) || - storage_file_write(file, ssid, ssid_length) != ssid_length) - { - FURI_LOG_E(TAG, "Failed to write SSID"); - } - - // Save the password length and data - size_t password_length = strlen(password) + 1; // Include null terminator - if (storage_file_write(file, &password_length, sizeof(size_t)) != sizeof(size_t) || - storage_file_write(file, password, password_length) != password_length) - { - FURI_LOG_E(TAG, "Failed to write password"); - } - - // Save the login_username_logged_out length and data - size_t username_out_length = strlen(login_username_logged_out) + 1; // Include null terminator - if (storage_file_write(file, &username_out_length, sizeof(size_t)) != sizeof(size_t) || - storage_file_write(file, login_username_logged_out, username_out_length) != username_out_length) - { - FURI_LOG_E(TAG, "Failed to write login_username_logged_out"); - } - - // Save the login_username_logged_in length and data - size_t username_in_length = strlen(login_username_logged_in) + 1; // Include null terminator - if (storage_file_write(file, &username_in_length, sizeof(size_t)) != sizeof(size_t) || - storage_file_write(file, login_username_logged_in, username_in_length) != username_in_length) - { - FURI_LOG_E(TAG, "Failed to write login_username_logged_in"); - } - - // Save the login_password_logged_out length and data - size_t password_out_length = strlen(login_password_logged_out) + 1; // Include null terminator - if (storage_file_write(file, &password_out_length, sizeof(size_t)) != sizeof(size_t) || - storage_file_write(file, login_password_logged_out, password_out_length) != password_out_length) - { - FURI_LOG_E(TAG, "Failed to write login_password_logged_out"); - } - - // Save the change_password_logged_in length and data - size_t change_password_length = strlen(change_password_logged_in) + 1; // Include null terminator - if (storage_file_write(file, &change_password_length, sizeof(size_t)) != sizeof(size_t) || - storage_file_write(file, change_password_logged_in, change_password_length) != change_password_length) - { - FURI_LOG_E(TAG, "Failed to write change_password_logged_in"); - } - - // Save the is_logged_in length and data - size_t is_logged_in_length = strlen(is_logged_in) + 1; // Include null terminator - if (storage_file_write(file, &is_logged_in_length, sizeof(size_t)) != sizeof(size_t) || - storage_file_write(file, is_logged_in, is_logged_in_length) != is_logged_in_length) - { - FURI_LOG_E(TAG, "Failed to write is_logged_in"); - } - - storage_file_close(file); - storage_file_free(file); - furi_record_close(RECORD_STORAGE); -} - -static bool load_settings( - char *ssid, - size_t ssid_size, - char *password, - size_t password_size, - char *login_username_logged_out, - size_t username_out_size, - char *login_username_logged_in, - size_t username_in_size, - char *login_password_logged_out, - size_t password_out_size, - char *change_password_logged_in, - size_t change_password_size, - char *is_logged_in, - size_t is_logged_in_size) -{ - Storage *storage = furi_record_open(RECORD_STORAGE); - File *file = storage_file_alloc(storage); - - if (!storage_file_open(file, SETTINGS_PATH, FSAM_READ, FSOM_OPEN_EXISTING)) - { - FURI_LOG_E(TAG, "Failed to open settings file for reading: %s", SETTINGS_PATH); - storage_file_free(file); - furi_record_close(RECORD_STORAGE); - return false; // Return false if the file does not exist - } - - // Load the ssid - size_t ssid_length; - if (storage_file_read(file, &ssid_length, sizeof(size_t)) != sizeof(size_t) || ssid_length > ssid_size || - storage_file_read(file, ssid, ssid_length) != ssid_length) - { - FURI_LOG_E(TAG, "Failed to read SSID"); - storage_file_close(file); - storage_file_free(file); - furi_record_close(RECORD_STORAGE); - return false; - } - else - { - ssid[ssid_length - 1] = '\0'; // Ensure null-termination - } - - // Load the password - size_t password_length; - if (storage_file_read(file, &password_length, sizeof(size_t)) != sizeof(size_t) || password_length > password_size || - storage_file_read(file, password, password_length) != password_length) - { - FURI_LOG_E(TAG, "Failed to read password"); - storage_file_close(file); - storage_file_free(file); - furi_record_close(RECORD_STORAGE); - return false; - } - else - { - password[password_length - 1] = '\0'; // Ensure null-termination - } - - // Load the login_username_logged_out - size_t username_out_length; - if (storage_file_read(file, &username_out_length, sizeof(size_t)) != sizeof(size_t) || username_out_length > username_out_size || - storage_file_read(file, login_username_logged_out, username_out_length) != username_out_length) - { - FURI_LOG_E(TAG, "Failed to read login_username_logged_out"); - // storage_file_close(file); - // storage_file_free(file); - // furi_record_close(RECORD_STORAGE); - // return false; - } - else - { - login_username_logged_out[username_out_length - 1] = '\0'; // Ensure null-termination - } - - // Load the login_username_logged_in - size_t username_in_length; - if (storage_file_read(file, &username_in_length, sizeof(size_t)) != sizeof(size_t) || username_in_length > username_in_size || - storage_file_read(file, login_username_logged_in, username_in_length) != username_in_length) - { - FURI_LOG_E(TAG, "Failed to read login_username_logged_in"); - // storage_file_close(file); - // storage_file_free(file); - // furi_record_close(RECORD_STORAGE); - // return false; - } - else - { - login_username_logged_in[username_in_length - 1] = '\0'; // Ensure null-termination - } - - // Load the login_password_logged_out - size_t password_out_length; - if (storage_file_read(file, &password_out_length, sizeof(size_t)) != sizeof(size_t) || password_out_length > password_out_size || - storage_file_read(file, login_password_logged_out, password_out_length) != password_out_length) - { - FURI_LOG_E(TAG, "Failed to read login_password_logged_out"); - // storage_file_close(file); - // storage_file_free(file); - // furi_record_close(RECORD_STORAGE); - // return false; - } - else - { - login_password_logged_out[password_out_length - 1] = '\0'; // Ensure null-termination - } - - // Load the change_password_logged_in - size_t change_password_length; - if (storage_file_read(file, &change_password_length, sizeof(size_t)) != sizeof(size_t) || change_password_length > change_password_size || - storage_file_read(file, change_password_logged_in, change_password_length) != change_password_length) - { - FURI_LOG_E(TAG, "Failed to read change_password_logged_in"); - // storage_file_close(file); - // storage_file_free(file); - // furi_record_close(RECORD_STORAGE); - // return false; - } - else - { - change_password_logged_in[change_password_length - 1] = '\0'; // Ensure null-termination - } - - // Load the is_logged_in - size_t is_logged_in_length; - if (storage_file_read(file, &is_logged_in_length, sizeof(size_t)) != sizeof(size_t) || is_logged_in_length > is_logged_in_size || - storage_file_read(file, is_logged_in, is_logged_in_length) != is_logged_in_length) - { - FURI_LOG_E(TAG, "Failed to read is_logged_in"); - // storage_file_close(file); - // storage_file_free(file); - // furi_record_close(RECORD_STORAGE); - // return false; - } - else - { - is_logged_in[is_logged_in_length - 1] = '\0'; // Ensure null-termination - } - - storage_file_close(file); - storage_file_free(file); - furi_record_close(RECORD_STORAGE); - - return true; -} - -#endif // FLIP_SOCIAL_STORAGE_H diff --git a/flipper_http.h b/flipper_http.h deleted file mode 100644 index abe3539c0..000000000 --- a/flipper_http.h +++ /dev/null @@ -1,1197 +0,0 @@ -// flipper_http.h -#ifndef FLIPPER_HTTP_H -#define FLIPPER_HTTP_H - -#include -#include -#include -#include -#include - -// STORAGE_EXT_PATH_PREFIX is defined in the Furi SDK as /ext - -#define HTTP_TAG "FlipSocial" // change this to your app name -#define http_tag "flip_social" // change this to your app id -#define UART_CH (FuriHalSerialIdUsart) // UART channel -#define TIMEOUT_DURATION_TICKS (6 * 1000) // 6 seconds -#define BAUDRATE (115200) // UART baudrate -#define RX_BUF_SIZE 128 // UART RX buffer size -#define RX_LINE_BUFFER_SIZE 8192 // UART RX line buffer size (increase for large responses) - -// Forward declaration for callback -typedef void (*FlipperHTTP_Callback)(const char *line, void *context); - -// Functions -bool flipper_http_init(FlipperHTTP_Callback callback, void *context); -void flipper_http_deinit(); -//--- -void flipper_http_rx_callback(const char *line, void *context); -bool flipper_http_send_data(const char *data); -//--- -bool flipper_http_connect_wifi(); -bool flipper_http_disconnect_wifi(); -bool flipper_http_ping(); -bool flipper_http_scan_wifi(); -bool flipper_http_save_wifi(const char *ssid, const char *password); -//--- -bool flipper_http_get_request(const char *url); -bool flipper_http_get_request_with_headers(const char *url, const char *headers); -bool flipper_http_post_request_with_headers(const char *url, const char *headers, const char *payload); -bool flipper_http_put_request_with_headers(const char *url, const char *headers, const char *payload); -bool flipper_http_delete_request_with_headers(const char *url, const char *headers, const char *payload); -//--- -bool flipper_http_save_received_data(size_t bytes_received, const char line_buffer[]); -static char *trim(const char *str); -// -bool flipper_http_process_response_async( - bool (*http_request)(void), - bool (*parse_json)(void)); - -// State variable to track the UART state -typedef enum -{ - INACTIVE, // Inactive state - IDLE, // Default state - RECEIVING, // Receiving data - SENDING, // Sending data - ISSUE, // Issue with connection -} SerialState; - -// Event Flags for UART Worker Thread -typedef enum -{ - WorkerEvtStop = (1 << 0), - WorkerEvtRxDone = (1 << 1), -} WorkerEvtFlags; - -// FlipperHTTP Structure -typedef struct -{ - FuriStreamBuffer *flipper_http_stream; // Stream buffer for UART communication - FuriHalSerialHandle *serial_handle; // Serial handle for UART communication - FuriThread *rx_thread; // Worker thread for UART - uint8_t rx_buf[RX_BUF_SIZE]; // Buffer for received data - FuriThreadId rx_thread_id; // Worker thread ID - FlipperHTTP_Callback handle_rx_line_cb; // Callback for received lines - void *callback_context; // Context for the callback - SerialState state; // State of the UART - - // variable to store the last received data from the UART - char *last_response; - - // Timer-related members - FuriTimer *get_timeout_timer; // Timer for HTTP request timeout - char *received_data; // Buffer to store received data - - bool started_receiving_get; // Indicates if a GET request has started - bool just_started_get; // Indicates if GET data reception has just started - - bool started_receiving_post; // Indicates if a POST request has started - bool just_started_post; // Indicates if POST data reception has just started - - bool started_receiving_put; // Indicates if a PUT request has started - bool just_started_put; // Indicates if PUT data reception has just started - - bool started_receiving_delete; // Indicates if a DELETE request has started - bool just_started_delete; // Indicates if DELETE data reception has just started -} FlipperHTTP; - -FlipperHTTP fhttp; - -// fhttp.received_data holds the received data from HTTP requests -// fhttp.last_response holds the last received data from the UART, which could be [GET/END], [POST/END], [PUT/END], [DELETE/END], etc - -// Timer callback function -/** - * @brief Callback function for the GET timeout timer. - * @return 0 - * @param context The context to pass to the callback. - * @note This function will be called when the GET request times out. - */ -void get_timeout_timer_callback(void *context) -{ - UNUSED(context); - FURI_LOG_E(HTTP_TAG, "Timeout reached: 2 seconds without receiving the end."); - - // Reset the state - fhttp.started_receiving_get = false; - fhttp.started_receiving_post = false; - fhttp.started_receiving_put = false; - fhttp.started_receiving_delete = false; - - // Free received data if any - if (fhttp.received_data) - { - free(fhttp.received_data); - fhttp.received_data = NULL; - } - - // Update UART state - fhttp.state = ISSUE; -} - -// UART RX Handler Callback (Interrupt Context) -/** - * @brief A private callback function to handle received data asynchronously. - * @return void - * @param handle The UART handle. - * @param event The event type. - * @param context The context to pass to the callback. - * @note This function will handle received data asynchronously via the callback. - */ -static void _flipper_http_rx_callback(FuriHalSerialHandle *handle, FuriHalSerialRxEvent event, void *context) -{ - UNUSED(context); - if (event == FuriHalSerialRxEventData) - { - uint8_t data = furi_hal_serial_async_rx(handle); - furi_stream_buffer_send(fhttp.flipper_http_stream, &data, 1, 0); - furi_thread_flags_set(fhttp.rx_thread_id, WorkerEvtRxDone); - } -} - -// UART worker thread -/** - * @brief Worker thread to handle UART data asynchronously. - * @return 0 - * @param context The context to pass to the callback. - * @note This function will handle received data asynchronously via the callback. - */ -static int32_t flipper_http_worker(void *context) -{ - UNUSED(context); - size_t rx_line_pos = 0; - char *rx_line_buffer = (char *)malloc(RX_LINE_BUFFER_SIZE); - - if (!rx_line_buffer) - { - // Handle malloc failure - FURI_LOG_E(HTTP_TAG, "Failed to allocate memory for rx_line_buffer"); - return -1; - } - - while (1) - { - uint32_t events = furi_thread_flags_wait(WorkerEvtStop | WorkerEvtRxDone, FuriFlagWaitAny, FuriWaitForever); - if (events & WorkerEvtStop) - break; - if (events & WorkerEvtRxDone) - { - size_t len = furi_stream_buffer_receive(fhttp.flipper_http_stream, fhttp.rx_buf, RX_BUF_SIZE + 1, 0); - for (size_t i = 0; i < len; i++) - { - char c = fhttp.rx_buf[i]; - if (c == '\n' || rx_line_pos >= RX_LINE_BUFFER_SIZE - 1) - { - rx_line_buffer[rx_line_pos] = '\0'; - // Invoke the callback with the complete line - if (fhttp.handle_rx_line_cb) - { - fhttp.handle_rx_line_cb(rx_line_buffer, fhttp.callback_context); - } - // Reset the line buffer - rx_line_pos = 0; - } - else - { - rx_line_buffer[rx_line_pos++] = c; - } - } - } - } - - // Free the allocated memory before exiting the thread - free(rx_line_buffer); - - return 0; -} - -// UART initialization function -/** - * @brief Initialize UART. - * @return true if the UART was initialized successfully, false otherwise. - * @param callback The callback function to handle received data (ex. flipper_http_rx_callback). - * @param context The context to pass to the callback. - * @note The received data will be handled asynchronously via the callback. - */ -bool flipper_http_init(FlipperHTTP_Callback callback, void *context) -{ - if (!context) - { - FURI_LOG_E(HTTP_TAG, "Invalid context provided to flipper_http_init."); - return false; - } - if (!callback) - { - FURI_LOG_E(HTTP_TAG, "Invalid callback provided to flipper_http_init."); - return false; - } - fhttp.flipper_http_stream = furi_stream_buffer_alloc(RX_BUF_SIZE, 1); - if (!fhttp.flipper_http_stream) - { - FURI_LOG_E(HTTP_TAG, "Failed to allocate UART stream buffer."); - return false; - } - - fhttp.rx_thread = furi_thread_alloc(); - if (!fhttp.rx_thread) - { - FURI_LOG_E(HTTP_TAG, "Failed to allocate UART thread."); - furi_stream_buffer_free(fhttp.flipper_http_stream); - return false; - } - - furi_thread_set_name(fhttp.rx_thread, "FlipperHTTP_RxThread"); - furi_thread_set_stack_size(fhttp.rx_thread, 1024); - furi_thread_set_context(fhttp.rx_thread, &fhttp); - furi_thread_set_callback(fhttp.rx_thread, flipper_http_worker); - - fhttp.handle_rx_line_cb = callback; - fhttp.callback_context = context; - - furi_thread_start(fhttp.rx_thread); - fhttp.rx_thread_id = furi_thread_get_id(fhttp.rx_thread); - - // handle when the UART control is busy to avoid furi_check failed - if (furi_hal_serial_control_is_busy(UART_CH)) - { - FURI_LOG_E(HTTP_TAG, "UART control is busy."); - return false; - } - - fhttp.serial_handle = furi_hal_serial_control_acquire(UART_CH); - if (!fhttp.serial_handle) - { - FURI_LOG_E(HTTP_TAG, "Failed to acquire UART control - handle is NULL"); - // Cleanup resources - furi_thread_free(fhttp.rx_thread); - furi_stream_buffer_free(fhttp.flipper_http_stream); - return false; - } - - // Initialize UART with acquired handle - furi_hal_serial_init(fhttp.serial_handle, BAUDRATE); - - // Enable RX direction - furi_hal_serial_enable_direction(fhttp.serial_handle, FuriHalSerialDirectionRx); - - // Start asynchronous RX with the callback - furi_hal_serial_async_rx_start(fhttp.serial_handle, _flipper_http_rx_callback, &fhttp, false); - - // Wait for the TX to complete to ensure UART is ready - furi_hal_serial_tx_wait_complete(fhttp.serial_handle); - - // Allocate the timer for handling timeouts - fhttp.get_timeout_timer = furi_timer_alloc( - get_timeout_timer_callback, // Callback function - FuriTimerTypeOnce, // One-shot timer - &fhttp // Context passed to callback - ); - - if (!fhttp.get_timeout_timer) - { - FURI_LOG_E(HTTP_TAG, "Failed to allocate HTTP request timeout timer."); - // Cleanup resources - furi_hal_serial_async_rx_stop(fhttp.serial_handle); - furi_hal_serial_disable_direction(fhttp.serial_handle, FuriHalSerialDirectionRx); - furi_hal_serial_control_release(fhttp.serial_handle); - furi_hal_serial_deinit(fhttp.serial_handle); - furi_thread_flags_set(fhttp.rx_thread_id, WorkerEvtStop); - furi_thread_join(fhttp.rx_thread); - furi_thread_free(fhttp.rx_thread); - furi_stream_buffer_free(fhttp.flipper_http_stream); - return false; - } - - // Set the timer thread priority if needed - furi_timer_set_thread_priority(FuriTimerThreadPriorityElevated); - - // FURI_LOG_I(HTTP_TAG, "UART initialized successfully."); - return true; -} - -// Deinitialize UART -/** - * @brief Deinitialize UART. - * @return void - * @note This function will stop the asynchronous RX, release the serial handle, and free the resources. - */ -void flipper_http_deinit() -{ - if (fhttp.serial_handle == NULL) - { - FURI_LOG_E(HTTP_TAG, "UART handle is NULL. Already deinitialized?"); - return; - } - // Stop asynchronous RX - furi_hal_serial_async_rx_stop(fhttp.serial_handle); - - // Release and deinitialize the serial handle - furi_hal_serial_disable_direction(fhttp.serial_handle, FuriHalSerialDirectionRx); - furi_hal_serial_control_release(fhttp.serial_handle); - furi_hal_serial_deinit(fhttp.serial_handle); - - // Signal the worker thread to stop - furi_thread_flags_set(fhttp.rx_thread_id, WorkerEvtStop); - // Wait for the thread to finish - furi_thread_join(fhttp.rx_thread); - // Free the thread resources - furi_thread_free(fhttp.rx_thread); - - // Free the stream buffer - furi_stream_buffer_free(fhttp.flipper_http_stream); - - // Free the timer - if (fhttp.get_timeout_timer) - { - furi_timer_free(fhttp.get_timeout_timer); - fhttp.get_timeout_timer = NULL; - } - - // Free received data if any - if (fhttp.received_data) - { - free(fhttp.received_data); - fhttp.received_data = NULL; - } - - // Free the last response - if (fhttp.last_response) - { - free(fhttp.last_response); - fhttp.last_response = NULL; - } - - // FURI_LOG_I("FlipperHTTP", "UART deinitialized successfully."); -} - -// Function to send data over UART with newline termination -/** - * @brief Send data over UART with newline termination. - * @return true if the data was sent successfully, false otherwise. - * @param data The data to send over UART. - * @note The data will be sent over UART with a newline character appended. - */ -bool flipper_http_send_data(const char *data) -{ - size_t data_length = strlen(data); - if (data_length == 0) - { - FURI_LOG_E("FlipperHTTP", "Attempted to send empty data."); - return false; - } - - // Create a buffer with data + '\n' - size_t send_length = data_length + 1; // +1 for '\n' - if (send_length > 256) - { // Ensure buffer size is sufficient - FURI_LOG_E("FlipperHTTP", "Data too long to send over FHTTP."); - return false; - } - - char send_buffer[257]; // 256 + 1 for safety - strncpy(send_buffer, data, 256); - send_buffer[data_length] = '\n'; // Append newline - send_buffer[data_length + 1] = '\0'; // Null-terminate - - if (fhttp.state == INACTIVE && ((strstr(send_buffer, "[PING]") == NULL) && (strstr(send_buffer, "[WIFI/CONNECT]") == NULL))) - { - FURI_LOG_E("FlipperHTTP", "Cannot send data while INACTIVE."); - fhttp.last_response = "Cannot send data while INACTIVE."; - return false; - } - - fhttp.state = SENDING; - furi_hal_serial_tx(fhttp.serial_handle, (const uint8_t *)send_buffer, send_length); - - // Uncomment below line to log the data sent over UART - // FURI_LOG_I("FlipperHTTP", "Sent data over UART: %s", send_buffer); - fhttp.state = IDLE; - return true; -} - -// Function to send a PING request -/** - * @brief Send a PING request to check if the Wifi Dev Board is connected. - * @return true if the request was successful, false otherwise. - * @note The received data will be handled asynchronously via the callback. - * @note This is best used to check if the Wifi Dev Board is connected. - * @note The state will remain INACTIVE until a PONG is received. - */ -bool flipper_http_ping() -{ - const char *command = "[PING]"; - if (!flipper_http_send_data(command)) - { - FURI_LOG_E("FlipperHTTP", "Failed to send PING command."); - return false; - } - // set state as INACTIVE to be made IDLE if PONG is received - fhttp.state = INACTIVE; - // The response will be handled asynchronously via the callback - return true; -} - -// Function to scan for WiFi networks -/** - * @brief Send a command to scan for WiFi networks. - * @return true if the request was successful, false otherwise. - * @note The received data will be handled asynchronously via the callback. - */ -bool flipper_http_scan_wifi() -{ - const char *command = "[WIFI/SCAN]"; - if (!flipper_http_send_data(command)) - { - FURI_LOG_E("FlipperHTTP", "Failed to send WiFi scan command."); - return false; - } - - // The response will be handled asynchronously via the callback - return true; -} - -// Function to save WiFi settings (returns true if successful) -/** - * @brief Send a command to save WiFi settings. - * @return true if the request was successful, false otherwise. - * @note The received data will be handled asynchronously via the callback. - */ -bool flipper_http_save_wifi(const char *ssid, const char *password) -{ - if (!ssid || !password) - { - FURI_LOG_E("FlipperHTTP", "Invalid arguments provided to flipper_http_save_wifi."); - return false; - } - char buffer[256]; - int ret = snprintf(buffer, sizeof(buffer), "[WIFI/SAVE]{\"ssid\":\"%s\",\"password\":\"%s\"}", ssid, password); - if (ret < 0 || ret >= (int)sizeof(buffer)) - { - FURI_LOG_E("FlipperHTTP", "Failed to format WiFi save command."); - return false; - } - - if (!flipper_http_send_data(buffer)) - { - FURI_LOG_E("FlipperHTTP", "Failed to send WiFi save command."); - return false; - } - - // The response will be handled asynchronously via the callback - return true; -} - -// Function to disconnect from WiFi (returns true if successful) -/** - * @brief Send a command to disconnect from WiFi. - * @return true if the request was successful, false otherwise. - * @note The received data will be handled asynchronously via the callback. - */ -bool flipper_http_disconnect_wifi() -{ - const char *command = "[WIFI/DISCONNECT]"; - if (!flipper_http_send_data(command)) - { - FURI_LOG_E("FlipperHTTP", "Failed to send WiFi disconnect command."); - return false; - } - - // The response will be handled asynchronously via the callback - return true; -} - -// Function to connect to WiFi (returns true if successful) -/** - * @brief Send a command to connect to WiFi. - * @return true if the request was successful, false otherwise. - * @note The received data will be handled asynchronously via the callback. - */ -bool flipper_http_connect_wifi() -{ - const char *command = "[WIFI/CONNECT]"; - if (!flipper_http_send_data(command)) - { - FURI_LOG_E("FlipperHTTP", "Failed to send WiFi connect command."); - return false; - } - - // The response will be handled asynchronously via the callback - return true; -} - -// Function to send a GET request -/** - * @brief Send a GET request to the specified URL. - * @return true if the request was successful, false otherwise. - * @param url The URL to send the GET request to. - * @note The received data will be handled asynchronously via the callback. - */ -bool flipper_http_get_request(const char *url) -{ - if (!url) - { - FURI_LOG_E("FlipperHTTP", "Invalid arguments provided to flipper_http_get_request."); - return false; - } - - // Prepare GET request command - char command[256]; - int ret = snprintf(command, sizeof(command), "[GET]%s", url); - if (ret < 0 || ret >= (int)sizeof(command)) - { - FURI_LOG_E("FlipperHTTP", "Failed to format GET request command."); - return false; - } - - // Send GET request via UART - if (!flipper_http_send_data(command)) - { - FURI_LOG_E("FlipperHTTP", "Failed to send GET request command."); - return false; - } - - // The response will be handled asynchronously via the callback - return true; -} -// Function to send a GET request with headers -/** - * @brief Send a GET request to the specified URL. - * @return true if the request was successful, false otherwise. - * @param url The URL to send the GET request to. - * @param headers The headers to send with the GET request. - * @note The received data will be handled asynchronously via the callback. - */ -bool flipper_http_get_request_with_headers(const char *url, const char *headers) -{ - if (!url || !headers) - { - FURI_LOG_E("FlipperHTTP", "Invalid arguments provided to flipper_http_get_request_with_headers."); - return false; - } - - // Prepare GET request command with headers - char command[256]; - int ret = snprintf(command, sizeof(command), "[GET/HTTP]{\"url\":\"%s\",\"headers\":%s}", url, headers); - if (ret < 0 || ret >= (int)sizeof(command)) - { - FURI_LOG_E("FlipperHTTP", "Failed to format GET request command with headers."); - return false; - } - - // Send GET request via UART - if (!flipper_http_send_data(command)) - { - FURI_LOG_E("FlipperHTTP", "Failed to send GET request command with headers."); - return false; - } - - // The response will be handled asynchronously via the callback - return true; -} -// Function to send a POST request with headers -/** - * @brief Send a POST request to the specified URL. - * @return true if the request was successful, false otherwise. - * @param url The URL to send the POST request to. - * @param headers The headers to send with the POST request. - * @param data The data to send with the POST request. - * @note The received data will be handled asynchronously via the callback. - */ -bool flipper_http_post_request_with_headers(const char *url, const char *headers, const char *payload) -{ - if (!url || !headers || !payload) - { - FURI_LOG_E("FlipperHTTP", "Invalid arguments provided to flipper_http_post_request_with_headers."); - return false; - } - - // Prepare POST request command with headers and data - char command[256]; - int ret = snprintf(command, sizeof(command), "[POST/HTTP]{\"url\":\"%s\",\"headers\":%s,\"payload\":%s}", url, headers, payload); - if (ret < 0 || ret >= (int)sizeof(command)) - { - FURI_LOG_E("FlipperHTTP", "Failed to format POST request command with headers and data."); - return false; - } - - // Send POST request via UART - if (!flipper_http_send_data(command)) - { - FURI_LOG_E("FlipperHTTP", "Failed to send POST request command with headers and data."); - return false; - } - - // The response will be handled asynchronously via the callback - return true; -} -// Function to send a PUT request with headers -/** - * @brief Send a PUT request to the specified URL. - * @return true if the request was successful, false otherwise. - * @param url The URL to send the PUT request to. - * @param headers The headers to send with the PUT request. - * @param data The data to send with the PUT request. - * @note The received data will be handled asynchronously via the callback. - */ -bool flipper_http_put_request_with_headers(const char *url, const char *headers, const char *payload) -{ - if (!url || !headers || !payload) - { - FURI_LOG_E("FlipperHTTP", "Invalid arguments provided to flipper_http_put_request_with_headers."); - return false; - } - - // Prepare PUT request command with headers and data - char command[256]; - int ret = snprintf(command, sizeof(command), "[PUT/HTTP]{\"url\":\"%s\",\"headers\":%s,\"payload\":%s}", url, headers, payload); - if (ret < 0 || ret >= (int)sizeof(command)) - { - FURI_LOG_E("FlipperHTTP", "Failed to format PUT request command with headers and data."); - return false; - } - - // Send PUT request via UART - if (!flipper_http_send_data(command)) - { - FURI_LOG_E("FlipperHTTP", "Failed to send PUT request command with headers and data."); - return false; - } - - // The response will be handled asynchronously via the callback - return true; -} -// Function to send a DELETE request with headers -/** - * @brief Send a DELETE request to the specified URL. - * @return true if the request was successful, false otherwise. - * @param url The URL to send the DELETE request to. - * @param headers The headers to send with the DELETE request. - * @param data The data to send with the DELETE request. - * @note The received data will be handled asynchronously via the callback. - */ -bool flipper_http_delete_request_with_headers(const char *url, const char *headers, const char *payload) -{ - if (!url || !headers || !payload) - { - FURI_LOG_E("FlipperHTTP", "Invalid arguments provided to flipper_http_delete_request_with_headers."); - return false; - } - - // Prepare DELETE request command with headers and data - char command[256]; - int ret = snprintf(command, sizeof(command), "[DELETE/HTTP]{\"url\":\"%s\",\"headers\":%s,\"payload\":%s}", url, headers, payload); - if (ret < 0 || ret >= (int)sizeof(command)) - { - FURI_LOG_E("FlipperHTTP", "Failed to format DELETE request command with headers and data."); - return false; - } - - // Send DELETE request via UART - if (!flipper_http_send_data(command)) - { - FURI_LOG_E("FlipperHTTP", "Failed to send DELETE request command with headers and data."); - return false; - } - - // The response will be handled asynchronously via the callback - return true; -} -// Function to handle received data asynchronously -/** - * @brief Callback function to handle received data asynchronously. - * @return void - * @param line The received line. - * @param context The context passed to the callback. - * @note The received data will be handled asynchronously via the callback and handles the state of the UART. - */ -void flipper_http_rx_callback(const char *line, void *context) -{ - - if (!line || !context) - { - FURI_LOG_E(HTTP_TAG, "Invalid arguments provided to flipper_http_rx_callback."); - return; - } - - // Trim the received line to check if it's empty - char *trimmed_line = trim(line); - if (trimmed_line != NULL && trimmed_line[0] != '\0') - { - fhttp.last_response = (char *)line; - } - free(trimmed_line); // Free the allocated memory for trimmed_line - - if (fhttp.state != INACTIVE && fhttp.state != ISSUE) - { - fhttp.state = RECEIVING; - } - - // Uncomment below line to log the data received over UART - FURI_LOG_I(HTTP_TAG, "Received UART line: %s", line); - - // Check if we've started receiving data from a GET request - if (fhttp.started_receiving_get) - { - // Restart the timeout timer each time new data is received - furi_timer_restart(fhttp.get_timeout_timer, TIMEOUT_DURATION_TICKS); - - if (strstr(line, "[GET/END]") != NULL) - { - FURI_LOG_I(HTTP_TAG, "GET request completed."); - // Stop the timer since we've completed the GET request - furi_timer_stop(fhttp.get_timeout_timer); - - if (fhttp.received_data) - { - // uncomment if you want to save the received data to the external storage - // flipper_http_save_received_data(strlen(fhttp.received_data), fhttp.received_data); - fhttp.started_receiving_get = false; - fhttp.just_started_get = false; - fhttp.state = IDLE; - return; - } - else - { - FURI_LOG_E(HTTP_TAG, "No data received."); - fhttp.started_receiving_get = false; - fhttp.just_started_get = false; - fhttp.state = IDLE; - return; - } - } - - // Append the new line to the existing data - if (fhttp.received_data == NULL) - { - fhttp.received_data = (char *)malloc(strlen(line) + 2); // +2 for newline and null terminator - if (fhttp.received_data) - { - strcpy(fhttp.received_data, line); - fhttp.received_data[strlen(line)] = '\n'; // Add newline - fhttp.received_data[strlen(line) + 1] = '\0'; // Null terminator - } - } - else - { - size_t current_len = strlen(fhttp.received_data); - size_t new_size = current_len + strlen(line) + 2; // +2 for newline and null terminator - fhttp.received_data = (char *)realloc(fhttp.received_data, new_size); - if (fhttp.received_data) - { - memcpy(fhttp.received_data + current_len, line, strlen(line)); // Copy line at the end of the current data - fhttp.received_data[current_len + strlen(line)] = '\n'; // Add newline - fhttp.received_data[current_len + strlen(line) + 1] = '\0'; // Null terminator - } - } - - if (!fhttp.just_started_get) - { - fhttp.just_started_get = true; - } - return; - } - - // Check if we've started receiving data from a POST request - else if (fhttp.started_receiving_post) - { - // Restart the timeout timer each time new data is received - furi_timer_restart(fhttp.get_timeout_timer, TIMEOUT_DURATION_TICKS); - - if (strstr(line, "[POST/END]") != NULL) - { - FURI_LOG_I(HTTP_TAG, "POST request completed."); - // Stop the timer since we've completed the POST request - furi_timer_stop(fhttp.get_timeout_timer); - - if (fhttp.received_data) - { - // uncomment if you want to save the received data to the external storage - // flipper_http_save_received_data(strlen(fhttp.received_data), fhttp.received_data); - fhttp.started_receiving_post = false; - fhttp.just_started_post = false; - fhttp.state = IDLE; - return; - } - else - { - FURI_LOG_E(HTTP_TAG, "No data received."); - fhttp.started_receiving_post = false; - fhttp.just_started_post = false; - fhttp.state = IDLE; - return; - } - } - - // Append the new line to the existing data - if (fhttp.received_data == NULL) - { - fhttp.received_data = (char *)malloc(strlen(line) + 2); // +2 for newline and null terminator - if (fhttp.received_data) - { - strcpy(fhttp.received_data, line); - fhttp.received_data[strlen(line)] = '\n'; // Add newline - fhttp.received_data[strlen(line) + 1] = '\0'; // Null terminator - } - } - else - { - size_t current_len = strlen(fhttp.received_data); - size_t new_size = current_len + strlen(line) + 2; // +2 for newline and null terminator - fhttp.received_data = (char *)realloc(fhttp.received_data, new_size); - if (fhttp.received_data) - { - memcpy(fhttp.received_data + current_len, line, strlen(line)); // Copy line at the end of the current data - fhttp.received_data[current_len + strlen(line)] = '\n'; // Add newline - fhttp.received_data[current_len + strlen(line) + 1] = '\0'; // Null terminator - } - } - - if (!fhttp.just_started_post) - { - fhttp.just_started_post = true; - } - return; - } - - // Check if we've started receiving data from a PUT request - else if (fhttp.started_receiving_put) - { - // Restart the timeout timer each time new data is received - furi_timer_restart(fhttp.get_timeout_timer, TIMEOUT_DURATION_TICKS); - - if (strstr(line, "[PUT/END]") != NULL) - { - FURI_LOG_I(HTTP_TAG, "PUT request completed."); - // Stop the timer since we've completed the PUT request - furi_timer_stop(fhttp.get_timeout_timer); - - if (fhttp.received_data) - { - // uncomment if you want to save the received data to the external storage - // flipper_http_save_received_data(strlen(fhttp.received_data), fhttp.received_data); - fhttp.started_receiving_put = false; - fhttp.just_started_put = false; - fhttp.state = IDLE; - return; - } - else - { - FURI_LOG_E(HTTP_TAG, "No data received."); - fhttp.started_receiving_put = false; - fhttp.just_started_put = false; - fhttp.state = IDLE; - return; - } - } - - // Append the new line to the existing data - if (fhttp.received_data == NULL) - { - fhttp.received_data = (char *)malloc(strlen(line) + 2); // +2 for newline and null terminator - if (fhttp.received_data) - { - strcpy(fhttp.received_data, line); - fhttp.received_data[strlen(line)] = '\n'; // Add newline - fhttp.received_data[strlen(line) + 1] = '\0'; // Null terminator - } - } - else - { - size_t current_len = strlen(fhttp.received_data); - size_t new_size = current_len + strlen(line) + 2; // +2 for newline and null terminator - fhttp.received_data = (char *)realloc(fhttp.received_data, new_size); - if (fhttp.received_data) - { - memcpy(fhttp.received_data + current_len, line, strlen(line)); // Copy line at the end of the current data - fhttp.received_data[current_len + strlen(line)] = '\n'; // Add newline - fhttp.received_data[current_len + strlen(line) + 1] = '\0'; // Null terminator - } - } - - if (!fhttp.just_started_put) - { - fhttp.just_started_put = true; - } - return; - } - - // Check if we've started receiving data from a DELETE request - else if (fhttp.started_receiving_delete) - { - // Restart the timeout timer each time new data is received - furi_timer_restart(fhttp.get_timeout_timer, TIMEOUT_DURATION_TICKS); - - if (strstr(line, "[DELETE/END]") != NULL) - { - FURI_LOG_I(HTTP_TAG, "DELETE request completed."); - // Stop the timer since we've completed the DELETE request - furi_timer_stop(fhttp.get_timeout_timer); - - if (fhttp.received_data) - { - // uncomment if you want to save the received data to the external storage - // flipper_http_save_received_data(strlen(fhttp.received_data), fhttp.received_data); - fhttp.started_receiving_delete = false; - fhttp.just_started_delete = false; - fhttp.state = IDLE; - return; - } - else - { - FURI_LOG_E(HTTP_TAG, "No data received."); - fhttp.started_receiving_delete = false; - fhttp.just_started_delete = false; - fhttp.state = IDLE; - return; - } - } - - // Append the new line to the existing data - if (fhttp.received_data == NULL) - { - fhttp.received_data = (char *)malloc(strlen(line) + 2); // +2 for newline and null terminator - if (fhttp.received_data) - { - strcpy(fhttp.received_data, line); - fhttp.received_data[strlen(line)] = '\n'; // Add newline - fhttp.received_data[strlen(line) + 1] = '\0'; // Null terminator - } - } - else - { - size_t current_len = strlen(fhttp.received_data); - size_t new_size = current_len + strlen(line) + 2; // +2 for newline and null terminator - fhttp.received_data = (char *)realloc(fhttp.received_data, new_size); - if (fhttp.received_data) - { - memcpy(fhttp.received_data + current_len, line, strlen(line)); // Copy line at the end of the current data - fhttp.received_data[current_len + strlen(line)] = '\n'; // Add newline - fhttp.received_data[current_len + strlen(line) + 1] = '\0'; // Null terminator - } - } - - if (!fhttp.just_started_delete) - { - fhttp.just_started_delete = true; - } - return; - } - - // Handle different types of responses - if (strstr(line, "[SUCCESS]") != NULL || strstr(line, "[CONNECTED]") != NULL) - { - // FURI_LOG_I(HTTP_TAG, "Operation succeeded."); - } - else if (strstr(line, "[INFO]") != NULL) - { - FURI_LOG_I(HTTP_TAG, "Received info: %s", line); - - if (fhttp.state == INACTIVE && strstr(line, "[INFO] Already connected to Wifi.") != NULL) - { - fhttp.state = IDLE; - } - } - else if (strstr(line, "[GET/SUCCESS]") != NULL) - { - FURI_LOG_I(HTTP_TAG, "GET request succeeded."); - fhttp.started_receiving_get = true; - furi_timer_start(fhttp.get_timeout_timer, TIMEOUT_DURATION_TICKS); - fhttp.state = RECEIVING; - fhttp.received_data = NULL; - return; - } - else if (strstr(line, "[POST/SUCCESS]") != NULL) - { - FURI_LOG_I(HTTP_TAG, "POST request succeeded."); - fhttp.started_receiving_post = true; - furi_timer_start(fhttp.get_timeout_timer, TIMEOUT_DURATION_TICKS); - fhttp.state = RECEIVING; - fhttp.received_data = NULL; - return; - } - else if (strstr(line, "[PUT/SUCCESS]") != NULL) - { - FURI_LOG_I(HTTP_TAG, "PUT request succeeded."); - fhttp.started_receiving_put = true; - furi_timer_start(fhttp.get_timeout_timer, TIMEOUT_DURATION_TICKS); - fhttp.state = RECEIVING; - fhttp.received_data = NULL; - return; - } - else if (strstr(line, "[DELETE/SUCCESS]") != NULL) - { - FURI_LOG_I(HTTP_TAG, "DELETE request succeeded."); - fhttp.started_receiving_delete = true; - furi_timer_start(fhttp.get_timeout_timer, TIMEOUT_DURATION_TICKS); - fhttp.state = RECEIVING; - fhttp.received_data = NULL; - return; - } - else if (strstr(line, "[DISCONNECTED]") != NULL) - { - FURI_LOG_I(HTTP_TAG, "WiFi disconnected successfully."); - } - else if (strstr(line, "[ERROR]") != NULL) - { - FURI_LOG_E(HTTP_TAG, "Received error: %s", line); - fhttp.state = ISSUE; - return; - } - else if (strstr(line, "[PONG]") != NULL) - { - FURI_LOG_I(HTTP_TAG, "Received PONG response: Wifi Dev Board is still alive."); - - // send command to connect to WiFi - if (fhttp.state == INACTIVE) - { - fhttp.state = IDLE; - return; - } - } - - if (fhttp.state == INACTIVE && strstr(line, "[PONG]") != NULL) - { - fhttp.state = IDLE; - } - else if (fhttp.state == INACTIVE && strstr(line, "[PONG]") == NULL) - { - fhttp.state = INACTIVE; - } - else - { - fhttp.state = IDLE; - } -} -// Function to save received data to a file -/** - * @brief Save the received data to a file. - * @return true if the data was saved successfully, false otherwise. - * @param bytes_received The number of bytes received. - * @param line_buffer The buffer containing the received data. - * @note The data will be saved to a file in the STORAGE_EXT_PATH_PREFIX "/apps_data/" http_tag "/received_data.txt" directory. - */ -bool flipper_http_save_received_data(size_t bytes_received, const char line_buffer[]) -{ - const char *output_file_path = STORAGE_EXT_PATH_PREFIX "/apps_data/" http_tag "/received_data.txt"; - - // Ensure the directory exists - char directory_path[128]; - snprintf(directory_path, sizeof(directory_path), STORAGE_EXT_PATH_PREFIX "/apps_data/" http_tag); - - Storage *_storage = NULL; - File *_file = NULL; - // Open the storage if not opened already - // Initialize storage and create the directory if it doesn't exist - _storage = furi_record_open(RECORD_STORAGE); - storage_common_mkdir(_storage, directory_path); // Create directory if it doesn't exist - _file = storage_file_alloc(_storage); - - // Open file for writing and append data line by line - if (!storage_file_open(_file, output_file_path, FSAM_WRITE, FSOM_CREATE_ALWAYS)) - { - FURI_LOG_E(HTTP_TAG, "Failed to open output file for writing."); - storage_file_free(_file); - furi_record_close(RECORD_STORAGE); - return false; - } - - // Write each line received from the UART to the file - if (bytes_received > 0 && _file) - { - storage_file_write(_file, line_buffer, bytes_received); - storage_file_write(_file, "\n", 1); // Add a newline after each line - } - else - { - FURI_LOG_E(HTTP_TAG, "No data received."); - return false; - } - - if (_file) - { - storage_file_close(_file); - storage_file_free(_file); - _file = NULL; - } - if (_storage) - { - furi_record_close(RECORD_STORAGE); - _storage = NULL; - } - - return true; -} -// Function to trim leading and trailing spaces and newlines from a constant string -char *trim(const char *str) -{ - const char *end; - char *trimmed_str; - size_t len; - - // Trim leading space - while (isspace((unsigned char)*str)) - str++; - - // All spaces? - if (*str == 0) - return strdup(""); // Return an empty string if all spaces - - // Trim trailing space - end = str + strlen(str) - 1; - while (end > str && isspace((unsigned char)*end)) - end--; - - // Set length for the trimmed string - len = end - str + 1; - - // Allocate space for the trimmed string and null terminator - trimmed_str = (char *)malloc(len + 1); - if (trimmed_str == NULL) - { - return NULL; // Handle memory allocation failure - } - - // Copy the trimmed part of the string into trimmed_str - strncpy(trimmed_str, str, len); - trimmed_str[len] = '\0'; // Null terminate the string - - return trimmed_str; -} - -/** - * @brief Process requests and parse JSON data asynchronously - * @param http_request The function to send the request - * @param parse_json The function to parse the JSON - * @return true if successful, false otherwise - */ -bool flipper_http_process_response_async( - bool (*http_request)(void), - bool (*parse_json)(void)) -{ - if (http_request()) // start the async request - { - furi_timer_start(fhttp.get_timeout_timer, TIMEOUT_DURATION_TICKS); - fhttp.state = RECEIVING; - } - else - { - FURI_LOG_E(HTTP_TAG, "Failed to send request"); - return false; - } - while (fhttp.state == RECEIVING && furi_timer_is_running(fhttp.get_timeout_timer) > 0) - { - // Wait for the request to be received - furi_delay_ms(100); - } - furi_timer_stop(fhttp.get_timeout_timer); - if (!parse_json()) // parse the JSON before switching to the view (synchonous) - { - FURI_LOG_E(HTTP_TAG, "Failed to parse the JSON..."); - return false; - } - furi_timer_stop(fhttp.get_timeout_timer); - return true; -} - -#endif // FLIPPER_HTTP_H diff --git a/jsmn.h b/jsmn.h deleted file mode 100644 index fac2e230d..000000000 --- a/jsmn.h +++ /dev/null @@ -1,865 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2010 Serge Zaitsev - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ -#ifndef JSMN_H -#define JSMN_H - -#include - -#ifdef __cplusplus -extern "C" -{ -#endif - -#ifdef JSMN_STATIC -#define JSMN_API static -#else -#define JSMN_API extern -#endif - - /** - * JSON type identifier. Basic types are: - * o Object - * o Array - * o String - * o Other primitive: number, boolean (true/false) or null - */ - typedef enum - { - JSMN_UNDEFINED = 0, - JSMN_OBJECT = 1 << 0, - JSMN_ARRAY = 1 << 1, - JSMN_STRING = 1 << 2, - JSMN_PRIMITIVE = 1 << 3 - } jsmntype_t; - - enum jsmnerr - { - /* Not enough tokens were provided */ - JSMN_ERROR_NOMEM = -1, - /* Invalid character inside JSON string */ - JSMN_ERROR_INVAL = -2, - /* The string is not a full JSON packet, more bytes expected */ - JSMN_ERROR_PART = -3 - }; - - /** - * JSON token description. - * type type (object, array, string etc.) - * start start position in JSON data string - * end end position in JSON data string - */ - typedef struct jsmntok - { - jsmntype_t type; - int start; - int end; - int size; -#ifdef JSMN_PARENT_LINKS - int parent; -#endif - } jsmntok_t; - - /** - * JSON parser. Contains an array of token blocks available. Also stores - * the string being parsed now and current position in that string. - */ - typedef struct jsmn_parser - { - unsigned int pos; /* offset in the JSON string */ - unsigned int toknext; /* next token to allocate */ - int toksuper; /* superior token node, e.g. parent object or array */ - } jsmn_parser; - - /** - * Create JSON parser over an array of tokens - */ - JSMN_API void jsmn_init(jsmn_parser *parser); - - /** - * Run JSON parser. It parses a JSON data string into and array of tokens, each - * describing - * a single JSON object. - */ - JSMN_API int jsmn_parse(jsmn_parser *parser, const char *js, const size_t len, - jsmntok_t *tokens, const unsigned int num_tokens); - -#ifndef JSMN_HEADER - /** - * Allocates a fresh unused token from the token pool. - */ - static jsmntok_t *jsmn_alloc_token(jsmn_parser *parser, jsmntok_t *tokens, - const size_t num_tokens) - { - jsmntok_t *tok; - if (parser->toknext >= num_tokens) - { - return NULL; - } - tok = &tokens[parser->toknext++]; - tok->start = tok->end = -1; - tok->size = 0; -#ifdef JSMN_PARENT_LINKS - tok->parent = -1; -#endif - return tok; - } - - /** - * Fills token type and boundaries. - */ - static void jsmn_fill_token(jsmntok_t *token, const jsmntype_t type, - const int start, const int end) - { - token->type = type; - token->start = start; - token->end = end; - token->size = 0; - } - - /** - * Fills next available token with JSON primitive. - */ - static int jsmn_parse_primitive(jsmn_parser *parser, const char *js, - const size_t len, jsmntok_t *tokens, - const size_t num_tokens) - { - jsmntok_t *token; - int start; - - start = parser->pos; - - for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) - { - switch (js[parser->pos]) - { -#ifndef JSMN_STRICT - /* In strict mode primitive must be followed by "," or "}" or "]" */ - case ':': -#endif - case '\t': - case '\r': - case '\n': - case ' ': - case ',': - case ']': - case '}': - goto found; - default: - /* to quiet a warning from gcc*/ - break; - } - if (js[parser->pos] < 32 || js[parser->pos] >= 127) - { - parser->pos = start; - return JSMN_ERROR_INVAL; - } - } -#ifdef JSMN_STRICT - /* In strict mode primitive must be followed by a comma/object/array */ - parser->pos = start; - return JSMN_ERROR_PART; -#endif - - found: - if (tokens == NULL) - { - parser->pos--; - return 0; - } - token = jsmn_alloc_token(parser, tokens, num_tokens); - if (token == NULL) - { - parser->pos = start; - return JSMN_ERROR_NOMEM; - } - jsmn_fill_token(token, JSMN_PRIMITIVE, start, parser->pos); -#ifdef JSMN_PARENT_LINKS - token->parent = parser->toksuper; -#endif - parser->pos--; - return 0; - } - - /** - * Fills next token with JSON string. - */ - static int jsmn_parse_string(jsmn_parser *parser, const char *js, - const size_t len, jsmntok_t *tokens, - const size_t num_tokens) - { - jsmntok_t *token; - - int start = parser->pos; - - /* Skip starting quote */ - parser->pos++; - - for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) - { - char c = js[parser->pos]; - - /* Quote: end of string */ - if (c == '\"') - { - if (tokens == NULL) - { - return 0; - } - token = jsmn_alloc_token(parser, tokens, num_tokens); - if (token == NULL) - { - parser->pos = start; - return JSMN_ERROR_NOMEM; - } - jsmn_fill_token(token, JSMN_STRING, start + 1, parser->pos); -#ifdef JSMN_PARENT_LINKS - token->parent = parser->toksuper; -#endif - return 0; - } - - /* Backslash: Quoted symbol expected */ - if (c == '\\' && parser->pos + 1 < len) - { - int i; - parser->pos++; - switch (js[parser->pos]) - { - /* Allowed escaped symbols */ - case '\"': - case '/': - case '\\': - case 'b': - case 'f': - case 'r': - case 'n': - case 't': - break; - /* Allows escaped symbol \uXXXX */ - case 'u': - parser->pos++; - for (i = 0; i < 4 && parser->pos < len && js[parser->pos] != '\0'; - i++) - { - /* If it isn't a hex character we have an error */ - if (!((js[parser->pos] >= 48 && js[parser->pos] <= 57) || /* 0-9 */ - (js[parser->pos] >= 65 && js[parser->pos] <= 70) || /* A-F */ - (js[parser->pos] >= 97 && js[parser->pos] <= 102))) - { /* a-f */ - parser->pos = start; - return JSMN_ERROR_INVAL; - } - parser->pos++; - } - parser->pos--; - break; - /* Unexpected symbol */ - default: - parser->pos = start; - return JSMN_ERROR_INVAL; - } - } - } - parser->pos = start; - return JSMN_ERROR_PART; - } - - /** - * Parse JSON string and fill tokens. - */ - JSMN_API int jsmn_parse(jsmn_parser *parser, const char *js, const size_t len, - jsmntok_t *tokens, const unsigned int num_tokens) - { - int r; - int i; - jsmntok_t *token; - int count = parser->toknext; - - for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) - { - char c; - jsmntype_t type; - - c = js[parser->pos]; - switch (c) - { - case '{': - case '[': - count++; - if (tokens == NULL) - { - break; - } - token = jsmn_alloc_token(parser, tokens, num_tokens); - if (token == NULL) - { - return JSMN_ERROR_NOMEM; - } - if (parser->toksuper != -1) - { - jsmntok_t *t = &tokens[parser->toksuper]; -#ifdef JSMN_STRICT - /* In strict mode an object or array can't become a key */ - if (t->type == JSMN_OBJECT) - { - return JSMN_ERROR_INVAL; - } -#endif - t->size++; -#ifdef JSMN_PARENT_LINKS - token->parent = parser->toksuper; -#endif - } - token->type = (c == '{' ? JSMN_OBJECT : JSMN_ARRAY); - token->start = parser->pos; - parser->toksuper = parser->toknext - 1; - break; - case '}': - case ']': - if (tokens == NULL) - { - break; - } - type = (c == '}' ? JSMN_OBJECT : JSMN_ARRAY); -#ifdef JSMN_PARENT_LINKS - if (parser->toknext < 1) - { - return JSMN_ERROR_INVAL; - } - token = &tokens[parser->toknext - 1]; - for (;;) - { - if (token->start != -1 && token->end == -1) - { - if (token->type != type) - { - return JSMN_ERROR_INVAL; - } - token->end = parser->pos + 1; - parser->toksuper = token->parent; - break; - } - if (token->parent == -1) - { - if (token->type != type || parser->toksuper == -1) - { - return JSMN_ERROR_INVAL; - } - break; - } - token = &tokens[token->parent]; - } -#else - for (i = parser->toknext - 1; i >= 0; i--) - { - token = &tokens[i]; - if (token->start != -1 && token->end == -1) - { - if (token->type != type) - { - return JSMN_ERROR_INVAL; - } - parser->toksuper = -1; - token->end = parser->pos + 1; - break; - } - } - /* Error if unmatched closing bracket */ - if (i == -1) - { - return JSMN_ERROR_INVAL; - } - for (; i >= 0; i--) - { - token = &tokens[i]; - if (token->start != -1 && token->end == -1) - { - parser->toksuper = i; - break; - } - } -#endif - break; - case '\"': - r = jsmn_parse_string(parser, js, len, tokens, num_tokens); - if (r < 0) - { - return r; - } - count++; - if (parser->toksuper != -1 && tokens != NULL) - { - tokens[parser->toksuper].size++; - } - break; - case '\t': - case '\r': - case '\n': - case ' ': - break; - case ':': - parser->toksuper = parser->toknext - 1; - break; - case ',': - if (tokens != NULL && parser->toksuper != -1 && - tokens[parser->toksuper].type != JSMN_ARRAY && - tokens[parser->toksuper].type != JSMN_OBJECT) - { -#ifdef JSMN_PARENT_LINKS - parser->toksuper = tokens[parser->toksuper].parent; -#else - for (i = parser->toknext - 1; i >= 0; i--) - { - if (tokens[i].type == JSMN_ARRAY || tokens[i].type == JSMN_OBJECT) - { - if (tokens[i].start != -1 && tokens[i].end == -1) - { - parser->toksuper = i; - break; - } - } - } -#endif - } - break; -#ifdef JSMN_STRICT - /* In strict mode primitives are: numbers and booleans */ - case '-': - case '0': - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - case '8': - case '9': - case 't': - case 'f': - case 'n': - /* And they must not be keys of the object */ - if (tokens != NULL && parser->toksuper != -1) - { - const jsmntok_t *t = &tokens[parser->toksuper]; - if (t->type == JSMN_OBJECT || - (t->type == JSMN_STRING && t->size != 0)) - { - return JSMN_ERROR_INVAL; - } - } -#else - /* In non-strict mode every unquoted value is a primitive */ - default: -#endif - r = jsmn_parse_primitive(parser, js, len, tokens, num_tokens); - if (r < 0) - { - return r; - } - count++; - if (parser->toksuper != -1 && tokens != NULL) - { - tokens[parser->toksuper].size++; - } - break; - -#ifdef JSMN_STRICT - /* Unexpected char in strict mode */ - default: - return JSMN_ERROR_INVAL; -#endif - } - } - - if (tokens != NULL) - { - for (i = parser->toknext - 1; i >= 0; i--) - { - /* Unmatched opened object or array */ - if (tokens[i].start != -1 && tokens[i].end == -1) - { - return JSMN_ERROR_PART; - } - } - } - - return count; - } - - /** - * Creates a new parser based over a given buffer with an array of tokens - * available. - */ - JSMN_API void jsmn_init(jsmn_parser *parser) - { - parser->pos = 0; - parser->toknext = 0; - parser->toksuper = -1; - } - -#endif /* JSMN_HEADER */ - -#ifdef __cplusplus -} -#endif - -#endif /* JSMN_H */ - -#ifndef JB_JSMN_EDIT -#define JB_JSMN_EDIT -/* Added in by JBlanked on 2024-10-16 for use in Flipper Zero SDK*/ - -#include -#include -#include -#include -#include -#include -#include - -// Helper function to create a JSON object -char *jsmn(const char *key, const char *value) -{ - int length = strlen(key) + strlen(value) + 8; // Calculate required length - char *result = (char *)malloc(length * sizeof(char)); // Allocate memory - if (result == NULL) - { - return NULL; // Handle memory allocation failure - } - snprintf(result, length, "{\"%s\":\"%s\"}", key, value); - return result; // Caller is responsible for freeing this memory -} - -// Helper function to compare JSON keys -int jsoneq(const char *json, jsmntok_t *tok, const char *s) -{ - if (tok->type == JSMN_STRING && (int)strlen(s) == tok->end - tok->start && - strncmp(json + tok->start, s, tok->end - tok->start) == 0) - { - return 0; - } - return -1; -} - -// return the value of the key in the JSON data -char *get_json_value(char *key, char *json_data, uint32_t max_tokens) -{ - // Parse the JSON feed - if (json_data != NULL) - { - jsmn_parser parser; - jsmn_init(&parser); - - // Allocate tokens array on the heap - jsmntok_t *tokens = malloc(sizeof(jsmntok_t) * max_tokens); - if (tokens == NULL) - { - FURI_LOG_E("JSMM.H", "Failed to allocate memory for JSON tokens."); - return NULL; - } - - int ret = jsmn_parse(&parser, json_data, strlen(json_data), tokens, max_tokens); - if (ret < 0) - { - // Handle parsing errors - FURI_LOG_E("JSMM.H", "Failed to parse JSON: %d", ret); - free(tokens); - return NULL; - } - - // Ensure that the root element is an object - if (ret < 1 || tokens[0].type != JSMN_OBJECT) - { - FURI_LOG_E("JSMM.H", "Root element is not an object."); - free(tokens); - return NULL; - } - - // Loop through the tokens to find the key - for (int i = 1; i < ret; i++) - { - if (jsoneq(json_data, &tokens[i], key) == 0) - { - // We found the key. Now, return the associated value. - int length = tokens[i + 1].end - tokens[i + 1].start; - char *value = malloc(length + 1); - if (value == NULL) - { - FURI_LOG_E("JSMM.H", "Failed to allocate memory for value."); - free(tokens); - return NULL; - } - strncpy(value, json_data + tokens[i + 1].start, length); - value[length] = '\0'; // Null-terminate the string - - free(tokens); // Free the token array - return value; // Return the extracted value - } - } - - // Free the token array if key was not found - free(tokens); - } - else - { - FURI_LOG_E("JSMM.H", "JSON data is NULL"); - } - FURI_LOG_E("JSMM.H", "Failed to find the key in the JSON."); - return NULL; // Return NULL if something goes wrong -} - -// Revised get_json_array_value function -char *get_json_array_value(char *key, uint32_t index, char *json_data, uint32_t max_tokens) -{ - // Retrieve the array string for the given key - char *array_str = get_json_value(key, json_data, max_tokens); - if (array_str == NULL) - { - FURI_LOG_E("JSMM.H", "Failed to get array for key: %s", key); - return NULL; - } - - // Initialize the JSON parser - jsmn_parser parser; - jsmn_init(&parser); - - // Allocate memory for JSON tokens - jsmntok_t *tokens = malloc(sizeof(jsmntok_t) * max_tokens); - if (tokens == NULL) - { - FURI_LOG_E("JSMM.H", "Failed to allocate memory for JSON tokens."); - free(array_str); - return NULL; - } - - // Parse the JSON array - int ret = jsmn_parse(&parser, array_str, strlen(array_str), tokens, max_tokens); - if (ret < 0) - { - FURI_LOG_E("JSMM.H", "Failed to parse JSON array: %d", ret); - free(tokens); - free(array_str); - return NULL; - } - - // Ensure the root element is an array - if (ret < 1 || tokens[0].type != JSMN_ARRAY) - { - FURI_LOG_E("JSMM.H", "Value for key '%s' is not an array.", key); - free(tokens); - free(array_str); - return NULL; - } - - // Check if the index is within bounds - if (index >= (uint32_t)tokens[0].size) - { - FURI_LOG_E("JSMM.H", "Index %lu out of bounds for array with size %d.", (unsigned long)index, tokens[0].size); - free(tokens); - free(array_str); - return NULL; - } - - // Locate the token corresponding to the desired array element - int current_token = 1; // Start after the array token - for (uint32_t i = 0; i < index; i++) - { - if (tokens[current_token].type == JSMN_OBJECT) - { - // For objects, skip all key-value pairs - current_token += 1 + 2 * tokens[current_token].size; - } - else if (tokens[current_token].type == JSMN_ARRAY) - { - // For nested arrays, skip all elements - current_token += 1 + tokens[current_token].size; - } - else - { - // For primitive types, simply move to the next token - current_token += 1; - } - - // Safety check to prevent out-of-bounds - if (current_token >= ret) - { - FURI_LOG_E("JSMM.H", "Unexpected end of tokens while traversing array."); - free(tokens); - free(array_str); - return NULL; - } - } - - // Extract the array element - jsmntok_t element = tokens[current_token]; - int length = element.end - element.start; - char *value = malloc(length + 1); - if (value == NULL) - { - FURI_LOG_E("JSMM.H", "Failed to allocate memory for array element."); - free(tokens); - free(array_str); - return NULL; - } - - // Copy the element value to a new string - strncpy(value, array_str + element.start, length); - value[length] = '\0'; // Null-terminate the string - - // Clean up - free(tokens); - free(array_str); - - return value; -} - -// Revised get_json_array_values function with correct token skipping -char **get_json_array_values(char *key, char *json_data, uint32_t max_tokens, int *num_values) -{ - // Retrieve the array string for the given key - char *array_str = get_json_value(key, json_data, max_tokens); - if (array_str == NULL) - { - FURI_LOG_E("JSMM.H", "Failed to get array for key: %s", key); - return NULL; - } - - // Initialize the JSON parser - jsmn_parser parser; - jsmn_init(&parser); - - // Allocate memory for JSON tokens - jsmntok_t *tokens = malloc(sizeof(jsmntok_t) * max_tokens); // Allocate on the heap - if (tokens == NULL) - { - FURI_LOG_E("JSMM.H", "Failed to allocate memory for JSON tokens."); - free(array_str); - return NULL; - } - - // Parse the JSON array - int ret = jsmn_parse(&parser, array_str, strlen(array_str), tokens, max_tokens); - if (ret < 0) - { - FURI_LOG_E("JSMM.H", "Failed to parse JSON array: %d", ret); - free(tokens); - free(array_str); - return NULL; - } - - // Ensure the root element is an array - if (tokens[0].type != JSMN_ARRAY) - { - FURI_LOG_E("JSMM.H", "Value for key '%s' is not an array.", key); - free(tokens); - free(array_str); - return NULL; - } - - // Allocate memory for the array of values (maximum possible) - int array_size = tokens[0].size; - char **values = malloc(array_size * sizeof(char *)); - if (values == NULL) - { - FURI_LOG_E("JSMM.H", "Failed to allocate memory for array of values."); - free(tokens); - free(array_str); - return NULL; - } - - int actual_num_values = 0; - - // Traverse the array and extract all object values - int current_token = 1; // Start after the array token - for (int i = 0; i < array_size; i++) - { - if (current_token >= ret) - { - FURI_LOG_E("JSMM.H", "Unexpected end of tokens while traversing array."); - break; - } - - jsmntok_t element = tokens[current_token]; - - if (element.type != JSMN_OBJECT) - { - FURI_LOG_E("JSMM.H", "Array element %d is not an object, skipping.", i); - // Skip this element - current_token += 1; - continue; - } - - int length = element.end - element.start; - - // Allocate a new string for the value and copy the data - char *value = malloc(length + 1); - if (value == NULL) - { - FURI_LOG_E("JSMM.H", "Failed to allocate memory for array element."); - for (int j = 0; j < actual_num_values; j++) - { - free(values[j]); - } - free(values); - free(tokens); - free(array_str); - return NULL; - } - - strncpy(value, array_str + element.start, length); - value[length] = '\0'; // Null-terminate the string - - values[actual_num_values] = value; - actual_num_values++; - - // Skip all tokens related to this object to avoid misparsing - current_token += 1 + (2 * element.size); // Each key-value pair consumes two tokens - } - - *num_values = actual_num_values; - - // Reallocate the values array to actual_num_values if necessary - if (actual_num_values < array_size) - { - char **reduced_values = realloc(values, actual_num_values * sizeof(char *)); - if (reduced_values != NULL) - { - values = reduced_values; - } - - // Free the remaining values - for (int i = actual_num_values; i < array_size; i++) - { - free(values[i]); - } - } - - // Clean up - free(tokens); - free(array_str); - return values; -} - -#endif /* JB_JSMN_EDIT */ \ No newline at end of file diff --git a/uart_text_input.h b/uart_text_input.h deleted file mode 100644 index d4a7a7c44..000000000 --- a/uart_text_input.h +++ /dev/null @@ -1,803 +0,0 @@ -// from https://github.com/xMasterX/all-the-plugins/blob/dev/base_pack/uart_terminal/uart_text_input.c -// all credits to xMasterX for the code -#ifndef UART_TEXT_INPUT_H -#define UART_TEXT_INPUT_H - -#include -#include "flip_social_icons.h" -#include -#include -#include -#include -#include -#include - -/** Text input anonymous structure */ -typedef struct UART_TextInput UART_TextInput; -typedef void (*UART_TextInputCallback)(void *context); -typedef bool (*UART_TextInputValidatorCallback)(const char *text, FuriString *error, void *context); - -UART_TextInputValidatorCallback -uart_text_input_get_validator_callback(UART_TextInput *uart_text_input); - -void uart_text_input_reset(UART_TextInput *uart_text_input); - -struct UART_TextInput -{ - View *view; - FuriTimer *timer; -}; - -typedef struct -{ - const char text; - const uint8_t x; - const uint8_t y; -} UART_TextInputKey; - -typedef struct -{ - const char *header; - char *text_buffer; - size_t text_buffer_size; - bool clear_default_text; - - UART_TextInputCallback callback; - void *callback_context; - - uint8_t selected_row; - uint8_t selected_column; - - UART_TextInputValidatorCallback validator_callback; - void *validator_callback_context; - FuriString *validator_text; - bool valadator_message_visible; -} UART_TextInputModel; - -static const uint8_t keyboard_origin_x = 1; -static const uint8_t keyboard_origin_y = 29; -static const uint8_t keyboard_row_count = 4; - -#define mode_AT "Send AT command to UART" - -#define ENTER_KEY '\r' -#define BACKSPACE_KEY '\b' - -static const UART_TextInputKey keyboard_keys_row_1[] = { - {'{', 1, 0}, - {'(', 9, 0}, - {'[', 17, 0}, - {'|', 25, 0}, - {'@', 33, 0}, - {'&', 41, 0}, - {'#', 49, 0}, - {';', 57, 0}, - {'^', 65, 0}, - {'*', 73, 0}, - {'`', 81, 0}, - {'"', 89, 0}, - {'~', 97, 0}, - {'\'', 105, 0}, - {'.', 113, 0}, - {'/', 120, 0}, -}; - -static const UART_TextInputKey keyboard_keys_row_2[] = { - {'q', 1, 10}, - {'w', 9, 10}, - {'e', 17, 10}, - {'r', 25, 10}, - {'t', 33, 10}, - {'y', 41, 10}, - {'u', 49, 10}, - {'i', 57, 10}, - {'o', 65, 10}, - {'p', 73, 10}, - {'0', 81, 10}, - {'1', 89, 10}, - {'2', 97, 10}, - {'3', 105, 10}, - {'=', 113, 10}, - {'-', 120, 10}, -}; - -static const UART_TextInputKey keyboard_keys_row_3[] = { - {'a', 1, 21}, - {'s', 9, 21}, - {'d', 18, 21}, - {'f', 25, 21}, - {'g', 33, 21}, - {'h', 41, 21}, - {'j', 49, 21}, - {'k', 57, 21}, - {'l', 65, 21}, - {BACKSPACE_KEY, 72, 13}, - {'4', 89, 21}, - {'5', 97, 21}, - {'6', 105, 21}, - {'$', 113, 21}, - {'%', 120, 21}, - -}; - -static const UART_TextInputKey keyboard_keys_row_4[] = { - {'z', 1, 33}, - {'x', 9, 33}, - {'c', 18, 33}, - {'v', 25, 33}, - {'b', 33, 33}, - {'n', 41, 33}, - {'m', 49, 33}, - {'_', 57, 33}, - {ENTER_KEY, 64, 24}, - {'7', 89, 33}, - {'8', 97, 33}, - {'9', 105, 33}, - {'!', 113, 33}, - {'+', 120, 33}, -}; - -static uint8_t get_row_size(uint8_t row_index) -{ - uint8_t row_size = 0; - - switch (row_index + 1) - { - case 1: - row_size = sizeof(keyboard_keys_row_1) / sizeof(UART_TextInputKey); - break; - case 2: - row_size = sizeof(keyboard_keys_row_2) / sizeof(UART_TextInputKey); - break; - case 3: - row_size = sizeof(keyboard_keys_row_3) / sizeof(UART_TextInputKey); - break; - case 4: - row_size = sizeof(keyboard_keys_row_4) / sizeof(UART_TextInputKey); - break; - } - - return row_size; -} - -static const UART_TextInputKey *get_row(uint8_t row_index) -{ - const UART_TextInputKey *row = NULL; - - switch (row_index + 1) - { - case 1: - row = keyboard_keys_row_1; - break; - case 2: - row = keyboard_keys_row_2; - break; - case 3: - row = keyboard_keys_row_3; - break; - case 4: - row = keyboard_keys_row_4; - break; - } - - return row; -} - -static char get_selected_char(UART_TextInputModel *model) -{ - return get_row(model->selected_row)[model->selected_column].text; -} - -static bool char_is_lowercase(char letter) -{ - return (letter >= 0x61 && letter <= 0x7A); -} - -static bool char_is_uppercase(char letter) -{ - return (letter >= 0x41 && letter <= 0x5A); -} - -static char char_to_lowercase(const char letter) -{ - switch (letter) - { - case ' ': - return 0x5f; - break; - case ')': - return 0x28; - break; - case '}': - return 0x7b; - break; - case ']': - return 0x5b; - break; - case '\\': - return 0x2f; - break; - case ':': - return 0x3b; - break; - case ',': - return 0x2e; - break; - case '?': - return 0x21; - break; - case '>': - return 0x3c; - break; - } - if (char_is_uppercase(letter)) - { - return (letter + 0x20); - } - else - { - return letter; - } -} - -static char char_to_uppercase(const char letter) -{ - switch (letter) - { - case '_': - return 0x20; - break; - case '(': - return 0x29; - break; - case '{': - return 0x7d; - break; - case '[': - return 0x5d; - break; - case '/': - return 0x5c; - break; - case ';': - return 0x3a; - break; - case '.': - return 0x2c; - break; - case '!': - return 0x3f; - break; - case '<': - return 0x3e; - break; - } - if (char_is_lowercase(letter)) - { - return (letter - 0x20); - } - else - { - return letter; - } -} - -static void uart_text_input_backspace_cb(UART_TextInputModel *model) -{ - uint8_t text_length = model->clear_default_text ? 1 : strlen(model->text_buffer); - if (text_length > 0) - { - model->text_buffer[text_length - 1] = 0; - } -} - -static void uart_text_input_view_draw_callback(Canvas *canvas, void *_model) -{ - UART_TextInputModel *model = _model; - // uint8_t text_length = model->text_buffer ? strlen(model->text_buffer) : 0; - uint8_t needed_string_width = canvas_width(canvas) - 8; - uint8_t start_pos = 4; - - const char *text = model->text_buffer; - - canvas_clear(canvas); - canvas_set_color(canvas, ColorBlack); - - canvas_draw_str(canvas, 2, 7, model->header); - elements_slightly_rounded_frame(canvas, 1, 8, 126, 12); - - if (canvas_string_width(canvas, text) > needed_string_width) - { - canvas_draw_str(canvas, start_pos, 17, "..."); - start_pos += 6; - needed_string_width -= 8; - } - - while (text != 0 && canvas_string_width(canvas, text) > needed_string_width) - { - text++; - } - - if (model->clear_default_text) - { - elements_slightly_rounded_box( - canvas, start_pos - 1, 14, canvas_string_width(canvas, text) + 2, 10); - canvas_set_color(canvas, ColorWhite); - } - else - { - canvas_draw_str(canvas, start_pos + canvas_string_width(canvas, text) + 1, 18, "|"); - canvas_draw_str(canvas, start_pos + canvas_string_width(canvas, text) + 2, 18, "|"); - } - canvas_draw_str(canvas, start_pos, 17, text); - - canvas_set_font(canvas, FontKeyboard); - - for (uint8_t row = 0; row <= keyboard_row_count; row++) - { - const uint8_t column_count = get_row_size(row); - const UART_TextInputKey *keys = get_row(row); - - for (size_t column = 0; column < column_count; column++) - { - if (keys[column].text == ENTER_KEY) - { - canvas_set_color(canvas, ColorBlack); - if (model->selected_row == row && model->selected_column == column) - { - canvas_draw_icon( - canvas, - keyboard_origin_x + keys[column].x, - keyboard_origin_y + keys[column].y, - &I_KeySaveSelected_24x11); - } - else - { - canvas_draw_icon( - canvas, - keyboard_origin_x + keys[column].x, - keyboard_origin_y + keys[column].y, - &I_KeySave_24x11); - } - } - else if (keys[column].text == BACKSPACE_KEY) - { - canvas_set_color(canvas, ColorBlack); - if (model->selected_row == row && model->selected_column == column) - { - canvas_draw_icon( - canvas, - keyboard_origin_x + keys[column].x, - keyboard_origin_y + keys[column].y, - &I_KeyBackspaceSelected_16x9); - } - else - { - canvas_draw_icon( - canvas, - keyboard_origin_x + keys[column].x, - keyboard_origin_y + keys[column].y, - &I_KeyBackspace_16x9); - } - } - else - { - if (model->selected_row == row && model->selected_column == column) - { - canvas_set_color(canvas, ColorBlack); - canvas_draw_box( - canvas, - keyboard_origin_x + keys[column].x - 1, - keyboard_origin_y + keys[column].y - 8, - 7, - 10); - canvas_set_color(canvas, ColorWhite); - } - else - { - canvas_set_color(canvas, ColorBlack); - } - if (0 == strcmp(model->header, mode_AT)) - { - canvas_draw_glyph( - canvas, - keyboard_origin_x + keys[column].x, - keyboard_origin_y + keys[column].y, - char_to_uppercase(keys[column].text)); - } - else - { - canvas_draw_glyph( - canvas, - keyboard_origin_x + keys[column].x, - keyboard_origin_y + keys[column].y, - keys[column].text); - } - } - } - } - if (model->valadator_message_visible) - { - canvas_set_font(canvas, FontSecondary); - canvas_set_color(canvas, ColorWhite); - canvas_draw_box(canvas, 8, 10, 110, 48); - canvas_set_color(canvas, ColorBlack); - canvas_draw_icon(canvas, 10, 14, &I_WarningDolphin_45x42); - canvas_draw_rframe(canvas, 8, 8, 112, 50, 3); - canvas_draw_rframe(canvas, 9, 9, 110, 48, 2); - elements_multiline_text(canvas, 62, 20, furi_string_get_cstr(model->validator_text)); - canvas_set_font(canvas, FontKeyboard); - } -} - -static void -uart_text_input_handle_up(UART_TextInput *uart_text_input, UART_TextInputModel *model) -{ - UNUSED(uart_text_input); - if (model->selected_row > 0) - { - model->selected_row--; - if (model->selected_column > get_row_size(model->selected_row) - 6) - { - model->selected_column = model->selected_column + 1; - } - } -} - -static void -uart_text_input_handle_down(UART_TextInput *uart_text_input, UART_TextInputModel *model) -{ - UNUSED(uart_text_input); - if (model->selected_row < keyboard_row_count - 1) - { - model->selected_row++; - if (model->selected_column > get_row_size(model->selected_row) - 4) - { - model->selected_column = model->selected_column - 1; - } - } -} - -static void -uart_text_input_handle_left(UART_TextInput *uart_text_input, UART_TextInputModel *model) -{ - UNUSED(uart_text_input); - if (model->selected_column > 0) - { - model->selected_column--; - } - else - { - model->selected_column = get_row_size(model->selected_row) - 1; - } -} - -static void -uart_text_input_handle_right(UART_TextInput *uart_text_input, UART_TextInputModel *model) -{ - UNUSED(uart_text_input); - if (model->selected_column < get_row_size(model->selected_row) - 1) - { - model->selected_column++; - } - else - { - model->selected_column = 0; - } -} - -static void uart_text_input_handle_ok( - UART_TextInput *uart_text_input, - UART_TextInputModel *model, - bool shift) -{ - char selected = get_selected_char(model); - uint8_t text_length = strlen(model->text_buffer); - - if (0 == strcmp(model->header, mode_AT)) - { - selected = char_to_uppercase(selected); - } - - if (shift) - { - if (0 == strcmp(model->header, mode_AT)) - { - selected = char_to_lowercase(selected); - } - else - { - selected = char_to_uppercase(selected); - } - } - - if (selected == ENTER_KEY) - { - if (model->validator_callback && - (!model->validator_callback( - model->text_buffer, model->validator_text, model->validator_callback_context))) - { - model->valadator_message_visible = true; - furi_timer_start(uart_text_input->timer, furi_kernel_get_tick_frequency() * 4); - } - else if (model->callback != 0 && text_length > 0) - { - model->callback(model->callback_context); - } - } - else if (selected == BACKSPACE_KEY) - { - uart_text_input_backspace_cb(model); - } - else - { - if (model->clear_default_text) - { - text_length = 0; - } - if (text_length < (model->text_buffer_size - 1)) - { - model->text_buffer[text_length] = selected; - model->text_buffer[text_length + 1] = 0; - } - } - model->clear_default_text = false; -} - -static bool uart_text_input_view_input_callback(InputEvent *event, void *context) -{ - UART_TextInput *uart_text_input = context; - furi_assert(uart_text_input); - - bool consumed = false; - - // Acquire model - UART_TextInputModel *model = view_get_model(uart_text_input->view); - - if ((!(event->type == InputTypePress) && !(event->type == InputTypeRelease)) && - model->valadator_message_visible) - { - model->valadator_message_visible = false; - consumed = true; - } - else if (event->type == InputTypeShort) - { - consumed = true; - switch (event->key) - { - case InputKeyUp: - uart_text_input_handle_up(uart_text_input, model); - break; - case InputKeyDown: - uart_text_input_handle_down(uart_text_input, model); - break; - case InputKeyLeft: - uart_text_input_handle_left(uart_text_input, model); - break; - case InputKeyRight: - uart_text_input_handle_right(uart_text_input, model); - break; - case InputKeyOk: - uart_text_input_handle_ok(uart_text_input, model, false); - break; - default: - consumed = false; - break; - } - } - else if (event->type == InputTypeLong) - { - consumed = true; - switch (event->key) - { - case InputKeyUp: - uart_text_input_handle_up(uart_text_input, model); - break; - case InputKeyDown: - uart_text_input_handle_down(uart_text_input, model); - break; - case InputKeyLeft: - uart_text_input_handle_left(uart_text_input, model); - break; - case InputKeyRight: - uart_text_input_handle_right(uart_text_input, model); - break; - case InputKeyOk: - uart_text_input_handle_ok(uart_text_input, model, true); - break; - case InputKeyBack: - uart_text_input_backspace_cb(model); - break; - default: - consumed = false; - break; - } - } - else if (event->type == InputTypeRepeat) - { - consumed = true; - switch (event->key) - { - case InputKeyUp: - uart_text_input_handle_up(uart_text_input, model); - break; - case InputKeyDown: - uart_text_input_handle_down(uart_text_input, model); - break; - case InputKeyLeft: - uart_text_input_handle_left(uart_text_input, model); - break; - case InputKeyRight: - uart_text_input_handle_right(uart_text_input, model); - break; - case InputKeyBack: - uart_text_input_backspace_cb(model); - break; - default: - consumed = false; - break; - } - } - - // Commit model - view_commit_model(uart_text_input->view, consumed); - - return consumed; -} - -void uart_text_input_timer_callback(void *context) -{ - furi_assert(context); - UART_TextInput *uart_text_input = context; - - with_view_model( - uart_text_input->view, - UART_TextInputModel * model, - { model->valadator_message_visible = false; }, - true); -} - -UART_TextInput *uart_text_input_alloc() -{ - UART_TextInput *uart_text_input = malloc(sizeof(UART_TextInput)); - uart_text_input->view = view_alloc(); - view_set_context(uart_text_input->view, uart_text_input); - view_allocate_model(uart_text_input->view, ViewModelTypeLocking, sizeof(UART_TextInputModel)); - view_set_draw_callback(uart_text_input->view, uart_text_input_view_draw_callback); - view_set_input_callback(uart_text_input->view, uart_text_input_view_input_callback); - - uart_text_input->timer = - furi_timer_alloc(uart_text_input_timer_callback, FuriTimerTypeOnce, uart_text_input); - - with_view_model( - uart_text_input->view, - UART_TextInputModel * model, - { model->validator_text = furi_string_alloc(); }, - false); - - uart_text_input_reset(uart_text_input); - - return uart_text_input; -} - -void uart_text_input_free(UART_TextInput *uart_text_input) -{ - furi_assert(uart_text_input); - with_view_model( - uart_text_input->view, - UART_TextInputModel * model, - { furi_string_free(model->validator_text); }, - false); - - // Send stop command - furi_timer_stop(uart_text_input->timer); - // Release allocated memory - furi_timer_free(uart_text_input->timer); - - view_free(uart_text_input->view); - - free(uart_text_input); -} - -void uart_text_input_reset(UART_TextInput *uart_text_input) -{ - furi_assert(uart_text_input); - with_view_model( - uart_text_input->view, - UART_TextInputModel * model, - { - model->text_buffer_size = 0; - model->header = ""; - model->selected_row = 0; - model->selected_column = 0; - model->clear_default_text = false; - model->text_buffer = NULL; - model->text_buffer_size = 0; - model->callback = NULL; - model->callback_context = NULL; - model->validator_callback = NULL; - model->validator_callback_context = NULL; - furi_string_reset(model->validator_text); - model->valadator_message_visible = false; - }, - true); -} - -View *uart_text_input_get_view(UART_TextInput *uart_text_input) -{ - furi_assert(uart_text_input); - return uart_text_input->view; -} - -void uart_text_input_set_result_callback( - UART_TextInput *uart_text_input, - UART_TextInputCallback callback, - void *callback_context, - char *text_buffer, - size_t text_buffer_size, - bool clear_default_text) -{ - with_view_model( - uart_text_input->view, - UART_TextInputModel * model, - { - model->callback = callback; - model->callback_context = callback_context; - model->text_buffer = text_buffer; - model->text_buffer_size = text_buffer_size; - model->clear_default_text = clear_default_text; - if (text_buffer && text_buffer[0] != '\0') - { - // Set focus on Save - model->selected_row = 2; - model->selected_column = 8; - } - }, - true); -} - -void uart_text_input_set_validator( - UART_TextInput *uart_text_input, - UART_TextInputValidatorCallback callback, - void *callback_context) -{ - with_view_model( - uart_text_input->view, - UART_TextInputModel * model, - { - model->validator_callback = callback; - model->validator_callback_context = callback_context; - }, - true); -} - -UART_TextInputValidatorCallback -uart_text_input_get_validator_callback(UART_TextInput *uart_text_input) -{ - UART_TextInputValidatorCallback validator_callback = NULL; - with_view_model( - uart_text_input->view, - UART_TextInputModel * model, - { validator_callback = model->validator_callback; }, - false); - return validator_callback; -} - -void *uart_text_input_get_validator_callback_context(UART_TextInput *uart_text_input) -{ - void *validator_callback_context = NULL; - with_view_model( - uart_text_input->view, - UART_TextInputModel * model, - { validator_callback_context = model->validator_callback_context; }, - false); - return validator_callback_context; -} - -void uart_text_input_set_header_text(UART_TextInput *uart_text_input, const char *text) -{ - with_view_model( - uart_text_input->view, UART_TextInputModel * model, { model->header = text; }, true); -} - -#endif // UART_TEXT_INPUT_H \ No newline at end of file From 6e8bf25d4e084297402cbbb795c381c0d64f7efe Mon Sep 17 00:00:00 2001 From: jblanked <82678820+jblanked@users.noreply.github.com> Date: Wed, 6 Nov 2024 20:54:11 -0500 Subject: [PATCH 3/3] Delete .DS_Store --- .DS_Store | Bin 6148 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 .DS_Store diff --git a/.DS_Store b/.DS_Store deleted file mode 100644 index 5015ce860ce7459c47ed32bc9fd849b7f3404ef7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeHKOG*Pl5Pcn^Mik9bTs0m*2p%9~91kGbW}*3zCtn6_5Lc}hkB$c4qBO2LbJZ!Xk$z)pc zRK;IqK%QL{Cm7-e^W5{>e7(%tCP^H3$4QsE+M@Y#{&@Rzmbdr6wNFQxcVpkZBHPS+ zj~RMM(8nN`9Y@F9z2K3pq4n`_`Zjbml)P4ztoO>*1IuLC!ZoXzVSqVi`Dzx;qCH#7 zyBQrkcU&#GJtoPjl65}0I!q*Ws3-%DHvzT8=9Y p5%EhsE