diff --git a/cli_bridge/cli_control.c b/cli_bridge/cli_control.c
index 1ff6226c4..7e529c4e5 100644
--- a/cli_bridge/cli_control.c
+++ b/cli_bridge/cli_control.c
@@ -16,7 +16,8 @@ static void tx_handler(const uint8_t* buffer, size_t size) {
furi_stream_buffer_send(cli_tx_stream, buffer, size, FuriWaitForever);
}
-static void tx_handler_stdout(const char* buffer, size_t size) {
+static void tx_handler_stdout(const char* buffer, size_t size, void* context) {
+ UNUSED(context);
tx_handler((const uint8_t*)buffer, size);
}
@@ -32,9 +33,9 @@ static size_t real_rx_handler(uint8_t* buffer, size_t size, uint32_t timeout) {
rx_cnt += len;
}
if(restore_tx_stdout) {
- furi_thread_set_stdout_callback(cli_vcp.tx_stdout);
+ furi_thread_set_stdout_callback(cli_vcp.tx_stdout, NULL);
} else {
- furi_thread_set_stdout_callback(tx_handler_stdout);
+ furi_thread_set_stdout_callback(tx_handler_stdout, NULL);
}
return rx_cnt;
}
@@ -87,7 +88,7 @@ void clicontrol_hijack(size_t tx_size, size_t rx_size) {
cli_session_close(global_cli);
restore_tx_stdout = false;
cli_session_open(global_cli, session);
- furi_thread_set_stdout_callback(prev_stdout);
+ furi_thread_set_stdout_callback(prev_stdout, NULL);
furi_record_close(RECORD_CLI);
}
@@ -134,7 +135,7 @@ void clicontrol_unhijack(bool persist) {
FuriThreadStdoutWriteCallback prev_stdout = furi_thread_get_stdout_callback();
cli_session_close(global_cli);
cli_session_open(global_cli, &cli_vcp);
- furi_thread_set_stdout_callback(prev_stdout);
+ furi_thread_set_stdout_callback(prev_stdout, NULL);
furi_record_close(RECORD_CLI);
// Unblock waiting rx handler, restore old cli_vcp.tx_stdout
diff --git a/cntdown_timer/application.fam b/cntdown_timer/application.fam
index 8968cacbf..0dc52aacf 100644
--- a/cntdown_timer/application.fam
+++ b/cntdown_timer/application.fam
@@ -13,6 +13,6 @@ App(
fap_category="Tools",
fap_author="@0w0mewo",
fap_weburl="https://github.com/0w0mewo/fpz_cntdown_timer",
- fap_version="1.4",
+ fap_version="1.5",
fap_description="Simple count down timer",
)
diff --git a/cntdown_timer/views/countdown_view.h b/cntdown_timer/views/countdown_view.h
index caa1466b3..a234a5efd 100644
--- a/cntdown_timer/views/countdown_view.h
+++ b/cntdown_timer/views/countdown_view.h
@@ -11,7 +11,7 @@
#define SCREEN_CENTER_X (SCREEN_WIDTH / 2)
#define SCREEN_CENTER_Y (SCREEN_HEIGHT / 2)
-#define INIT_COUNT 10
+#define INIT_COUNT 60
typedef enum {
CountDownTimerMinuteUp,
diff --git a/color_guess/application.fam b/color_guess/application.fam
index cd9be5635..268307072 100644
--- a/color_guess/application.fam
+++ b/color_guess/application.fam
@@ -11,7 +11,7 @@ App(
order=10,
fap_icon="icons/color_guess_10px.png",
fap_icon_assets="icons",
- fap_version="1.5",
+ fap_version="1.6",
fap_category="Games",
fap_author="Leedave",
fap_description="Color Guessing Game",
diff --git a/color_guess/changelog.md b/color_guess/changelog.md
deleted file mode 100644
index c4d4c1b0a..000000000
--- a/color_guess/changelog.md
+++ /dev/null
@@ -1,7 +0,0 @@
-## v1.1
-
-Added GFX to start screen
-
-## v1.0
-
-First release to Application Catalog
\ No newline at end of file
diff --git a/color_guess/color_guess.c b/color_guess/color_guess.c
index 9d447bf67..89cb879ae 100644
--- a/color_guess/color_guess.c
+++ b/color_guess/color_guess.c
@@ -24,7 +24,6 @@ ColorGuess* color_guess_app_alloc() {
ColorGuess* app = malloc(sizeof(ColorGuess));
app->gui = furi_record_open(RECORD_GUI);
app->notification = furi_record_open(RECORD_NOTIFICATION);
- app->error = false;
// Set Defaults if no config exists
app->haptic = 1;
@@ -38,7 +37,6 @@ ColorGuess* color_guess_app_alloc() {
//Scene additions
app->view_dispatcher = view_dispatcher_alloc();
-
app->scene_manager = scene_manager_alloc(&color_guess_scene_handlers, app);
view_dispatcher_set_event_callback_context(app->view_dispatcher, app);
view_dispatcher_set_navigation_event_callback(
@@ -112,9 +110,6 @@ void color_guess_app_free(ColorGuess* app) {
int32_t color_guess_app(void* p) {
UNUSED(p);
ColorGuess* app = color_guess_app_alloc();
- if(app->error) {
- return 255;
- }
view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen);
diff --git a/color_guess/color_guess.h b/color_guess/color_guess.h
index 79a95861d..c8ccbf215 100644
--- a/color_guess/color_guess.h
+++ b/color_guess/color_guess.h
@@ -17,7 +17,7 @@
#include "views/color_guess_startscreen.h"
#include "helpers/color_guess_storage.h"
-#define COLOR_GUESS_VERSION "1.5"
+#define COLOR_GUESS_VERSION "1.6"
#define TAG "Color_Guess"
typedef struct {
@@ -32,9 +32,7 @@ typedef struct {
ColorGuessPlay* color_guess_play;
ColorGuessStartscreen* color_guess_startscreen;
Submenu* color_guess_settings;
- bool error;
uint32_t haptic;
- //uint32_t speaker;
uint32_t led;
uint32_t save_settings;
} ColorGuess;
@@ -52,11 +50,6 @@ typedef enum {
ColorGuessHapticOn,
} ColorGuessHapticState;
-typedef enum {
- ColorGuessSpeakerOff,
- ColorGuessSpeakerOn,
-} ColorGuessSpeakerState;
-
typedef enum {
ColorGuessLedOff,
ColorGuessLedOn,
diff --git a/color_guess/docs/changelog.md b/color_guess/docs/changelog.md
index 4aafc252d..17a2d3928 100644
--- a/color_guess/docs/changelog.md
+++ b/color_guess/docs/changelog.md
@@ -1,21 +1,26 @@
-## 1.5
+## v1.6
+- Reducing code
+- Removal of deprecated view dispatcher queue
+
+## v1.5
- Additional Memory Management fixes by Willy-JL
- Redraw Issue fixed by Willy-JL
- Added GNU License
- Added version number
-## 1.4
-- Prevent value changing on win view
-- Fix issues with FW build 0.99.x
+## v1.4
+- Compatibility for FW above 0.99.0
+- Fix for Value changing on success screen
-## 1.3
-- Patched Memory Leak in storage
+## v1.3
+- Fixed minor memory leak in storage
## v1.2
-- Updated compatibility to 0.95.0-rc
+- Updated Application for Flipper Zero 0.95.0-rc
## v1.1
-- Updated Launch Screen GFX
+
+Added GFX to start screen
## v1.0
diff --git a/color_guess/scenes/color_guess_scene_settings.c b/color_guess/scenes/color_guess_scene_settings.c
index d23fa3fb3..a24a7fcd9 100644
--- a/color_guess/scenes/color_guess_scene_settings.c
+++ b/color_guess/scenes/color_guess_scene_settings.c
@@ -11,22 +11,12 @@ const char* const haptic_text[2] = {
"OFF",
"ON",
};
+
const uint32_t haptic_value[2] = {
ColorGuessHapticOff,
ColorGuessHapticOn,
};
-/* Speaker currently not used
-const char* const speaker_text[2] = {
- "OFF",
- "ON",
-};
-const uint32_t speaker_value[2] = {
- ColorGuessSpeakerOff,
- ColorGuessSpeakerOn,
-};
-*/
-
/* Game doesn't make sense with LED off, but the setting is there */
const char* const led_text[2] = {
"OFF",
@@ -45,15 +35,6 @@ static void color_guess_scene_settings_set_haptic(VariableItem* item) {
app->haptic = haptic_value[index];
}
-/*
-static void color_guess_scene_settings_set_speaker(VariableItem* item) {
- ColorGuess* app = variable_item_get_context(item);
- uint8_t index = variable_item_get_current_value_index(item);
- variable_item_set_current_value_text(item, speaker_text[index]);
- app->speaker = speaker_value[index];
-}
-*/
-
static void color_guess_scene_settings_set_led(VariableItem* item) {
ColorGuess* app = variable_item_get_context(item);
uint8_t index = variable_item_get_current_value_index(item);
@@ -78,18 +59,6 @@ void color_guess_scene_settings_on_enter(void* context) {
variable_item_set_current_value_index(item, value_index);
variable_item_set_current_value_text(item, haptic_text[value_index]);
- // Sound on/off
- /*
- item = variable_item_list_add(
- app->variable_item_list,
- "Sound:",
- 2,
- color_guess_scene_settings_set_speaker,
- app);
- value_index = value_index_uint32(app->speaker, speaker_value, 2);
- variable_item_set_current_value_index(item, value_index);
- variable_item_set_current_value_text(item, speaker_text[value_index]);*/
-
// LED Effects on/off
item = variable_item_list_add(
app->variable_item_list, "LED FX:", 2, color_guess_scene_settings_set_led, app);
diff --git a/cross_remote/.gitignore b/cross_remote/.gitignore
new file mode 100644
index 000000000..bad38f9a7
--- /dev/null
+++ b/cross_remote/.gitignore
@@ -0,0 +1,9 @@
+dist/*
+.vscode
+.clang-format
+.clangd
+.editorconfig
+.env
+.ufbt
+.clang-format
+.vscode/settings.json
diff --git a/cross_remote/README.md b/cross_remote/README.md
index 012a9bc47..8717ee509 100644
--- a/cross_remote/README.md
+++ b/cross_remote/README.md
@@ -29,6 +29,7 @@ Wouldn't it be nicer to simply click one button and let everything happen? This
- Save chained commands to a file
- Add pauses, becaue target systems are not always fast enough for multiple commands
- Run file containing chained IR & SubGhz commands
+- Loop Transmissions until quit
### Settings
- LED FX, allow the LED to blink
@@ -52,5 +53,10 @@ Then run the command:
```
The application will be compiled and copied onto your device.
+## Thank you notes
+- [Willy-JL](https://github.com/Willy-JL) for distributing in Momentum Firmware
+- [Roguemaster](https://github.com/RogueMaster/flipperzero-firmware-wPlugins) for distributing in Roguemaster Firmware
+- [Miccayo](https://github.com/miccayo) for contributing the loop transmit feature
+
## Licensing
This code is open-source and may be used for whatever you want to do with it.
\ No newline at end of file
diff --git a/cross_remote/application.fam b/cross_remote/application.fam
index 3538b48ef..6919ad55a 100644
--- a/cross_remote/application.fam
+++ b/cross_remote/application.fam
@@ -6,7 +6,7 @@ App(
stack_size=3 * 1024,
fap_icon="icons/xremote_10px.png",
fap_icon_assets="icons",
- fap_version="2.7",
+ fap_version="3.0",
fap_category="Infrared",
fap_author="Leedave",
fap_description="One-Click, sends multiple commands",
diff --git a/cross_remote/docs/README.md b/cross_remote/docs/README.md
index 24f758fd6..e3cd326dd 100644
--- a/cross_remote/docs/README.md
+++ b/cross_remote/docs/README.md
@@ -11,6 +11,7 @@ This app combines your IR and SubGhz commands into a playlist that can be run wi
- Disable LED effects if not wanted
- Configure duration of IR Signals
- Configure default duration of Encoded SubGhz Signals
+- Loop Transmissions until quit
## What good is this?
diff --git a/cross_remote/docs/changelog.md b/cross_remote/docs/changelog.md
index 0b09234ef..f45ec135b 100644
--- a/cross_remote/docs/changelog.md
+++ b/cross_remote/docs/changelog.md
@@ -1,3 +1,11 @@
+## 3.0
+- Added loop transmit feature (thanks to miccayo)
+- Replaced transmission counter with animations
+- Refactored the transmission part to allow interruption of command chains
+
+## 2.8
+- Update SubGhz Protocoll to include flippers official rolling code support
+
## 2.7
- Replaced custom keyboard for timing with new number_input from Firmware
- Requires minimum OFW version 0.105.0 or custom firmware based on this
diff --git a/cross_remote/helpers/subghz/subghz.c b/cross_remote/helpers/subghz/subghz.c
index 61dcddac2..0ac5f1f2c 100644
--- a/cross_remote/helpers/subghz/subghz.c
+++ b/cross_remote/helpers/subghz/subghz.c
@@ -9,6 +9,7 @@ SubGhz* subghz_alloc() {
subghz->file_path = furi_string_alloc();
subghz->txrx = subghz_txrx_alloc();
+ subghz_txrx_set_need_save_callback(subghz->txrx, subghz_save_to_file, subghz);
subghz->dialogs = furi_record_open(RECORD_DIALOGS);
return subghz;
diff --git a/cross_remote/helpers/subghz/subghz_i.c b/cross_remote/helpers/subghz/subghz_i.c
index c75739da3..099f6e0a1 100644
--- a/cross_remote/helpers/subghz/subghz_i.c
+++ b/cross_remote/helpers/subghz/subghz_i.c
@@ -188,6 +188,60 @@ SubGhzLoadTypeFile subghz_get_load_type_file(SubGhz* subghz) {
return subghz->load_type_file;
}
+bool subghz_save_protocol_to_file(
+ SubGhz* subghz,
+ FlipperFormat* flipper_format,
+ const char* dev_file_name) {
+ furi_assert(subghz);
+ furi_assert(flipper_format);
+ furi_assert(dev_file_name);
+
+ Storage* storage = furi_record_open(RECORD_STORAGE);
+ Stream* flipper_format_stream = flipper_format_get_raw_stream(flipper_format);
+
+ bool saved = false;
+ FuriString* file_dir = furi_string_alloc();
+
+ path_extract_dirname(dev_file_name, file_dir);
+ do {
+ //removing additional fields
+ flipper_format_delete_key(flipper_format, "Repeat");
+ flipper_format_delete_key(flipper_format, "Manufacture");
+
+ // Create subghz folder directory if necessary
+ if(!storage_simply_mkdir(storage, furi_string_get_cstr(file_dir))) {
+ dialog_message_show_storage_error(subghz->dialogs, "Cannot create\nfolder");
+ break;
+ }
+
+ if(!storage_simply_remove(storage, dev_file_name)) {
+ break;
+ }
+ stream_seek(flipper_format_stream, 0, StreamOffsetFromStart);
+ stream_save_to_file(flipper_format_stream, storage, dev_file_name, FSOM_CREATE_ALWAYS);
+
+ if(storage_common_stat(storage, dev_file_name, NULL) != FSE_OK) {
+ break;
+ }
+
+ saved = true;
+ } while(0);
+ furi_string_free(file_dir);
+ furi_record_close(RECORD_STORAGE);
+ return saved;
+}
+
+void subghz_save_to_file(void* context) {
+ furi_assert(context);
+ SubGhz* subghz = context;
+ if(subghz_path_is_file(subghz->file_path)) {
+ subghz_save_protocol_to_file(
+ subghz,
+ subghz_txrx_get_fff_data(subghz->txrx),
+ furi_string_get_cstr(subghz->file_path));
+ }
+}
+
bool subghz_load_protocol_from_file(SubGhz* subghz, const char* path) {
furi_assert(subghz);
@@ -214,9 +268,9 @@ bool subghz_load_protocol_from_file(SubGhz* subghz, const char* path) {
return ret;
}*/
-/*bool subghz_path_is_file(FuriString* path) {
+bool subghz_path_is_file(FuriString* path) {
return furi_string_end_with(path, SUBGHZ_APP_FILENAME_EXTENSION);
-}*/
+}
/*void subghz_lock(SubGhz* subghz) {
furi_assert(subghz);
diff --git a/cross_remote/helpers/subghz/subghz_i.h b/cross_remote/helpers/subghz/subghz_i.h
index fd9581d70..44e88cca5 100644
--- a/cross_remote/helpers/subghz/subghz_i.h
+++ b/cross_remote/helpers/subghz/subghz_i.h
@@ -37,6 +37,7 @@
#include "subghz_txrx.h"
#define SUBGHZ_MAX_LEN_NAME 64
+#define SUBGHZ_APP_FILENAME_EXTENSION ".sub"
typedef struct SubGhz SubGhz;
@@ -67,6 +68,12 @@ struct SubGhz {
//void subghz_set_default_preset(SubGhz* subghz);
//void subghz_blink_start(SubGhz* subghz);
//void subghz_blink_stop(SubGhz* subghz);
+bool subghz_save_protocol_to_file(
+ SubGhz* subghz,
+ FlipperFormat* flipper_format,
+ const char* dev_file_name);
+void subghz_save_to_file(void* context);
+bool subghz_path_is_file(FuriString* path);
// Used on Encoded SubGhz
bool subghz_tx_start(SubGhz* subghz, FlipperFormat* flipper_format);
diff --git a/cross_remote/helpers/subghz/subghz_txrx.c b/cross_remote/helpers/subghz/subghz_txrx.c
index c0a2be045..b7e394fb0 100644
--- a/cross_remote/helpers/subghz/subghz_txrx.c
+++ b/cross_remote/helpers/subghz/subghz_txrx.c
@@ -27,7 +27,7 @@ static void subghz_txrx_radio_device_power_off(SubGhzTxRx* instance) {
if(furi_hal_power_is_otg_enabled()) furi_hal_power_disable_otg();
}
-SubGhzTxRx* subghz_txrx_alloc() {
+SubGhzTxRx* subghz_txrx_alloc(void) {
SubGhzTxRx* instance = malloc(sizeof(SubGhzTxRx));
instance->setting = subghz_setting_alloc();
subghz_setting_load(instance->setting, EXT_PATH("subghz/assets/setting_user"));
@@ -71,14 +71,11 @@ SubGhzTxRx* subghz_txrx_alloc() {
instance->radio_device_type =
subghz_txrx_radio_device_set(instance, SubGhzRadioDeviceTypeExternalCC1101);
- FURI_LOG_D(TAG, "completed TXRX alloc");
-
return instance;
}
void subghz_txrx_free(SubGhzTxRx* instance) {
furi_assert(instance);
- FURI_LOG_D(TAG, "freeing TXRX");
if(instance->radio_device_type != SubGhzRadioDeviceTypeInternal) {
subghz_txrx_radio_device_power_off(instance);
@@ -194,7 +191,6 @@ static void subghz_txrx_idle(SubGhzTxRx* instance) {
subghz_txrx_speaker_off(instance);
instance->txrx_state = SubGhzTxRxStateIDLE;
}
- FURI_LOG_D(TAG, "completed subghz_txrx_idle");
}
/*static void subghz_txrx_rx_end(SubGhzTxRx* instance) {
@@ -228,7 +224,6 @@ static bool subghz_txrx_tx(SubGhzTxRx* instance, uint32_t frequency) {
instance->txrx_state = SubGhzTxRxStateTx;
}
- FURI_LOG_D(TAG, "completed subghz_txrx_tx");
return ret;
}
@@ -241,10 +236,7 @@ SubGhzTxRxStartTxState subghz_txrx_tx_start(SubGhzTxRx* instance, FlipperFormat*
SubGhzTxRxStartTxState ret = SubGhzTxRxStartTxStateErrorParserOthers;
FuriString* temp_str = furi_string_alloc();
uint32_t repeat = 200;
-
- FURI_LOG_D(TAG, "starting loop in subghz_txrx_tx_start");
do {
- FURI_LOG_D(TAG, "looping");
if(!flipper_format_rewind(flipper_format)) {
FURI_LOG_E(TAG, "Rewind error");
break;
@@ -257,7 +249,6 @@ SubGhzTxRxStartTxState subghz_txrx_tx_start(SubGhzTxRx* instance, FlipperFormat*
FURI_LOG_E(TAG, "Unable Repeat");
break;
}
- //FURI_LOG_D(TAG, "File loaded");
ret = SubGhzTxRxStartTxStateOk;
SubGhzRadioPreset* preset = instance->preset;
@@ -265,24 +256,18 @@ SubGhzTxRxStartTxState subghz_txrx_tx_start(SubGhzTxRx* instance, FlipperFormat*
subghz_transmitter_alloc_init(instance->environment, furi_string_get_cstr(temp_str));
if(instance->transmitter) {
- FURI_LOG_D(TAG, "transmitter found");
if(subghz_transmitter_deserialize(instance->transmitter, flipper_format) ==
SubGhzProtocolStatusOk) {
- //if (false) {
- FURI_LOG_D(TAG, "deserialization");
if(strcmp(furi_string_get_cstr(preset->name), "") != 0) {
- FURI_LOG_D(TAG, "got preset name");
subghz_txrx_begin(
instance,
subghz_setting_get_preset_data_by_name(
instance->setting, furi_string_get_cstr(preset->name)));
- FURI_LOG_D(TAG, "loaded preset by name");
if(preset->frequency) {
if(!subghz_txrx_tx(instance, preset->frequency)) {
FURI_LOG_E(TAG, "Only Rx");
ret = SubGhzTxRxStartTxStateErrorOnlyRx;
}
- FURI_LOG_D(TAG, "got frequency");
} else {
ret = SubGhzTxRxStartTxStateErrorParserOthers;
}
@@ -295,12 +280,10 @@ SubGhzTxRxStartTxState subghz_txrx_tx_start(SubGhzTxRx* instance, FlipperFormat*
if(ret == SubGhzTxRxStartTxStateOk) {
//Start TX
- FURI_LOG_D(TAG, "starting Async TX");
subghz_devices_start_async_tx(
instance->radio_device, subghz_transmitter_yield, instance->transmitter);
}
} else {
- FURI_LOG_D(TAG, "no deserialization");
ret = SubGhzTxRxStartTxStateErrorParserOthers;
}
} else {
@@ -328,14 +311,14 @@ SubGhzTxRxStartTxState subghz_txrx_tx_start(SubGhzTxRx* instance, FlipperFormat*
subghz_txrx_rx(instance, instance->preset->frequency);
}*/
-/*void subghz_txrx_set_need_save_callback(
+void subghz_txrx_set_need_save_callback(
SubGhzTxRx* instance,
SubGhzTxRxNeedSaveCallback callback,
void* context) {
furi_assert(instance);
instance->need_save_callback = callback;
instance->need_save_context = context;
-}*/
+}
static void subghz_txrx_tx_stop(SubGhzTxRx* instance) {
furi_assert(instance);
@@ -346,11 +329,11 @@ static void subghz_txrx_tx_stop(SubGhzTxRx* instance) {
subghz_transmitter_free(instance->transmitter);
//if protocol dynamic then we save the last upload
- /*if(instance->decoder_result->protocol->type == SubGhzProtocolTypeDynamic) {
+ if(instance->decoder_result->protocol->type == SubGhzProtocolTypeDynamic) {
if(instance->need_save_callback) {
instance->need_save_callback(instance->need_save_context);
}
- }*/
+ }
subghz_txrx_idle(instance);
subghz_txrx_speaker_off(instance);
}
diff --git a/cross_remote/helpers/subghz/subghz_txrx.h b/cross_remote/helpers/subghz/subghz_txrx.h
index a74051ade..d8c6aba87 100644
--- a/cross_remote/helpers/subghz/subghz_txrx.h
+++ b/cross_remote/helpers/subghz/subghz_txrx.h
@@ -221,10 +221,10 @@ SubGhzProtocolDecoderBase* subghz_txrx_get_decoder(SubGhzTxRx* instance);
* @param callback Callback for save data
* @param context Context for callback
*/
-/*void subghz_txrx_set_need_save_callback(
+void subghz_txrx_set_need_save_callback(
SubGhzTxRx* instance,
SubGhzTxRxNeedSaveCallback callback,
- void* context);*/
+ void* context);
/**
* Get pointer to a load data key
diff --git a/cross_remote/helpers/xremote_storage.c b/cross_remote/helpers/xremote_storage.c
index 7077b01a1..c63e39c33 100644
--- a/cross_remote/helpers/xremote_storage.c
+++ b/cross_remote/helpers/xremote_storage.c
@@ -59,6 +59,8 @@ void xremote_save_settings(void* context) {
fff_file, XREMOTE_SETTINGS_KEY_SAVE_SETTINGS, &app->save_settings, 1);
flipper_format_write_uint32(fff_file, XREMOTE_SETTINGS_KEY_IR_TIMING, &app->ir_timing, 1);
flipper_format_write_uint32(fff_file, XREMOTE_SETTINGS_KEY_SG_TIMING, &app->sg_timing, 1);
+ flipper_format_write_uint32(
+ fff_file, XREMOTE_SETTINGS_KEY_LOOP_TRANSMIT, &app->loop_transmit, 1);
if(!flipper_format_rewind(fff_file)) {
xremote_close_config_file(fff_file);
@@ -112,6 +114,8 @@ void xremote_read_settings(void* context) {
fff_file, XREMOTE_SETTINGS_KEY_SAVE_SETTINGS, &app->save_settings, 1);
flipper_format_read_uint32(fff_file, XREMOTE_SETTINGS_KEY_IR_TIMING, &app->ir_timing, 1);
flipper_format_read_uint32(fff_file, XREMOTE_SETTINGS_KEY_SG_TIMING, &app->sg_timing, 1);
+ flipper_format_read_uint32(
+ fff_file, XREMOTE_SETTINGS_KEY_LOOP_TRANSMIT, &app->loop_transmit, 1);
flipper_format_rewind(fff_file);
diff --git a/cross_remote/helpers/xremote_storage.h b/cross_remote/helpers/xremote_storage.h
index 650fac2c8..166faaee5 100644
--- a/cross_remote/helpers/xremote_storage.h
+++ b/cross_remote/helpers/xremote_storage.h
@@ -13,6 +13,7 @@
#define XREMOTE_SETTINGS_KEY_SAVE_SETTINGS "SaveSettings"
#define XREMOTE_SETTINGS_KEY_IR_TIMING "IRTiming"
#define XREMOTE_SETTINGS_KEY_SG_TIMING "SGTiming"
+#define XREMOTE_SETTINGS_KEY_LOOP_TRANSMIT "LoopTransmit"
void xremote_save_settings(void* context);
void xremote_read_settings(void* context);
\ No newline at end of file
diff --git a/cross_remote/icons/KeyBackspaceSelected_16x9.png b/cross_remote/icons/KeyBackspaceSelected_16x9.png
deleted file mode 100644
index 7cc0759a8..000000000
Binary files a/cross_remote/icons/KeyBackspaceSelected_16x9.png and /dev/null differ
diff --git a/cross_remote/icons/KeyBackspace_16x9.png b/cross_remote/icons/KeyBackspace_16x9.png
deleted file mode 100644
index 9946232d9..000000000
Binary files a/cross_remote/icons/KeyBackspace_16x9.png and /dev/null differ
diff --git a/cross_remote/icons/KeySaveSelected_24x11.png b/cross_remote/icons/KeySaveSelected_24x11.png
deleted file mode 100644
index eeb3569d3..000000000
Binary files a/cross_remote/icons/KeySaveSelected_24x11.png and /dev/null differ
diff --git a/cross_remote/icons/KeySave_24x11.png b/cross_remote/icons/KeySave_24x11.png
deleted file mode 100644
index e7dba987a..000000000
Binary files a/cross_remote/icons/KeySave_24x11.png and /dev/null differ
diff --git a/cross_remote/icons/ir_ani_1_32x22.png b/cross_remote/icons/ir_ani_1_32x22.png
new file mode 100644
index 000000000..c11f9f1cf
Binary files /dev/null and b/cross_remote/icons/ir_ani_1_32x22.png differ
diff --git a/cross_remote/icons/ir_ani_2_32x22.png b/cross_remote/icons/ir_ani_2_32x22.png
new file mode 100644
index 000000000..3e33bc834
Binary files /dev/null and b/cross_remote/icons/ir_ani_2_32x22.png differ
diff --git a/cross_remote/icons/ir_ani_32x22.png b/cross_remote/icons/ir_ani_32x22.png
new file mode 100644
index 000000000..ec7b52ec1
Binary files /dev/null and b/cross_remote/icons/ir_ani_32x22.png differ
diff --git a/cross_remote/icons/ir_ani_3_32x22.png b/cross_remote/icons/ir_ani_3_32x22.png
new file mode 100644
index 000000000..75acfa2ad
Binary files /dev/null and b/cross_remote/icons/ir_ani_3_32x22.png differ
diff --git a/cross_remote/icons/pause_ani_1_22x23.png b/cross_remote/icons/pause_ani_1_22x23.png
new file mode 100644
index 000000000..055c8ee86
Binary files /dev/null and b/cross_remote/icons/pause_ani_1_22x23.png differ
diff --git a/cross_remote/icons/pause_ani_2_22x23.png b/cross_remote/icons/pause_ani_2_22x23.png
new file mode 100644
index 000000000..b8884185d
Binary files /dev/null and b/cross_remote/icons/pause_ani_2_22x23.png differ
diff --git a/cross_remote/icons/pause_ani_3_22x23.png b/cross_remote/icons/pause_ani_3_22x23.png
new file mode 100644
index 000000000..c8cb97727
Binary files /dev/null and b/cross_remote/icons/pause_ani_3_22x23.png differ
diff --git a/cross_remote/icons/sg_ani_1_19x13.png b/cross_remote/icons/sg_ani_1_19x13.png
new file mode 100644
index 000000000..6e3c8d0cf
Binary files /dev/null and b/cross_remote/icons/sg_ani_1_19x13.png differ
diff --git a/cross_remote/icons/sg_ani_2_19x13.png b/cross_remote/icons/sg_ani_2_19x13.png
new file mode 100644
index 000000000..92ac3c054
Binary files /dev/null and b/cross_remote/icons/sg_ani_2_19x13.png differ
diff --git a/cross_remote/icons/sg_ani_3_19x13.png b/cross_remote/icons/sg_ani_3_19x13.png
new file mode 100644
index 000000000..966cc04af
Binary files /dev/null and b/cross_remote/icons/sg_ani_3_19x13.png differ
diff --git a/cross_remote/scenes/xremote_scene_settings.c b/cross_remote/scenes/xremote_scene_settings.c
index 7e66d2551..d60115d67 100644
--- a/cross_remote/scenes/xremote_scene_settings.c
+++ b/cross_remote/scenes/xremote_scene_settings.c
@@ -33,7 +33,14 @@ const uint32_t led_value[2] = {
XRemoteLedOff,
XRemoteLedOn,
};
-
+const char* const loop_text[2] = {
+ "OFF",
+ "ON",
+};
+const uint32_t loop_value[2] = {
+ XRemoteLoopOff,
+ XRemoteLoopOn,
+};
const char* const settings_text[2] = {
"OFF",
"ON",
@@ -72,6 +79,13 @@ static void xremote_scene_settings_set_save_settings(VariableItem* item) {
app->save_settings = settings_value[index];
}
+static void xremote_scene_settings_set_loop(VariableItem* item) {
+ XRemote* app = variable_item_get_context(item);
+ uint8_t index = variable_item_get_current_value_index(item);
+ variable_item_set_current_value_text(item, loop_text[index]);
+ app->loop_transmit = loop_value[index];
+}
+
static void xremote_scene_settings_set_ir_timing(VariableItem* item) {
XRemote* app = variable_item_get_context(item);
uint32_t index = variable_item_get_current_value_index(item);
@@ -116,11 +130,18 @@ void xremote_scene_settings_on_enter(void* context) {
// LED Effects on/off
item = variable_item_list_add(
- app->variable_item_list, "LED FX:", 2, xremote_scene_settings_set_led, app);
+ app->variable_item_list, "LED FX", 2, xremote_scene_settings_set_led, app);
value_index = value_index_uint32(app->led, led_value, 2);
variable_item_set_current_value_index(item, value_index);
variable_item_set_current_value_text(item, led_text[value_index]);
+ /* NEW: Loop saved command functionality */
+ item = variable_item_list_add(
+ app->variable_item_list, "Loop Transmit", 2, xremote_scene_settings_set_loop, app);
+ value_index = value_index_uint32(app->loop_transmit, loop_value, 2);
+ variable_item_set_current_value_index(item, value_index);
+ variable_item_set_current_value_text(item, loop_text[value_index]);
+
// Save Settings to File
item = variable_item_list_add(
app->variable_item_list, "Save Settings", 2, xremote_scene_settings_set_save_settings, app);
diff --git a/cross_remote/scenes/xremote_scene_transmit.c b/cross_remote/scenes/xremote_scene_transmit.c
index 339d4b7cb..053f40819 100644
--- a/cross_remote/scenes/xremote_scene_transmit.c
+++ b/cross_remote/scenes/xremote_scene_transmit.c
@@ -18,9 +18,10 @@ static const NotificationSequence* xremote_notification_sequences[] = {
&sequence_blink_stop,
};
-void xremote_transmit_callback(XRemoteCustomEvent event, void* context) {
+void xremote_scene_transmit_callback(XRemoteCustomEvent event, void* context) {
furi_assert(context);
XRemote* app = context;
+ FURI_LOG_D(TAG, "trigger xremote_transmit_callback");
view_dispatcher_send_custom_event(app->view_dispatcher, event);
}
@@ -114,44 +115,51 @@ void xremote_scene_transmit_send_signal(void* context, CrossRemoteItem* item) {
}
}
-void xremote_scene_transmit_run_remote(void* context) {
- furi_assert(context);
- XRemote* app = context;
+static void xremote_scene_transmit_end_scene(XRemote* app) {
+ xremote_scene_ir_notification_message(app, InfraredNotificationMessageBlinkStop);
+ scene_manager_previous_scene(app->scene_manager);
+}
+
+static void xremote_scene_transmit_run_single_transmit(XRemote* app) {
CrossRemote* remote = app->cross_remote;
+ if(xremote_cross_remote_get_transmitting(remote) == XRemoteTransmittingIdle) {
+ xremote_cross_remote_set_transmitting(remote, XRemoteTransmittingStart);
+ CrossRemoteItem* item = xremote_cross_remote_get_item(remote, app->transmit_item);
+ xremote_scene_transmit_send_signal(app, item);
+ } else if(xremote_cross_remote_get_transmitting(remote) == XRemoteTransmittingStopSubghz) {
+ app->transmit_item++;
+ app->state_notifications = SubGhzNotificationStateIDLE;
+ app->transmitting = false;
+ subghz_txrx_stop(app->subghz->txrx);
+ xremote_scene_ir_notification_message(app, SubGhzNotificationMessageBlinkStop);
+ xremote_cross_remote_set_transmitting(remote, XRemoteTransmittingIdle);
+ } else if(xremote_cross_remote_get_transmitting(remote) == XRemoteTransmittingStop) {
+ app->transmit_item++;
+ xremote_cross_remote_set_transmitting(remote, XRemoteTransmittingIdle);
+ }
+}
+static void xremote_scene_transmit_run_next_transmission(XRemote* app) {
+ CrossRemote* remote = app->cross_remote;
size_t item_count = xremote_cross_remote_get_item_count(remote);
- for(size_t i = 0; i < item_count;) {
- if(xremote_cross_remote_get_transmitting(remote) == XRemoteTransmittingIdle) {
- xremote_cross_remote_set_transmitting(remote, XRemoteTransmittingStart);
- CrossRemoteItem* item = xremote_cross_remote_get_item(remote, i);
- xremote_scene_transmit_send_signal(app, item);
- //furi_thread_flags_wait(0, FuriFlagWaitAny, 1000);
- } else if(xremote_cross_remote_get_transmitting(remote) == XRemoteTransmittingStopSubghz) {
- i++;
- app->state_notifications = SubGhzNotificationStateIDLE;
- app->transmitting = false;
- subghz_txrx_stop(app->subghz->txrx);
- xremote_scene_ir_notification_message(app, SubGhzNotificationMessageBlinkStop);
- xremote_cross_remote_set_transmitting(remote, XRemoteTransmittingIdle);
- //furi_thread_flags_wait(0, FuriFlagWaitAny, 1000);
- } else if(xremote_cross_remote_get_transmitting(remote) == XRemoteTransmittingStop) {
- i++;
- xremote_cross_remote_set_transmitting(remote, XRemoteTransmittingIdle);
- }
+ if(app->transmit_item < item_count) {
+ xremote_scene_transmit_run_single_transmit(app);
+ return;
}
- xremote_scene_ir_notification_message(app, InfraredNotificationMessageBlinkStop);
-
- scene_manager_previous_scene(app->scene_manager);
- //xremote_transmit_model_set_name(app->xremote_transmit, xremote_cross_remote_get_name(remote));
+ if(app->loop_transmit && !app->stop_transmit) {
+ app->transmit_item = 0;
+ return;
+ }
+ xremote_scene_transmit_end_scene(app);
}
void xremote_scene_transmit_on_enter(void* context) {
furi_assert(context);
XRemote* app = context;
- xremote_transmit_set_callback(app->xremote_transmit, xremote_transmit_callback, app);
+ app->transmit_item = 0;
+ xremote_transmit_set_callback(app->xremote_transmit, xremote_scene_transmit_callback, app);
view_dispatcher_switch_to_view(app->view_dispatcher, XRemoteViewIdTransmit);
- xremote_scene_transmit_run_remote(app);
}
bool xremote_scene_transmit_on_event(void* context, SceneManagerEvent event) {
@@ -162,13 +170,14 @@ bool xremote_scene_transmit_on_event(void* context, SceneManagerEvent event) {
FURI_LOG_D(TAG, "Custom Event");
switch(event.event) {
case XRemoteCustomEventViewTransmitterSendStop:
- FURI_LOG_D("SUBGHZ", "stop event"); // doesn't trigger
+ app->stop_transmit = true;
break;
default:
break;
}
} else if(event.type == SceneManagerEventTypeTick) {
FURI_LOG_D(TAG, "Tick Event");
+ xremote_scene_transmit_run_next_transmission(app);
with_view_model(
xremote_transmit_get_view(app->xremote_transmit),
void* model,
@@ -177,6 +186,10 @@ bool xremote_scene_transmit_on_event(void* context, SceneManagerEvent event) {
if(app->state_notifications == SubGhzNotificationStateTx && app->led == 1) {
//blink for subghz
}
+ if(app->stop_transmit == true) {
+ app->stop_transmit = false;
+ xremote_scene_transmit_end_scene(app);
+ }
}
return consumed;
diff --git a/cross_remote/views/xremote_transmit.c b/cross_remote/views/xremote_transmit.c
index f87bf7d2d..ea657c373 100644
--- a/cross_remote/views/xremote_transmit.c
+++ b/cross_remote/views/xremote_transmit.c
@@ -50,12 +50,16 @@ void xremote_transmit_draw_ir(Canvas* canvas, XRemoteTransmitModel* model) {
canvas_set_font(canvas, FontPrimary);
canvas_draw_str_aligned(canvas, 74, 5, AlignLeft, AlignTop, "Sending");
canvas_set_font(canvas, FontSecondary);
- canvas_draw_str_aligned(canvas, 74, 20, AlignLeft, AlignTop, "Infrared");
- canvas_draw_str_aligned(canvas, 74, 30, AlignLeft, AlignTop, model->name);
-
- char temp_str[18];
- snprintf(temp_str, 18, "%u", model->time);
- canvas_draw_str_aligned(canvas, 74, 40, AlignLeft, AlignTop, temp_str);
+ canvas_draw_str_aligned(canvas, 74, 15, AlignLeft, AlignTop, "Infrared");
+ canvas_draw_str_aligned(canvas, 74, 25, AlignLeft, AlignTop, model->name);
+
+ if(model->time == 0) {
+ canvas_draw_icon(canvas, 36, 2, &I_ir_ani_1_32x22);
+ } else if(model->time == 1) {
+ canvas_draw_icon(canvas, 36, 2, &I_ir_ani_2_32x22);
+ } else if(model->time == 2) {
+ canvas_draw_icon(canvas, 36, 2, &I_ir_ani_3_32x22);
+ }
}
void xremote_transmit_draw_pause(Canvas* canvas, XRemoteTransmitModel* model) {
@@ -66,12 +70,16 @@ void xremote_transmit_draw_pause(Canvas* canvas, XRemoteTransmitModel* model) {
canvas_set_font(canvas, FontPrimary);
canvas_draw_str_aligned(canvas, 74, 5, AlignLeft, AlignTop, "Waiting");
canvas_set_font(canvas, FontSecondary);
- canvas_draw_str_aligned(canvas, 74, 20, AlignLeft, AlignTop, "Sequence");
- canvas_draw_str_aligned(canvas, 74, 30, AlignLeft, AlignTop, model->name);
-
- char temp_str[18];
- snprintf(temp_str, 18, "%u", model->time);
- canvas_draw_str_aligned(canvas, 74, 40, AlignLeft, AlignTop, temp_str);
+ canvas_draw_str_aligned(canvas, 74, 15, AlignLeft, AlignTop, "Sequence");
+ canvas_draw_str_aligned(canvas, 74, 25, AlignLeft, AlignTop, model->name);
+
+ if(model->time == 0) {
+ canvas_draw_icon(canvas, 9, 28, &I_pause_ani_1_22x23);
+ } else if(model->time == 1) {
+ canvas_draw_icon(canvas, 9, 28, &I_pause_ani_2_22x23);
+ } else if(model->time == 2) {
+ canvas_draw_icon(canvas, 9, 28, &I_pause_ani_3_22x23);
+ }
}
void xremote_transmit_draw_subghz(Canvas* canvas, XRemoteTransmitModel* model) {
@@ -82,12 +90,16 @@ void xremote_transmit_draw_subghz(Canvas* canvas, XRemoteTransmitModel* model) {
canvas_set_font(canvas, FontPrimary);
canvas_draw_str_aligned(canvas, 74, 5, AlignLeft, AlignTop, "Sending");
canvas_set_font(canvas, FontSecondary);
- canvas_draw_str_aligned(canvas, 74, 20, AlignLeft, AlignTop, "SubGhz");
- canvas_draw_str_aligned(canvas, 74, 30, AlignLeft, AlignTop, model->name);
-
- char temp_str[18];
- snprintf(temp_str, 18, "%u", model->time);
- canvas_draw_str_aligned(canvas, 74, 40, AlignLeft, AlignTop, temp_str);
+ canvas_draw_str_aligned(canvas, 74, 15, AlignLeft, AlignTop, "SubGhz");
+ canvas_draw_str_aligned(canvas, 74, 25, AlignLeft, AlignTop, model->name);
+
+ if(model->time == 0) {
+ canvas_draw_icon(canvas, 15, 1, &I_sg_ani_1_19x13);
+ } else if(model->time == 1) {
+ canvas_draw_icon(canvas, 15, 1, &I_sg_ani_2_19x13);
+ } else if(model->time == 2) {
+ canvas_draw_icon(canvas, 15, 1, &I_sg_ani_3_19x13);
+ }
}
void xremote_transmit_draw(Canvas* canvas, XRemoteTransmitModel* model) {
@@ -98,6 +110,10 @@ void xremote_transmit_draw(Canvas* canvas, XRemoteTransmitModel* model) {
} else if(model->type == XRemoteRemoteItemTypePause) {
xremote_transmit_draw_pause(canvas, model);
}
+ if(model->time > 2) {
+ model->time = 0;
+ }
+ elements_button_right(canvas, "exit");
}
bool xremote_transmit_input(InputEvent* event, void* context) {
@@ -106,6 +122,7 @@ bool xremote_transmit_input(InputEvent* event, void* context) {
if(event->type == InputTypeRelease) {
switch(event->key) {
case InputKeyBack:
+ case InputKeyRight:
with_view_model(
instance->view,
XRemoteTransmitModel * model,
@@ -123,6 +140,16 @@ bool xremote_transmit_input(InputEvent* event, void* context) {
return true;
}
+void xremote_transmit_enter(void* context) {
+ furi_assert(context);
+ XRemoteTransmit* instance = (XRemoteTransmit*)context;
+ with_view_model(
+ instance->view,
+ XRemoteTransmitModel * model,
+ { xremote_transmit_model_init(model); },
+ true);
+}
+
XRemoteTransmit* xremote_transmit_alloc() {
XRemoteTransmit* instance = malloc(sizeof(XRemoteTransmit));
instance->view = view_alloc();
@@ -130,7 +157,7 @@ XRemoteTransmit* xremote_transmit_alloc() {
view_set_context(instance->view, instance);
view_set_draw_callback(instance->view, (ViewDrawCallback)xremote_transmit_draw);
view_set_input_callback(instance->view, xremote_transmit_input);
- view_set_enter_callback(instance->view, xremote_transmit_enter);
+ //view_set_enter_callback(instance->view, xremote_transmit_enter);
with_view_model(
instance->view,
@@ -141,16 +168,6 @@ XRemoteTransmit* xremote_transmit_alloc() {
return instance;
}
-void xremote_transmit_enter(void* context) {
- furi_assert(context);
- XRemoteTransmit* instance = (XRemoteTransmit*)context;
- with_view_model(
- instance->view,
- XRemoteTransmitModel * model,
- { xremote_transmit_model_init(model); },
- true);
-}
-
void xremote_transmit_free(XRemoteTransmit* instance) {
furi_assert(instance);
diff --git a/cross_remote/xremote.c b/cross_remote/xremote.c
index 6d87a6ba3..e3ddbb860 100644
--- a/cross_remote/xremote.c
+++ b/cross_remote/xremote.c
@@ -51,6 +51,8 @@ XRemote* xremote_app_alloc() {
app->sg_timing = 500;
app->sg_timing_char = "500";
app->stop_transmit = false;
+ app->loop_transmit = 0;
+ app->transmit_item = 0;
// Load configs
xremote_read_settings(app);
diff --git a/cross_remote/xremote.h b/cross_remote/xremote.h
index 3314d9a37..244b68eb0 100644
--- a/cross_remote/xremote.h
+++ b/cross_remote/xremote.h
@@ -42,6 +42,7 @@ typedef struct {
uint32_t speaker;
uint32_t led;
uint32_t save_settings;
+ uint32_t loop_transmit;
uint32_t edit_item;
uint32_t ir_timing;
char* ir_timing_char;
@@ -49,6 +50,7 @@ typedef struct {
char* sg_timing_char;
bool transmitting;
bool stop_transmit;
+ size_t transmit_item;
char text_store[XREMOTE_TEXT_STORE_NUM][XREMOTE_TEXT_STORE_SIZE + 1];
SubGhz* subghz;
NumberInput* number_input;
@@ -85,6 +87,11 @@ typedef enum {
XRemoteLedOn,
} XRemoteLedState;
+typedef enum {
+ XRemoteLoopOff,
+ XRemoteLoopOn,
+} XRemoteLoopState;
+
typedef enum {
XRemoteSettingsOff,
XRemoteSettingsOn,
diff --git a/cross_remote/xremote_i.h b/cross_remote/xremote_i.h
index 5886644a6..890f47037 100644
--- a/cross_remote/xremote_i.h
+++ b/cross_remote/xremote_i.h
@@ -51,10 +51,10 @@
#define XREMOTE_TEXT_STORE_SIZE 128
#define XREMOTE_MAX_ITEM_NAME_LENGTH 22
#define XREMOTE_MAX_REMOTE_NAME_LENGTH 22
-#define XREMOTE_VERSION "2.7"
+#define XREMOTE_VERSION "3.0"
#define INFRARED_APP_EXTENSION ".ir"
-#define INFRARED_APP_FOLDER ANY_PATH("infrared")
+#define INFRARED_APP_FOLDER EXT_PATH("infrared")
#define TAG "XRemote"
diff --git a/esp_flasher/.gitsubtree b/esp_flasher/.gitsubtree
index e2f0b4677..a98d418ea 100644
--- a/esp_flasher/.gitsubtree
+++ b/esp_flasher/.gitsubtree
@@ -1,2 +1,2 @@
-https://github.com/xMasterX/all-the-plugins dev non_catalog_apps/esp_flasher 4558d74c9da36abc851edd96a95d18f7d5511a75
+https://github.com/xMasterX/all-the-plugins dev non_catalog_apps/esp_flasher c450eaa657997d2a8776df11b837e22a69fabd77
https://github.com/0xchocolate/flipperzero-esp-flasher main /
diff --git a/esp_flasher/resources/apps_data/esp_flasher/assets/flipperhttp/s2/flipper_http_bootloader.bin b/esp_flasher/resources/apps_data/esp_flasher/assets/flipperhttp/s2/flipper_http_bootloader.bin
index 15e26392d..ded603b18 100644
Binary files a/esp_flasher/resources/apps_data/esp_flasher/assets/flipperhttp/s2/flipper_http_bootloader.bin and b/esp_flasher/resources/apps_data/esp_flasher/assets/flipperhttp/s2/flipper_http_bootloader.bin differ
diff --git a/esp_flasher/resources/apps_data/esp_flasher/assets/flipperhttp/s2/flipper_http_firmware_a.bin b/esp_flasher/resources/apps_data/esp_flasher/assets/flipperhttp/s2/flipper_http_firmware_a.bin
index c10bca3a6..dd66e4f76 100644
Binary files a/esp_flasher/resources/apps_data/esp_flasher/assets/flipperhttp/s2/flipper_http_firmware_a.bin and b/esp_flasher/resources/apps_data/esp_flasher/assets/flipperhttp/s2/flipper_http_firmware_a.bin differ
diff --git a/esp_flasher/resources/apps_data/esp_flasher/assets/marauder/s2/esp32_marauder.flipper.bin b/esp_flasher/resources/apps_data/esp_flasher/assets/marauder/s2/esp32_marauder.flipper.bin
index d089c7b62..93a908d60 100644
Binary files a/esp_flasher/resources/apps_data/esp_flasher/assets/marauder/s2/esp32_marauder.flipper.bin and b/esp_flasher/resources/apps_data/esp_flasher/assets/marauder/s2/esp32_marauder.flipper.bin differ
diff --git a/esp_flasher/resources/apps_data/esp_flasher/assets/marauder/s3/esp32_marauder.multiboardS3.bin b/esp_flasher/resources/apps_data/esp_flasher/assets/marauder/s3/esp32_marauder.multiboardS3.bin
index 4f6f02ef0..058bd80ac 100644
Binary files a/esp_flasher/resources/apps_data/esp_flasher/assets/marauder/s3/esp32_marauder.multiboardS3.bin and b/esp_flasher/resources/apps_data/esp_flasher/assets/marauder/s3/esp32_marauder.multiboardS3.bin differ
diff --git a/esp_flasher/resources/apps_data/esp_flasher/assets/marauder/wroom/esp32_marauder.dev_board_pro.bin b/esp_flasher/resources/apps_data/esp_flasher/assets/marauder/wroom/esp32_marauder.dev_board_pro.bin
index 8d93838bc..e8e020158 100644
Binary files a/esp_flasher/resources/apps_data/esp_flasher/assets/marauder/wroom/esp32_marauder.dev_board_pro.bin and b/esp_flasher/resources/apps_data/esp_flasher/assets/marauder/wroom/esp32_marauder.dev_board_pro.bin differ
diff --git a/flip_library/jsmn/jsmn.c b/flip_library/jsmn/jsmn.c
index eb33b3cc7..b85ab83b5 100644
--- a/flip_library/jsmn/jsmn.c
+++ b/flip_library/jsmn/jsmn.c
@@ -13,11 +13,13 @@
/**
* 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;
+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) {
+ if (parser->toknext >= num_tokens)
+ {
return NULL;
}
tok = &tokens[parser->toknext++];
@@ -32,8 +34,9 @@ static jsmntok_t*
/**
* Fills token type and boundaries.
*/
-static void
- jsmn_fill_token(jsmntok_t* token, const jsmntype_t type, const int start, const int end) {
+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;
@@ -43,19 +46,19 @@ static void
/**
* 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;
+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]) {
+ 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 ':':
@@ -72,7 +75,8 @@ static int jsmn_parse_primitive(
/* to quiet a warning from gcc*/
break;
}
- if(js[parser->pos] < 32 || js[parser->pos] >= 127) {
+ if (js[parser->pos] < 32 || js[parser->pos] >= 127)
+ {
parser->pos = start;
return JSMN_ERROR_INVAL;
}
@@ -84,12 +88,14 @@ static int jsmn_parse_primitive(
#endif
found:
- if(tokens == NULL) {
+ if (tokens == NULL)
+ {
parser->pos--;
return 0;
}
token = jsmn_alloc_token(parser, tokens, num_tokens);
- if(token == NULL) {
+ if (token == NULL)
+ {
parser->pos = start;
return JSMN_ERROR_NOMEM;
}
@@ -104,29 +110,31 @@ static int jsmn_parse_primitive(
/**
* 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;
+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++) {
+ for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++)
+ {
char c = js[parser->pos];
/* Quote: end of string */
- if(c == '\"') {
- if(tokens == NULL) {
+ if (c == '\"')
+ {
+ if (tokens == NULL)
+ {
return 0;
}
token = jsmn_alloc_token(parser, tokens, num_tokens);
- if(token == NULL) {
+ if (token == NULL)
+ {
parser->pos = start;
return JSMN_ERROR_NOMEM;
}
@@ -138,10 +146,12 @@ static int jsmn_parse_string(
}
/* Backslash: Quoted symbol expected */
- if(c == '\\' && parser->pos + 1 < len) {
+ if (c == '\\' && parser->pos + 1 < len)
+ {
int i;
parser->pos++;
- switch(js[parser->pos]) {
+ switch (js[parser->pos])
+ {
/* Allowed escaped symbols */
case '\"':
case '/':
@@ -155,11 +165,13 @@ static int jsmn_parse_string(
/* Allows escaped symbol \uXXXX */
case 'u':
parser->pos++;
- for(i = 0; i < 4 && parser->pos < len && js[parser->pos] != '\0'; i++) {
+ 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 */
+ 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;
}
@@ -181,7 +193,8 @@ static int jsmn_parse_string(
/**
* Create JSON parser over an array of tokens
*/
-void jsmn_init(jsmn_parser* parser) {
+void jsmn_init(jsmn_parser *parser)
+{
parser->pos = 0;
parser->toknext = 0;
parser->toksuper = -1;
@@ -190,38 +203,41 @@ void jsmn_init(jsmn_parser* parser) {
/**
* 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 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;
+ jsmntok_t *token;
int count = parser->toknext;
- for(; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) {
+ for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++)
+ {
char c;
jsmntype_t type;
c = js[parser->pos];
- switch(c) {
+ switch (c)
+ {
case '{':
case '[':
count++;
- if(tokens == NULL) {
+ if (tokens == NULL)
+ {
break;
}
token = jsmn_alloc_token(parser, tokens, num_tokens);
- if(token == NULL) {
+ if (token == NULL)
+ {
return JSMN_ERROR_NOMEM;
}
- if(parser->toksuper != -1) {
- jsmntok_t* t = &tokens[parser->toksuper];
+ 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) {
+ if (t->type == JSMN_OBJECT)
+ {
return JSMN_ERROR_INVAL;
}
#endif
@@ -236,26 +252,33 @@ int jsmn_parse(
break;
case '}':
case ']':
- if(tokens == NULL) {
+ if (tokens == NULL)
+ {
break;
}
type = (c == '}' ? JSMN_OBJECT : JSMN_ARRAY);
#ifdef JSMN_PARENT_LINKS
- if(parser->toknext < 1) {
+ 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) {
+ 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) {
+ if (token->parent == -1)
+ {
+ if (token->type != type || parser->toksuper == -1)
+ {
return JSMN_ERROR_INVAL;
}
break;
@@ -263,10 +286,13 @@ int jsmn_parse(
token = &tokens[token->parent];
}
#else
- for(i = parser->toknext - 1; i >= 0; i--) {
+ for (i = parser->toknext - 1; i >= 0; i--)
+ {
token = &tokens[i];
- if(token->start != -1 && token->end == -1) {
- if(token->type != type) {
+ if (token->start != -1 && token->end == -1)
+ {
+ if (token->type != type)
+ {
return JSMN_ERROR_INVAL;
}
parser->toksuper = -1;
@@ -275,12 +301,15 @@ int jsmn_parse(
}
}
/* Error if unmatched closing bracket */
- if(i == -1) {
+ if (i == -1)
+ {
return JSMN_ERROR_INVAL;
}
- for(; i >= 0; i--) {
+ for (; i >= 0; i--)
+ {
token = &tokens[i];
- if(token->start != -1 && token->end == -1) {
+ if (token->start != -1 && token->end == -1)
+ {
parser->toksuper = i;
break;
}
@@ -289,11 +318,13 @@ int jsmn_parse(
break;
case '\"':
r = jsmn_parse_string(parser, js, len, tokens, num_tokens);
- if(r < 0) {
+ if (r < 0)
+ {
return r;
}
count++;
- if(parser->toksuper != -1 && tokens != NULL) {
+ if (parser->toksuper != -1 && tokens != NULL)
+ {
tokens[parser->toksuper].size++;
}
break;
@@ -306,15 +337,19 @@ int jsmn_parse(
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) {
+ 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) {
+ 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;
}
@@ -340,9 +375,12 @@ int jsmn_parse(
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)) {
+ 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;
}
}
@@ -351,11 +389,13 @@ int jsmn_parse(
default:
#endif
r = jsmn_parse_primitive(parser, js, len, tokens, num_tokens);
- if(r < 0) {
+ if (r < 0)
+ {
return r;
}
count++;
- if(parser->toksuper != -1 && tokens != NULL) {
+ if (parser->toksuper != -1 && tokens != NULL)
+ {
tokens[parser->toksuper].size++;
}
break;
@@ -368,10 +408,13 @@ int jsmn_parse(
}
}
- if(tokens != NULL) {
- for(i = parser->toknext - 1; i >= 0; i--) {
+ 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) {
+ if (tokens[i].start != -1 && tokens[i].end == -1)
+ {
return JSMN_ERROR_PART;
}
}
@@ -381,10 +424,12 @@ int jsmn_parse(
}
// 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) {
+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);
@@ -392,30 +437,36 @@ 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) {
- if(tok->type == JSMN_STRING && (int)strlen(s) == tok->end - tok->start &&
- strncmp(json + tok->start, s, tok->end - tok->start) == 0) {
+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) {
+char *get_json_value(char *key, char *json_data, uint32_t max_tokens)
+{
// Parse the JSON feed
- if(json_data != NULL) {
+ 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) {
+ 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) {
+ if (ret < 0)
+ {
// Handle parsing errors
FURI_LOG_E("JSMM.H", "Failed to parse JSON: %d", ret);
free(tokens);
@@ -423,19 +474,23 @@ char* get_json_value(char* key, char* json_data, uint32_t max_tokens) {
}
// Ensure that the root element is an object
- if(ret < 1 || tokens[0].type != JSMN_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) {
+ 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) {
+ char *value = malloc(length + 1);
+ if (value == NULL)
+ {
FURI_LOG_E("JSMM.H", "Failed to allocate memory for value.");
free(tokens);
return NULL;
@@ -450,7 +505,9 @@ char* get_json_value(char* key, char* json_data, uint32_t max_tokens) {
// Free the token array if key was not found
free(tokens);
- } else {
+ }
+ else
+ {
FURI_LOG_E("JSMM.H", "JSON data is NULL");
}
FURI_LOG_E("JSMM.H", "Failed to find the key in the JSON.");
@@ -458,10 +515,12 @@ 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) {
+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) {
+ 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;
}
@@ -471,8 +530,9 @@ char* get_json_array_value(char* key, uint32_t index, char* json_data, uint32_t
jsmn_init(&parser);
// Allocate memory for JSON tokens
- jsmntok_t* tokens = malloc(sizeof(jsmntok_t) * max_tokens);
- if(tokens == NULL) {
+ 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;
@@ -480,7 +540,8 @@ char* get_json_array_value(char* key, uint32_t index, char* json_data, uint32_t
// Parse the JSON array
int ret = jsmn_parse(&parser, array_str, strlen(array_str), tokens, max_tokens);
- if(ret < 0) {
+ if (ret < 0)
+ {
FURI_LOG_E("JSMM.H", "Failed to parse JSON array: %d", ret);
free(tokens);
free(array_str);
@@ -488,7 +549,8 @@ char* get_json_array_value(char* key, uint32_t index, char* json_data, uint32_t
}
// Ensure the root element is an array
- if(ret < 1 || tokens[0].type != JSMN_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);
@@ -496,12 +558,9 @@ char* get_json_array_value(char* key, uint32_t index, char* json_data, uint32_t
}
// 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);
+ 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;
@@ -509,20 +568,27 @@ char* get_json_array_value(char* key, uint32_t index, char* json_data, uint32_t
// 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 (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) {
+ }
+ else if (tokens[current_token].type == JSMN_ARRAY)
+ {
// For nested arrays, skip all elements
current_token += 1 + tokens[current_token].size;
- } else {
+ }
+ else
+ {
// For primitive types, simply move to the next token
current_token += 1;
}
// Safety check to prevent out-of-bounds
- if(current_token >= ret) {
+ if (current_token >= ret)
+ {
FURI_LOG_E("JSMM.H", "Unexpected end of tokens while traversing array.");
free(tokens);
free(array_str);
@@ -533,8 +599,9 @@ char* get_json_array_value(char* key, uint32_t index, char* json_data, uint32_t
// Extract the array element
jsmntok_t element = tokens[current_token];
int length = element.end - element.start;
- char* value = malloc(length + 1);
- if(value == NULL) {
+ 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);
@@ -553,10 +620,12 @@ char* get_json_array_value(char* key, uint32_t index, char* json_data, uint32_t
}
// 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) {
+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) {
+ 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;
}
@@ -566,8 +635,9 @@ char** get_json_array_values(char* key, char* json_data, uint32_t max_tokens, in
jsmn_init(&parser);
// Allocate memory for JSON tokens
- jsmntok_t* tokens = malloc(sizeof(jsmntok_t) * max_tokens); // Allocate on the heap
- if(tokens == NULL) {
+ 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;
@@ -575,7 +645,8 @@ char** get_json_array_values(char* key, char* json_data, uint32_t max_tokens, in
// Parse the JSON array
int ret = jsmn_parse(&parser, array_str, strlen(array_str), tokens, max_tokens);
- if(ret < 0) {
+ if (ret < 0)
+ {
FURI_LOG_E("JSMM.H", "Failed to parse JSON array: %d", ret);
free(tokens);
free(array_str);
@@ -583,7 +654,8 @@ char** get_json_array_values(char* key, char* json_data, uint32_t max_tokens, in
}
// Ensure the root element is an array
- if(tokens[0].type != JSMN_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);
@@ -592,8 +664,9 @@ char** get_json_array_values(char* key, char* json_data, uint32_t max_tokens, in
// 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) {
+ 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);
@@ -604,15 +677,18 @@ char** get_json_array_values(char* key, char* json_data, uint32_t max_tokens, in
// 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) {
+ 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) {
+ 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;
@@ -622,10 +698,12 @@ char** get_json_array_values(char* key, char* json_data, uint32_t max_tokens, in
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) {
+ 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++) {
+ for (int j = 0; j < actual_num_values; j++)
+ {
free(values[j]);
}
free(values);
@@ -647,14 +725,17 @@ char** get_json_array_values(char* key, char* json_data, uint32_t max_tokens, in
*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) {
+ 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++) {
+ for (int i = actual_num_values; i < array_size; i++)
+ {
free(values[i]);
}
}
diff --git a/flip_library/jsmn/jsmn.h b/flip_library/jsmn/jsmn.h
index cd95a0e58..74cdccf95 100644
--- a/flip_library/jsmn/jsmn.h
+++ b/flip_library/jsmn/jsmn.h
@@ -19,7 +19,8 @@
#include
#ifdef __cplusplus
-extern "C" {
+extern "C"
+{
#endif
#ifdef JSMN_STATIC
@@ -28,71 +29,71 @@ extern "C" {
#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
-};
-
-/**
+ 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;
+ typedef struct
+ {
+ jsmntype_t type;
+ int start;
+ int end;
+ int size;
#ifdef JSMN_PARENT_LINKS
- int parent;
+ int parent;
#endif
-} jsmntok_t;
+ } 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;
-
-/**
+ 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);
+ 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);
+ 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 */
@@ -116,16 +117,16 @@ JSMN_API int jsmn_parse(
#include
// Helper function to create a JSON object
-char* jsmn(const char* key, const char* value);
+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);
+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);
+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);
+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);
+char **get_json_array_values(char *key, char *json_data, uint32_t max_tokens, int *num_values);
#endif /* JB_JSMN_EDIT */
diff --git a/flip_social/README.md b/flip_social/README.md
index 7f29ee275..5b09349b1 100644
--- a/flip_social/README.md
+++ b/flip_social/README.md
@@ -3,12 +3,9 @@ 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/WebCrawler-FlipperZero/tree/main/assets/FlipperHTTP
-
## Requirements
- WiFi Developer Board, Raspberry Pi, or ESP32 Device with FlipperHTTP Flash: https://github.com/jblanked/FlipperHTTP
-- WiFi Access Point
-
+- 2.4 Ghz WiFi Access Point
## Features
- Login/Logout
@@ -16,15 +13,16 @@ FlipSocial uses the FlipperHTTP flash for the WiFi Devboard, first introduced in
- Feed
- Profile
- Customizable Pre-Saves
-- Explore (NEW)
-- Friends (NEW)
-- Direct Messaging (NEW)
+- Explore
+- Friends
+- Direct Messaging
+
**Login/Logout:** Log in to your account to view and post on the Feed. You can also change your password and log out when needed.
**Registration:** Create an account with just a username and password—no email or personal information required or collected.
-**Feed:** View up to 50 of the latest posts, create your own posts, and "Flip" a post—FlipSocial’s version of liking or favoriting a post.
+**Feed:** View the latest posts, create your own posts, and "Flip" a post—FlipSocial’s version of liking or favoriting a post.
**Customizable Pre-Saves:** The biggest challenge with a social media app on the Flipper Zero is using only the directional pad for input. To address this, I implemented a pre-saved text system. The pre-saves are stored in a pre_saved_messages.txt file on your SD card. You can edit the pre-saves by opening qFlipper, downloading the file from the /apps_data/flip_social/ folder, adding your pre-saves (separated by new lines), and then copying it back to your SD card. You can also create pre-saves directly within the app.
@@ -32,66 +30,4 @@ FlipSocial uses the FlipperHTTP flash for the WiFi Devboard, first introduced in
**Friends:** View and remove friends.
-**Direct Messaging:** Send direct messages to other Flipper users and view your conversations.
-
-## Roadmap
-**v0.2**
-- Stability Patch
-
-**v0.3**
-- Explore Page
-- Friends
-
-**v0.4**
-- Direct Messaging
-
-**v0.5**
-- Improve memory allocation
-- Improve Feed Page
-- Raspberry Pi Pico W Support
-
-**v0.6**
-- Improve memory allocation
-- Update the Direct Messaging View
-- Update the Pre-Save View
-
-**v0.7**
-- Improve memory allocation
-- Loading screens.
-
-**v0.8**
-- Improve User Profile
-- Improve Explore Page
-
-**v1.0**
-- Official Release
-
-## Contribution
-This is a big project, and I welcome all contributors, especially developers interested in animations and graphics. Fork the repository, create a pull request, and I will review your edits.
-
-## Known Bugs
-1. When clicking any button other than the BACK button in the Feed view, post creation view, messages view, or the friends view, the app doesn't respond to inputs.
-- **Solution:** Restart your Flipper device.
-
-2. When trying to log in, the app shows "Awaiting response..." and nothing happens for more than 30 seconds.
-- **Solution 1:** Update your WiFi credentials. Sometimes, you just need to hit Save again on either the SSID or password.
-- **Solution 2:** Ensure your WiFi Devboard is plugged in, then restart the app.
-- **Solution 3:** Ensure your WiFi Devboard is plugged in, then restart your Flipper device.
-
-3. When accessing the Feed, I keep getting the message "Either the feed didn’t load or there was a server error."
-- **Solution 1:** Update your WiFi credentials. Sometimes, you just need to hit Save again on either the SSID or password.
-- **Solution 2:** Ensure your WiFi Devboard is plugged in, then restart the app.
-- **Solution 3:** Ensure your WiFi Devboard is plugged in, then restart your Flipper device.
-
-4. The Feed is empty.
-- **Solution 1:** Update your WiFi credentials. Sometimes, you just need to hit Save again on either the SSID or password.
-- **Solution 2:** Ensure your WiFi Devboard is plugged in, then restart the app.
-- **Solution 3:** Ensure your WiFi Devboard is plugged in, then restart your Flipper device.
-
-5. Out of memory when starting the app or after visiting the feed and post views back-to-back.
-- **Solution 1:** Restart your Flipper device.
-- **Solution 2:** Update the app to version 0.7 (or higher).
-
-6. I can no longer access the Messages.
-- **Solution 1:** Uppdate the app to version 0.6.3 (or higher)
-- **Solution 2:** Click the logout button then login again. Make sure your password is correct before clicking "Login".
+**Direct Messaging:** Send direct messages to other Flipper users and view your conversations.
\ No newline at end of file
diff --git a/flip_social/alloc/alloc.c b/flip_social/alloc/alloc.c
new file mode 100644
index 000000000..7966b35a0
--- /dev/null
+++ b/flip_social/alloc/alloc.c
@@ -0,0 +1,894 @@
+#include
+bool went_to_friends = false;
+void auth_headers_alloc(void)
+{
+ if (!app_instance)
+ {
+ snprintf(auth_headers, sizeof(auth_headers), "{\"Content-Type\":\"application/json\"}");
+ return;
+ }
+
+ 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)
+ {
+ snprintf(auth_headers, sizeof(auth_headers), "{\"Content-Type\":\"application/json\",\"username\":\"%s\",\"password\":\"%s\"}", app_instance->login_username_logged_out, app_instance->login_password_logged_out);
+ }
+ else if (app_instance->login_username_logged_in && app_instance->change_password_logged_in && strlen(app_instance->login_username_logged_in) > 0 && strlen(app_instance->change_password_logged_in) > 0)
+ {
+ snprintf(auth_headers, sizeof(auth_headers), "{\"Content-Type\":\"application/json\",\"username\":\"%s\",\"password\":\"%s\"}", app_instance->login_username_logged_in, app_instance->change_password_logged_in);
+ }
+ else
+ {
+ snprintf(auth_headers, sizeof(auth_headers), "{\"Content-Type\":\"application/json\"}");
+ }
+}
+
+FlipSocialFeedMini *flip_feed_info_alloc(void)
+{
+ FlipSocialFeedMini *feed_info = (FlipSocialFeedMini *)malloc(sizeof(FlipSocialFeedMini));
+ if (!feed_info)
+ {
+ FURI_LOG_E(TAG, "Failed to allocate memory for feed_info");
+ return NULL;
+ }
+ feed_info->count = 0;
+ feed_info->index = 0;
+ return feed_info;
+}
+bool messages_dialog_alloc(bool free_first)
+{
+ if (free_first)
+ {
+ flip_social_free_messages_dialog();
+ }
+ if (!app_instance->dialog_messages)
+ {
+ if (!easy_flipper_set_dialog_ex(
+ &app_instance->dialog_messages,
+ FlipSocialViewMessagesDialog,
+ flip_social_messages->usernames[flip_social_messages->index],
+ 0,
+ 0,
+ updated_user_message(flip_social_messages->messages[flip_social_messages->index]),
+ 0,
+ 10,
+ flip_social_messages->index != 0 ? "Prev" : NULL,
+ flip_social_messages->index != flip_social_messages->count - 1 ? "Next" : NULL,
+ "Create",
+ messages_dialog_callback,
+ flip_social_callback_to_messages_logged_in,
+ &app_instance->view_dispatcher,
+ app_instance))
+ {
+ return false;
+ }
+ return true;
+ }
+ return false;
+}
+char *updated_user_message(const char *user_message)
+{
+ if (user_message == NULL)
+ {
+ FURI_LOG_E(TAG, "User message is NULL.");
+ return NULL;
+ }
+
+ size_t msg_length = strlen(user_message);
+ size_t start = 0;
+ int line_num = 0;
+
+ // Allocate memory for the updated message
+ char *updated_message = malloc(MAX_MESSAGE_LENGTH + 10);
+ if (updated_message == NULL)
+ {
+ FURI_LOG_E(TAG, "Failed to allocate memory for updated_message.");
+ return NULL;
+ }
+ size_t current_pos = 0; // Tracks the current position in updated_message
+ updated_message[0] = '\0'; // Initialize as empty string
+
+ while (start < msg_length && line_num < 4)
+ {
+ size_t remaining = msg_length - start;
+ size_t len = (remaining > MAX_LINE_LENGTH) ? MAX_LINE_LENGTH : remaining;
+
+ // Adjust length to the last space if the line exceeds MAX_LINE_LENGTH
+ if (remaining > MAX_LINE_LENGTH)
+ {
+ 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
+ }
+ }
+
+ // Check if the new line fits in the updated_message buffer
+ if (current_pos + len + 1 >= (MAX_MESSAGE_LENGTH + 10))
+ {
+ FURI_LOG_E(TAG, "Updated message exceeds maximum length.");
+ // break and return what we have so far
+ break;
+ }
+
+ // Copy the line and append a newline character
+ memcpy(updated_message + current_pos, user_message + start, len);
+ current_pos += len;
+ updated_message[current_pos++] = '\n'; // Append newline
+
+ // 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++;
+ }
+
+ // Null-terminate the final string
+ if (current_pos < (MAX_MESSAGE_LENGTH + 10))
+ {
+ updated_message[current_pos] = '\0';
+ }
+ else
+ {
+ FURI_LOG_E(TAG, "Buffer overflow while null-terminating.");
+ free(updated_message);
+ return NULL;
+ }
+
+ return updated_message;
+}
+
+typedef enum
+{
+ ActionNone,
+ ActionBack,
+ ActionNext,
+ ActionPrev,
+ ActionFlip,
+} Action;
+
+static Action action = ActionNone;
+
+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;
+ }
+}
+
+// Make sure to define a suitable MAX_LINE_LENGTH
+// For example:
+
+#define MAX_LINES 6
+#define LINE_HEIGHT 8
+#define MAX_LINE_WIDTH_PX 128
+#define TEMP_BUF_SIZE 128
+
+static void draw_user_message(Canvas *canvas, const char *user_message, int x, int y)
+{
+ if (!user_message)
+ {
+ FURI_LOG_E(TAG, "User message is NULL.");
+ return;
+ }
+
+ // We will read through user_message and extract words manually
+ const char *p = user_message;
+
+ // Skip leading spaces
+ while (*p == ' ')
+ p++;
+
+ char line[TEMP_BUF_SIZE];
+ size_t line_len = 0;
+ line[0] = '\0';
+ int line_num = 0;
+
+ while (*p && line_num < MAX_LINES)
+ {
+ // Find the end of the next word
+ const char *word_start = p;
+ while (*p && *p != ' ')
+ p++;
+ size_t word_len = p - word_start;
+
+ // Extract the word into a temporary buffer
+ char word[TEMP_BUF_SIZE];
+ if (word_len > TEMP_BUF_SIZE - 1)
+ {
+ word_len = TEMP_BUF_SIZE - 1; // Just to avoid overflow if extremely large
+ }
+ memcpy(word, word_start, word_len);
+ word[word_len] = '\0';
+
+ // Skip trailing spaces for the next iteration
+ while (*p == ' ')
+ p++;
+
+ if (word_len == 0)
+ {
+ // Empty word (consecutive spaces?), just continue
+ continue;
+ }
+
+ // Check how the word fits into the current line
+ char test_line[TEMP_BUF_SIZE + 128];
+ if (line_len == 0)
+ {
+ // If line is empty, the line would just be this word
+ strncpy(test_line, word, sizeof(test_line) - 1);
+ test_line[sizeof(test_line) - 1] = '\0';
+ }
+ else
+ {
+ // If not empty, we add a space and then the word
+ snprintf(test_line, sizeof(test_line), "%s %s", line, word);
+ }
+
+ uint16_t width = canvas_string_width(canvas, test_line);
+ if (width <= MAX_LINE_WIDTH_PX)
+ {
+ // The word fits on this line
+ strcpy(line, test_line);
+ line_len = strlen(line);
+ }
+ else
+ {
+ // The word doesn't fit on this line
+ // First, draw the current line if it's not empty
+ if (line_len > 0)
+ {
+ canvas_draw_str_aligned(canvas, x, y + line_num * LINE_HEIGHT, AlignLeft, AlignTop, line);
+ line_num++;
+ if (line_num >= MAX_LINES)
+ break;
+ }
+
+ // Now we try to put the current word on a new line
+ // Check if the word itself fits on an empty line
+ width = canvas_string_width(canvas, word);
+ if (width <= MAX_LINE_WIDTH_PX)
+ {
+ // The whole word fits on a new line
+ strcpy(line, word);
+ line_len = word_len;
+ }
+ else
+ {
+ // The word alone doesn't fit. We must truncate it.
+ // We'll find the largest substring of the word that fits.
+ size_t truncate_len = word_len;
+ while (truncate_len > 0)
+ {
+ char truncated[TEMP_BUF_SIZE];
+ strncpy(truncated, word, truncate_len);
+ truncated[truncate_len] = '\0';
+ if (canvas_string_width(canvas, truncated) <= MAX_LINE_WIDTH_PX)
+ {
+ // Found a substring that fits
+ strcpy(line, truncated);
+ line_len = truncate_len;
+ break;
+ }
+ truncate_len--;
+ }
+
+ if (line_len == 0)
+ {
+ // Could not fit a single character. Skip this word.
+ }
+ }
+ }
+ }
+
+ // Draw any remaining text in the buffer if we have lines left
+ if (line_len > 0 && line_num < MAX_LINES)
+ {
+ canvas_draw_str_aligned(canvas, x, y + line_num * LINE_HEIGHT, AlignLeft, AlignTop, line);
+ }
+}
+
+static void flip_social_feed_draw_callback(Canvas *canvas, void *model)
+{
+ UNUSED(model);
+ canvas_clear(canvas);
+ canvas_set_font_custom(canvas, FONT_SIZE_LARGE);
+ canvas_draw_str(canvas, 0, 7, flip_feed_item->username);
+ canvas_set_font_custom(canvas, FONT_SIZE_MEDIUM);
+ draw_user_message(canvas, flip_feed_item->message, 0, 12);
+ canvas_set_font_custom(canvas, FONT_SIZE_SMALL);
+ char flip_message[32];
+ snprintf(flip_message, sizeof(flip_message), "%u %s", flip_feed_item->flips, flip_feed_item->flips == 1 ? "flip" : "flips");
+ canvas_draw_str(canvas, 0, 60, flip_message); // Draw the number of flips
+ char flip_status[16];
+ snprintf(flip_status, sizeof(flip_status), flip_feed_item->is_flipped ? "Unflip" : "Flip");
+ canvas_draw_str(canvas, 32, 60, flip_status); // Draw the flip status
+ canvas_draw_str(canvas, 64, 60, flip_feed_item->date_created); // Draw the date
+}
+
+static bool flip_social_feed_input_callback(InputEvent *event, void *context)
+{
+ UNUSED(context);
+ furi_assert(app_instance);
+
+ // if back button is pressed
+ if (event->type == InputTypePress && event->key == InputKeyBack)
+ {
+ view_dispatcher_switch_to_view(app_instance->view_dispatcher, FlipSocialViewLoggedInSubmenu);
+ return true;
+ }
+
+ if (event->type == InputTypePress && event->key == InputKeyLeft) // Previous message
+ {
+ if (flip_feed_info->index > 0)
+ {
+ flip_feed_info->index--;
+ }
+ // switch view, free dialog, re-alloc dialog, switch back to dialog
+ view_dispatcher_switch_to_view(app_instance->view_dispatcher, FlipSocialViewWidgetResult);
+ flip_social_free_feed_view();
+ // load feed item
+ if (!flip_social_load_feed_post(flip_feed_info->ids[flip_feed_info->index]))
+ {
+ FURI_LOG_E(TAG, "Failed to load nexy feed post");
+ fhttp.state = ISSUE;
+ return false;
+ }
+ if (feed_view_alloc())
+ {
+ view_dispatcher_switch_to_view(app_instance->view_dispatcher, FlipSocialViewLoggedInFeed);
+ }
+ else
+ {
+ FURI_LOG_E(TAG, "Failed to allocate feed dialog");
+ fhttp.state = ISSUE;
+ return false;
+ }
+ }
+ else if (event->type == InputTypePress && event->key == InputKeyRight) // Next message
+ {
+ // if next message is the last message, then use flip_social_load_initial_feed
+ if (flip_feed_info->index == flip_feed_info->count - 1)
+ {
+ char series_index[16];
+ load_char("series_index", series_index, sizeof(series_index));
+ flip_feed_info->series_index = atoi(series_index) + 1;
+ char new_series_index[16];
+ snprintf(new_series_index, sizeof(new_series_index), "%d", flip_feed_info->series_index);
+
+ save_char("series_index", new_series_index);
+
+ if (!flip_social_load_initial_feed(true, flip_feed_info->series_index))
+ {
+ FURI_LOG_E(TAG, "Failed to load initial feed");
+ fhttp.state = ISSUE;
+ return false;
+ }
+ // switch view, free dialog, re-alloc dialog, switch back to dialog
+ view_dispatcher_switch_to_view(app_instance->view_dispatcher, FlipSocialViewWidgetResult);
+ flip_social_free_feed_view();
+ // load feed item
+ if (!flip_social_load_feed_post(flip_feed_info->ids[flip_feed_info->index]))
+ {
+ FURI_LOG_E(TAG, "Failed to load nexy feed post");
+ fhttp.state = ISSUE;
+ return false;
+ }
+ if (feed_view_alloc())
+ {
+ view_dispatcher_switch_to_view(app_instance->view_dispatcher, FlipSocialViewLoggedInFeed);
+ }
+ else
+ {
+ FURI_LOG_E(TAG, "Failed to allocate feed dialog");
+ fhttp.state = ISSUE;
+ return false;
+ }
+ }
+ if (flip_feed_info->index < flip_feed_info->count - 1)
+ {
+ flip_feed_info->index++;
+ }
+ // switch view, free dialog, re-alloc dialog, switch back to dialog
+ view_dispatcher_switch_to_view(app_instance->view_dispatcher, FlipSocialViewWidgetResult);
+ flip_social_free_feed_view();
+ // load feed item
+ if (!flip_social_load_feed_post(flip_feed_info->ids[flip_feed_info->index]))
+ {
+ FURI_LOG_E(TAG, "Failed to load nexy feed post");
+ fhttp.state = ISSUE;
+ return false;
+ }
+ if (feed_view_alloc())
+ {
+ view_dispatcher_switch_to_view(app_instance->view_dispatcher, FlipSocialViewLoggedInFeed);
+ }
+ else
+ {
+ FURI_LOG_E(TAG, "Failed to allocate feed dialog");
+ fhttp.state = ISSUE;
+ return false;
+ }
+ }
+ else if (event->type == InputTypePress && event->key == InputKeyOk) // Flip/Unflip
+ {
+ // Moved to above the is_flipped check
+ if (!flip_feed_item->is_flipped)
+ {
+ // increase the flip count
+ flip_feed_item->flips++;
+ }
+ else
+ {
+ // decrease the flip count
+ if (flip_feed_item->flips > 0)
+ flip_feed_item->flips--;
+ }
+ // change the flip status
+ flip_feed_item->is_flipped = !flip_feed_item->is_flipped;
+
+ // send post request to flip the message
+ if (app_instance->login_username_logged_in == NULL)
+ {
+ FURI_LOG_E(TAG, "Username is NULL");
+ return false;
+ }
+ if (!flipper_http_init(flipper_http_rx_callback, app_instance))
+ {
+ FURI_LOG_E(TAG, "Failed to initialize FlipperHTTP");
+ return false;
+ }
+ auth_headers_alloc();
+ char payload[256];
+ snprintf(payload, sizeof(payload), "{\"username\":\"%s\",\"post_id\":\"%u\"}", app_instance->login_username_logged_in, flip_feed_item->id);
+ if (flipper_http_post_request_with_headers("https://www.flipsocial.net/api/feed/flip/", auth_headers, payload))
+ {
+ // save feed item
+ char new_save[512];
+ snprintf(new_save, sizeof(new_save), "{\"id\":%u,\"username\":\"%s\",\"message\":\"%s\",\"flip_count\":%u,\"flipped\":%s,\"date_created\":\"%s\"}",
+ flip_feed_item->id, flip_feed_item->username, flip_feed_item->message, flip_feed_item->flips, flip_feed_item->is_flipped ? "true" : "false", flip_feed_item->date_created);
+ char id[16];
+ snprintf(id, sizeof(id), "%u", flip_feed_item->id);
+ if (!flip_social_save_post(id, new_save))
+ {
+ FURI_LOG_E(TAG, "Failed to save the feed post");
+ flipper_http_deinit();
+ return false;
+ }
+ }
+ // switch view, free dialog, re-alloc dialog, switch back to dialog
+ view_dispatcher_switch_to_view(app_instance->view_dispatcher, FlipSocialViewWidgetResult);
+ flip_social_free_feed_view();
+ // load feed item
+ if (!flip_social_load_feed_post(flip_feed_info->ids[flip_feed_info->index]))
+ {
+ FURI_LOG_E(TAG, "Failed to load nexy feed post");
+ fhttp.state = ISSUE;
+ return false;
+ }
+ if (feed_view_alloc())
+ {
+ view_dispatcher_switch_to_view(app_instance->view_dispatcher, FlipSocialViewLoggedInFeed);
+ }
+ else
+ {
+ FURI_LOG_E(TAG, "Failed to allocate feed dialog");
+ }
+ flipper_http_deinit();
+ }
+ return false;
+}
+
+bool feed_view_alloc()
+{
+ if (!app_instance)
+ {
+ return false;
+ }
+ if (!flip_feed_item)
+ {
+ FURI_LOG_E(TAG, "Feed item is NULL");
+ return false;
+ }
+ flip_social_free_feed_view();
+ if (!app_instance->view_feed)
+ {
+ if (!easy_flipper_set_view(
+ &app_instance->view_feed,
+ FlipSocialViewLoggedInFeed,
+ flip_social_feed_draw_callback,
+ flip_social_feed_input_callback,
+ flip_social_callback_to_submenu_logged_in,
+ &app_instance->view_dispatcher,
+ app_instance))
+ {
+ return false;
+ }
+ return true;
+ }
+ return false;
+}
+
+bool alloc_text_input(uint32_t view_id)
+{
+ if (!app_instance)
+ {
+ return false;
+ }
+ if (!app_instance->text_input)
+ {
+ switch (view_id)
+ {
+ case FlipSocialViewLoggedOutWifiSettingsSSIDInput:
+ // memset(app_instance->wifi_ssid_logged_out_temp_buffer, 0, app_instance->wifi_ssid_logged_out_temp_buffer_size);
+ if (!easy_flipper_set_uart_text_input(&app_instance->text_input, FlipSocialViewTextInput, "Enter SSID", app_instance->wifi_ssid_logged_out_temp_buffer, app_instance->wifi_ssid_logged_out_temp_buffer_size, flip_social_logged_out_wifi_settings_ssid_updated, flip_social_callback_to_wifi_settings_logged_out, &app_instance->view_dispatcher, app_instance))
+ {
+ return false;
+ }
+ break;
+ case FlipSocialViewLoggedOutWifiSettingsPasswordInput:
+ // memset(app_instance->wifi_password_logged_out_temp_buffer, 0, app_instance->wifi_password_logged_out_temp_buffer_size);
+ if (!easy_flipper_set_uart_text_input(&app_instance->text_input, FlipSocialViewTextInput, "Enter Password", app_instance->wifi_password_logged_out_temp_buffer, app_instance->wifi_password_logged_out_temp_buffer_size, flip_social_logged_out_wifi_settings_password_updated, flip_social_callback_to_wifi_settings_logged_out, &app_instance->view_dispatcher, app_instance))
+ {
+ return false;
+ }
+ break;
+ case FlipSocialViewLoggedOutLoginUsernameInput:
+ // memset(app_instance->login_username_logged_out_temp_buffer, 0, app_instance->login_username_logged_out_temp_buffer_size);
+ if (!easy_flipper_set_uart_text_input(&app_instance->text_input, FlipSocialViewTextInput, "Enter Username", app_instance->login_username_logged_out_temp_buffer, app_instance->login_username_logged_out_temp_buffer_size, flip_social_logged_out_login_username_updated, flip_social_callback_to_login_logged_out, &app_instance->view_dispatcher, app_instance))
+ {
+ return false;
+ }
+ break;
+ case FlipSocialViewLoggedOutLoginPasswordInput:
+ // memset(app_instance->login_password_logged_out_temp_buffer, 0, app_instance->login_password_logged_out_temp_buffer_size);
+ if (!easy_flipper_set_uart_text_input(&app_instance->text_input, FlipSocialViewTextInput, "Enter Password", app_instance->login_password_logged_out_temp_buffer, app_instance->login_password_logged_out_temp_buffer_size, flip_social_logged_out_login_password_updated, flip_social_callback_to_login_logged_out, &app_instance->view_dispatcher, app_instance))
+ {
+ return false;
+ }
+ break;
+ case FlipSocialViewLoggedOutRegisterUsernameInput:
+ memset(app_instance->register_username_logged_out_temp_buffer, 0, app_instance->register_username_logged_out_temp_buffer_size);
+ if (!easy_flipper_set_uart_text_input(&app_instance->text_input, FlipSocialViewTextInput, "Enter Username", app_instance->register_username_logged_out_temp_buffer, app_instance->register_username_logged_out_temp_buffer_size, flip_social_logged_out_register_username_updated, flip_social_callback_to_register_logged_out, &app_instance->view_dispatcher, app_instance))
+ {
+ return false;
+ }
+ break;
+ case FlipSocialViewLoggedOutRegisterPasswordInput:
+ memset(app_instance->register_password_logged_out_temp_buffer, 0, app_instance->register_password_logged_out_temp_buffer_size);
+ if (!easy_flipper_set_uart_text_input(&app_instance->text_input, FlipSocialViewTextInput, "Enter Password", app_instance->register_password_logged_out_temp_buffer, app_instance->register_password_logged_out_temp_buffer_size, flip_social_logged_out_register_password_updated, flip_social_callback_to_register_logged_out, &app_instance->view_dispatcher, app_instance))
+ {
+ return false;
+ }
+ break;
+ case FlipSocialViewLoggedOutRegisterPassword2Input:
+ memset(app_instance->register_password_2_logged_out_temp_buffer, 0, app_instance->register_password_2_logged_out_temp_buffer_size);
+ if (!easy_flipper_set_uart_text_input(&app_instance->text_input, FlipSocialViewTextInput, "Confirm Password", app_instance->register_password_2_logged_out_temp_buffer, app_instance->register_password_2_logged_out_temp_buffer_size, flip_social_logged_out_register_password_2_updated, flip_social_callback_to_register_logged_out, &app_instance->view_dispatcher, app_instance))
+ {
+ return false;
+ }
+ break;
+ case FlipSocialViewLoggedInChangePasswordInput:
+ // memset(app_instance->change_password_logged_in_temp_buffer, 0, app_instance->change_password_logged_in_temp_buffer_size);
+ if (!easy_flipper_set_uart_text_input(&app_instance->text_input, FlipSocialViewTextInput, "Change Password", app_instance->change_password_logged_in_temp_buffer, app_instance->change_password_logged_in_temp_buffer_size, flip_social_logged_in_profile_change_password_updated, flip_social_callback_to_profile_logged_in, &app_instance->view_dispatcher, app_instance))
+ {
+ return false;
+ }
+ break;
+ case FlipSocialViewLoggedInChangeBioInput:
+ // memset(app_instance->change_bio_logged_in_temp_buffer, 0, app_instance->change_bio_logged_in_temp_buffer_size);
+ if (!easy_flipper_set_uart_text_input(&app_instance->text_input, FlipSocialViewTextInput, "Bio", app_instance->change_bio_logged_in_temp_buffer, app_instance->change_bio_logged_in_temp_buffer_size, flip_social_logged_in_profile_change_bio_updated, flip_social_callback_to_profile_logged_in, &app_instance->view_dispatcher, app_instance))
+ {
+ return false;
+ }
+ break;
+ case FlipSocialViewLoggedInComposeAddPreSaveInput:
+ memset(app_instance->compose_pre_save_logged_in_temp_buffer, 0, app_instance->compose_pre_save_logged_in_temp_buffer_size);
+ if (!easy_flipper_set_uart_text_input(&app_instance->text_input, FlipSocialViewTextInput, "Enter Pre-Save Message", app_instance->compose_pre_save_logged_in_temp_buffer, app_instance->compose_pre_save_logged_in_temp_buffer_size, flip_social_logged_in_compose_pre_save_updated, flip_social_callback_to_compose_logged_in, &app_instance->view_dispatcher, app_instance))
+ {
+ return false;
+ }
+ break;
+ case FlipSocialViewLoggedInWifiSettingsSSIDInput:
+ // memset(app_instance->wifi_ssid_logged_in_temp_buffer, 0, app_instance->wifi_ssid_logged_in_temp_buffer_size);
+ if (!easy_flipper_set_uart_text_input(&app_instance->text_input, FlipSocialViewTextInput, "Enter SSID", app_instance->wifi_ssid_logged_in_temp_buffer, app_instance->wifi_ssid_logged_in_temp_buffer_size, flip_social_logged_in_wifi_settings_ssid_updated, flip_social_callback_to_wifi_settings_logged_in, &app_instance->view_dispatcher, app_instance))
+ {
+ return false;
+ }
+ break;
+ case FlipSocialViewLoggedInWifiSettingsPasswordInput:
+ // memset(app_instance->wifi_password_logged_in_temp_buffer, 0, app_instance->wifi_password_logged_in_temp_buffer_size);
+ if (!easy_flipper_set_uart_text_input(&app_instance->text_input, FlipSocialViewTextInput, "Enter Password", app_instance->wifi_password_logged_in_temp_buffer, app_instance->wifi_password_logged_in_temp_buffer_size, flip_social_logged_in_wifi_settings_password_updated, flip_social_callback_to_wifi_settings_logged_in, &app_instance->view_dispatcher, app_instance))
+ {
+ return false;
+ }
+ break;
+ case FlipSocialViewLoggedInMessagesNewMessageInput:
+ memset(app_instance->messages_new_message_logged_in_temp_buffer, 0, app_instance->messages_new_message_logged_in_temp_buffer_size);
+ if (!easy_flipper_set_uart_text_input(&app_instance->text_input, FlipSocialViewTextInput, "Enter Message", app_instance->messages_new_message_logged_in_temp_buffer, app_instance->messages_new_message_logged_in_temp_buffer_size, flip_social_logged_in_messages_new_message_updated, flip_social_callback_to_messages_logged_in, &app_instance->view_dispatcher, app_instance))
+ {
+ return false;
+ }
+ break;
+ case FlipSocialViewLoggedInMessagesNewMessageUserChoicesInput:
+ memset(app_instance->message_user_choice_logged_in_temp_buffer, 0, app_instance->message_user_choice_logged_in_temp_buffer_size);
+ if (!easy_flipper_set_uart_text_input(&app_instance->text_input, FlipSocialViewTextInput, "Enter Message", app_instance->message_user_choice_logged_in_temp_buffer, app_instance->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_instance->view_dispatcher, app_instance))
+ {
+ return false;
+ }
+ break;
+ case FlipSocialViewLoggedInExploreInput:
+ memset(app_instance->explore_logged_in_temp_buffer, 0, app_instance->explore_logged_in_temp_buffer_size);
+ if (!easy_flipper_set_uart_text_input(&app_instance->text_input, FlipSocialViewTextInput, "Enter Username or Keyword", app_instance->explore_logged_in_temp_buffer, app_instance->explore_logged_in_temp_buffer_size, flip_social_logged_in_explore_updated, flip_social_callback_to_submenu_logged_in, &app_instance->view_dispatcher, app_instance))
+ {
+ return false;
+ }
+ break;
+ case FlipSocialViewLoggedInMessageUsersInput:
+ memset(app_instance->message_users_logged_in_temp_buffer, 0, app_instance->message_users_logged_in_temp_buffer_size);
+ if (!easy_flipper_set_uart_text_input(&app_instance->text_input, FlipSocialViewTextInput, "Enter Username or Keyword", app_instance->message_users_logged_in_temp_buffer, app_instance->message_users_logged_in_temp_buffer_size, flip_social_logged_in_message_users_updated, flip_social_callback_to_submenu_logged_in, &app_instance->view_dispatcher, app_instance))
+ {
+ return false;
+ }
+ break;
+ default:
+ return false;
+ }
+ }
+ return true;
+}
+
+bool about_widget_alloc(bool is_logged_in)
+{
+ if (!is_logged_in)
+ {
+ if (!app_instance->widget_logged_out_about)
+ {
+ return easy_flipper_set_widget(&app_instance->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_instance->view_dispatcher);
+ }
+ }
+ else
+ {
+ if (!app_instance->widget_logged_in_about)
+ {
+ return easy_flipper_set_widget(&app_instance->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_instance->view_dispatcher);
+ }
+ }
+ return true;
+}
+
+bool alloc_submenu(uint32_t view_id)
+{
+ if (!app_instance)
+ {
+ return false;
+ }
+ if (!app_instance->submenu)
+ {
+ switch (view_id)
+ {
+ case FlipSocialViewLoggedInSettings:
+ if (!easy_flipper_set_submenu(&app_instance->submenu, FlipSocialViewSubmenu, "Settings", flip_social_callback_to_submenu_logged_in, &app_instance->view_dispatcher))
+ {
+ return false;
+ }
+ submenu_reset(app_instance->submenu);
+ submenu_add_item(app_instance->submenu, "About", FlipSocialSubmenuLoggedInIndexAbout, flip_social_callback_submenu_choices, app_instance);
+ submenu_add_item(app_instance->submenu, "WiFi", FlipSocialSubmenuLoggedInIndexWifiSettings, flip_social_callback_submenu_choices, app_instance);
+ submenu_add_item(app_instance->submenu, "User", FlipSocialSubmenuLoggedInIndexUserSettings, flip_social_callback_submenu_choices, app_instance);
+ break;
+ case FlipSocialViewLoggedInCompose:
+ if (!easy_flipper_set_submenu(&app_instance->submenu, FlipSocialViewSubmenu, "Create A Post", flip_social_callback_to_submenu_logged_in, &app_instance->view_dispatcher))
+ {
+ return false;
+ }
+ submenu_reset(app_instance->submenu);
+ submenu_add_item(app_instance->submenu, "Add Pre-Save", FlipSocialSubmenuComposeIndexAddPreSave, flip_social_callback_submenu_choices, app_instance);
+
+ // Load the playlist
+ if (load_playlist(&app_instance->pre_saved_messages))
+ {
+ // Update the playlist submenu
+ for (uint32_t i = 0; i < app_instance->pre_saved_messages.count; i++)
+ {
+ if (app_instance->pre_saved_messages.messages[i][0] != '\0') // Check if the string is not empty
+ {
+ submenu_add_item(app_instance->submenu, app_instance->pre_saved_messages.messages[i], FlipSocialSubemnuComposeIndexStartIndex + i, flip_social_callback_submenu_choices, app_instance);
+ }
+ }
+ }
+ break;
+ case FlipSocialViewLoggedInFriendsSubmenu:
+ if (!easy_flipper_set_submenu(&app_instance->submenu, FlipSocialViewSubmenu, "Friends", flip_social_callback_to_profile_logged_in, &app_instance->view_dispatcher))
+ {
+ FURI_LOG_E(TAG, "Failed to set submenu for friends");
+ return false;
+ }
+ submenu_reset(app_instance->submenu);
+ went_to_friends = true;
+ break;
+ case FlipSocialViewLoggedInMessagesUserChoices:
+ if (!easy_flipper_set_submenu(&app_instance->submenu, FlipSocialViewSubmenu, "Users", flip_social_callback_to_messages_logged_in, &app_instance->view_dispatcher))
+ {
+ FURI_LOG_E(TAG, "Failed to set submenu for user choices");
+ return false;
+ }
+ submenu_reset(app_instance->submenu);
+ break;
+ case FlipSocialViewLoggedInMessagesSubmenu:
+ if (!easy_flipper_set_submenu(&app_instance->submenu, FlipSocialViewSubmenu, "Messages", flip_social_callback_to_submenu_logged_in, &app_instance->view_dispatcher))
+ {
+ return false;
+ }
+ submenu_reset(app_instance->submenu);
+ break;
+ case FlipSocialViewLoggedInExploreSubmenu:
+ if (!easy_flipper_set_submenu(&app_instance->submenu, FlipSocialViewSubmenu, "Explore", flip_social_callback_to_submenu_logged_in, &app_instance->view_dispatcher))
+ {
+ return false;
+ }
+ submenu_reset(app_instance->submenu);
+ break;
+ }
+ }
+ return true;
+}
+static void flip_social_feed_type_change(VariableItem *item)
+{
+ uint8_t index = variable_item_get_current_value_index(item);
+ variable_item_set_current_value_text(item, flip_social_feed_type[index]);
+ flip_social_feed_type_index = index;
+ variable_item_set_current_value_index(item, index);
+
+ // save the feed type
+ save_char("user_feed_type", strstr(flip_social_feed_type[index], "Global") ? "global" : "friends");
+}
+static void flip_social_notification_type_change(VariableItem *item)
+{
+ uint8_t index = variable_item_get_current_value_index(item);
+ variable_item_set_current_value_text(item, flip_social_notification_type[index]);
+ flip_social_notification_type_index = index;
+ variable_item_set_current_value_index(item, index);
+
+ // save the notification type
+ save_char("user_notifications", strstr(flip_social_notification_type[index], "ON") ? "on" : "off");
+}
+
+bool alloc_variable_item_list(uint32_t view_id)
+{
+ if (!app_instance)
+ {
+ return false;
+ }
+ if (!app_instance->variable_item_list)
+ {
+ switch (view_id)
+ {
+ case FlipSocialViewLoggedOutWifiSettings:
+ if (!easy_flipper_set_variable_item_list(&app_instance->variable_item_list, FlipSocialViewVariableItemList, flip_social_text_input_logged_out_wifi_settings_item_selected, flip_social_callback_to_submenu_logged_out, &app_instance->view_dispatcher, app_instance))
+ {
+ return false;
+ }
+ app_instance->variable_item_logged_out_wifi_settings_ssid = variable_item_list_add(app_instance->variable_item_list, "SSID", 1, NULL, app_instance);
+ app_instance->variable_item_logged_out_wifi_settings_password = variable_item_list_add(app_instance->variable_item_list, "Password", 1, NULL, app_instance);
+ if (app_instance->wifi_ssid_logged_out)
+ variable_item_set_current_value_text(app_instance->variable_item_logged_out_wifi_settings_ssid, app_instance->wifi_ssid_logged_out);
+ return true;
+ case FlipSocialViewLoggedOutLogin:
+ if (!easy_flipper_set_variable_item_list(&app_instance->variable_item_list, FlipSocialViewVariableItemList, flip_social_text_input_logged_out_login_item_selected, flip_social_callback_to_submenu_logged_out, &app_instance->view_dispatcher, app_instance))
+ {
+ return false;
+ }
+ app_instance->variable_item_logged_out_login_username = variable_item_list_add(app_instance->variable_item_list, "Username", 1, NULL, app_instance);
+ app_instance->variable_item_logged_out_login_password = variable_item_list_add(app_instance->variable_item_list, "Password", 1, NULL, app_instance);
+ app_instance->variable_item_logged_out_login_button = variable_item_list_add(app_instance->variable_item_list, "Login", 0, NULL, app_instance);
+ if (app_instance->login_username_logged_out)
+ variable_item_set_current_value_text(app_instance->variable_item_logged_out_login_username, app_instance->login_username_logged_out);
+ return true;
+ case FlipSocialViewLoggedOutRegister:
+ if (!easy_flipper_set_variable_item_list(&app_instance->variable_item_list, FlipSocialViewVariableItemList, flip_social_text_input_logged_out_register_item_selected, flip_social_callback_to_submenu_logged_out, &app_instance->view_dispatcher, app_instance))
+ {
+ return false;
+ }
+ app_instance->variable_item_logged_out_register_username = variable_item_list_add(app_instance->variable_item_list, "Username", 1, NULL, app_instance);
+ app_instance->variable_item_logged_out_register_password = variable_item_list_add(app_instance->variable_item_list, "Password", 1, NULL, app_instance);
+ app_instance->variable_item_logged_out_register_password_2 = variable_item_list_add(app_instance->variable_item_list, "Confirm Password", 1, NULL, app_instance);
+ app_instance->variable_item_logged_out_register_button = variable_item_list_add(app_instance->variable_item_list, "Register", 0, NULL, app_instance);
+ return true;
+ case FlipSocialViewLoggedInProfile:
+ if (!easy_flipper_set_variable_item_list(&app_instance->variable_item_list, FlipSocialViewVariableItemList, flip_social_text_input_logged_in_profile_item_selected, flip_social_callback_to_submenu_logged_in, &app_instance->view_dispatcher, app_instance))
+ {
+ return false;
+ }
+ app_instance->variable_item_logged_in_profile_username = variable_item_list_add(app_instance->variable_item_list, "Username", 1, NULL, app_instance);
+ app_instance->variable_item_logged_in_profile_change_password = variable_item_list_add(app_instance->variable_item_list, "Password", 1, NULL, app_instance);
+ app_instance->variable_item_logged_in_profile_change_bio = variable_item_list_add(app_instance->variable_item_list, "Bio", 1, NULL, app_instance);
+ app_instance->variable_item_logged_in_profile_friends = variable_item_list_add(app_instance->variable_item_list, "Friends", 0, NULL, app_instance);
+ if (app_instance->login_username_logged_in)
+ variable_item_set_current_value_text(app_instance->variable_item_logged_in_profile_username, app_instance->login_username_logged_in);
+ if (app_instance->change_bio_logged_in)
+ variable_item_set_current_value_text(app_instance->variable_item_logged_in_profile_change_bio, app_instance->change_bio_logged_in);
+ return true;
+ case FlipSocialViewLoggedInSettingsWifi:
+ if (!easy_flipper_set_variable_item_list(&app_instance->variable_item_list, FlipSocialViewVariableItemList, flip_social_text_input_logged_in_wifi_settings_item_selected, flip_social_callback_to_settings_logged_in, &app_instance->view_dispatcher, app_instance))
+ {
+ return false;
+ }
+ app_instance->variable_item_logged_in_wifi_settings_ssid = variable_item_list_add(app_instance->variable_item_list, "SSID", 1, NULL, app_instance);
+ app_instance->variable_item_logged_in_wifi_settings_password = variable_item_list_add(app_instance->variable_item_list, "Password", 1, NULL, app_instance);
+ if (app_instance->wifi_ssid_logged_in)
+ variable_item_set_current_value_text(app_instance->variable_item_logged_in_wifi_settings_ssid, app_instance->wifi_ssid_logged_in);
+ return true;
+ case FlipSocialViewLoggedInSettingsUser:
+ if (!easy_flipper_set_variable_item_list(&app_instance->variable_item_list, FlipSocialViewVariableItemList, flip_social_logged_in_user_settings_item_selected, flip_social_callback_to_settings_logged_in, &app_instance->view_dispatcher, app_instance))
+ {
+ return false;
+ }
+ app_instance->variable_item_logged_in_user_settings_feed_type = variable_item_list_add(app_instance->variable_item_list, "Feed Type", 2, flip_social_feed_type_change, app_instance);
+ app_instance->variable_item_logged_in_user_settings_notifications = variable_item_list_add(app_instance->variable_item_list, "Notifications", 2, flip_social_notification_type_change, app_instance);
+ variable_item_set_current_value_text(app_instance->variable_item_logged_in_user_settings_feed_type, flip_social_feed_type[flip_social_feed_type_index]);
+ variable_item_set_current_value_index(app_instance->variable_item_logged_in_user_settings_feed_type, flip_social_feed_type_index);
+ variable_item_set_current_value_text(app_instance->variable_item_logged_in_user_settings_notifications, flip_social_notification_type[flip_social_notification_type_index]);
+ variable_item_set_current_value_index(app_instance->variable_item_logged_in_user_settings_notifications, flip_social_notification_type_index);
+ char user_feed_type[32];
+ char user_notifications[32];
+ if (load_char("user_feed_type", user_feed_type, sizeof(user_feed_type)))
+ {
+ flip_social_feed_type_index = strstr(user_feed_type, "friends") ? 1 : 0;
+ variable_item_set_current_value_text(app_instance->variable_item_logged_in_user_settings_feed_type, flip_social_feed_type[flip_social_feed_type_index]);
+ variable_item_set_current_value_index(app_instance->variable_item_logged_in_user_settings_feed_type, flip_social_feed_type_index);
+ }
+ if (load_char("user_notifications", user_notifications, sizeof(user_notifications)))
+ {
+ flip_social_notification_type_index = strstr(user_notifications, "on") ? 1 : 0;
+ variable_item_set_current_value_text(app_instance->variable_item_logged_in_user_settings_notifications, flip_social_notification_type[flip_social_notification_type_index]);
+ variable_item_set_current_value_index(app_instance->variable_item_logged_in_user_settings_notifications, flip_social_notification_type_index);
+ }
+ return true;
+ default:
+ return false;
+ }
+ }
+ return false;
+}
diff --git a/flip_social/alloc/alloc.h b/flip_social/alloc/alloc.h
new file mode 100644
index 000000000..10501aed5
--- /dev/null
+++ b/flip_social/alloc/alloc.h
@@ -0,0 +1,14 @@
+#pragma once
+#include
+#include
+#include
+void auth_headers_alloc(void);
+FlipSocialFeedMini *flip_feed_info_alloc(void);
+bool messages_dialog_alloc(bool free_first);
+bool feed_view_alloc();
+char *updated_user_message(const char *user_message);
+bool alloc_text_input(uint32_t view_id);
+bool about_widget_alloc(bool is_logged_in);
+bool alloc_variable_item_list(uint32_t view_id);
+bool alloc_submenu(uint32_t view_id);
+extern bool went_to_friends;
\ No newline at end of file
diff --git a/flip_social/alloc/flip_social_alloc.c b/flip_social/alloc/flip_social_alloc.c
index 1ea7cce42..56a14e4f2 100644
--- a/flip_social/alloc/flip_social_alloc.c
+++ b/flip_social/alloc/flip_social_alloc.c
@@ -8,13 +8,6 @@ FlipSocialApp *flip_social_app_alloc()
// 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))
{
@@ -27,7 +20,7 @@ FlipSocialApp *flip_social_app_alloc()
return NULL;
}
flip_social_loader_init(app->view_loader);
- if (!easy_flipper_set_widget(&app->widget_result, FlipSocialViewWidgetResult, "Error, try again.", flip_social_callback_to_submenu_logged_out, &app->view_dispatcher))
+ if (!easy_flipper_set_widget(&app->widget_result, FlipSocialViewWidgetResult, "", flip_social_callback_to_submenu_logged_out, &app->view_dispatcher))
{
return NULL;
}
@@ -199,16 +192,11 @@ FlipSocialApp *flip_social_app_alloc()
}
// Allocate Submenu(s)
- if (!easy_flipper_set_submenu(&app->submenu_logged_out, FlipSocialViewLoggedOutSubmenu, "FlipSocial v0.8", flip_social_callback_exit_app, &app->view_dispatcher))
+ if (!easy_flipper_set_submenu(&app->submenu_logged_out, FlipSocialViewLoggedOutSubmenu, VERSION_TAG, flip_social_callback_exit_app, &app->view_dispatcher))
{
return NULL;
}
- if (!easy_flipper_set_submenu(&app->submenu_logged_in, FlipSocialViewLoggedInSubmenu, "FlipSocial v0.8", flip_social_callback_exit_app, &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))
+ if (!easy_flipper_set_submenu(&app->submenu_logged_in, FlipSocialViewLoggedInSubmenu, VERSION_TAG, flip_social_callback_exit_app, &app->view_dispatcher))
{
return NULL;
}
@@ -226,123 +214,6 @@ FlipSocialApp *flip_social_app_alloc()
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);
- // 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, app);
- app->variable_item_logged_out_wifi_settings_password = variable_item_list_add(app->variable_item_list_logged_out_wifi_settings, "Password", 1, NULL, app);
- //
- app->variable_item_logged_out_login_username = variable_item_list_add(app->variable_item_list_logged_out_login, "Username", 1, NULL, app);
- app->variable_item_logged_out_login_password = variable_item_list_add(app->variable_item_list_logged_out_login, "Password", 1, NULL, app);
- app->variable_item_logged_out_login_button = variable_item_list_add(app->variable_item_list_logged_out_login, "Login", 0, NULL, app);
- //
- app->variable_item_logged_out_register_username = variable_item_list_add(app->variable_item_list_logged_out_register, "Username", 1, NULL, app);
- app->variable_item_logged_out_register_password = variable_item_list_add(app->variable_item_list_logged_out_register, "Password", 1, NULL, app);
- app->variable_item_logged_out_register_password_2 = variable_item_list_add(app->variable_item_list_logged_out_register, "Confirm Password", 1, NULL, app);
- app->variable_item_logged_out_register_button = variable_item_list_add(app->variable_item_list_logged_out_register, "Register", 0, NULL, app);
- //
- app->variable_item_logged_in_profile_username = variable_item_list_add(app->variable_item_list_logged_in_profile, "Username", 1, NULL, app);
- app->variable_item_logged_in_profile_change_password = variable_item_list_add(app->variable_item_list_logged_in_profile, "Password", 1, NULL, app);
- app->variable_item_logged_in_profile_change_bio = variable_item_list_add(app->variable_item_list_logged_in_profile, "Bio", 1, NULL, app);
- app->variable_item_logged_in_profile_friends = variable_item_list_add(app->variable_item_list_logged_in_profile, "Friends", 0, NULL, app);
- //
- app->variable_item_logged_in_settings_about = variable_item_list_add(app->variable_item_list_logged_in_settings, "About", 0, NULL, app);
- app->variable_item_logged_in_settings_wifi = variable_item_list_add(app->variable_item_list_logged_in_settings, "WiFi", 0, NULL, app);
- //
- app->variable_item_logged_in_wifi_settings_ssid = variable_item_list_add(app->variable_item_list_logged_in_settings_wifi, "SSID", 1, NULL, app);
- app->variable_item_logged_in_wifi_settings_password = variable_item_list_add(app->variable_item_list_logged_in_settings_wifi, "Password", 1, NULL, app);
-
- // 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, "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_change_bio, FlipSocialViewLoggedInChangeBioInput, "Bio", app->change_bio_logged_in_temp_buffer, app->change_bio_logged_in_temp_buffer_size, flip_social_logged_in_profile_change_bio_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;
- }
- if (!easy_flipper_set_uart_text_input(&app->text_input_logged_in_explore, FlipSocialViewLoggedInExploreInput, "Enter Username or Keyword", app->explore_logged_in_temp_buffer, app->explore_logged_in_temp_buffer_size, flip_social_logged_in_explore_updated, flip_social_callback_to_submenu_logged_in, &app->view_dispatcher, app))
- {
- return NULL;
- }
- if (!easy_flipper_set_uart_text_input(&app->text_input_logged_in_message_users, FlipSocialViewLoggedInMessageUsersInput, "Enter Username or Keyword", app->message_users_logged_in_temp_buffer, app->message_users_logged_in_temp_buffer_size, flip_social_logged_in_message_users_updated, flip_social_callback_to_submenu_logged_in, &app->view_dispatcher, app))
- {
- return NULL;
- }
-
// Load the settings
if (!load_settings(app->wifi_ssid_logged_out,
app->wifi_ssid_logged_out_temp_buffer_size,
@@ -508,20 +379,14 @@ FlipSocialApp *flip_social_app_alloc()
auth_headers_alloc();
- // 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);
- variable_item_set_current_value_text(app->variable_item_logged_in_profile_change_bio, app->change_bio_logged_in);
- //
-
if (app->is_logged_in != NULL && strcmp(app->is_logged_in, "true") == 0)
{
+ save_char("is_logged_in", "true");
view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedInSubmenu);
}
else
{
+ save_char("is_logged_in", "false");
view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedOutSubmenu);
}
}
diff --git a/flip_social/alloc/free.c b/flip_social/alloc/free.c
new file mode 100644
index 000000000..03dc7a63d
--- /dev/null
+++ b/flip_social/alloc/free.c
@@ -0,0 +1,146 @@
+#include
+void free_all(bool should_free_variable_item_list, bool should_free_submenu)
+{
+
+ if (should_free_submenu)
+ {
+ flip_social_free_explore();
+ free_submenu();
+ }
+ if (should_free_variable_item_list)
+ {
+ free_variable_item_list();
+ }
+ free_text_input();
+ flip_social_free_friends();
+ flip_social_free_messages();
+ flip_social_free_feed_view();
+ flip_social_free_compose_dialog();
+ flip_social_free_explore_dialog();
+ flip_social_free_friends_dialog();
+ flip_social_free_messages_dialog();
+ flip_feed_info_free();
+ free_about_widget(true);
+ free_about_widget(false);
+
+ if (went_to_friends)
+ {
+ flipper_http_deinit();
+ went_to_friends = false;
+ }
+}
+void free_text_input()
+{
+ if (app_instance->text_input)
+ {
+ text_input_free(app_instance->text_input);
+ app_instance->text_input = NULL;
+ view_dispatcher_remove_view(app_instance->view_dispatcher, FlipSocialViewTextInput);
+ }
+}
+void flip_social_free_explore_dialog()
+{
+ if (app_instance->dialog_explore)
+ {
+ dialog_ex_free(app_instance->dialog_explore);
+ app_instance->dialog_explore = NULL;
+ view_dispatcher_remove_view(app_instance->view_dispatcher, FlipSocialViewExploreDialog);
+ }
+}
+void flip_social_free_friends_dialog()
+{
+ if (app_instance->dialog_friends)
+ {
+ dialog_ex_free(app_instance->dialog_friends);
+ app_instance->dialog_friends = NULL;
+ view_dispatcher_remove_view(app_instance->view_dispatcher, FlipSocialViewFriendsDialog);
+ }
+}
+void flip_social_free_messages_dialog()
+{
+ if (app_instance->dialog_messages)
+ {
+ dialog_ex_free(app_instance->dialog_messages);
+ app_instance->dialog_messages = NULL;
+ view_dispatcher_remove_view(app_instance->view_dispatcher, FlipSocialViewMessagesDialog);
+ return;
+ }
+}
+void flip_social_free_compose_dialog()
+{
+ if (app_instance->dialog_compose)
+ {
+ dialog_ex_free(app_instance->dialog_compose);
+ app_instance->dialog_compose = NULL;
+ view_dispatcher_remove_view(app_instance->view_dispatcher, FlipSocialViewComposeDialog);
+ }
+}
+void flip_social_free_feed_view()
+{
+ if (app_instance->view_feed)
+ {
+ view_free(app_instance->view_feed);
+ app_instance->view_feed = NULL;
+ view_dispatcher_remove_view(app_instance->view_dispatcher, FlipSocialViewLoggedInFeed);
+ }
+}
+
+void free_about_widget(bool is_logged_in)
+{
+ if (is_logged_in && app_instance->widget_logged_in_about)
+ {
+ widget_free(app_instance->widget_logged_in_about);
+ app_instance->widget_logged_in_about = NULL;
+ view_dispatcher_remove_view(app_instance->view_dispatcher, FlipSocialViewLoggedInSettingsAbout);
+ }
+ if (!is_logged_in && app_instance->widget_logged_out_about)
+ {
+ widget_free(app_instance->widget_logged_out_about);
+ app_instance->widget_logged_out_about = NULL;
+ view_dispatcher_remove_view(app_instance->view_dispatcher, FlipSocialViewLoggedOutAbout);
+ }
+}
+
+void flip_social_free_friends(void)
+{
+ if (!flip_social_friends)
+ {
+ return;
+ }
+ free(flip_social_friends);
+ flip_social_friends = NULL;
+}
+
+void flip_feed_info_free(void)
+{
+ if (!flip_feed_info)
+ {
+ return;
+ }
+ free(flip_feed_info);
+ flip_feed_info = NULL;
+}
+
+void free_variable_item_list(void)
+{
+ if (app_instance->variable_item_list)
+ {
+ variable_item_list_free(app_instance->variable_item_list);
+ app_instance->variable_item_list = NULL;
+ view_dispatcher_remove_view(app_instance->view_dispatcher, FlipSocialViewVariableItemList);
+ }
+}
+
+void free_submenu(void)
+{
+ if (!app_instance)
+ {
+ return;
+ }
+ if (app_instance->submenu)
+ {
+ submenu_free(app_instance->submenu);
+ app_instance->submenu = NULL;
+ view_dispatcher_remove_view(app_instance->view_dispatcher, FlipSocialViewSubmenu);
+ }
+}
\ No newline at end of file
diff --git a/flip_social/alloc/free.h b/flip_social/alloc/free.h
new file mode 100644
index 000000000..e8208af35
--- /dev/null
+++ b/flip_social/alloc/free.h
@@ -0,0 +1,15 @@
+#pragma once
+#include
+#include
+void free_all(bool should_free_variable_item_list, bool should_free_submenu);
+void free_text_input();
+void flip_social_free_explore_dialog();
+void flip_social_free_friends_dialog();
+void flip_social_free_messages_dialog();
+void flip_social_free_compose_dialog();
+void flip_social_free_feed_view();
+void free_about_widget(bool is_logged_in);
+void flip_social_free_friends(void);
+void flip_feed_info_free(void);
+void free_variable_item_list(void);
+void free_submenu(void);
diff --git a/flip_social/app.c b/flip_social/app.c
index 38a0083d7..939c6afe2 100644
--- a/flip_social/app.c
+++ b/flip_social/app.c
@@ -17,21 +17,24 @@ int32_t main_flip_social(void *p)
if (!app_instance)
{
// Allocation failed
+ FURI_LOG_E(TAG, "Failed to allocate FlipSocialApp");
return -1; // Indicate failure
}
- if (!flipper_http_ping())
+ // check if board is connected (Derek Jamison)
+ uint8_t counter = 10;
+ // initialize the http
+ if (flipper_http_init(flipper_http_rx_callback, app_instance))
{
- FURI_LOG_E(TAG, "Failed to ping the device");
- return -1;
- }
+ fhttp.state = INACTIVE; // set inactive for the ping
+
+ if (!flipper_http_ping())
+ {
+ FURI_LOG_E(TAG, "Failed to ping the device");
+ return -1;
+ }
- // Thanks to Derek Jamison for the following edits
- if (app_instance->wifi_ssid_logged_out != NULL &&
- app_instance->wifi_password_logged_out != NULL)
- {
// Try to wait for pong response.
- uint8_t counter = 10;
while (fhttp.state == INACTIVE && --counter > 0)
{
FURI_LOG_D(TAG, "Waiting for PONG");
@@ -40,20 +43,35 @@ int32_t main_flip_social(void *p)
if (counter == 0)
{
- DialogsApp *dialogs = furi_record_open(RECORD_DIALOGS);
- DialogMessage *message = dialog_message_alloc();
- dialog_message_set_header(
- message, "[FlipperHTTP Error]", 64, 0, AlignCenter, AlignTop);
- dialog_message_set_text(
- message,
- "Ensure your WiFi Developer\nBoard or Pico W is connected\nand the latest FlipperHTTP\nfirmware is installed.",
- 0,
- 63,
- AlignLeft,
- AlignBottom);
- dialog_message_show(dialogs, message);
- dialog_message_free(message);
- furi_record_close(RECORD_DIALOGS);
+ easy_flipper_dialog("FlipperHTTP Error", "Ensure your WiFi Developer\nBoard or Pico W is connected\nand the latest FlipperHTTP\nfirmware is installed.");
+ }
+ else
+ {
+ save_char("is_connected", "true");
+ }
+
+ flipper_http_deinit();
+ }
+ else
+ {
+ easy_flipper_dialog("FlipperHTTP Error", "The UART is likely busy.\nEnsure you have the correct\nflash for your board then\nrestart your Flipper Zero.");
+ }
+
+ // if counter is not 0, check notifications
+ if (counter != 0)
+ {
+ char is_connected[5];
+ char is_logged_in[5];
+ char is_notifications[5];
+ load_char("is_connected", is_connected, 5);
+ load_char("is_logged_in", is_logged_in, 5);
+ load_char("user_notifications", is_notifications, 5);
+
+ if (strcmp(is_connected, "true") == 0 &&
+ strcmp(is_notifications, "on") == 0 &&
+ strcmp(is_logged_in, "true") == 0)
+ {
+ flip_social_home_notification();
}
}
diff --git a/flip_social/application.fam b/flip_social/application.fam
index 776c69b94..6247772f9 100644
--- a/flip_social/application.fam
+++ b/flip_social/application.fam
@@ -7,8 +7,8 @@ App(
fap_icon="app_new.png",
fap_category="GPIO/FlipperHTTP",
fap_icon_assets="assets",
- fap_author="jblanked",
+ fap_author="JBlanked",
fap_weburl="https://github.com/jblanked/FlipSocial",
- fap_version="0.8",
+ fap_version="1.0.3",
fap_description="Social media platform for the Flipper Zero.",
)
diff --git a/flip_social/assets/CHANGELOG.md b/flip_social/assets/CHANGELOG.md
index e41468ce8..9b54a1a07 100644
--- a/flip_social/assets/CHANGELOG.md
+++ b/flip_social/assets/CHANGELOG.md
@@ -1,3 +1,24 @@
+## 1.0.3
+- Updated to ensure the flip_social data folder is created when saving settings.
+
+## 1.0.2
+- Fixed the Feed Type and Notifications toggles, in the User Settings, to work as intended.
+- Added flip status to each feed post.
+
+## 1.0.1
+- Fixed a freeze that occurred when sending a direct message.
+
+## 1.0 - Official Release
+- Final memory improvements.
+- Updated the New Message option in the Messages view to allow users to search for recipients to send messages to.
+- Updated the display of the Feed to use a custom view and custom font, and to show how long ago each post was created.
+- Organized the files saved/utilized in the apps_data folder.
+- Fixed bugs in the Direct Messaging View.
+- Fixed bugs in the Pre-Save View.
+- Added home announcements and notifications.
+- Added User Settings (Notifications and Feed Type). With the Feed Type option, users can choose between a private feed, which only consists of posts from their friends, and a global feed, which consists of posts from all Flippers. The Notifications settings allow users to turn home notifications on and off.
+- Updated the Feed to be "endless." Once you reach the end of the feed, previous posts will load in sets of 20.
+
## 0.8 - New Features
- Added support for RPC_KEYBOARD (thanks to Derek Jamison).
- Introduced a bio feature in the Profile section: View and update your bio.
diff --git a/flip_social/assets/README.md b/flip_social/assets/README.md
index 7f29ee275..5b09349b1 100644
--- a/flip_social/assets/README.md
+++ b/flip_social/assets/README.md
@@ -3,12 +3,9 @@ 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/WebCrawler-FlipperZero/tree/main/assets/FlipperHTTP
-
## Requirements
- WiFi Developer Board, Raspberry Pi, or ESP32 Device with FlipperHTTP Flash: https://github.com/jblanked/FlipperHTTP
-- WiFi Access Point
-
+- 2.4 Ghz WiFi Access Point
## Features
- Login/Logout
@@ -16,15 +13,16 @@ FlipSocial uses the FlipperHTTP flash for the WiFi Devboard, first introduced in
- Feed
- Profile
- Customizable Pre-Saves
-- Explore (NEW)
-- Friends (NEW)
-- Direct Messaging (NEW)
+- Explore
+- Friends
+- Direct Messaging
+
**Login/Logout:** Log in to your account to view and post on the Feed. You can also change your password and log out when needed.
**Registration:** Create an account with just a username and password—no email or personal information required or collected.
-**Feed:** View up to 50 of the latest posts, create your own posts, and "Flip" a post—FlipSocial’s version of liking or favoriting a post.
+**Feed:** View the latest posts, create your own posts, and "Flip" a post—FlipSocial’s version of liking or favoriting a post.
**Customizable Pre-Saves:** The biggest challenge with a social media app on the Flipper Zero is using only the directional pad for input. To address this, I implemented a pre-saved text system. The pre-saves are stored in a pre_saved_messages.txt file on your SD card. You can edit the pre-saves by opening qFlipper, downloading the file from the /apps_data/flip_social/ folder, adding your pre-saves (separated by new lines), and then copying it back to your SD card. You can also create pre-saves directly within the app.
@@ -32,66 +30,4 @@ FlipSocial uses the FlipperHTTP flash for the WiFi Devboard, first introduced in
**Friends:** View and remove friends.
-**Direct Messaging:** Send direct messages to other Flipper users and view your conversations.
-
-## Roadmap
-**v0.2**
-- Stability Patch
-
-**v0.3**
-- Explore Page
-- Friends
-
-**v0.4**
-- Direct Messaging
-
-**v0.5**
-- Improve memory allocation
-- Improve Feed Page
-- Raspberry Pi Pico W Support
-
-**v0.6**
-- Improve memory allocation
-- Update the Direct Messaging View
-- Update the Pre-Save View
-
-**v0.7**
-- Improve memory allocation
-- Loading screens.
-
-**v0.8**
-- Improve User Profile
-- Improve Explore Page
-
-**v1.0**
-- Official Release
-
-## Contribution
-This is a big project, and I welcome all contributors, especially developers interested in animations and graphics. Fork the repository, create a pull request, and I will review your edits.
-
-## Known Bugs
-1. When clicking any button other than the BACK button in the Feed view, post creation view, messages view, or the friends view, the app doesn't respond to inputs.
-- **Solution:** Restart your Flipper device.
-
-2. When trying to log in, the app shows "Awaiting response..." and nothing happens for more than 30 seconds.
-- **Solution 1:** Update your WiFi credentials. Sometimes, you just need to hit Save again on either the SSID or password.
-- **Solution 2:** Ensure your WiFi Devboard is plugged in, then restart the app.
-- **Solution 3:** Ensure your WiFi Devboard is plugged in, then restart your Flipper device.
-
-3. When accessing the Feed, I keep getting the message "Either the feed didn’t load or there was a server error."
-- **Solution 1:** Update your WiFi credentials. Sometimes, you just need to hit Save again on either the SSID or password.
-- **Solution 2:** Ensure your WiFi Devboard is plugged in, then restart the app.
-- **Solution 3:** Ensure your WiFi Devboard is plugged in, then restart your Flipper device.
-
-4. The Feed is empty.
-- **Solution 1:** Update your WiFi credentials. Sometimes, you just need to hit Save again on either the SSID or password.
-- **Solution 2:** Ensure your WiFi Devboard is plugged in, then restart the app.
-- **Solution 3:** Ensure your WiFi Devboard is plugged in, then restart your Flipper device.
-
-5. Out of memory when starting the app or after visiting the feed and post views back-to-back.
-- **Solution 1:** Restart your Flipper device.
-- **Solution 2:** Update the app to version 0.7 (or higher).
-
-6. I can no longer access the Messages.
-- **Solution 1:** Uppdate the app to version 0.6.3 (or higher)
-- **Solution 2:** Click the logout button then login again. Make sure your password is correct before clicking "Login".
+**Direct Messaging:** Send direct messages to other Flipper users and view your conversations.
\ No newline at end of file
diff --git a/flip_social/assets/flip-social-main-menu.png b/flip_social/assets/flip-social-main-menu.png
index 0503fe708..07808d6aa 100644
Binary files a/flip_social/assets/flip-social-main-menu.png and b/flip_social/assets/flip-social-main-menu.png differ
diff --git a/flip_social/callback/flip_social_callback.c b/flip_social/callback/flip_social_callback.c
index 7d0920010..bde904af1 100644
--- a/flip_social/callback/flip_social_callback.c
+++ b/flip_social/callback/flip_social_callback.c
@@ -10,102 +10,6 @@
#define DEV_CRASH()
#endif
-static bool about_widget_alloc(bool is_logged_in)
-{
- if (!is_logged_in)
- {
- if (!app_instance->widget_logged_out_about)
- {
- return easy_flipper_set_widget(&app_instance->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_instance->view_dispatcher);
- }
- }
- else
- {
- if (!app_instance->widget_logged_in_about)
- {
- return easy_flipper_set_widget(&app_instance->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_instance->view_dispatcher);
- }
- }
- return true;
-}
-static void free_about_widget(bool is_logged_in)
-{
- if (is_logged_in && app_instance->widget_logged_in_about)
- {
- widget_free(app_instance->widget_logged_in_about);
- app_instance->widget_logged_in_about = NULL;
- view_dispatcher_remove_view(app_instance->view_dispatcher, FlipSocialViewLoggedInSettingsAbout);
- }
- if (!is_logged_in && app_instance->widget_logged_out_about)
- {
- widget_free(app_instance->widget_logged_out_about);
- app_instance->widget_logged_out_about = NULL;
- view_dispatcher_remove_view(app_instance->view_dispatcher, FlipSocialViewLoggedOutAbout);
- }
-}
-static bool pre_saved_messages_alloc(void)
-{
- if (!app_instance)
- {
- return false;
- }
- if (!app_instance->submenu_compose)
- {
- if (!easy_flipper_set_submenu(&app_instance->submenu_compose, FlipSocialViewLoggedInCompose, "Create A Post", flip_social_callback_to_submenu_logged_in, &app_instance->view_dispatcher))
- {
- return false;
- }
- submenu_reset(app_instance->submenu_compose);
- submenu_add_item(app_instance->submenu_compose, "Add Pre-Save", FlipSocialSubmenuComposeIndexAddPreSave, flip_social_callback_submenu_choices, app_instance);
- // load the playlist
- if (load_playlist(&app_instance->pre_saved_messages))
- {
- // Update the playlist submenu
- for (uint32_t i = 0; i < app_instance->pre_saved_messages.count; i++)
- {
- if (app_instance->pre_saved_messages.messages[i])
- {
- submenu_add_item(app_instance->submenu_compose, app_instance->pre_saved_messages.messages[i], FlipSocialSubemnuComposeIndexStartIndex + i, flip_social_callback_submenu_choices, app_instance);
- }
- }
- }
- }
- return true;
-}
-static void free_pre_saved_messages(void)
-{
- if (app_instance->submenu_compose)
- {
- submenu_free(app_instance->submenu_compose);
- app_instance->submenu_compose = NULL;
- view_dispatcher_remove_view(app_instance->view_dispatcher, FlipSocialViewLoggedInCompose);
- }
- for (uint32_t i = 0; i < app_instance->pre_saved_messages.count; i++)
- {
- if (app_instance->pre_saved_messages.messages[i])
- {
- free(app_instance->pre_saved_messages.messages[i]);
- app_instance->pre_saved_messages.messages[i] = NULL;
- }
- }
-}
-
-static void flip_social_free_friends(void)
-{
- if (!flip_social_friends)
- {
- return;
- }
- free(flip_social_friends);
- flip_social_friends = NULL;
- if (app_instance->submenu_friends)
- {
- submenu_free(app_instance->submenu_friends);
- app_instance->submenu_friends = NULL;
- view_dispatcher_remove_view(app_instance->view_dispatcher, FlipSocialViewLoggedInFriendsSubmenu);
- }
-}
-
static void flip_social_request_error_draw(Canvas *canvas)
{
if (canvas == NULL)
@@ -162,32 +66,38 @@ static void flip_social_request_error_draw(Canvas *canvas)
static bool flip_social_login_fetch(DataLoaderModel *model)
{
UNUSED(model);
+ if (!app_instance)
+ {
+ FURI_LOG_E(TAG, "app_instance is NULL");
+ return false;
+ }
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)
{
return false;
}
-
char buffer[256];
snprintf(buffer, sizeof(buffer), "{\"username\":\"%s\",\"password\":\"%s\"}", app_instance->login_username_logged_out, app_instance->login_password_logged_out);
auth_headers_alloc();
- if (flipper_http_post_request_with_headers("https://www.flipsocial.net/api/user/login/", auth_headers, buffer))
- {
- fhttp.state = RECEIVING;
- return true;
- }
- else
- {
- fhttp.state = ISSUE;
- return false;
- }
+ return flipper_http_post_request_with_headers("https://www.flipsocial.net/api/user/login/", auth_headers, buffer);
}
static char *flip_social_login_parse(DataLoaderModel *model)
{
UNUSED(model);
+ if (!app_instance)
+ {
+ FURI_LOG_E(TAG, "app_instance is NULL");
+ return "Failed to login...";
+ }
+ if (!fhttp.last_response)
+ {
+ flipper_http_deinit();
+ return "Failed to login...";
+ }
// read response
if (strstr(fhttp.last_response, "[SUCCESS]") != NULL || strstr(fhttp.last_response, "User found") != NULL)
{
+ flipper_http_deinit();
app_instance->is_logged_in = "true";
// set the logged_in_username and change_password_logged_in
@@ -208,10 +118,12 @@ static char *flip_social_login_parse(DataLoaderModel *model)
}
else if (strstr(fhttp.last_response, "User not found") != NULL)
{
+ flipper_http_deinit();
return "Account not found...";
}
else
{
+ flipper_http_deinit();
return "Failed to login...";
}
}
@@ -237,21 +149,10 @@ static bool flip_social_register_fetch(DataLoaderModel *model)
FURI_LOG_E(TAG, "Passwords do not match");
return false;
}
-
char buffer[128];
snprintf(buffer, sizeof(buffer), "{\"username\":\"%s\",\"password\":\"%s\"}", app_instance->register_username_logged_out, app_instance->register_password_logged_out);
- if (flipper_http_post_request_with_headers("https://www.flipsocial.net/api/user/register/", "{\"Content-Type\":\"application/json\"}", buffer))
- {
- // Set the state to RECEIVING to ensure we continue to see the receiving message
- fhttp.state = RECEIVING;
- return true;
- }
- else
- {
- fhttp.state = ISSUE;
- return false;
- }
+ return flipper_http_post_request_with_headers("https://www.flipsocial.net/api/user/register/", "{\"Content-Type\":\"application/json\"}", buffer);
}
static char *flip_social_register_parse(DataLoaderModel *model)
@@ -260,6 +161,7 @@ static char *flip_social_register_parse(DataLoaderModel *model)
// read response
if (fhttp.last_response != NULL && (strstr(fhttp.last_response, "[SUCCESS]") != NULL || strstr(fhttp.last_response, "User created") != NULL))
{
+ flipper_http_deinit();
// set the login credentials
if (app_instance->login_username_logged_out)
{
@@ -289,14 +191,17 @@ static char *flip_social_register_parse(DataLoaderModel *model)
}
else if (strstr(fhttp.last_response, "Username or password not provided") != NULL)
{
+ flipper_http_deinit();
return "Please enter your credentials.\nPress BACK to return.";
}
else if (strstr(fhttp.last_response, "User already exists") != NULL || strstr(fhttp.last_response, "Multiple users found") != NULL)
{
+ flipper_http_deinit();
return "Registration failed...\nUsername already exists.\nPress BACK to return.";
}
else
{
+ flipper_http_deinit();
return "Registration failed...\nUpdate your credentials.\nPress BACK to return.";
}
}
@@ -317,53 +222,6 @@ uint32_t flip_social_callback_to_submenu_logged_out(void *context)
return FlipSocialViewLoggedOutSubmenu;
}
-static void flip_social_free_explore_dialog()
-{
- if (app_instance->dialog_explore)
- {
- dialog_ex_free(app_instance->dialog_explore);
- app_instance->dialog_explore = NULL;
- view_dispatcher_remove_view(app_instance->view_dispatcher, FlipSocialViewExploreDialog);
- }
-}
-static void flip_social_free_friends_dialog()
-{
- if (app_instance->dialog_friends)
- {
- dialog_ex_free(app_instance->dialog_friends);
- app_instance->dialog_friends = NULL;
- view_dispatcher_remove_view(app_instance->view_dispatcher, FlipSocialViewFriendsDialog);
- }
-}
-static void flip_social_free_messages_dialog()
-{
- if (app_instance->dialog_messages)
- {
- dialog_ex_free(app_instance->dialog_messages);
- app_instance->dialog_messages = NULL;
- view_dispatcher_remove_view(app_instance->view_dispatcher, FlipSocialViewMessagesDialog);
- return;
- }
-}
-static void flip_social_free_compose_dialog()
-{
- if (app_instance->dialog_compose)
- {
- dialog_ex_free(app_instance->dialog_compose);
- app_instance->dialog_compose = NULL;
- view_dispatcher_remove_view(app_instance->view_dispatcher, FlipSocialViewComposeDialog);
- }
-}
-static void flip_social_free_feed_dialog()
-{
- if (app_instance->dialog_feed)
- {
- dialog_ex_free(app_instance->dialog_feed);
- app_instance->dialog_feed = NULL;
- view_dispatcher_remove_view(app_instance->view_dispatcher, FlipSocialViewFeedDialog);
- }
-}
-
/**
* @brief Navigation callback to go back to the submenu Logged in.
* @param context The context - unused
@@ -388,6 +246,7 @@ uint32_t flip_social_callback_to_submenu_logged_in(void *context)
flip_social_free_friends_dialog();
flip_social_free_messages_dialog();
flip_social_free_compose_dialog();
+
return FlipSocialViewLoggedInSubmenu;
}
@@ -401,7 +260,7 @@ 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;
+ return FlipSocialViewVariableItemList;
}
/**
@@ -414,7 +273,7 @@ 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;
+ return FlipSocialViewVariableItemList;
}
/**
@@ -425,7 +284,7 @@ uint32_t flip_social_callback_to_register_logged_out(void *context)
uint32_t flip_social_callback_to_wifi_settings_logged_out(void *context)
{
UNUSED(context);
- return FlipSocialViewLoggedOutWifiSettings;
+ return FlipSocialViewVariableItemList;
}
/**
@@ -436,7 +295,7 @@ uint32_t flip_social_callback_to_wifi_settings_logged_out(void *context)
uint32_t flip_social_callback_to_wifi_settings_logged_in(void *context)
{
UNUSED(context);
- return FlipSocialViewLoggedInSettingsWifi;
+ return FlipSocialViewVariableItemList;
}
/**
@@ -447,7 +306,7 @@ uint32_t flip_social_callback_to_wifi_settings_logged_in(void *context)
uint32_t flip_social_callback_to_settings_logged_in(void *context)
{
UNUSED(context);
- return FlipSocialViewLoggedInSettings;
+ return FlipSocialViewSubmenu;
}
/**
@@ -458,7 +317,7 @@ uint32_t flip_social_callback_to_settings_logged_in(void *context)
uint32_t flip_social_callback_to_compose_logged_in(void *context)
{
UNUSED(context);
- return FlipSocialViewLoggedInCompose;
+ return FlipSocialViewSubmenu;
}
/**
@@ -469,13 +328,13 @@ uint32_t flip_social_callback_to_compose_logged_in(void *context)
uint32_t flip_social_callback_to_profile_logged_in(void *context)
{
UNUSED(context);
- return FlipSocialViewLoggedInProfile;
+ return FlipSocialViewVariableItemList;
}
/**
* @brief Navigation callback to bring the user back to the Explore submenu
* @param context The context - unused
- * @return next view id (FlipSocialViewLoggedInExploreSubmenu)
+ * @return next view id (FlipSocialViewSubmenu)
*/
uint32_t flip_social_callback_to_explore_logged_in(void *context)
{
@@ -486,7 +345,7 @@ uint32_t flip_social_callback_to_explore_logged_in(void *context)
{
flip_social_explore->index = 0;
}
- return FlipSocialViewLoggedInExploreSubmenu;
+ return FlipSocialViewSubmenu;
}
/**
@@ -500,7 +359,7 @@ uint32_t flip_social_callback_to_friends_logged_in(void *context)
flip_social_dialog_stop = false;
flip_social_dialog_shown = false;
flip_social_friends->index = 0;
- return FlipSocialViewLoggedInFriendsSubmenu;
+ return FlipSocialViewSubmenu;
}
/**
@@ -511,7 +370,7 @@ uint32_t flip_social_callback_to_friends_logged_in(void *context)
uint32_t flip_social_callback_to_messages_logged_in(void *context)
{
UNUSED(context);
- return FlipSocialViewLoggedInMessagesSubmenu;
+ return FlipSocialViewSubmenu;
}
/**
@@ -522,14 +381,7 @@ uint32_t flip_social_callback_to_messages_logged_in(void *context)
uint32_t flip_social_callback_to_messages_user_choices(void *context)
{
UNUSED(context);
- return FlipSocialViewLoggedInMessagesUserChoices;
-}
-
-static void free_flip_social_group()
-{
- flip_social_free_messages();
- flip_social_free_explore();
- flip_social_free_feed_dialog();
+ return FlipSocialViewSubmenu;
}
/**
@@ -541,9 +393,8 @@ uint32_t flip_social_callback_exit_app(void *context)
{
// Exit the application
UNUSED(context);
- free_flip_social_group();
- free_pre_saved_messages();
- return VIEW_NONE; // Return VIEW_NONE to exit the app
+ free_all(true, true);
+ return VIEW_NONE;
}
static void explore_dialog_callback(DialogExResult result, void *context)
@@ -552,23 +403,33 @@ static void explore_dialog_callback(DialogExResult result, void *context)
FlipSocialApp *app = (FlipSocialApp *)context;
if (result == DialogExResultLeft) // Remove
{
- // 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]);
- auth_headers_alloc();
- flipper_http_post_request_with_headers("https://www.flipsocial.net/api/user/remove-friend/", auth_headers, remove_payload);
- view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedInExploreSubmenu);
- flip_social_free_explore_dialog();
+ if (flipper_http_init(flipper_http_rx_callback, app_instance))
+ {
+ // 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]);
+ auth_headers_alloc();
+ flipper_http_post_request_with_headers("https://www.flipsocial.net/api/user/remove-friend/", auth_headers, remove_payload);
+ view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewSubmenu);
+ flip_social_free_explore_dialog();
+ furi_delay_ms(1000);
+ flipper_http_deinit();
+ }
}
else if (result == DialogExResultRight)
{
- // 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]);
- auth_headers_alloc();
- flipper_http_post_request_with_headers("https://www.flipsocial.net/api/user/add-friend/", auth_headers, add_payload);
- view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedInExploreSubmenu);
- flip_social_free_explore_dialog();
+ if (flipper_http_init(flipper_http_rx_callback, app_instance))
+ {
+ // 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]);
+ auth_headers_alloc();
+ flipper_http_post_request_with_headers("https://www.flipsocial.net/api/user/add-friend/", auth_headers, add_payload);
+ view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewSubmenu);
+ flip_social_free_explore_dialog();
+ furi_delay_ms(1000);
+ flipper_http_deinit();
+ }
}
}
static void friends_dialog_callback(DialogExResult result, void *context)
@@ -577,16 +438,21 @@ static void friends_dialog_callback(DialogExResult result, void *context)
FlipSocialApp *app = (FlipSocialApp *)context;
if (result == DialogExResultLeft) // Remove
{
- // 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]);
- auth_headers_alloc();
- flipper_http_post_request_with_headers("https://www.flipsocial.net/api/user/remove-friend/", auth_headers, remove_payload);
- view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedInFriendsSubmenu);
- flip_social_free_friends_dialog();
+ if (flipper_http_init(flipper_http_rx_callback, app_instance))
+ {
+ // 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]);
+ auth_headers_alloc();
+ flipper_http_post_request_with_headers("https://www.flipsocial.net/api/user/remove-friend/", auth_headers, remove_payload);
+ view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewSubmenu);
+ flip_social_free_friends_dialog();
+ furi_delay_ms(1000);
+ flipper_http_deinit();
+ }
}
}
-static void messages_dialog_callback(DialogExResult result, void *context)
+void messages_dialog_callback(DialogExResult result, void *context)
{
furi_assert(context);
FlipSocialApp *app = (FlipSocialApp *)context;
@@ -597,7 +463,7 @@ static void messages_dialog_callback(DialogExResult result, void *context)
flip_social_messages->index--;
dialog_ex_reset(app->dialog_messages);
dialog_ex_set_header(app->dialog_messages, flip_social_messages->usernames[flip_social_messages->index], 0, 0, AlignLeft, AlignTop);
- dialog_ex_set_text(app->dialog_messages, flip_social_messages->messages[flip_social_messages->index], 0, 10, AlignLeft, AlignTop);
+ dialog_ex_set_text(app->dialog_messages, updated_user_message(flip_social_messages->messages[flip_social_messages->index]), 0, 10, AlignLeft, AlignTop);
if (flip_social_messages->index != 0)
{
dialog_ex_set_left_button_text(app->dialog_messages, "Prev");
@@ -605,7 +471,7 @@ static void messages_dialog_callback(DialogExResult result, void *context)
dialog_ex_set_right_button_text(app->dialog_messages, "Next");
dialog_ex_set_center_button_text(app->dialog_messages, "Create");
// switch view, free dialog, re-alloc dialog, switch back to dialog
- view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedInMessagesSubmenu);
+ view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewWidgetResult);
flip_social_free_messages_dialog();
messages_dialog_alloc(false);
view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewMessagesDialog);
@@ -618,7 +484,7 @@ static void messages_dialog_callback(DialogExResult result, void *context)
flip_social_messages->index++;
dialog_ex_reset(app->dialog_messages);
dialog_ex_set_header(app->dialog_messages, flip_social_messages->usernames[flip_social_messages->index], 0, 0, AlignLeft, AlignTop);
- dialog_ex_set_text(app->dialog_messages, flip_social_messages->messages[flip_social_messages->index], 0, 10, AlignLeft, AlignTop);
+ dialog_ex_set_text(app->dialog_messages, updated_user_message(flip_social_messages->messages[flip_social_messages->index]), 0, 10, AlignLeft, AlignTop);
dialog_ex_set_left_button_text(app->dialog_messages, "Prev");
if (flip_social_messages->index != flip_social_messages->count - 1)
{
@@ -626,7 +492,7 @@ static void messages_dialog_callback(DialogExResult result, void *context)
}
dialog_ex_set_center_button_text(app->dialog_messages, "Create");
// switch view, free dialog, re-alloc dialog, switch back to dialog
- view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedInMessagesSubmenu);
+ view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewWidgetResult);
flip_social_free_messages_dialog();
messages_dialog_alloc(false);
view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewMessagesDialog);
@@ -634,40 +500,14 @@ static void messages_dialog_callback(DialogExResult result, void *context)
}
else if (result == DialogExResultCenter) // new message
{
- view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedInMessagesNewMessageInput);
- }
-}
-
-bool messages_dialog_alloc(bool free_first)
-{
- if (free_first)
- {
- flip_social_free_messages_dialog();
- }
- if (!app_instance->dialog_messages)
- {
- if (!easy_flipper_set_dialog_ex(
- &app_instance->dialog_messages,
- FlipSocialViewMessagesDialog,
- flip_social_messages->usernames[flip_social_messages->index],
- 0,
- 0,
- flip_social_messages->messages[flip_social_messages->index],
- 0,
- 10,
- flip_social_messages->index != 0 ? "Prev" : NULL,
- flip_social_messages->index != flip_social_messages->count - 1 ? "Next" : NULL,
- "Create",
- messages_dialog_callback,
- flip_social_callback_to_messages_logged_in,
- &app_instance->view_dispatcher,
- app_instance))
+ free_text_input();
+ if (!alloc_text_input(FlipSocialViewLoggedInMessagesNewMessageInput))
{
- return false;
+ FURI_LOG_E(TAG, "Failed to allocate text input");
+ return;
}
- return true;
+ view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewTextInput);
}
- return false;
}
static void compose_dialog_callback(DialogExResult result, void *context)
@@ -676,39 +516,63 @@ static void compose_dialog_callback(DialogExResult result, void *context)
FlipSocialApp *app = (FlipSocialApp *)context;
if (result == DialogExResultLeft) // Delete
{
- // delete message
- app->pre_saved_messages.messages[app_instance->pre_saved_messages.index] = NULL;
+ // Ensure index is within bounds
+ if (app_instance->pre_saved_messages.index >= app_instance->pre_saved_messages.count)
+ {
+ FURI_LOG_E(TAG, "Invalid index for deletion: %zu", app_instance->pre_saved_messages.index);
+ return;
+ }
- for (uint32_t i = app->pre_saved_messages.index; i < app->pre_saved_messages.count - 1; i++)
+ // Shift messages to remove the selected message
+ for (size_t i = app_instance->pre_saved_messages.index; i < app_instance->pre_saved_messages.count - 1; i++)
{
- app->pre_saved_messages.messages[i] = app->pre_saved_messages.messages[i + 1];
+ strncpy(app_instance->pre_saved_messages.messages[i],
+ app_instance->pre_saved_messages.messages[i + 1],
+ MAX_MESSAGE_LENGTH);
}
- app->pre_saved_messages.count--;
- // add the item to the submenu
- submenu_reset(app_instance->submenu_compose);
+ // Clear the last message after shifting
+ memset(app_instance->pre_saved_messages.messages[app_instance->pre_saved_messages.count - 1], 0, MAX_MESSAGE_LENGTH);
+ app_instance->pre_saved_messages.count--;
- submenu_add_item(app_instance->submenu_compose, "Add Pre-Save", FlipSocialSubmenuComposeIndexAddPreSave, flip_social_callback_submenu_choices, app);
+ // Reset and rebuild the submenu
+ submenu_reset(app_instance->submenu);
+ submenu_add_item(app_instance->submenu, "Add Pre-Save", FlipSocialSubmenuComposeIndexAddPreSave, flip_social_callback_submenu_choices, app);
- for (uint32_t i = 0; i < app->pre_saved_messages.count; i++)
+ for (size_t i = 0; i < app_instance->pre_saved_messages.count; i++)
{
- submenu_add_item(app_instance->submenu_compose, app->pre_saved_messages.messages[i], FlipSocialSubemnuComposeIndexStartIndex + i, flip_social_callback_submenu_choices, app);
+ submenu_add_item(app_instance->submenu,
+ app_instance->pre_saved_messages.messages[i],
+ FlipSocialSubemnuComposeIndexStartIndex + i,
+ flip_social_callback_submenu_choices,
+ app);
}
- // save playlist
+ // Save the updated playlist
save_playlist(&app_instance->pre_saved_messages);
- view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedInCompose);
+
+ // Switch back to the compose view
+ view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewSubmenu);
+
+ // Free the dialog resources
flip_social_free_compose_dialog();
}
+
else if (result == DialogExResultRight) // Post
{
// post the message
// send selected_message
+ if (!flipper_http_init(flipper_http_rx_callback, app_instance))
+ {
+ FURI_LOG_E(TAG, "Failed to initialize FlipperHTTP");
+ return;
+ }
if (selected_message && app_instance->login_username_logged_in)
{
if (strlen(selected_message) > MAX_MESSAGE_LENGTH)
{
FURI_LOG_E(TAG, "Message is too long");
+ flipper_http_deinit();
return;
}
// Send the selected_message
@@ -721,7 +585,7 @@ static void compose_dialog_callback(DialogExResult result, void *context)
command))
{
FURI_LOG_E(TAG, "Failed to send HTTP request for feed");
- fhttp.state = ISSUE;
+ flipper_http_deinit();
return;
}
@@ -731,255 +595,33 @@ static void compose_dialog_callback(DialogExResult result, void *context)
else
{
FURI_LOG_E(TAG, "Message or username is NULL");
+ flipper_http_deinit();
return;
}
while (fhttp.state == RECEIVING && furi_timer_is_running(fhttp.get_timeout_timer) > 0)
{
furi_delay_ms(100);
}
- if (flip_social_load_initial_feed())
+ if (flip_social_load_initial_feed(false, 1))
{
flip_social_free_compose_dialog();
}
- FURI_LOG_E(TAG, "Failed to load initial feed");
- return;
- }
-}
-static void feed_dialog_callback(DialogExResult result, void *context)
-{
- furi_assert(context);
- FlipSocialApp *app = (FlipSocialApp *)context;
- if (result == DialogExResultLeft) // Previous message
- {
- if (flip_feed_info->index > 0)
- {
- flip_feed_info->index--;
- }
- // switch view, free dialog, re-alloc dialog, switch back to dialog
- view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedInSettings);
- // load feed item
- if (!flip_social_load_feed_post(flip_feed_info->ids[flip_feed_info->index]))
- {
- FURI_LOG_E(TAG, "Failed to load nexy feed post");
- fhttp.state = ISSUE;
- return;
- }
- if (feed_dialog_alloc())
- {
- view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewFeedDialog);
- }
- else
- {
- FURI_LOG_E(TAG, "Failed to allocate feed dialog");
- fhttp.state = ISSUE;
- return;
- }
- }
- else if (result == DialogExResultRight) // Next message
- {
- if (flip_feed_info->index < flip_feed_info->count - 1)
- {
- flip_feed_info->index++;
- }
- // switch view, free dialog, re-alloc dialog, switch back to dialog
- view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedInSettings);
- // load feed item
- if (!flip_social_load_feed_post(flip_feed_info->ids[flip_feed_info->index]))
- {
- FURI_LOG_E(TAG, "Failed to load nexy feed post");
- fhttp.state = ISSUE;
- return;
- }
- if (feed_dialog_alloc())
- {
- view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewFeedDialog);
- }
- else
- {
- FURI_LOG_E(TAG, "Failed to allocate feed dialog");
- fhttp.state = ISSUE;
- return;
- }
- }
- else if (result == DialogExResultCenter) // Flip/Unflip
- {
- // Moved to above the is_flipped check
- if (!flip_feed_item->is_flipped)
- {
- // increase the flip count
- flip_feed_item->flips++;
- }
else
{
- // decrease the flip count
- flip_feed_item->flips--;
- }
- // change the flip status
- flip_feed_item->is_flipped = !flip_feed_item->is_flipped;
- // 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_feed_item->id);
- if (flipper_http_post_request_with_headers("https://www.flipsocial.net/api/feed/flip/", auth_headers, payload))
- {
- // save feed item
- char new_save[256];
- snprintf(new_save, sizeof(new_save), "{\"id\":%u,\"username\":\"%s\",\"message\":\"%s\",\"flip_count\":%u,\"flipped\":%s}",
- flip_feed_item->id, flip_feed_item->username, flip_feed_item->message, flip_feed_item->flips, flip_feed_item->is_flipped ? "true" : "false");
- // if (!flip_social_save_post((char *)flip_feed_item->id, new_save))
- // {
- // FURI_LOG_E(TAG, "Failed to save the feed post");
- // fhttp.state = ISSUE;
- // return;
- // }
- }
- // switch view, free dialog, re-alloc dialog, switch back to dialog
- view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedInSettings);
- if (feed_dialog_alloc())
- {
- view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewFeedDialog);
- }
- else
- {
- FURI_LOG_E(TAG, "Failed to allocate feed dialog");
- fhttp.state = ISSUE;
- return;
- }
- }
-}
-
-static char *updated_user_message(const char *user_message)
-{
- if (user_message == NULL)
- {
- FURI_LOG_E(TAG, "User message is NULL.");
- return NULL;
- }
-
- size_t msg_length = strlen(user_message);
- size_t start = 0;
- int line_num = 0;
-
- // Allocate memory for the updated message
- char *updated_message = malloc(MAX_MESSAGE_LENGTH + 10);
- if (updated_message == NULL)
- {
- FURI_LOG_E(TAG, "Failed to allocate memory for updated_message.");
- return NULL;
- }
- size_t current_pos = 0; // Tracks the current position in updated_message
- updated_message[0] = '\0'; // Initialize as empty string
-
- while (start < msg_length && line_num < 4)
- {
- size_t remaining = msg_length - start;
- size_t len = (remaining > MAX_LINE_LENGTH) ? MAX_LINE_LENGTH : remaining;
-
- // Adjust length to the last space if the line exceeds MAX_LINE_LENGTH
- if (remaining > MAX_LINE_LENGTH)
- {
- 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
- }
- }
-
- // Check if the new line fits in the updated_message buffer
- if (current_pos + len + 1 >= (MAX_MESSAGE_LENGTH + 10))
- {
- FURI_LOG_E(TAG, "Updated message exceeds maximum length.");
- // break and return what we have so far
- break;
- }
-
- // Copy the line and append a newline character
- memcpy(updated_message + current_pos, user_message + start, len);
- current_pos += len;
- updated_message[current_pos++] = '\n'; // Append newline
-
- // 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++;
+ FURI_LOG_E(TAG, "Failed to load the initial feed");
+ flipper_http_deinit();
}
-
- // Increment the line number
- line_num++;
- }
-
- // Null-terminate the final string
- if (current_pos < (MAX_MESSAGE_LENGTH + 10))
- {
- updated_message[current_pos] = '\0';
}
- else
- {
- FURI_LOG_E(TAG, "Buffer overflow while null-terminating.");
- free(updated_message);
- return NULL;
- }
-
- return updated_message;
}
-bool feed_dialog_alloc()
+static bool flip_social_get_user_info()
{
- if (!flip_feed_item)
+ if (!flipper_http_init(flipper_http_rx_callback, app_instance))
{
- FURI_LOG_E(TAG, "Feed item is NULL");
+ FURI_LOG_E(TAG, "Failed to initialize FlipperHTTP");
+ fhttp.state = ISSUE;
return false;
}
- flip_social_free_feed_dialog();
- if (!app_instance->dialog_feed)
- {
- char updated_message[MAX_MESSAGE_LENGTH + 10];
- snprintf(updated_message, MAX_MESSAGE_LENGTH + 10, "%s (%u %s)", flip_feed_item->message, flip_feed_item->flips, flip_feed_item->flips == 1 ? "flip" : "flips");
- char *real_message = updated_user_message(updated_message);
- if (!real_message)
- {
- FURI_LOG_E(TAG, "Failed to update the user message");
- return false;
- }
- if (!easy_flipper_set_dialog_ex(
- &app_instance->dialog_feed,
- FlipSocialViewFeedDialog,
- flip_feed_item->username,
- 0,
- 0,
- updated_message,
- 0,
- 10,
- flip_feed_info->index != 0 ? "Prev" : NULL,
- flip_feed_info->index != flip_feed_info->count - 1 ? "Next" : NULL,
- flip_feed_item->is_flipped ? "Unflip" : "Flip",
- feed_dialog_callback,
- flip_social_callback_to_submenu_logged_in,
- &app_instance->view_dispatcher,
- app_instance))
- {
- free(real_message);
- return false;
- }
- free(real_message);
- return true;
- }
- return false;
-}
-static bool flip_social_get_user_info()
-{
char url[256];
snprintf(url, sizeof(url), "https://www.flipsocial.net/api/user/users/%s/extended/", flip_social_explore->usernames[flip_social_explore->index]);
if (!flipper_http_get_request_with_headers(url, auth_headers))
@@ -1003,8 +645,9 @@ static bool flip_social_parse_user_info()
FURI_LOG_E(TAG, "App instance is NULL");
return false;
}
- char *bio = get_json_value("bio", fhttp.last_response, 32);
- char *friends = get_json_value("friends", fhttp.last_response, 32);
+ char *bio = get_json_value("bio", fhttp.last_response);
+ char *friends = get_json_value("friends", fhttp.last_response);
+ bool parse_success = false;
if (bio && friends)
{
if (strlen(bio) != 0)
@@ -1016,9 +659,11 @@ static bool flip_social_parse_user_info()
snprintf(app_instance->explore_user_bio, MAX_MESSAGE_LENGTH, "%s friends", friends);
}
free(bio);
- return true;
+ free(friends);
+ parse_success = true;
}
- return false;
+ flipper_http_deinit();
+ return parse_success;
}
/**
@@ -1040,12 +685,24 @@ void flip_social_callback_submenu_choices(void *context, uint32_t index)
case FlipSocialSubmenuLoggedOutIndexLogin:
flip_social_sent_login_request = false;
flip_social_login_success = false;
- view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedOutLogin);
+ free_all(true, true);
+ if (!alloc_variable_item_list(FlipSocialViewLoggedOutLogin))
+ {
+ FURI_LOG_E(TAG, "Failed to allocate variable item list");
+ return;
+ }
+ view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewVariableItemList);
break;
case FlipSocialSubmenuLoggedOutIndexRegister:
flip_social_sent_register_request = false;
flip_social_register_success = false;
- view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedOutRegister);
+ free_all(true, true);
+ if (!alloc_variable_item_list(FlipSocialViewLoggedOutRegister))
+ {
+ FURI_LOG_E(TAG, "Failed to allocate variable item list");
+ return;
+ }
+ view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewVariableItemList);
break;
case FlipSocialSubmenuLoggedOutIndexAbout:
if (!about_widget_alloc(false))
@@ -1055,50 +712,111 @@ void flip_social_callback_submenu_choices(void *context, uint32_t index)
view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedOutAbout);
break;
case FlipSocialSubmenuLoggedOutIndexWifiSettings:
- view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedOutWifiSettings);
+ free_all(false, false);
+ if (!alloc_variable_item_list(FlipSocialViewLoggedOutWifiSettings))
+ {
+ FURI_LOG_E(TAG, "Failed to allocate text input");
+ return;
+ }
+ view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewVariableItemList);
break;
case FlipSocialSubmenuLoggedInIndexProfile:
- view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedInProfile);
+ free_all(true, true);
+ if (!alloc_variable_item_list(FlipSocialViewLoggedInProfile))
+ {
+ FURI_LOG_E(TAG, "Failed to allocate variable item list");
+ return;
+ }
+ view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewVariableItemList);
break;
case FlipSocialSubmenuLoggedInIndexMessages:
- free_flip_social_group();
+ free_all(true, true);
+ if (!alloc_submenu(FlipSocialViewLoggedInMessagesSubmenu))
+ {
+ FURI_LOG_E(TAG, "Failed to allocate submenu");
+ return;
+ }
flipper_http_loading_task(
- flip_social_get_message_users, // get the message users
- flip_social_parse_json_message_users, // parse the message users
- FlipSocialViewLoggedInMessagesSubmenu, // switch to the messages submenu if successful
- FlipSocialViewLoggedInSubmenu, // switch back to the main submenu if failed
- &app->view_dispatcher); // view dispatcher
+ flip_social_get_message_users, // get the message users
+ flip_social_parse_json_message_users, // parse the message users
+ FlipSocialViewSubmenu, // switch to the messages submenu if successful
+ FlipSocialViewLoggedInSubmenu, // switch back to the main submenu if failed
+ &app->view_dispatcher); // view dispatcher
break;
case FlipSocialSubmenuLoggedInIndexMessagesNewMessage:
- view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedInMessageUsersInput);
+ // they need to search for the user to send a message
+ free_text_input();
+ if (!alloc_text_input(FlipSocialViewLoggedInMessageUsersInput))
+ {
+ FURI_LOG_E(TAG, "Failed to allocate text input");
+ return;
+ }
+ view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewTextInput);
break;
case FlipSocialSubmenuLoggedInIndexFeed:
- free_flip_social_group();
- if (!flip_social_load_initial_feed())
+ free_all(true, true);
+ if (!flip_social_load_initial_feed(true, 1))
{
FURI_LOG_E(TAG, "Failed to load the initial feed");
return;
}
- free_pre_saved_messages();
break;
case FlipSocialSubmenuExploreIndex:
- free_flip_social_group();
- view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedInExploreInput);
+ free_all(true, true);
+ if (!alloc_text_input(FlipSocialViewLoggedInExploreInput))
+ {
+ FURI_LOG_E(TAG, "Failed to allocate text input");
+ return;
+ }
+ view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewTextInput);
break;
case FlipSocialSubmenuLoggedInIndexCompose:
- free_pre_saved_messages();
- if (!pre_saved_messages_alloc())
+ free_all(true, true);
+ if (!alloc_submenu(FlipSocialViewLoggedInCompose))
{
- FURI_LOG_E(TAG, "Failed to allocate pre-saved messages");
+ FURI_LOG_E(TAG, "Failed to allocate submenu");
return;
}
- view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedInCompose);
+ view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewSubmenu);
break;
case FlipSocialSubmenuLoggedInIndexSettings:
- view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedInSettings);
+ free_all(true, true);
+ if (!alloc_submenu(FlipSocialViewLoggedInSettings))
+ {
+ FURI_LOG_E(TAG, "Failed to allocate submenu");
+ return;
+ }
+ view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewSubmenu);
+ break;
+ case FlipSocialSubmenuLoggedInIndexAbout:
+ free_all(true, false);
+ if (!about_widget_alloc(true))
+ {
+ FURI_LOG_E(TAG, "Failed to allocate about widget");
+ return;
+ }
+ view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedInSettingsAbout);
+ break;
+ case FlipSocialSubmenuLoggedInIndexWifiSettings:
+ free_all(true, false);
+ if (!alloc_variable_item_list(FlipSocialViewLoggedInSettingsWifi))
+ {
+ FURI_LOG_E(TAG, "Failed to allocate variable item list");
+ return;
+ }
+ view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewVariableItemList);
+ break;
+ case FlipSocialSubmenuLoggedInIndexUserSettings:
+ free_all(true, false);
+ if (!alloc_variable_item_list(FlipSocialViewLoggedInSettingsUser))
+ {
+ FURI_LOG_E(TAG, "Failed to allocate variable item list");
+ return;
+ }
+ view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewVariableItemList);
break;
case FlipSocialSubmenuLoggedInSignOutButton:
- free_flip_social_group();
+ free_all(true, true);
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->change_bio_logged_in, app->is_logged_in);
@@ -1106,7 +824,13 @@ void flip_social_callback_submenu_choices(void *context, uint32_t index)
view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedOutSubmenu);
break;
case FlipSocialSubmenuComposeIndexAddPreSave:
- view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedInComposeAddPreSaveInput);
+ free_text_input();
+ if (!alloc_text_input(FlipSocialViewLoggedInComposeAddPreSaveInput))
+ {
+ FURI_LOG_E(TAG, "Failed to allocate text input");
+ return;
+ }
+ view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewTextInput);
break;
default:
// Handle the pre-saved message selection (has a max of 25 items)
@@ -1128,7 +852,7 @@ void flip_social_callback_submenu_choices(void *context, uint32_t index)
"New Feed Post",
0,
0,
- selected_message,
+ updated_user_message(selected_message),
0,
10,
"Delete",
@@ -1175,7 +899,7 @@ void flip_social_callback_submenu_choices(void *context, uint32_t index)
flip_social_explore->usernames[flip_social_explore->index],
0,
0,
- app->explore_user_bio,
+ updated_user_message(app->explore_user_bio),
0,
10,
"Remove", // remove if user is a friend (future update)
@@ -1282,7 +1006,13 @@ void flip_social_callback_submenu_choices(void *context, uint32_t index)
return;
}
flip_social_explore->index = index - FlipSocialSubmenuLoggedInIndexMessagesUserChoicesIndexStart;
- view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedInMessagesNewMessageUserChoicesInput);
+ free_text_input();
+ if (!alloc_text_input(FlipSocialViewLoggedInMessagesNewMessageUserChoicesInput))
+ {
+ FURI_LOG_E(TAG, "Failed to allocate text input");
+ return;
+ } // switch to the text input view
+ view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewTextInput);
}
else
{
@@ -1325,17 +1055,22 @@ void flip_social_logged_out_wifi_settings_ssid_updated(void *context)
// update the wifi settings
if (strlen(app->wifi_ssid_logged_out) > 0 && strlen(app->wifi_password_logged_out) > 0)
{
- if (!flipper_http_save_wifi(app->wifi_ssid_logged_out, app->wifi_password_logged_out))
+ if (flipper_http_init(flipper_http_rx_callback, app_instance))
{
- 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");
+ 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");
+ }
+ furi_delay_ms(500);
+ flipper_http_deinit();
}
}
// 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->change_bio_logged_in, app_instance->is_logged_in);
- view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedOutWifiSettings);
+ view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewVariableItemList);
}
/**
@@ -1370,17 +1105,22 @@ void flip_social_logged_out_wifi_settings_password_updated(void *context)
// update the wifi settings
if (strlen(app->wifi_ssid_logged_out) > 0 && strlen(app->wifi_password_logged_out) > 0)
{
- if (!flipper_http_save_wifi(app->wifi_ssid_logged_out, app->wifi_password_logged_out))
+ if (flipper_http_init(flipper_http_rx_callback, app_instance))
{
- 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");
+ 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");
+ }
+ furi_delay_ms(500);
+ flipper_http_deinit();
}
}
// 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->change_bio_logged_in, app_instance->is_logged_in);
- view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedOutWifiSettings);
+ view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewVariableItemList);
}
/**
@@ -1400,10 +1140,24 @@ void flip_social_text_input_logged_out_wifi_settings_item_selected(void *context
switch (index)
{
case 0: // Input SSID
- view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedOutWifiSettingsSSIDInput);
+ // view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedOutWifiSettingsSSIDInput);
+ free_text_input();
+ if (!alloc_text_input(FlipSocialViewLoggedOutWifiSettingsSSIDInput))
+ {
+ FURI_LOG_E(TAG, "Failed to allocate text input");
+ return;
+ }
+ view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewTextInput);
break;
case 1: // Input Password
- view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedOutWifiSettingsPasswordInput);
+ // view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedOutWifiSettingsPasswordInput);
+ free_text_input();
+ if (!alloc_text_input(FlipSocialViewLoggedOutWifiSettingsPasswordInput))
+ {
+ FURI_LOG_E(TAG, "Failed to allocate text input");
+ return;
+ }
+ view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewTextInput);
break;
default:
FURI_LOG_E(TAG, "Unknown configuration item index");
@@ -1443,7 +1197,7 @@ void flip_social_logged_out_login_username_updated(void *context)
// 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->change_bio_logged_in, app_instance->is_logged_in);
- view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedOutLogin);
+ view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewVariableItemList);
}
/**
@@ -1480,7 +1234,7 @@ void flip_social_logged_out_login_password_updated(void *context)
// 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->change_bio_logged_in, app_instance->is_logged_in);
- view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedOutLogin);
+ view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewVariableItemList);
}
/**
@@ -1500,12 +1254,31 @@ void flip_social_text_input_logged_out_login_item_selected(void *context, uint32
switch (index)
{
case 0: // Input Username
- view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedOutLoginUsernameInput);
+ // view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedOutLoginUsernameInput);
+ free_all(false, true);
+ if (!alloc_text_input(FlipSocialViewLoggedOutLoginUsernameInput))
+ {
+ FURI_LOG_E(TAG, "Failed to allocate text input");
+ return;
+ }
+ view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewTextInput);
break;
case 1: // Input Password
- view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedOutLoginPasswordInput);
+ // view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedOutLoginPasswordInput);
+ free_all(false, true);
+ if (!alloc_text_input(FlipSocialViewLoggedOutLoginPasswordInput))
+ {
+ FURI_LOG_E(TAG, "Failed to allocate text input");
+ return;
+ }
+ view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewTextInput);
break;
case 2: // Login Button
+ if (!flipper_http_init(flipper_http_rx_callback, app_instance))
+ {
+ FURI_LOG_E(TAG, "Failed to initialize FlipperHTTP");
+ return;
+ }
flip_social_login_switch_to_view(app);
break;
default:
@@ -1539,7 +1312,7 @@ void flip_social_logged_out_register_username_updated(void *context)
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);
+ view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewVariableItemList);
}
/**
@@ -1567,7 +1340,7 @@ void flip_social_logged_out_register_password_updated(void *context)
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);
+ view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewVariableItemList);
}
/**
@@ -1595,7 +1368,7 @@ void flip_social_logged_out_register_password_2_updated(void *context)
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);
+ view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewVariableItemList);
}
/**
@@ -1615,15 +1388,38 @@ void flip_social_text_input_logged_out_register_item_selected(void *context, uin
switch (index)
{
case 0: // Input Username
- view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedOutRegisterUsernameInput);
+ free_all(false, true);
+ if (!alloc_text_input(FlipSocialViewLoggedOutRegisterUsernameInput))
+ {
+ FURI_LOG_E(TAG, "Failed to allocate text input");
+ return;
+ }
+ view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewTextInput);
break;
case 1: // Input Password
- view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedOutRegisterPasswordInput);
+ free_all(false, true);
+ if (!alloc_text_input(FlipSocialViewLoggedOutRegisterPasswordInput))
+ {
+ FURI_LOG_E(TAG, "Failed to allocate text input");
+ return;
+ }
+ view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewTextInput);
break;
case 2: // Input Password 2
- view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedOutRegisterPassword2Input);
+ free_all(false, true);
+ if (!alloc_text_input(FlipSocialViewLoggedOutRegisterPassword2Input))
+ {
+ FURI_LOG_E(TAG, "Failed to allocate text input");
+ return;
+ }
+ view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewTextInput);
break;
case 3: // Register button
+ if (!flipper_http_init(flipper_http_rx_callback, app_instance))
+ {
+ FURI_LOG_E(TAG, "Failed to initialize FlipperHTTP");
+ return;
+ }
flip_social_register_switch_to_view(app);
break;
default:
@@ -1667,14 +1463,19 @@ void flip_social_logged_in_wifi_settings_ssid_updated(void *context)
// 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))
+ if (flipper_http_init(flipper_http_rx_callback, app_instance))
{
- 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");
+ 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");
+ }
+ furi_delay_ms(500);
+ flipper_http_deinit();
}
}
- view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedInSettingsWifi);
+ view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewVariableItemList);
}
/**
@@ -1713,14 +1514,19 @@ void flip_social_logged_in_wifi_settings_password_updated(void *context)
// 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))
+ if (flipper_http_init(flipper_http_rx_callback, app_instance))
{
- 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");
+ 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");
+ }
+ furi_delay_ms(500);
+ flipper_http_deinit();
}
}
- view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedInSettingsWifi);
+ view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewVariableItemList);
}
/**
@@ -1740,10 +1546,24 @@ void flip_social_text_input_logged_in_wifi_settings_item_selected(void *context,
switch (index)
{
case 0: // Input SSID
- view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedInWifiSettingsSSIDInput);
+ // view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedInWifiSettingsSSIDInput);
+ free_all(false, false);
+ if (!alloc_text_input(FlipSocialViewLoggedInWifiSettingsSSIDInput))
+ {
+ FURI_LOG_E(TAG, "Failed to allocate text input for SSID");
+ return;
+ }
+ view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewTextInput);
break;
case 1: // Input Password
- view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedInWifiSettingsPasswordInput);
+ // view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedInWifiSettingsPasswordInput);
+ free_all(false, false);
+ if (!alloc_text_input(FlipSocialViewLoggedInWifiSettingsPasswordInput))
+ {
+ FURI_LOG_E(TAG, "Failed to allocate text input for Password");
+ return;
+ }
+ view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewTextInput);
break;
default:
FURI_LOG_E(TAG, "Unknown configuration item index");
@@ -1751,6 +1571,25 @@ void flip_social_text_input_logged_in_wifi_settings_item_selected(void *context,
}
}
+void flip_social_logged_in_user_settings_item_selected(void *context, uint32_t index)
+{
+ FlipSocialApp *app = (FlipSocialApp *)context;
+ if (!app)
+ {
+ FURI_LOG_E(TAG, "FlipSocialApp is NULL");
+ return;
+ }
+
+ // Switch to the appropriate view
+ switch (index)
+ {
+ case 0: // Feed Type
+ break;
+ case 1: // Notifications
+ 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.
@@ -1765,37 +1604,50 @@ void flip_social_logged_in_compose_pre_save_updated(void *context)
return;
}
- // check if the message is empty or if adding in the message would exceed the MAX_PRE_SAVED_MESSAGES
+ // Check if the message is empty or if adding the message would exceed 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);
+ view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewSubmenu);
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);
+ // Copy the entered message into the next available slot
+ strncpy(
+ app->pre_saved_messages.messages[app->pre_saved_messages.count],
+ app->compose_pre_save_logged_in_temp_buffer,
+ MAX_MESSAGE_LENGTH - 1);
// 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);
+ app->pre_saved_messages.messages[app->pre_saved_messages.count][MAX_MESSAGE_LENGTH - 1] = '\0';
- // 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;
+ // Increment the count
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++)
+ // Rebuild the submenu
+ submenu_reset(app->submenu);
+ submenu_add_item(
+ app->submenu,
+ "Add Pre-Save",
+ FlipSocialSubmenuComposeIndexAddPreSave,
+ flip_social_callback_submenu_choices,
+ app);
+
+ for (size_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);
+ submenu_add_item(
+ app->submenu,
+ app->pre_saved_messages.messages[i],
+ FlipSocialSubemnuComposeIndexStartIndex + i,
+ flip_social_callback_submenu_choices,
+ app);
}
- // save playlist
+ // Save the updated playlist
save_playlist(&app->pre_saved_messages);
- view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedInCompose);
+ // Switch back to the compose view
+ view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewSubmenu);
}
/**
@@ -1832,19 +1684,23 @@ void flip_social_logged_in_profile_change_password_updated(void *context)
}
// send post request to change password
- auth_headers_alloc();
- 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);
- if (!flipper_http_post_request_with_headers("https://www.flipsocial.net/api/user/change-password/", auth_headers, payload))
+ if (flipper_http_init(flipper_http_rx_callback, app))
{
- 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");
- return;
+ auth_headers_alloc();
+ 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);
+ if (!flipper_http_post_request_with_headers("https://www.flipsocial.net/api/user/change-password/", auth_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");
+ }
+ flipper_http_deinit();
}
// 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->change_bio_logged_in, app_instance->is_logged_in);
- view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedInProfile);
+ // instead of going to a view, just show a success message
+ easy_flipper_dialog("Success", "Password updated successfully\n\n\nPress BACK to return :D");
}
void flip_social_logged_in_profile_change_bio_updated(void *context)
@@ -1869,19 +1725,24 @@ void flip_social_logged_in_profile_change_bio_updated(void *context)
}
// send post request to change bio
- auth_headers_alloc();
- char payload[256];
- snprintf(payload, sizeof(payload), "{\"username\":\"%s\",\"bio\":\"%s\"}", app->login_username_logged_out, app->change_bio_logged_in);
- if (!flipper_http_post_request_with_headers("https://www.flipsocial.net/api/user/change-bio/", auth_headers, payload))
+ if (flipper_http_init(flipper_http_rx_callback, app))
{
- FURI_LOG_E(TAG, "Failed to send post request to change bio");
- FURI_LOG_E(TAG, "Make sure the Flipper is connected to the Wifi Dev Board");
- return;
+ auth_headers_alloc();
+ char payload[256];
+ snprintf(payload, sizeof(payload), "{\"username\":\"%s\",\"bio\":\"%s\"}", app->login_username_logged_out, app->change_bio_logged_in);
+ if (!flipper_http_post_request_with_headers("https://www.flipsocial.net/api/user/change-bio/", auth_headers, payload))
+ {
+ FURI_LOG_E(TAG, "Failed to send post request to change bio");
+ FURI_LOG_E(TAG, "Make sure the Flipper is connected to the Wifi Dev Board");
+ }
+ furi_delay_ms(500);
+ flipper_http_deinit();
}
// 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->change_bio_logged_in, app_instance->is_logged_in);
+ 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->change_bio_logged_in, app->is_logged_in);
- view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedInProfile);
+ // instead of going to a view, just show a success message
+ easy_flipper_dialog("Success", "Bio updated successfully\n\n\nPress BACK to return :D");
}
/**
@@ -1904,57 +1765,46 @@ void flip_social_text_input_logged_in_profile_item_selected(void *context, uint3
// do nothing since username cannot be changed
break;
case 1: // Change Password
- view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedInChangePasswordInput);
+ // view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedInChangePasswordInput);
+ free_text_input();
+ if (!alloc_text_input(FlipSocialViewLoggedInChangePasswordInput))
+ {
+ FURI_LOG_E(TAG, "Failed to allocate text input for change password");
+ return;
+ }
+ view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewTextInput);
break;
case 2: // Change Bio
- view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedInChangeBioInput);
+ // view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedInChangeBioInput);
+ free_text_input();
+ if (!alloc_text_input(FlipSocialViewLoggedInChangeBioInput))
+ {
+ FURI_LOG_E(TAG, "Failed to allocate text input for change bio");
+ return;
+ }
+ view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewTextInput);
break;
case 3: // Friends
- flip_social_free_friends();
- free_flip_social_group();
- if (!app->submenu_friends)
+ free_all(false, true);
+ if (!alloc_submenu(FlipSocialViewLoggedInFriendsSubmenu))
{
- if (!easy_flipper_set_submenu(&app->submenu_friends, FlipSocialViewLoggedInFriendsSubmenu, "Friends", flip_social_callback_to_profile_logged_in, &app->view_dispatcher))
- {
- FURI_LOG_DEV(TAG, "Failed to set submenu for friends");
- return;
- }
+ FURI_LOG_E(TAG, "Failed to allocate submenu for friends");
+ return;
}
- flipper_http_loading_task(flip_social_get_friends, flip_social_parse_json_friends, FlipSocialViewLoggedInFriendsSubmenu, FlipSocialViewLoggedInProfile, &app->view_dispatcher);
- 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
- if (!about_widget_alloc(true))
+ if (!flipper_http_init(flipper_http_rx_callback, app))
{
+ FURI_LOG_E(TAG, "Failed to initialize FlipperHTTP");
return;
}
- view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedInSettingsAbout);
- break;
- case 1: // Wifi
- view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedInSettingsWifi);
+ flipper_http_loading_task(
+ flip_social_get_friends,
+ flip_social_parse_json_friends,
+ FlipSocialViewSubmenu,
+ FlipSocialViewVariableItemList,
+ &app->view_dispatcher);
break;
default:
+ FURI_LOG_E(TAG, "Unknown configuration item index");
break;
}
}
@@ -1977,7 +1827,7 @@ void flip_social_logged_in_messages_user_choice_message_updated(void *context)
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);
+ view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewTextInput);
return;
}
@@ -1988,38 +1838,49 @@ void flip_social_logged_in_messages_user_choice_message_updated(void *context)
app->message_user_choice_logged_in[app->message_user_choice_logged_in_temp_buffer_size - 1] = '\0';
// send post request to send message
+ if (!flipper_http_init(flipper_http_rx_callback, app))
+ {
+ FURI_LOG_E(TAG, "Failed to initialize HTTP");
+ return;
+ }
auth_headers_alloc();
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);
- if (flipper_http_post_request_with_headers(url, auth_headers, payload)) // start the async request
- {
- furi_timer_start(fhttp.get_timeout_timer, TIMEOUT_DURATION_TICKS);
- fhttp.state = RECEIVING;
- }
- else
+ if (!flipper_http_post_request_with_headers(url, auth_headers, payload)) // start the async request
{
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");
- fhttp.state = ISSUE;
- return;
}
- while (fhttp.state == RECEIVING && furi_timer_is_running(fhttp.get_timeout_timer) > 0)
+ furi_delay_ms(1000);
+ flipper_http_deinit();
+ // add user to the top of the list if not already there
+ for (int i = 0; i < flip_social_message_users->count; i++)
+ {
+ if (strcmp(flip_social_message_users->usernames[i], flip_social_explore->usernames[flip_social_explore->index]) == 0)
+ {
+ // remove the user from the list
+ for (int j = i; j < flip_social_message_users->count - 1; j++)
+ {
+ strncpy(flip_social_message_users->usernames[j], flip_social_message_users->usernames[j + 1], strlen(flip_social_message_users->usernames[j + 1]));
+ }
+ flip_social_message_users->count--;
+ break;
+ }
+ }
+ // add the user to the top of the list
+ for (int i = flip_social_message_users->count; i > 0; i--)
{
- // Wait for the request to be received
- furi_delay_ms(10);
+ strncpy(flip_social_message_users->usernames[i], flip_social_message_users->usernames[i - 1], strlen(flip_social_message_users->usernames[i - 1]));
}
- 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]));
+ strncpy(flip_social_message_users->usernames[0], 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);
+ view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewSubmenu);
}
/**
@@ -2050,32 +1911,47 @@ void flip_social_logged_in_messages_new_message_updated(void *context)
// 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
- auth_headers_alloc();
- 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);
-
- if (flipper_http_post_request_with_headers(url, auth_headers, payload)) // start the async request
+ bool send_message_to_user()
{
- furi_timer_start(fhttp.get_timeout_timer, TIMEOUT_DURATION_TICKS);
+ // send post request to send message
+ if (!flipper_http_init(flipper_http_rx_callback, app))
+ {
+ FURI_LOG_E(TAG, "Failed to initialize HTTP");
+ return false;
+ }
+ auth_headers_alloc();
+ 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);
+ if (!flipper_http_post_request_with_headers(url, auth_headers, payload))
+ {
+ 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");
+ easy_flipper_dialog("Error", "Failed to send message\n\n\nPress BACK to return :D");
+ flipper_http_deinit();
+ view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewSubmenu);
+ return false;
+ }
fhttp.state = RECEIVING;
+ return true;
}
- 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");
- fhttp.state = ISSUE;
- return;
- }
- while (fhttp.state == RECEIVING && furi_timer_is_running(fhttp.get_timeout_timer) > 0)
+ bool parse_message_to_user()
{
- // Wait for the request to be received
- furi_delay_ms(10);
+ while (fhttp.state != IDLE)
+ {
+ furi_delay_ms(10);
+ }
+ flipper_http_deinit();
+ return true;
}
- furi_timer_stop(fhttp.get_timeout_timer);
- view_dispatcher_switch_to_view(app->view_dispatcher, FlipSocialViewLoggedInMessagesSubmenu);
+ // well, we got a freeze here, so let's use the loading task to switch views and force refresh
+ flipper_http_loading_task(
+ send_message_to_user,
+ parse_message_to_user,
+ FlipSocialViewSubmenu,
+ FlipSocialViewLoggedInMessagesNewMessageInput,
+ &app->view_dispatcher);
}
void flip_social_logged_in_explore_updated(void *context)
@@ -2101,14 +1977,22 @@ void flip_social_logged_in_explore_updated(void *context)
// Ensure null-termination
app->explore_logged_in[app->explore_logged_in_temp_buffer_size - 1] = '\0';
+ free_submenu();
+
+ if (!alloc_submenu(FlipSocialViewLoggedInExploreSubmenu))
+ {
+ FURI_LOG_E(TAG, "Failed to allocate submenu for explore");
+ return;
+ }
flipper_http_loading_task(
- flip_social_get_explore, // get the explore users
- flip_social_parse_json_explore, // parse the explore users
- FlipSocialViewLoggedInExploreSubmenu, // switch to the explore submenu if successful
- FlipSocialViewLoggedInSubmenu, // switch back to the main submenu if failed
- &app->view_dispatcher); // view dispatcher
+ flip_social_get_explore, // get the explore users
+ flip_social_parse_json_explore, // parse the explore users
+ FlipSocialViewSubmenu, // switch to the explore submenu if successful
+ FlipSocialViewLoggedInSubmenu, // switch back to the main submenu if failed
+ &app->view_dispatcher); // view dispatcher
}
+
void flip_social_logged_in_message_users_updated(void *context)
{
FlipSocialApp *app = (FlipSocialApp *)context;
@@ -2119,7 +2003,7 @@ void flip_social_logged_in_message_users_updated(void *context)
}
// check if the message is empty
- if (app->message_users_logged_in_temp_buffer_size == 0)
+ if (!app->message_users_logged_in_temp_buffer || app->message_users_logged_in_temp_buffer_size == 0)
{
FURI_LOG_E(TAG, "Message is empty");
strncpy(app->message_users_logged_in, "a", 2);
@@ -2133,10 +2017,19 @@ void flip_social_logged_in_message_users_updated(void *context)
// Ensure null-termination
app->message_users_logged_in[app->message_users_logged_in_temp_buffer_size - 1] = '\0';
+ free_submenu();
+
+ if (!alloc_submenu(FlipSocialViewLoggedInExploreSubmenu))
+ {
+ FURI_LOG_E(TAG, "Failed to allocate submenu for explore");
+ return;
+ }
+
+ // get users
flipper_http_loading_task(
flip_social_get_explore_2, // get the explore users
flip_social_parse_json_message_user_choices, // parse the explore users
- FlipSocialViewLoggedInMessagesUserChoices, // switch to the user choices if successful
+ FlipSocialViewSubmenu, // switch to the explore submenu if successful
FlipSocialViewLoggedInSubmenu, // switch back to the main submenu if failed
&app->view_dispatcher); // view dispatcher
}
@@ -2434,7 +2327,7 @@ static void flip_social_loader_process_callback(void *context)
}
else
{
- flip_social_widget_set_text(model->data_text != NULL ? model->data_text : "Empty result", &app_instance->widget_result);
+ flip_social_widget_set_text(model->data_text != NULL ? model->data_text : "", &app_instance->widget_result);
if (model->data_text != NULL)
{
free(model->data_text);
@@ -2599,3 +2492,190 @@ void flip_social_generic_switch_to_view(FlipSocialApp *app, char *title, DataLoa
view_dispatcher_switch_to_view(app->view_dispatcher, view_id);
}
+
+static bool flip_social_get_home_notification()
+{
+ if (!app_instance)
+ {
+ FURI_LOG_E(TAG, "app_instance is NULL");
+ return false;
+ }
+ if (!flipper_http_init(flipper_http_rx_callback, app_instance))
+ {
+ FURI_LOG_E(TAG, "Failed to initialize FlipperHTTP");
+ return false;
+ }
+ // Create the directory for saving settings
+ char directory_path[256];
+ snprintf(directory_path, sizeof(directory_path), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_social/data");
+
+ // Create the directory
+ Storage *storage = furi_record_open(RECORD_STORAGE);
+ storage_common_mkdir(storage, directory_path);
+ furi_record_close(RECORD_STORAGE);
+ auth_headers_alloc();
+ snprintf(
+ fhttp.file_path,
+ sizeof(fhttp.file_path),
+ STORAGE_EXT_PATH_PREFIX "/apps_data/flip_social/data/notification.json");
+
+ fhttp.save_received_data = true;
+ return flipper_http_get_request_with_headers("https://www.flipsocial.net/api/flip-social-notifications/", auth_headers);
+}
+static bool flip_social_parse_home_notification()
+{
+ FuriString *notification = flipper_http_load_from_file(fhttp.file_path);
+ if (notification == NULL)
+ {
+ FURI_LOG_E(TAG, "Failed to load notification from file");
+ flipper_http_deinit();
+ return false;
+ }
+ flipper_http_deinit();
+
+ // Check if announcement and analytics key exists
+ FuriString *announcement_json = get_json_value_furi("announcement", notification);
+ FuriString *analytics_json = get_json_value_furi("analytics", notification);
+ if (announcement_json == NULL || analytics_json == NULL)
+ {
+ FURI_LOG_E(TAG, "Failed to get announcement or analytics from notification");
+ if (announcement_json)
+ {
+ furi_string_free(announcement_json);
+ }
+ if (analytics_json)
+ {
+ furi_string_free(analytics_json);
+ }
+ furi_string_free(notification);
+ return false;
+ }
+
+ // Extract values from JSON
+ FuriString *announcement_value = get_json_value_furi("content", announcement_json);
+ FuriString *announcement_time = get_json_value_furi("date_created", announcement_json);
+ FuriString *analytics_value = get_json_value_furi("count", analytics_json);
+ FuriString *analytics_time = get_json_value_furi("time", analytics_json);
+ if (!announcement_value || !announcement_time || !analytics_value || !analytics_time)
+ {
+ FURI_LOG_E(TAG, "Failed to get announcement or analytics value from notification");
+ if (announcement_value)
+ {
+ furi_string_free(announcement_value);
+ }
+ if (announcement_time)
+ {
+ furi_string_free(announcement_time);
+ }
+ if (analytics_value)
+ {
+ furi_string_free(analytics_value);
+ }
+ if (analytics_time)
+ {
+ furi_string_free(analytics_time);
+ }
+ furi_string_free(announcement_json);
+ furi_string_free(analytics_json);
+ furi_string_free(notification);
+ return false;
+ }
+
+ // Load previous announcement and analytics times
+ char past_analytics_time[32] = {0};
+ char past_announcement_time[32] = {0};
+ bool analytics_time_loaded = load_char("analytics_time", past_analytics_time, sizeof(past_analytics_time));
+ bool announcement_time_loaded = load_char("announcement_time", past_announcement_time, sizeof(past_announcement_time));
+
+ bool new_announcement = false;
+ bool new_analytics = false;
+
+ // Check for new announcement
+ if (!announcement_time_loaded || strcmp(furi_string_get_cstr(announcement_time), past_announcement_time) != 0)
+ {
+ new_announcement = true;
+ }
+
+ // Check for new analytics
+ if (!analytics_time_loaded || strcmp(furi_string_get_cstr(analytics_time), past_analytics_time) != 0)
+ {
+ new_analytics = true;
+ }
+
+ // If no new announcement and no new analytics, exit early
+ if (!new_announcement && !new_analytics)
+ {
+ FURI_LOG_D(TAG, "No new announcement or analytics");
+ furi_string_free(announcement_value);
+ furi_string_free(announcement_time);
+ furi_string_free(analytics_value);
+ furi_string_free(analytics_time);
+ furi_string_free(announcement_json);
+ furi_string_free(analytics_json);
+ furi_string_free(notification);
+ return true;
+ }
+
+ // Save the new announcement and analytics times if they are new
+ if (new_announcement)
+ {
+ save_char("announcement_time", furi_string_get_cstr(announcement_time));
+ }
+
+ if (new_analytics)
+ {
+ save_char("analytics_time", furi_string_get_cstr(analytics_time));
+ }
+
+ // Prepare and show dialogs based on what is new
+ if (new_announcement)
+ {
+ easy_flipper_dialog("Announcement", (char *)furi_string_get_cstr(announcement_value));
+ }
+
+ if (new_analytics)
+ {
+ char analytics_text[128] = {0};
+ // Determine the new posts count
+ if (atoi(furi_string_get_cstr(analytics_value)) > 0)
+ {
+ char past_analytics_value[32] = {0};
+ int new_posts = 0;
+ if (load_char("analytics_value", past_analytics_value, sizeof(past_analytics_value)))
+ {
+ int past_posts = atoi(past_analytics_value);
+ int current_posts = atoi(furi_string_get_cstr(analytics_value));
+ new_posts = current_posts - past_posts;
+ snprintf(analytics_text, sizeof(analytics_text), "%d new posts", new_posts);
+ }
+ else
+ {
+ snprintf(analytics_text, sizeof(analytics_text), "%s feed posts", furi_string_get_cstr(analytics_value));
+ }
+ save_char("analytics_value", furi_string_get_cstr(analytics_value));
+ }
+ else
+ {
+ snprintf(analytics_text, sizeof(analytics_text), "%s feed posts", furi_string_get_cstr(analytics_value));
+ }
+
+ easy_flipper_dialog("Notifications", analytics_text);
+ }
+
+ // Free allocated resources
+ furi_string_free(announcement_value);
+ furi_string_free(announcement_time);
+ furi_string_free(analytics_value);
+ furi_string_free(analytics_time);
+ furi_string_free(announcement_json);
+ furi_string_free(analytics_json);
+ furi_string_free(notification);
+
+ return true;
+}
+
+// home notification
+bool flip_social_home_notification()
+{
+ return flipper_http_process_response_async(flip_social_get_home_notification, flip_social_parse_home_notification);
+}
\ No newline at end of file
diff --git a/flip_social/callback/flip_social_callback.h b/flip_social/callback/flip_social_callback.h
index 722dee083..455146dea 100644
--- a/flip_social/callback/flip_social_callback.h
+++ b/flip_social/callback/flip_social_callback.h
@@ -7,6 +7,8 @@
#include
#include
#include
+#include
+#include
/**
* @brief Navigation callback to go back to the submenu Logged out.
@@ -252,6 +254,7 @@ void flip_social_logged_in_messages_new_message_updated(void *context);
* @return void
*/
void flip_social_text_input_logged_out_register_item_selected(void *context, uint32_t index);
+void flip_social_logged_in_user_settings_item_selected(void *context, uint32_t index);
void flip_social_logged_in_explore_updated(void *context);
void flip_social_logged_in_message_users_updated(void *context);
@@ -296,9 +299,9 @@ void flip_social_loader_draw_callback(Canvas *canvas, void *model);
void flip_social_loader_init(View *view);
void flip_social_loader_free_model(View *view);
-
bool flip_social_custom_event_callback(void *context, uint32_t index);
-
-bool messages_dialog_alloc(bool free_first);
-bool feed_dialog_alloc();
+void messages_dialog_callback(DialogExResult result, void *context);
+void feed_dialog_callback(DialogExResult result, void *context);
+//
+bool flip_social_home_notification();
#endif
\ No newline at end of file
diff --git a/flip_social/easy_flipper/easy_flipper.c b/flip_social/easy_flipper/easy_flipper.c
index f61b7d231..c2ce949e4 100644
--- a/flip_social/easy_flipper/easy_flipper.c
+++ b/flip_social/easy_flipper/easy_flipper.c
@@ -1,5 +1,25 @@
#include
+void easy_flipper_dialog(
+ char *header,
+ char *text)
+{
+ DialogsApp *dialogs = furi_record_open(RECORD_DIALOGS);
+ DialogMessage *message = dialog_message_alloc();
+ dialog_message_set_header(
+ message, header, 64, 0, AlignCenter, AlignTop);
+ dialog_message_set_text(
+ message,
+ text,
+ 0,
+ 63,
+ AlignLeft,
+ AlignBottom);
+ dialog_message_show(dialogs, message);
+ dialog_message_free(message);
+ furi_record_close(RECORD_DIALOGS);
+}
+
/**
* @brief Navigation callback for exiting the application
* @param context The context - unused
diff --git a/flip_social/easy_flipper/easy_flipper.h b/flip_social/easy_flipper/easy_flipper.h
index 3ccfe66b8..8c71106c7 100644
--- a/flip_social/easy_flipper/easy_flipper.h
+++ b/flip_social/easy_flipper/easy_flipper.h
@@ -8,6 +8,7 @@
#include
#include
#include
+#include
#include
#include
#include
@@ -15,14 +16,21 @@
#include
#include
#include
+#include
+#include
#include
#include
#include
#include
#include
+#include
#define EASY_TAG "EasyFlipper"
+void easy_flipper_dialog(
+ char *header,
+ char *text);
+
/**
* @brief Navigation callback for exiting the application
* @param context The context - unused
diff --git a/flip_social/explore/flip_social_explore.c b/flip_social/explore/flip_social_explore.c
index ee18bbc90..36108a66c 100644
--- a/flip_social/explore/flip_social_explore.c
+++ b/flip_social/explore/flip_social_explore.c
@@ -2,19 +2,6 @@
FlipSocialModel *flip_social_explore_alloc(void)
{
- if (!app_instance)
- {
- return NULL;
- }
-
- if (!app_instance->submenu_explore)
- {
- if (!easy_flipper_set_submenu(&app_instance->submenu_explore, FlipSocialViewLoggedInExploreSubmenu, "Explore", flip_social_callback_to_submenu_logged_in, &app_instance->view_dispatcher))
- {
- return NULL;
- }
- }
-
// Allocate memory for each username only if not already allocated
FlipSocialModel *explore = malloc(sizeof(FlipSocialModel));
if (explore == NULL)
@@ -22,55 +9,38 @@ FlipSocialModel *flip_social_explore_alloc(void)
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(void)
{
- if (!app_instance)
- {
- return;
- }
- if (app_instance->submenu_explore)
- {
- submenu_free(app_instance->submenu_explore);
- app_instance->submenu_explore = NULL;
- view_dispatcher_remove_view(app_instance->view_dispatcher, FlipSocialViewLoggedInExploreSubmenu);
- }
- if (!flip_social_explore)
+ if (flip_social_explore)
{
- return;
+ free(flip_social_explore);
+ flip_social_explore = NULL;
}
- free(flip_social_explore);
- flip_social_explore = NULL;
}
// 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(void)
{
- if (fhttp.state == INACTIVE)
+ if (!flipper_http_init(flipper_http_rx_callback, app_instance))
{
- FURI_LOG_E(TAG, "HTTP state is INACTIVE");
+ FURI_LOG_E(TAG, "Failed to initialize FlipperHTTP");
return false;
}
+ char directory[128];
+ snprintf(directory, sizeof(directory), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_social/explore");
+
+ // Create the directory
+ Storage *storage = furi_record_open(RECORD_STORAGE);
+ storage_common_mkdir(storage, directory);
char *keyword = !app_instance->explore_logged_in || strlen(app_instance->explore_logged_in) == 0 ? "a" : app_instance->explore_logged_in;
snprintf(
fhttp.file_path,
sizeof(fhttp.file_path),
- STORAGE_EXT_PATH_PREFIX "/apps_data/flip_social/%s.json",
+ STORAGE_EXT_PATH_PREFIX "/apps_data/flip_social/explore/%s.json",
keyword);
fhttp.save_received_data = true;
@@ -88,30 +58,33 @@ bool flip_social_get_explore(void)
}
bool flip_social_get_explore_2(void)
{
- if (fhttp.state == INACTIVE)
+ if (!app_instance)
+ {
+ return false;
+ }
+ if (!flipper_http_init(flipper_http_rx_callback, app_instance))
{
- FURI_LOG_E(TAG, "HTTP state is INACTIVE");
+ FURI_LOG_E(TAG, "Failed to initialize FlipperHTTP");
return false;
}
+ char directory[128];
+ snprintf(directory, sizeof(directory), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_social/explore");
+
+ // Create the directory
+ Storage *storage = furi_record_open(RECORD_STORAGE);
+ storage_common_mkdir(storage, directory);
char *keyword = !app_instance->message_users_logged_in || strlen(app_instance->message_users_logged_in) == 0 ? "a" : app_instance->message_users_logged_in;
snprintf(
fhttp.file_path,
sizeof(fhttp.file_path),
- STORAGE_EXT_PATH_PREFIX "/apps_data/flip_social/%s.json",
+ STORAGE_EXT_PATH_PREFIX "/apps_data/flip_social/explore/%s.json",
keyword);
fhttp.save_received_data = true;
auth_headers_alloc();
char url[256];
snprintf(url, sizeof(url), "https://www.flipsocial.net/api/user/explore/%s/%d/", keyword, MAX_EXPLORE_USERS);
- if (!flipper_http_get_request_with_headers(url, auth_headers))
- {
- FURI_LOG_E(TAG, "Failed to send HTTP request for explore");
- fhttp.state = ISSUE;
- return false;
- }
- fhttp.state = RECEIVING;
- return true;
+ return flipper_http_get_request_with_headers(url, auth_headers);
}
bool flip_social_parse_json_explore()
@@ -121,15 +94,11 @@ bool flip_social_parse_json_explore()
if (user_data == NULL)
{
FURI_LOG_E(TAG, "Failed to load received data from file.");
+ flipper_http_deinit();
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;
- }
+
+ flipper_http_deinit();
// Allocate memory for each username only if not already allocated
flip_social_explore = flip_social_explore_alloc();
@@ -137,7 +106,6 @@ bool flip_social_parse_json_explore()
{
FURI_LOG_E(TAG, "Failed to allocate memory for explore usernames.");
furi_string_free(user_data);
- free(data_cstr);
return false;
}
@@ -145,22 +113,21 @@ bool flip_social_parse_json_explore()
flip_social_explore->count = 0;
// set submenu
- submenu_reset(app_instance->submenu_explore);
- submenu_set_header(app_instance->submenu_explore, "Explore");
+ submenu_reset(app_instance->submenu);
+ submenu_set_header(app_instance->submenu, "Explore");
// Parse the JSON array of usernames
for (size_t i = 0; i < MAX_EXPLORE_USERS; i++)
{
- char *username = get_json_array_value("users", i, data_cstr, 64); // currently its 53 tokens (with max explore users at 50)
+ FuriString *username = get_json_array_value_furi("users", i, user_data); // currently its 53 tokens (with max explore users at 50)
if (username == NULL)
{
break;
}
- snprintf(flip_social_explore->usernames[i], MAX_USER_LENGTH, "%s", username);
- submenu_add_item(app_instance->submenu_explore, flip_social_explore->usernames[i], FlipSocialSubmenuExploreIndexStartIndex + i, flip_social_callback_submenu_choices, app_instance);
+ snprintf(flip_social_explore->usernames[i], MAX_USER_LENGTH, "%s", furi_string_get_cstr(username));
+ submenu_add_item(app_instance->submenu, flip_social_explore->usernames[i], FlipSocialSubmenuExploreIndexStartIndex + i, flip_social_callback_submenu_choices, app_instance);
flip_social_explore->count++;
- free(username);
+ furi_string_free(username);
}
- free(data_cstr);
furi_string_free(user_data);
return flip_social_explore->count > 0;
}
diff --git a/flip_social/feed/flip_social_feed.c b/flip_social/feed/flip_social_feed.c
index 379c44167..f355ecdcf 100644
--- a/flip_social/feed/flip_social_feed.c
+++ b/flip_social/feed/flip_social_feed.c
@@ -1,15 +1,15 @@
#include "flip_social_feed.h"
-bool flip_social_get_feed()
+bool flip_social_get_feed(bool alloc_http, int series_index)
{
if (!app_instance)
{
FURI_LOG_E(TAG, "FlipSocialApp is NULL");
return false;
}
- if (fhttp.state == INACTIVE)
+ if (alloc_http && !flipper_http_init(flipper_http_rx_callback, app_instance))
{
- FURI_LOG_E(TAG, "HTTP state is INACTIVE");
+ FURI_LOG_E(TAG, "Failed to initialize FlipperHTTP");
return false;
}
// Get the feed from the server
@@ -26,7 +26,14 @@ bool flip_social_get_feed()
fhttp.save_received_data = true;
auth_headers_alloc();
char command[96];
- snprintf(command, 96, "https://www.flipsocial.net/api/feed/50/%s/extended/", app_instance->login_username_logged_out);
+ if (strstr(flip_social_feed_type[flip_social_feed_type_index], "Global"))
+ {
+ snprintf(command, 96, "https://www.flipsocial.net/api/feed/%d/%s/%d/max/series/", MAX_FEED_ITEMS, app_instance->login_username_logged_out, series_index);
+ }
+ else
+ {
+ snprintf(command, 96, "https://www.flipsocial.net/api/feed/%d/%s/%d/max/friends/series/", MAX_FEED_ITEMS, app_instance->login_username_logged_out, series_index);
+ }
if (!flipper_http_get_request_with_headers(command, auth_headers))
{
FURI_LOG_E(TAG, "Failed to send HTTP request for feed");
@@ -44,15 +51,10 @@ FlipSocialFeedMini *flip_social_parse_json_feed()
if (feed_data == NULL)
{
FURI_LOG_E(TAG, "Failed to load received data from file.");
+ flipper_http_deinit();
return NULL;
}
- 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 NULL;
- }
+ flipper_http_deinit();
FlipSocialFeedMini *feed_info = (FlipSocialFeedMini *)malloc(sizeof(FlipSocialFeedMini));
if (!feed_info)
@@ -61,65 +63,40 @@ FlipSocialFeedMini *flip_social_parse_json_feed()
return NULL;
}
- // Remove newlines
- char *pos = data_cstr;
- while ((pos = strchr(pos, '\n')) != NULL)
- {
- *pos = ' ';
- }
-
int 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);
+ FuriString *item = get_json_array_value_furi("feed", i, feed_data);
if (item == NULL)
{
break;
}
// Extract individual fields from the JSON object
- char *username = get_json_value("username", item, 40);
- char *message = get_json_value("message", item, 40);
- char *flipped = get_json_value("flipped", item, 40);
- char *flips = get_json_value("flip_count", item, 40);
- char *id = get_json_value("id", item, 40);
-
- if (username == NULL || message == NULL || flipped == NULL || id == NULL)
+ FuriString *id = get_json_value_furi("id", item);
+ if (id == NULL)
{
FURI_LOG_E(TAG, "Failed to parse item fields.");
- free(item);
- free(username);
- free(message);
- free(flipped);
- free(flips);
- free(id);
+ furi_string_free(item);
+ furi_string_free(id);
continue;
}
-
- if (!flip_social_save_post(id, item))
+ if (!flip_social_save_post(furi_string_get_cstr(id), furi_string_get_cstr(item)))
{
FURI_LOG_E(TAG, "Failed to save post.");
- free(item);
- free(username);
- free(message);
- free(flipped);
- free(flips);
- free(id);
+ furi_string_free(item);
+ furi_string_free(id);
continue;
}
feed_count++;
- feed_info->ids[i] = atoi(id);
+ feed_info->ids[i] = atoi(furi_string_get_cstr(id));
- // Free allocated memory
- free(item);
- free(username);
- free(message);
- free(flipped);
- free(flips);
- free(id);
+ // Furi_string_free allocated memory
+ furi_string_free(item);
+ furi_string_free(id);
}
// Store the number of feed items
@@ -127,14 +104,13 @@ FlipSocialFeedMini *flip_social_parse_json_feed()
feed_info->index = 0;
furi_string_free(feed_data);
- free(data_cstr);
return feed_info;
}
bool flip_social_load_feed_post(int post_id)
{
char file_path[128];
- snprintf(file_path, sizeof(file_path), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_social/feed_post_%d.json", post_id);
+ snprintf(file_path, sizeof(file_path), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_social/feed/feed_post_%d.json", post_id);
// load the received data from the saved file
FuriString *feed_data = flipper_http_load_from_file(file_path);
@@ -143,85 +119,82 @@ bool flip_social_load_feed_post(int post_id)
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)
+
+ // Parse the feed post
+ if (flip_feed_item)
+ {
+ free(flip_feed_item);
+ }
+ else
{
- FURI_LOG_E(TAG, "Failed to get C-string from FuriString.");
- furi_string_free(feed_data);
- return false;
+ // first time
+ save_char("series_index", "1");
}
- // Parse the feed post
- if (!flip_feed_item)
+ flip_feed_item = (FlipSocialFeedItem *)malloc(sizeof(FlipSocialFeedItem));
+ if (flip_feed_item == NULL)
{
- flip_feed_item = (FlipSocialFeedItem *)malloc(sizeof(FlipSocialFeedItem));
- if (flip_feed_item == NULL)
- {
- FURI_LOG_E(TAG, "Failed to allocate memory for feed post.");
- furi_string_free(feed_data);
- free(data_cstr);
- return false;
- }
- flip_feed_item->username = malloc(MAX_USER_LENGTH);
- flip_feed_item->message = malloc(MAX_MESSAGE_LENGTH);
+ FURI_LOG_E(TAG, "Failed to allocate memory for feed post.");
+ furi_string_free(feed_data);
+ return false;
}
// Extract individual fields from the JSON object
- char *username = get_json_value("username", data_cstr, 16);
- char *message = get_json_value("message", data_cstr, 16);
- char *flipped = get_json_value("flipped", data_cstr, 16);
- char *flips = get_json_value("flip_count", data_cstr, 16);
- char *id = get_json_value("id", data_cstr, 16);
-
- if (username == NULL || message == NULL || flipped == NULL || id == NULL)
+ FuriString *username = get_json_value_furi("username", feed_data);
+ FuriString *message = get_json_value_furi("message", feed_data);
+ FuriString *flipped = get_json_value_furi("flipped", feed_data);
+ FuriString *flips = get_json_value_furi("flip_count", feed_data);
+ FuriString *date_created = get_json_value_furi("date_created", feed_data);
+ if (username == NULL || message == NULL || flipped == NULL || flips == NULL || date_created == NULL)
{
FURI_LOG_E(TAG, "Failed to parse item fields.");
- free(username);
- free(message);
- free(flipped);
- free(flips);
- free(id);
- free(data_cstr);
+ furi_string_free(username);
+ furi_string_free(message);
+ furi_string_free(flipped);
+ furi_string_free(flips);
+ furi_string_free(date_created);
furi_string_free(feed_data);
return false;
}
// Safely copy strings with bounds checking
- snprintf(flip_feed_item->username, MAX_USER_LENGTH, "%s", username);
- snprintf(flip_feed_item->message, MAX_MESSAGE_LENGTH, "%s", message);
+ snprintf(flip_feed_item->username, MAX_USER_LENGTH, "%s", furi_string_get_cstr(username));
+ snprintf(flip_feed_item->message, MAX_MESSAGE_LENGTH, "%s", furi_string_get_cstr(message));
+ snprintf(flip_feed_item->date_created, MAX_LINE_LENGTH, "%s", furi_string_get_cstr(date_created));
// Store boolean and integer values
- flip_feed_item->is_flipped = strstr(flipped, "true") != NULL;
- flip_feed_item->id = atoi(id);
- flip_feed_item->flips = atoi(flips);
+ flip_feed_item->is_flipped = strstr(furi_string_get_cstr(flipped), "true") ? true : false;
+ flip_feed_item->id = post_id;
+ flip_feed_item->flips = atoi(furi_string_get_cstr(flips));
// Free allocated memory
- free(username);
- free(message);
- free(flipped);
- free(flips);
- free(id);
-
+ furi_string_free(username);
+ furi_string_free(message);
+ furi_string_free(flipped);
+ furi_string_free(flips);
+ furi_string_free(date_created);
furi_string_free(feed_data);
- free(data_cstr);
-
return true;
}
-bool flip_social_load_initial_feed()
+bool flip_social_load_initial_feed(bool alloc_http, int series_index)
{
if (fhttp.state == INACTIVE)
{
FURI_LOG_E(TAG, "HTTP state is INACTIVE");
return false;
}
- if (!easy_flipper_set_loading(&app_instance->loading, FlipSocialViewLoading, flip_social_callback_to_submenu_logged_in, &app_instance->view_dispatcher))
+ Loading *loading;
+ int32_t loading_view_id = 987654321; // Random ID
+ loading = loading_alloc();
+ if (!loading)
{
FURI_LOG_E(TAG, "Failed to set loading screen");
return false;
}
- view_dispatcher_switch_to_view(app_instance->view_dispatcher, FlipSocialViewLoading);
- if (flip_social_get_feed()) // start the async request
+ view_dispatcher_add_view(app_instance->view_dispatcher, loading_view_id, loading_get_view(loading));
+ view_dispatcher_switch_to_view(app_instance->view_dispatcher, loading_view_id);
+ if (flip_social_get_feed(alloc_http, series_index)) // start the async request
{
furi_timer_start(fhttp.get_timeout_timer, TIMEOUT_DURATION_TICKS);
fhttp.state = RECEIVING;
@@ -231,8 +204,8 @@ bool flip_social_load_initial_feed()
FURI_LOG_E(HTTP_TAG, "Failed to send request");
fhttp.state = ISSUE;
view_dispatcher_switch_to_view(app_instance->view_dispatcher, FlipSocialViewLoggedInSubmenu);
- view_dispatcher_remove_view(app_instance->view_dispatcher, FlipSocialViewLoading);
- loading_free(app_instance->loading);
+ view_dispatcher_remove_view(app_instance->view_dispatcher, loading_view_id);
+ loading_free(loading);
return false;
}
while (fhttp.state == RECEIVING && furi_timer_is_running(fhttp.get_timeout_timer) > 0)
@@ -247,10 +220,9 @@ bool flip_social_load_initial_feed()
if (!flip_feed_info || flip_feed_info->count < 1)
{
FURI_LOG_E(TAG, "Failed to parse JSON feed");
- fhttp.state = ISSUE;
view_dispatcher_switch_to_view(app_instance->view_dispatcher, FlipSocialViewLoggedInSubmenu);
- view_dispatcher_remove_view(app_instance->view_dispatcher, FlipSocialViewLoading);
- loading_free(app_instance->loading);
+ view_dispatcher_remove_view(app_instance->view_dispatcher, loading_view_id);
+ loading_free(loading);
return false;
}
@@ -258,24 +230,22 @@ bool flip_social_load_initial_feed()
if (!flip_social_load_feed_post(flip_feed_info->ids[flip_feed_info->index]))
{
FURI_LOG_E(TAG, "Failed to load feed post");
- fhttp.state = ISSUE;
view_dispatcher_switch_to_view(app_instance->view_dispatcher, FlipSocialViewLoggedInSubmenu);
- view_dispatcher_remove_view(app_instance->view_dispatcher, FlipSocialViewLoading);
- loading_free(app_instance->loading);
+ view_dispatcher_remove_view(app_instance->view_dispatcher, loading_view_id);
+ loading_free(loading);
return false;
}
- if (!feed_dialog_alloc())
+ if (!feed_view_alloc())
{
FURI_LOG_E(TAG, "Failed to allocate feed dialog");
- fhttp.state = ISSUE;
view_dispatcher_switch_to_view(app_instance->view_dispatcher, FlipSocialViewLoggedInSubmenu);
- view_dispatcher_remove_view(app_instance->view_dispatcher, FlipSocialViewLoading);
- loading_free(app_instance->loading);
+ view_dispatcher_remove_view(app_instance->view_dispatcher, loading_view_id);
+ loading_free(loading);
return false;
}
- view_dispatcher_switch_to_view(app_instance->view_dispatcher, FlipSocialViewFeedDialog);
- view_dispatcher_remove_view(app_instance->view_dispatcher, FlipSocialViewLoading);
- loading_free(app_instance->loading);
+ view_dispatcher_switch_to_view(app_instance->view_dispatcher, FlipSocialViewLoggedInFeed);
+ view_dispatcher_remove_view(app_instance->view_dispatcher, loading_view_id);
+ loading_free(loading);
return true;
}
\ No newline at end of file
diff --git a/flip_social/feed/flip_social_feed.h b/flip_social/feed/flip_social_feed.h
index a4b92ca6c..e346ecc72 100644
--- a/flip_social/feed/flip_social_feed.h
+++ b/flip_social/feed/flip_social_feed.h
@@ -4,8 +4,8 @@
#include
#include
-bool flip_social_get_feed();
+bool flip_social_get_feed(bool alloc_http, int series_index);
bool flip_social_load_feed_post(int post_id);
-bool flip_social_load_initial_feed();
+bool flip_social_load_initial_feed(bool alloc_http, int series_index);
FlipSocialFeedMini *flip_social_parse_json_feed();
#endif
diff --git a/flip_social/flip_social.c b/flip_social/flip_social.c
index 4b18ccc0a..c58b17f00 100644
--- a/flip_social/flip_social.c
+++ b/flip_social/flip_social.c
@@ -18,6 +18,10 @@ bool flip_social_send_message = false;
char *selected_message = NULL;
char auth_headers[256] = {0};
+char *flip_social_feed_type[] = {"Global", "Friends"};
+uint8_t flip_social_feed_type_index = 0;
+char *flip_social_notification_type[] = {"OFF", "ON"};
+uint8_t flip_social_notification_type_index = 0;
void flip_social_loader_free_model(View *view);
@@ -52,125 +56,6 @@ void flip_social_app_free(FlipSocialApp *app)
submenu_free(app->submenu_logged_in);
}
//
- 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);
- 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);
- 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);
- 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);
- 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);
- 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);
- 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);
- 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);
- text_input_free(app->text_input_logged_in_change_password);
- }
- if (app->text_input_logged_in_change_bio)
- {
- view_dispatcher_remove_view(app->view_dispatcher, FlipSocialViewLoggedInChangeBioInput);
- text_input_free(app->text_input_logged_in_change_bio);
- }
- if (app->text_input_logged_in_compose_pre_save_input)
- {
- view_dispatcher_remove_view(app->view_dispatcher, FlipSocialViewLoggedInComposeAddPreSaveInput);
- 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);
- 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);
- 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);
- 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);
- text_input_free(app->text_input_logged_in_messages_new_message_user_choices);
- }
- if (app->text_input_logged_in_explore)
- {
- view_dispatcher_remove_view(app->view_dispatcher, FlipSocialViewLoggedInExploreInput);
- text_input_free(app->text_input_logged_in_explore);
- }
- if (app->text_input_logged_in_message_users)
- {
- view_dispatcher_remove_view(app->view_dispatcher, FlipSocialViewLoggedInMessageUsersInput);
- text_input_free(app->text_input_logged_in_message_users);
- }
// Free Widget(s)
if (app->widget_result)
@@ -264,55 +149,7 @@ void flip_social_app_free(FlipSocialApp *app)
if (app->explore_user_bio)
free(app->explore_user_bio);
- // DeInit UART
- flipper_http_deinit();
-
// Free the app structure
if (app_instance)
free(app_instance);
-}
-
-void auth_headers_alloc(void)
-{
- if (!app_instance)
- {
- snprintf(auth_headers, sizeof(auth_headers), "{\"Content-Type\":\"application/json\"}");
- return;
- }
-
- 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)
- {
- snprintf(auth_headers, sizeof(auth_headers), "{\"Content-Type\":\"application/json\",\"username\":\"%s\",\"password\":\"%s\"}", app_instance->login_username_logged_out, app_instance->login_password_logged_out);
- }
- else if (app_instance->login_username_logged_in && app_instance->change_password_logged_in && strlen(app_instance->login_username_logged_in) > 0 && strlen(app_instance->change_password_logged_in) > 0)
- {
- snprintf(auth_headers, sizeof(auth_headers), "{\"Content-Type\":\"application/json\",\"username\":\"%s\",\"password\":\"%s\"}", app_instance->login_username_logged_in, app_instance->change_password_logged_in);
- }
- else
- {
- snprintf(auth_headers, sizeof(auth_headers), "{\"Content-Type\":\"application/json\"}");
- }
-}
-
-FlipSocialFeedMini *flip_feed_info_alloc(void)
-{
- FlipSocialFeedMini *feed_info = (FlipSocialFeedMini *)malloc(sizeof(FlipSocialFeedMini));
- if (!feed_info)
- {
- FURI_LOG_E(TAG, "Failed to allocate memory for feed_info");
- return NULL;
- }
- feed_info->count = 0;
- feed_info->index = 0;
- return feed_info;
-}
-
-void flip_feed_info_free(void)
-{
- if (!flip_feed_info)
- {
- return;
- }
- free(flip_feed_info);
- flip_feed_info = NULL;
-}
+}
\ No newline at end of file
diff --git a/flip_social/flip_social.h b/flip_social/flip_social.h
index 10f29f62b..4e2ec168d 100644
--- a/flip_social/flip_social.h
+++ b/flip_social/flip_social.h
@@ -7,16 +7,18 @@
#include
#include
#include
+#include
+
#define TAG "FlipSocial"
+#define VERSION_TAG TAG " v1.0.3"
#define MAX_PRE_SAVED_MESSAGES 20 // 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 576 // Adjust based on expected JSON tokens
-#define MAX_FEED_ITEMS 50 // Maximum number of feed items
-#define MAX_LINE_LENGTH 30
+#define MAX_FEED_ITEMS 20 // Maximum number of feed items
+#define MAX_LINE_LENGTH 27
#define MAX_MESSAGE_USERS 40 // Maximum number of users to display in the submenu
#define MAX_MESSAGES 20 // Maximum number of meesages between each user
@@ -26,10 +28,15 @@
// 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
+ 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
+ FlipSocialSubmenuLoggedInIndexUserSettings, // click to go to the user settings screen
+ //
+ FlipSocialSubmenuLoggedInIndexAbout, // click to go to the about screen
+ FlipSocialSubmenuLoggedInIndexWifiSettings, // click to go to the wifi settings screen
//
FlipSocialSubmenuLoggedInIndexProfile, // click to go to the profile screen
FlipSocialSubmenuExploreIndex, // click to go to the explore
@@ -56,7 +63,7 @@ typedef enum
// Define the ScriptPlaylist structure
typedef struct
{
- char *messages[MAX_PRE_SAVED_MESSAGES];
+ char messages[MAX_PRE_SAVED_MESSAGES][MAX_MESSAGE_LENGTH];
size_t count;
size_t index;
} PreSavedPlaylist;
@@ -64,8 +71,9 @@ typedef struct
// Define a FlipSocialFeed individual item
typedef struct
{
- char *username;
- char *message;
+ char username[MAX_USER_LENGTH];
+ char message[MAX_MESSAGE_LENGTH];
+ char date_created[MAX_LINE_LENGTH];
bool is_flipped;
int id;
int flips;
@@ -76,26 +84,27 @@ typedef struct
int ids[MAX_FEED_ITEMS];
size_t count;
size_t index;
+ int series_index;
} FlipSocialFeedMini;
typedef struct
{
- char *usernames[MAX_EXPLORE_USERS];
+ char usernames[MAX_EXPLORE_USERS][MAX_USER_LENGTH];
int count;
int index;
} FlipSocialModel;
typedef struct
{
- char *usernames[MAX_MESSAGE_USERS];
+ char usernames[MAX_MESSAGE_USERS][MAX_USER_LENGTH];
int count;
int index;
} FlipSocialModel2;
typedef struct
{
- char *usernames[MAX_MESSAGES];
- char *messages[MAX_MESSAGES];
+ char usernames[MAX_MESSAGES][MAX_USER_LENGTH];
+ char messages[MAX_MESSAGES][MAX_MESSAGE_LENGTH];
int count;
int index;
} FlipSocialMessage;
@@ -137,6 +146,7 @@ typedef enum
//
FlipSocialViewLoggedInSettingsAbout, // The about screen
FlipSocialViewLoggedInSettingsWifi, // The wifi settings screen
+ FlipSocialViewLoggedInSettingsUser, // The user settings screen
FlipSocialViewLoggedInWifiSettingsSSIDInput, // Text input screen for SSID input on wifi screen
FlipSocialViewLoggedInWifiSettingsPasswordInput, // Text input screen for Password input on wifi screen
//
@@ -155,7 +165,11 @@ typedef enum
FlipSocialViewFriendsDialog, // The dialog for the friends screen
FlipSocialViewMessagesDialog, // The dialog for the messages screen
FlipSocialViewComposeDialog, // The dialog for the compose screen
- FlipSocialViewFeedDialog, // The dialog for the feed screen
+ //
+ FlipSocialViewTextInput, // The text input screen
+ FlipSocialViewVariableItemList,
+ //
+ FlipSocialViewSubmenu,
} FlipSocialView;
// Define the application structure
@@ -164,44 +178,18 @@ typedef struct
View *view_loader;
Widget *widget_result;
//
- 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)
-
- 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
-
- TextInput *text_input_logged_out_wifi_settings_ssid; // Text input for ssid input on wifi settings screen
- TextInput *text_input_logged_out_wifi_settings_password; // Text input for password input on wifi settings screen
- TextInput *text_input_logged_out_login_username; // Text input for username input on login screen
- TextInput *text_input_logged_out_login_password; // Text input for password input on login screen
- TextInput *text_input_logged_out_register_username; // Text input for username input on register screen
- TextInput *text_input_logged_out_register_password; // Text input for password input on register screen
- TextInput *text_input_logged_out_register_password_2; // Text input for password 2 input on register screen
- //
- TextInput *text_input_logged_in_change_password; // Text input for password input on change password screen
- TextInput *text_input_logged_in_change_bio; // Text input for bio input on profile screen
- TextInput *text_input_logged_in_compose_pre_save_input; // Text input for pre save input on compose screen
- TextInput *text_input_logged_in_wifi_settings_ssid; // Text input for ssid input on wifi settings screen
- TextInput *text_input_logged_in_wifi_settings_password; // Text input for password input on wifi settings screen
- //
- TextInput *text_input_logged_in_messages_new_message; // Text input for new message input on messages screen
- TextInput *text_input_logged_in_messages_new_message_user_choices; //
- //
- TextInput *text_input_logged_in_explore; // Text input for explore input on explore screen
- TextInput *text_input_logged_in_message_users;
+ 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;
+ //
+ Widget *widget_logged_out_about; // The about screen (logged out)
+ Widget *widget_logged_in_about; // The about screen (logged in)
+
+ VariableItemList *variable_item_list; // The main menu
+
+ TextInput *text_input; // The text input
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
@@ -217,10 +205,13 @@ typedef struct
VariableItem *variable_item_logged_in_profile_change_password; // Reference to the change password configuration item
VariableItem *variable_item_logged_in_profile_change_bio; // Reference to the change bio 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_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_settings_user; // Reference to the user settings configuration item
+ VariableItem *variable_item_logged_in_user_settings_feed_type; // Reference to the feed type configuration item
+ VariableItem *variable_item_logged_in_user_settings_notifications; // Reference to the notifications 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
//
@@ -299,12 +290,12 @@ typedef struct
char *message_users_logged_in_temp_buffer; // Temporary buffer for message users text input
uint32_t message_users_logged_in_temp_buffer_size; // Size of the message users temporary buffer
- Loading *loading; // The loading screen
DialogEx *dialog_explore;
DialogEx *dialog_friends;
DialogEx *dialog_messages;
DialogEx *dialog_compose;
- DialogEx *dialog_feed;
+
+ View *view_feed;
char *explore_user_bio; // Store the bio of the selected user
} FlipSocialApp;
@@ -328,8 +319,10 @@ extern bool flip_social_dialog_stop;
extern bool flip_social_send_message;
extern char *selected_message;
extern char auth_headers[256];
-
-void auth_headers_alloc(void);
-FlipSocialFeedMini *flip_feed_info_alloc(void);
-void flip_feed_info_free(void);
+//
+extern char *flip_social_feed_type[];
+extern uint8_t flip_social_feed_type_index;
+//
+extern char *flip_social_notification_type[];
+extern uint8_t flip_social_notification_type_index;
#endif
\ No newline at end of file
diff --git a/flip_social/flip_storage/flip_social_storage.c b/flip_social/flip_storage/flip_social_storage.c
index dea5b2a71..8edfdc49e 100644
--- a/flip_social/flip_storage/flip_social_storage.c
+++ b/flip_social/flip_storage/flip_social_storage.c
@@ -8,6 +8,7 @@ void save_playlist(const PreSavedPlaylist *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");
@@ -29,7 +30,7 @@ void save_playlist(const PreSavedPlaylist *playlist)
// Write each playlist message on a separate line
for (size_t i = 0; i < playlist->count; ++i)
{
- // Add a newline character after each message
+ // Write the 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);
@@ -42,34 +43,23 @@ void save_playlist(const PreSavedPlaylist *playlist)
}
}
+ // Close the file and storage
storage_file_close(file);
storage_file_free(file);
furi_record_close(RECORD_STORAGE);
}
-// 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
- }
- }
- }
+ // Clear existing data in the playlist
+ memset(playlist->messages, 0, sizeof(playlist->messages));
+ playlist->count = 0;
// Open the storage
Storage *storage = furi_record_open(RECORD_STORAGE);
@@ -80,29 +70,24 @@ bool load_playlist(PreSavedPlaylist *playlist)
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
+ return false;
}
- // Initialize the playlist count
- playlist->count = 0;
-
- // Read the file byte by byte to simulate reading lines
+ // Read the file line by line
+ char line[MAX_MESSAGE_LENGTH] = {0};
+ size_t line_pos = 0;
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
+ while (storage_file_read(file, &ch, 1) == 1)
{
- message_started = true;
-
- if (ch == '\n' || message_pos >= (MAX_MESSAGE_LENGTH - 1)) // End of line or message is too long
+ if (ch == '\n' || line_pos >= (MAX_MESSAGE_LENGTH - 1)) // End of line or max length reached
{
- 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;
+ line[line_pos] = '\0'; // Null-terminate the line
+ strncpy(playlist->messages[playlist->count], line, MAX_MESSAGE_LENGTH);
+ playlist->count++;
+ line_pos = 0;
- // Ensure the playlist count does not exceed the maximum
+ // Ensure playlist count does not exceed maximum allowed
if (playlist->count >= MAX_PRE_SAVED_MESSAGES)
{
FURI_LOG_W(TAG, "Reached maximum playlist messages");
@@ -111,21 +96,16 @@ bool load_playlist(PreSavedPlaylist *playlist)
}
else
{
- playlist->messages[playlist->count][message_pos++] = ch; // Add character to current message
+ line[line_pos++] = ch;
}
}
- // Handle the case where the last message does not end with a newline
- if (message_started && message_pos > 0)
+ // Handle the last line if it does not end with a newline
+ if (line_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");
- }
+ line[line_pos] = '\0'; // Null-terminate the last line
+ strncpy(playlist->messages[playlist->count], line, MAX_MESSAGE_LENGTH);
+ playlist->count++;
}
// Close the file and storage
@@ -135,6 +115,7 @@ bool load_playlist(PreSavedPlaylist *playlist)
return true;
}
+
void save_settings(
const char *ssid,
const char *password,
@@ -396,13 +377,25 @@ bool load_settings(
return true;
}
-bool flip_social_save_post(char *post_id, char *json_feed_data)
+bool flip_social_save_post(const char *post_id, const char *json_feed_data)
{
+ if (!post_id || !json_feed_data)
+ {
+ FURI_LOG_E(TAG, "Post ID or JSON feed data is NULL");
+ return false;
+ }
Storage *storage = furi_record_open(RECORD_STORAGE);
File *file = storage_file_alloc(storage);
+ // Create the directory for saving the feed
+ char directory_path[128];
+ snprintf(directory_path, sizeof(directory_path), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_social");
+ storage_common_mkdir(storage, directory_path);
+ snprintf(directory_path, sizeof(directory_path), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_social/feed");
+ storage_common_mkdir(storage, directory_path);
+
char file_path[128];
- snprintf(file_path, sizeof(file_path), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_social/feed_post_%s.json", post_id);
+ snprintf(file_path, sizeof(file_path), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_social/feed/feed_post_%s.json", post_id);
if (!storage_file_open(file, file_path, FSAM_WRITE, FSOM_CREATE_ALWAYS))
{
@@ -425,4 +418,97 @@ bool flip_social_save_post(char *post_id, char *json_feed_data)
storage_file_free(file);
furi_record_close(RECORD_STORAGE);
return true;
+}
+//
+bool save_char(
+ const char *path_name, const char *value)
+{
+ if (!value)
+ {
+ return false;
+ }
+ // Create the directory for saving settings
+ char directory_path[256];
+ 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);
+ snprintf(directory_path, sizeof(directory_path), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_social/data");
+ storage_common_mkdir(storage, directory_path);
+
+ // Open the settings file
+ File *file = storage_file_alloc(storage);
+ char file_path[256];
+ snprintf(file_path, sizeof(file_path), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_social/data/%s.txt", path_name);
+
+ // 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;
+ }
+
+ // Write the data to the file
+ size_t data_size = strlen(value) + 1; // Include null terminator
+ if (storage_file_write(file, value, 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;
+}
+
+bool load_char(
+ const char *path_name,
+ char *value,
+ size_t value_size)
+{
+ if (!value)
+ {
+ return false;
+ }
+ Storage *storage = furi_record_open(RECORD_STORAGE);
+ File *file = storage_file_alloc(storage);
+
+ char file_path[256];
+ snprintf(file_path, sizeof(file_path), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_social/data/%s.txt", path_name);
+
+ // 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
+ }
+
+ // Read data into the buffer
+ size_t read_count = storage_file_read(file, value, value_size);
+ if (storage_file_get_error(file) != FSE_OK)
+ {
+ FURI_LOG_E(HTTP_TAG, "Error reading from file.");
+ storage_file_close(file);
+ storage_file_free(file);
+ furi_record_close(RECORD_STORAGE);
+ return false;
+ }
+
+ // Ensure null-termination
+ value[read_count - 1] = '\0';
+
+ storage_file_close(file);
+ storage_file_free(file);
+ furi_record_close(RECORD_STORAGE);
+
+ return strlen(value) > 0;
}
\ No newline at end of file
diff --git a/flip_social/flip_storage/flip_social_storage.h b/flip_social/flip_storage/flip_social_storage.h
index 94a1fbd76..108c3d997 100644
--- a/flip_social/flip_storage/flip_social_storage.h
+++ b/flip_social/flip_storage/flip_social_storage.h
@@ -37,5 +37,13 @@ bool load_settings(
char *is_logged_in,
size_t is_logged_in_size);
-bool flip_social_save_post(char *post_id, char *json_feed_data);
+bool flip_social_save_post(const char *post_id, const char *json_feed_data);
+//
+
+bool save_char(
+ const char *path_name, const char *value);
+bool load_char(
+ const char *path_name,
+ char *value,
+ size_t value_size);
#endif
\ No newline at end of file
diff --git a/flip_social/flipper_http/flipper_http.c b/flip_social/flipper_http/flipper_http.c
index 0d5a518e3..f3d7743cc 100644
--- a/flip_social/flipper_http/flipper_http.c
+++ b/flip_social/flipper_http/flipper_http.c
@@ -1,8 +1,5 @@
#include // change this to where flipper_http.h is located
-FlipperHTTP fhttp;
-char rx_line_buffer[RX_LINE_BUFFER_SIZE];
-uint8_t file_buffer[FILE_BUFFER_SIZE];
-size_t file_buffer_len = 0;
+FlipperHTTP fhttp = {0};
// Function to append received data to file
// make sure to initialize the file path before calling this function
bool flipper_http_append_to_file(
@@ -187,19 +184,19 @@ int32_t flipper_http_worker(void *context)
if (fhttp.save_bytes)
{
// Add byte to the buffer
- file_buffer[file_buffer_len++] = c;
+ fhttp.file_buffer[fhttp.file_buffer_len++] = c;
// Write to file if buffer is full
- if (file_buffer_len >= FILE_BUFFER_SIZE)
+ if (fhttp.file_buffer_len >= FILE_BUFFER_SIZE)
{
if (!flipper_http_append_to_file(
- file_buffer,
- file_buffer_len,
+ fhttp.file_buffer,
+ fhttp.file_buffer_len,
fhttp.just_started_bytes,
fhttp.file_path))
{
FURI_LOG_E(HTTP_TAG, "Failed to append data to file");
}
- file_buffer_len = 0;
+ fhttp.file_buffer_len = 0;
fhttp.just_started_bytes = false;
}
}
@@ -210,17 +207,17 @@ int32_t flipper_http_worker(void *context)
// 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
+ fhttp.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);
+ fhttp.handle_rx_line_cb(fhttp.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
+ fhttp.rx_line_buffer[rx_line_pos++] = c; // Add character to the line buffer
}
}
}
@@ -382,6 +379,7 @@ bool flipper_http_init(FlipperHTTP_Callback callback, void *context)
}
// FURI_LOG_I(HTTP_TAG, "UART initialized successfully.");
+ fhttp.state = IDLE; // set idle for easy use
return true;
}
@@ -1114,27 +1112,27 @@ void flipper_http_rx_callback(const char *line, void *context)
const char marker[] = "[GET/END]";
const size_t marker_len = sizeof(marker) - 1; // Exclude null terminator
- for (size_t i = 0; i <= file_buffer_len - marker_len; i++)
+ for (size_t i = 0; i <= fhttp.file_buffer_len - marker_len; i++)
{
// Check if the marker is found
- if (memcmp(&file_buffer[i], marker, marker_len) == 0)
+ if (memcmp(&fhttp.file_buffer[i], marker, marker_len) == 0)
{
// Remove the marker by shifting the remaining data left
- size_t remaining_len = file_buffer_len - (i + marker_len);
- memmove(&file_buffer[i], &file_buffer[i + marker_len], remaining_len);
- file_buffer_len -= marker_len;
+ size_t remaining_len = fhttp.file_buffer_len - (i + marker_len);
+ memmove(&fhttp.file_buffer[i], &fhttp.file_buffer[i + marker_len], remaining_len);
+ fhttp.file_buffer_len -= marker_len;
break;
}
}
// If there is data left in the buffer, append it to the file
- if (file_buffer_len > 0)
+ if (fhttp.file_buffer_len > 0)
{
- if (!flipper_http_append_to_file(file_buffer, file_buffer_len, false, fhttp.file_path))
+ if (!flipper_http_append_to_file(fhttp.file_buffer, fhttp.file_buffer_len, false, fhttp.file_path))
{
FURI_LOG_E(HTTP_TAG, "Failed to append data to file.");
}
- file_buffer_len = 0;
+ fhttp.file_buffer_len = 0;
}
}
@@ -1184,27 +1182,27 @@ void flipper_http_rx_callback(const char *line, void *context)
const char marker[] = "[POST/END]";
const size_t marker_len = sizeof(marker) - 1; // Exclude null terminator
- for (size_t i = 0; i <= file_buffer_len - marker_len; i++)
+ for (size_t i = 0; i <= fhttp.file_buffer_len - marker_len; i++)
{
// Check if the marker is found
- if (memcmp(&file_buffer[i], marker, marker_len) == 0)
+ if (memcmp(&fhttp.file_buffer[i], marker, marker_len) == 0)
{
// Remove the marker by shifting the remaining data left
- size_t remaining_len = file_buffer_len - (i + marker_len);
- memmove(&file_buffer[i], &file_buffer[i + marker_len], remaining_len);
- file_buffer_len -= marker_len;
+ size_t remaining_len = fhttp.file_buffer_len - (i + marker_len);
+ memmove(&fhttp.file_buffer[i], &fhttp.file_buffer[i + marker_len], remaining_len);
+ fhttp.file_buffer_len -= marker_len;
break;
}
}
// If there is data left in the buffer, append it to the file
- if (file_buffer_len > 0)
+ if (fhttp.file_buffer_len > 0)
{
- if (!flipper_http_append_to_file(file_buffer, file_buffer_len, false, fhttp.file_path))
+ if (!flipper_http_append_to_file(fhttp.file_buffer, fhttp.file_buffer_len, false, fhttp.file_path))
{
FURI_LOG_E(HTTP_TAG, "Failed to append data to file.");
}
- file_buffer_len = 0;
+ fhttp.file_buffer_len = 0;
}
}
@@ -1332,7 +1330,7 @@ void flipper_http_rx_callback(const char *line, void *context)
// for GET request, save data only if it's a bytes request
fhttp.save_bytes = fhttp.is_bytes_request;
fhttp.just_started_bytes = true;
- file_buffer_len = 0;
+ fhttp.file_buffer_len = 0;
return;
}
else if (strstr(line, "[POST/SUCCESS]") != NULL)
@@ -1344,7 +1342,7 @@ void flipper_http_rx_callback(const char *line, void *context)
// for POST request, save data only if it's a bytes request
fhttp.save_bytes = fhttp.is_bytes_request;
fhttp.just_started_bytes = true;
- file_buffer_len = 0;
+ fhttp.file_buffer_len = 0;
return;
}
else if (strstr(line, "[PUT/SUCCESS]") != NULL)
@@ -1593,5 +1591,5 @@ void flipper_http_loading_task_2(bool (*http_request)(void),
// Switch to the success view
view_dispatcher_switch_to_view(*view_dispatcher, success_view_id);
view_dispatcher_remove_view(*view_dispatcher, loading_view_id);
- loading_free(loading);
+ // loading_free(loading);
}
\ No newline at end of file
diff --git a/flip_social/flipper_http/flipper_http.h b/flip_social/flipper_http/flipper_http.h
index 1b4bbb30f..e6bb821d3 100644
--- a/flip_social/flipper_http/flipper_http.h
+++ b/flip_social/flipper_http/flipper_http.h
@@ -20,7 +20,7 @@
#define UART_CH (momentum_settings.uart_esp_channel) // UART channel
#define TIMEOUT_DURATION_TICKS (8 * 1000) // 8 seconds (increased for Pico W)
#define BAUDRATE (115200) // UART baudrate
-#define RX_BUF_SIZE 128 // UART RX buffer size
+#define RX_BUF_SIZE 2048 // UART RX buffer size
#define RX_LINE_BUFFER_SIZE 7000 // UART RX line buffer size (increase for large responses)
#define MAX_FILE_SHOW 7000 // Maximum data from file to show
#define FILE_BUFFER_SIZE 512 // File buffer size
@@ -83,13 +83,13 @@ typedef struct
bool save_received_data; // Flag to save the received data to a file
bool just_started_bytes; // Indicates if bytes data reception has just started
+
+ char rx_line_buffer[RX_LINE_BUFFER_SIZE];
+ uint8_t file_buffer[FILE_BUFFER_SIZE];
+ size_t file_buffer_len;
} 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];
-extern size_t file_buffer_len;
// fhttp.last_response holds the last received data from the UART
diff --git a/flip_social/font/font.c b/flip_social/font/font.c
new file mode 100644
index 000000000..8f19c501e
--- /dev/null
+++ b/flip_social/font/font.c
@@ -0,0 +1,316 @@
+#include
+
+static const uint8_t u8g2_font_4x6_tf[];
+static const uint8_t u8g2_font_6x10_tf[];
+static const uint8_t u8g2_font_5x8_tf[];
+static const uint8_t u8g2_font_9x15_tf[];
+
+bool canvas_set_font_custom(Canvas *canvas, FontSize font_size)
+{
+ if (!canvas)
+ {
+ return false;
+ }
+ switch (font_size)
+ {
+ case FONT_SIZE_SMALL:
+ canvas_set_custom_u8g2_font(canvas, u8g2_font_4x6_tf);
+ break;
+ case FONT_SIZE_MEDIUM:
+ canvas_set_custom_u8g2_font(canvas, u8g2_font_5x8_tf);
+ break;
+ case FONT_SIZE_LARGE:
+ canvas_set_custom_u8g2_font(canvas, u8g2_font_6x10_tf);
+ break;
+ case FONT_SIZE_XLARGE:
+ canvas_set_custom_u8g2_font(canvas, u8g2_font_9x15_tf);
+ break;
+ default:
+ return false;
+ }
+ return true;
+}
+
+void canvas_draw_str_multi(Canvas *canvas, uint8_t x, uint8_t y, const char *str)
+{
+ if (!canvas || !str)
+ {
+ return;
+ }
+ elements_multiline_text(canvas, x, y, str);
+}
+
+/*
+ Fontname: -Misc-Fixed-Medium-R-Normal--6-60-75-75-C-40-ISO10646-1
+ Copyright: Public domain font. Share and enjoy.
+ Glyphs: 191/919
+ BBX Build Mode: 0
+*/
+static const uint8_t u8g2_font_4x6_tf[] =
+ "\277\0\2\2\3\3\2\4\4\4\6\0\377\5\377\5\377\0\351\1\323\5\216 \5\200\315\0!\6\351\310"
+ "\254\0\42\6\223\313$\25#\12\254\310\244\64T\32*\1$\11\263\307\245\241GJ\0%\10\253\310d"
+ "\324F\1&\11\254\310\305\24\253\230\2'\5\321\313\10(\7\362\307\251f\0)\10\262\307\304T)\0"
+ "*\7\253\310\244j\65+\10\253\310\305\264b\2,\6\222\307)\0-\5\213\312\14.\5\311\310\4/"
+ "\7\253\310Ve\4\60\10\253\310UCU\0\61\7\253\310%Y\15\62\7\253\310\65S\32\63\10\253\310"
+ "\314\224\27\0\64\10\253\310$\65b\1\65\10\253\310\214\250\27\0\66\7\253\310M\325\2\67\10\253\310\314"
+ "TF\0\70\7\253\310\255\326\2\71\7\253\310\265\344\2:\6\341\310\304\0;\7\252\307e\250\0<\7"
+ "\253\310\246\272\0=\6\233\311\354\1>\7\253\310\344\252\4\77\10\253\310\350\224a\2@\6\253\310-["
+ "A\10\253\310UC\251\0B\10\253\310\250\264\322\2C\10\253\310U\62U\0D\10\253\310\250d-\0"
+ "E\10\253\310\214\250\342\0F\10\253\310\214\250b\4G\10\253\310\315\244\222\0H\10\253\310$\65\224\12"
+ "I\7\253\310\254X\15J\7\253\310\226\252\2K\10\253\310$\265\222\12L\7\253\310\304\346\0M\10\253"
+ "\310\244\61\224\12N\10\253\310\252\241$\0O\7\253\310UV\5P\10\253\310\250\264b\4Q\7\263\307"
+ "UV\35R\10\253\310\250\264\222\12S\7\253\310\355\274\0T\7\253\310\254\330\2U\7\253\310$\327\10"
+ "V\10\253\310$k\244\4W\10\253\310$\65\206\12X\10\253\310$\325R\1Y\10\253\310$UV\0"
+ "Z\7\253\310\314T\16[\6\352\310\254J\134\7\253\310\304\134\6]\6\252\310\250j^\5\223\313\65_"
+ "\5\213\307\14`\6\322\313\304\0a\7\243\310-\225\4b\10\253\310D\225\324\2c\6\243\310\315,d"
+ "\10\253\310\246\245\222\0e\6\243\310USf\10\253\310\246\264b\2g\10\253\307\255$\27\0h\10\253"
+ "\310D\225\254\0i\10\253\310e$\323\0j\10\263\307fX.\0k\10\253\310\304\264\222\12l\7\253"
+ "\310\310\326\0m\10\243\310\244\241T\0n\7\243\310\250d\5o\7\243\310U\252\2p\10\253\307\250\264"
+ "b\4q\10\253\307-\225d\0r\10\243\310\244\25#\0s\7\243\310\215\274\0t\10\253\310\245\25s"
+ "\0u\7\243\310$+\11v\7\243\310$\253\2w\10\243\310$\65T\0x\7\243\310\244\62\25y\10"
+ "\253\307$\225\344\2z\7\243\310\314\224\6{\10\263\307\246$\353\0|\6\351\310\14\1}\11\263\307\344"
+ "\250b\212\0~\7\224\313%\225\0\240\5\200\315\0\241\6\351\310\244\1\242\10\253\310\245\21W\2\243\7"
+ "\253\310\246\250\32\244\10\244\310\304$U\14\245\10\253\310\244j\305\4\246\6\351\310(\1\247\10\263\307\215"
+ "T\311\5\250\6\213\314\244\0\251\11\264\307\251\270\226L\12\252\7\253\310\255\244\7\253\10\234\311%\25S"
+ "\0\254\6\223\311\314\0\255\5\213\312\14\256\10\244\311\251\261\222\2\257\5\213\314\14\260\6\233\312u\1\261"
+ "\10\253\310\245\225\321\0\262\6\242\311(\3\263\7\252\310(\251\0\264\6\322\313)\0\265\10\253\307$k"
+ "E\0\266\7\254\310\15\265z\267\5\311\312\4\270\6\322\310)\0\271\6\242\311\255\2\272\7\253\310u\243"
+ "\1\273\10\234\311\244\230T\2\274\10\264\307\344\32U;\275\10\264\307\344J\307,\276\11\264\307\350\230Q"
+ "\262\3\277\10\253\310e\230\262\0\300\10\253\310\344\224\206\12\301\10\253\310\246j\250\0\302\10\253\310\310\224"
+ "\206\12\303\10\253\310\215\224\206\12\304\10\253\310\244\326P\1\305\10\253\310\305\224\206\12\306\11\254\310\215\224"
+ "\206\252\4\307\10\263\307U\62\65\1\310\10\253\310\304\241\342\0\311\7\253\310\216\25\7\312\10\253\310\215\221"
+ "\342\0\313\10\253\310\244\261\342\0\314\10\253\310\304\25\323\0\315\10\253\310\216\24\323\0\316\10\253\310\245\25"
+ "\323\0\317\7\253\310\244\262\32\320\11\254\310\314\264\252\221\0\321\10\254\310%\225\256\12\322\10\253\310\344\224"
+ "T\5\323\10\253\310\246JU\0\324\10\253\310\305\224T\5\325\10\254\310\215\325\31\1\326\10\253\310\244\226"
+ "\252\0\327\6\233\311\244\16\330\7\253\310\35j\1\331\10\253\310\344\224\324\10\332\10\253\310\246J\215\0\333"
+ "\10\253\310e\224\324\10\334\10\253\310\244\234\324\10\335\10\253\310\346T&\0\336\10\253\310D\225V\4\337"
+ "\10\263\307U+\15\11\340\10\253\310\344\270\222\0\341\10\253\310\246\270\222\0\342\10\253\310i\264\222\0\343"
+ "\11\254\310%\25U\251\0\344\10\253\310\244\214V\22\345\10\253\310e\270\222\0\346\10\244\310\215\264\342\0"
+ "\347\10\253\307UZ%\0\350\10\253\310\344\224\246\0\351\7\253\310\246j\12\352\10\253\310\310\224\246\0\353"
+ "\7\253\310\244\326\24\354\7\253\310\344X\15\355\6\253\310\316j\356\7\253\310u\246\1\357\10\253\310\244,"
+ "\323\0\360\10\253\310\244rU\0\361\11\254\310%\225dj\1\362\10\253\310\344\230Z\0\363\10\253\310\246"
+ "\230Z\0\364\10\253\310e\230Z\0\365\7\253\310l\324\5\366\10\253\310\244\214\272\0\367\10\253\310e\264"
+ "Q\2\370\7\243\310-\265\0\371\10\253\310\344\224T\22\372\10\253\310\246J%\1\373\10\253\310e\224T"
+ "\22\374\10\253\310\244\234T\22\375\10\263\307\246j\304\5\376\11\263\307\304\250\322\212\0\377\11\263\307\244\234"
+ "F\134\0\0\0\0\4\377\377\0";
+/*
+ Fontname: -Misc-Fixed-Medium-R-Normal--8-80-75-75-C-50-ISO10646-1
+ Copyright: Public domain font. Share and enjoy.
+ Glyphs: 191/1426
+ BBX Build Mode: 0
+*/
+static const uint8_t u8g2_font_5x8_tf[] =
+ "\277\0\2\2\3\4\3\4\4\5\10\0\377\6\377\6\0\1\32\2\61\6\226 \5\0~\3!\7\61c"
+ "\63R\0\42\7\233n\223\254\0#\15=bW\246\64T\65T\231\22\0$\12=b\233W\275S\332"
+ "\21%\10\253f\23Sg\0&\12\10\263b\223\313T\2\77\11\263b"
+ "\327L\31&\0@\14E^+\243\134I%YC\5A\11\64b\247\242\34S\6B\12\64b\263\342"
+ "HQ\216\4C\11\64b\247\242.\223\2D\11\64b\263\242s$\0E\11\64b\63\364\312y\4F"
+ "\11\64b\63\364\312\65\0G\12\64b\247\242N\63)\0H\11\64b\23\345\230f\0I\7\263b\263"
+ "bkJ\11\64b\67sUF\0K\11\64b\23U\222\251\63L\10\64b\223\273G\0M\11\64b"
+ "\23\307\21\315\0N\11\64b\23\327Xg\0O\11\64b\247\242\63)\0P\12\64b\263\242\34)g"
+ "\0Q\11<^\247\242\134n\24R\12\64b\263\242\34)\312\0S\12\64b\247b\312\250L\12T\10"
+ "\263b\263b\27\0U\10\64b\23=\223\2V\11\64b\23\235I*\0W\11\64b\23\315q\304\0"
+ "X\12\64b\23e\222*\312\0Y\13\65b\223u\252\63\312(\2Z\11\64b\63rl\217\0[\7"
+ "\263b\63bs\134\12\64b\223\63\312(\243\34]\7\263b\63\233#^\6\223r\327\0_\6\14^"
+ "\63\2`\6\222r\23\3a\10$b\67\242L\3b\12\64b\223\363\212r$\0c\7\243b\67\263"
+ "\0d\11\64b_\215(\323\0e\10$b\247\322\310\12f\11\64b[\225\63G\0g\11,^\247"
+ "b\332I\1h\11\64b\223\363\212f\0i\10\263b\227\221\254\6j\11\273^\233a\251*\0k\11"
+ "\64b\223\313\221\242\14l\7\263b#\273\6m\11%b\243Z*\251\2n\7$b\263\242\31o\10"
+ "$b\247\242L\12p\11,^\263\342H\71\3q\10,^\67b\332\5r\10$b\223\222\235\1s"
+ "\7\243b\67\362\2t\12\64b\227\343\314)&\0u\7$b\23\315\64v\7\243b\223\254\12w\11"
+ "%b\223UR]\0x\10$b\23\223T\61y\12,^\23e\32\61)\0z\10$b\63b\71"
+ "\2{\13O\225\0-\6\15Ng\10.\7\233>/\255\4/\13=B\37e\224\273QF"
+ "\0\60\12=B\67\247\332Nu\4\61\14=B\67\313\224QF\31\305!\62\14=Bo\345\214\242\314"
+ "\31\15\1\63\14=Bgh\224\263\206:-\0\64\14=B\77\313T\246\241\63J\0\65\13=B\347"
+ "F\311\314H\247\5\66\13=BW\346\214\222\251\323\2\67\14=Bgh\224\63\312\65\312\0\70\13="
+ "Boe\235V\326i\1\71\14=Boe\251TF\71J\0:\12\273>/\255\14\323J\0;\11"
+ "\273>/\255\14U\11<\12\274B\77\266QF\31\5=\10\35Jgh\70\4>\13\274B'\243\214"
+ "\62\212m\0\77\12=Bo\345\66\312t\4@\13=Boe\271\222\225\341\2A\13=B\67\247Z"
+ "\217\221u\0B\14=Bg\304*\246Y\305\241\0C\14=Boe\215\62\312(\247\5D\15=B"
+ "g\304*\246\230b\212C\1E\14=B\347F\31\215\224QFCF\15=B\347F\31\215\224QF"
+ "\31\1G\14=Boe\215\62\212;-\0H\11=B'\333cd;I\10\273Bg\305\256\1J"
+ "\14=Bwg\224QFe\224\0K\13=B'\313T\352\24\253\34L\16=B'\243\214\62\312("
+ "\243\214\206\0M\12=B'\353\265\222\266\3N\12=B'\353\251\222\334:O\11=Boe\357\264"
+ "\0P\15=Bg\244\254\207\312(\243\214\0Q\12E>oe\257j\303\0R\13=Bg\244\254\207"
+ "*\253\34S\13=Boe\15\67\324i\1T\16=Bg\310\214\62\312(\243\214\42\0U\10=B"
+ "'\373N\13V\13=B'\333\251L\61\345\10W\12=B'\273\222Jw\0X\12=B'\353T"
+ "W\265\16Y\14=B'\353Tg\224QF\21Z\12=Bgh\224\273\321\20[\10\273Bg\304\316"
+ "\1\134\15=B'\243\14\63\314\60\303\214\2]\10\273Bgv\216\0^\7\35R\67\247:_\6\15"
+ ">g\10`\6\22['\6a\12-Bo\303\64t\32\1b\14=B'\243\214\222\251\247R\0c"
+ "\12-Boe\215rZ\0d\13=B\37e\224\314-\225\12e\12-Bo\345\61\62\134\0f\14"
+ "=BWVy\304\214\62\312\0g\14=:oh\235FF:-\0h\13=B'\243\214\222\251\355"
+ "\0i\10\273B/#\331\32j\13\314:\77c]K\231\24\0k\14=B'\243\214\262L\263\312\1"
+ "l\7\273BG\366\32m\12-BG\265TRI\7n\10-B'\231\332\16o\11-Boe;"
+ "-\0p\14=:'\231z*\225QF\0q\13=:\317\334R\251\214\62\12r\13-B'\231\32"
+ "e\224\21\0s\12-Boe\270\341P\0t\15=B/\243\67\17\25SLy\304\10\243\13=BWVyg\24\225\2\244\12-B'"
+ "\247\231\342\312\1\245\15E>'\353T\307!\63\312(\2\246\6\71Cg\15\247\13E>oe\64;"
+ "\67J\13\250\6\213^'\5\251\14=Boe\225\246J:-\0\252\12\264FoD\245j\64\2\253"
+ "\13.B\267\212)\346\230c\0\254\7\224Jg\344\0\255\6\214Ng\4\256\13=Bo\345\221\346\324"
+ "i\1\257\6\15^g\10\260\6\233R\257\13\261\13\65B\67\243\70dFq\10\262\10\254NO\305\346"
+ "\10\263\12\254Ng\243\244\321H\0\264\6\22[O\1\265\12\65>'\333S\251\214\0\266\16=Bo"
+ "\214\64RR\61\305\24S\0\267\5\11O'\270\6\22;O\1\271\7\253N/\311j\272\12\264FO"
+ "E\231\64\34\1\273\14.B'\346\230c\212)F\0\274\20N>/#\15\63\314hf\244S\34\31"
+ "\6\275\20N>/#\15\63\314heT\303\214\62\32\276\16M>G\303\234a\224Y\246\64\62\12\277"
+ "\12=B\67\323\31\345\326\2\300\14EB/\303\274\262\36#\353\0\301\13EB\277^Y\217\221u\0"
+ "\302\14EB\67\247\270\262\36#\353\0\303\14EB/*\271\262\36#\353\0\304\13EB\257\246V\326"
+ "cd\35\305\14EB\67\247\270\262\36#\353\0\306\13>Bw\244\262\71Fl\16\307\15M:oe"
+ "\215\62\312(\247]\3\310\16EB/\217\215\62\32)\243\214\206\0\311\16EB\77\215\215\62\32)\243"
+ "\214\206\0\312\16EB\67\216\215\62\32)\243\214\206\0\313\16EB\257\32\33e\64RF\31\15\1\314"
+ "\11\303B'\247\25[\3\315\11\303B\67\245\25[\3\316\11\303B\257\32)\266\6\317\11\303B'\345"
+ "\25[\3\320\15=Bg\304*\216T\246\70\24\0\321\13EB\67uO\225\344\326\1\322\13EB/"
+ "\303\274\262;-\0\323\12EB\277^\331\235\26\0\324\13EB\67\247\270\262;-\0\325\12EB\67"
+ "\65Wv\247\5\326\12EB\257\246Vv\247\5\327\11-B'\247\272\252\3\330\13=Bo\305+\315"
+ "\231\26\0\331\12EB/\303\332;-\0\332\11EB\277\314\336i\1\333\13EB\67\247\214\263;-"
+ "\0\334\12EB\257\306\331;-\0\335\14EB\277\314:\325\31e\24\1\336\16=B'\243\221\362P"
+ "\31e\224\21\0\337\13=Boe\231\312*+\5\340\14EB/\303Ln\230\206N#\341\13EB"
+ "\277&\67LC\247\21\342\14EB\67\247Lm\230\206N#\343\14EB\67\265\251\15\323\320i\4\344"
+ "\13=B\257\246\66LC\247\21\345\14EB\67\247\234\67LC\247\21\346\13.BodT\215\231\207"
+ "\0\347\13=:oe\215r\332\65\0\350\14EB/\303L\256\14"
+ "\1>\12\245\12\63\322\216YG\0\77\16\247\11s\6%U\343\264\71\207\63\0@\21\247\11s\6%"
+ "\65\15J\246D\223\42\347\203\2A\15\247\11\363\322$\253\244\326\341j\15B\22\247\11\63\6)LR"
+ "\61\31\244\60I\215\311 \1C\15\247\11s\6%\225\373\232\14\12\0D\15\247\11\63\6)LR\77"
+ "&\203\4E\15\247\11\63\16ry\220\342\346a\10F\14\247\11\63\16ry\220\342\316\0G\17\247\11"
+ "s\6%\225\333\206\324\232\14\12\0H\12\247\11\63R\327\341\352\65I\12\245\12\63\6)\354\247AJ"
+ "\15\250\11\363\6\65\357S\230\15\31\0K\21\247\11\63R\61\311\242\332\230\204QV\12\223\64L\12\247"
+ "\11\63\342\376<\14\1M\20\247\11\63Ru[*JE\212\244H\265\6N\17\247\11\63RuT\62"
+ ")\322\22q\265\6O\14\247\11s\6%\365\327dP\0P\15\247\11\63.\251u\30\222\270\63\0Q"
+ "\16\307\351r\6%\365K&U\6\65\27R\20\247\11\63.\251u\30\222(+\205I\252\6S\16\247"
+ "\11s\6%\265\357V\65\31\24\0T\12\247\11\63\16Y\334\337\0U\13\247\11\63R\377\232\14\12\0"
+ "V\21\247\11\63Rk\222EY\224U\302$L\322\14W\20\247\11\63RO\221\24I\221\24)\335\22"
+ "\0X\20\247\11\63R\65\311*i\234&Y%U\3Y\15\247\11\63R\65\311*i\334\33\0Z\14"
+ "\247\11\63\16q\332\347x\30\2[\12\304\373\62\6\255\177\33\2\134\13\247\11\63\362\70/\347\345<]"
+ "\12\304\372\62\206\254\177\33\4^\12Gi\363\322$\253\244\1_\7\30\370\62\16\2`\7\63\213\63\262"
+ "\2a\16w\11s\6=N\206!\25\225!\11b\17\247\11\63\342\226!\21U\353\250\14\11\0c\14"
+ "w\11s\6%\225[\223A\1d\15\247\11\263[\206D\134\35\225!\11e\15w\11s\6%U\207"
+ "s>(\0f\16\247\11\363\266R\26\305\341 \306\215\0g\23\247\331r\206DL\302$\214\206(\37"
+ "\224TM\6\5h\14\247\11\63\342\226!\21U\257\1i\12\245\12stt\354i\20j\15\326\331\62"
+ "u\312\332U\64&C\2k\17\247\11\63\342VMI\64\65\321\62%\15l\11\245\12\63\306\376\64\10"
+ "m\20w\11\63\26%\212\244H\212\244H\212\324\0n\13w\11\63\222!\21U\257\1o\14w\11s"
+ "\6%\365\232\14\12\0p\17\247\331\62\222!\21U\353\250\14I\134\6q\15\247\331r\206D\134\35\225"
+ "!\211\33r\14w\11\63\242IK\302$n\5s\15w\11s\6%\325\7]M\6\5t\14\227\11"
+ "\263\342p\330\342n\331\2u\20w\11\63\302$L\302$L\302$\214\206$v\16w\11\63R\65\311"
+ "\242\254\22&i\6w\16w\11\63RS$ER\244tK\0x\15w\11\63\322$\253\244\225\254\222"
+ "\6y\15\246\331\62B\337\224%\25\223!\1z\12w\11\63\16i\257\303\20{\15\305\373\262\226\260\32"
+ "ij\26V\7|\6\301\374\62>}\16\305\371\62\326\260\226jR\32V&\0~\12\67ys\64)"
+ "\322\24\0\240\5\0\310\63\241\10\261\14\63\244a\10\242\21\206\11\63\243!\211\22\251\222%Q\62D!"
+ "\0\243\21\247\11\363\266R\34\16b\234\212I\226$\13\0\244\16g\71\63\322d\220\262(\213\6%\15"
+ "\245\20\247\11\63R\65\311*\331 \206\203\30\327\0\246\10\261\374\62\6e\20\247\16\264\372r\224HT"
+ "\42S\42J\211\2\250\7%\232\63\62-\251\25\230\30\263\206,L\42K\224(\241\22%\222\224\204\331"
+ "\20\1\252\14u\71s\244\322\22EC:\10\253\15\207\31\363\242~\213\302(\214\302(\254\7F)\63"
+ "\256\15\255\7\25J\63\6\1\256\25\230\30\263\206,L\222I\211\22eRJJ\224\24\263!\2\257\6"
+ "\26\231\63\16\260\12Dks\224HJ\24\0\261\16\227\31\363\342\332\60dq\35\33\206\0\262\13dI"
+ "s\224(K\224l\10\263\13dIs\224\250(%\12\0\264\10\63\213\263\222\22\0\265\14\227\351\62R"
+ "\257\333\262\310\61\0\266\26\247\11s\206!K\264DK\222!\11\223\60\11\223\60\11\223\0\267\7\42L"
+ "\63\206\0\270\10\64\332\262\246D\1\271\10cIs\22\251e\272\12eIs\226LK\346A\273\16\207"
+ "\31\63\242\60\12\243\60\312\242~\3\274\17\247\11sR\271q\210\304$\213\62%\25\275\16\247\11sR"
+ "\271I\31\242\70\24\343!\276\21\247\11s\304(\315\263\250\42\211I\26eJ*\277\15\247\11\363r\270"
+ "\332\234\252\311\240\0\300\16\307\11s\362:\272URu\270Z\3\301\15\307\11s\333\321\255\222\252\303\325"
+ "\32\302\17\307\11\363\322$\253c[%U\207\253\65\303\17\267\11s\64i\307\266J\252\16Wk\0\304"
+ "\17\267\11s\262(\313\261\255\222\252\303\325\32\305\17\267\11\263\266\332\230d\225T\35\256\326\0\306\25\247"
+ "\11s\224!\312\242,\312\242lX\242,\312\242,\32\2\307\20\327\331r\6%\225\373\232\14\242\26\205"
+ "\32\0\310\21\307\11s\362:\66\14I\34\17Y\134\35\206\0\311\20\307\11s\333\261aH\342x\310\342"
+ "\352\60\4\312\22\307\11\363\322$\253#\303\220\304\361\220\305\325a\10\313\22\267\11s\262(\313\221aH"
+ "\342x\310\342\352\60\4\314\14\305\12\63\322\372 \205=\15\2\315\14\305\12\63\263\372 \205=\15\2\316"
+ "\15\305\12\263\262\244\226\16R\330\323 \317\14\265\12\63\62-\35\244\260\247A\320\25\250\10s\6-\214"
+ "\322$\35\302$M\322$M\302h\220\0\321\22\267\11s\64iG\322Q\311\244H\212\264D\134\3\322"
+ "\16\307\11s\362:\70(\251\257\311\240\0\323\15\307\11s\333\301AI}M\6\5\324\20\307\11\363\322"
+ "$\253C\203\222\372\232\14\12\0\325\17\267\11s\64i\207\6%\365\65\31\24\0\326\17\267\11s\262("
+ "\313\241AI}M\6\5\327\15w\31\63\322$\253\244\225\254\222\6\330\26\307\371\262\223A\11\267DK"
+ "\244H\212\224L\311\306dPb\0\331\15\307\11s\362:\226\372\65\31\24\0\332\14\307\11s\333\261\324"
+ "\257\311\240\0\333\16\307\11\363\322$\253#\251_\223A\1\334\16\267\11s\262(\313\221\324\257\311\240\0"
+ "\335\16\307\11s\333\261TM\262J\32\267\1\336\16\247\11\63\342xXR\353\60$q\31\337\24\246\11"
+ "\263\246,\311\222(Q\262\250\226dI\226$\12\0\340\21\267\11\263\362:\66\350q\62\14\251\250\14I"
+ "\0\341\20\267\11s\333\301A\217\223aHEeH\2\342\22\267\11\363\322$\253C\203\36'\303\220\212"
+ "\312\220\4\343\22\247\11\263\244$\322\241A\217\223aHEeH\2\344\22\247\11s\262(\313\241A\217"
+ "\223aHEeH\2\345\22\267\11\363\304(\324\261A\217\223aHEeH\2\346\17w\11s,Q"
+ "-J\6%\312\242\212\62\347\17\247\331r\6%\225[\223A\324\242P\3\350\17\267\11s\362:\70("
+ "\251:\234\363A\1\351\17\267\11s\333\301AI\325\341\234\17\12\0\352\21\267\11\363\322$\253C\203\222"
+ "\252\303\71\37\24\0\353\21\247\11s\262(\313\241AI\325\341\234\17\12\0\354\12\265\12\63\322\372\330\323"
+ " \355\12\265\12\363\332\221\261\247A\356\15\266\11\263\302$\312rd\355m\20\357\14\245\12\63\242$\212"
+ "\307\236\6\1\360\20\267\11s\242PL\362lPR\257\311\240\0\361\16\247\11s\64iG\222!\21U"
+ "\257\1\362\16\267\11s\362:\70(\251\327dP\0\363\15\267\11s\333\301AI\275&\203\2\364\17\267"
+ "\11\363\322$\253C\203\222zM\6\5\365\17\247\11s\64i\207\6%\365\232\14\12\0\366\17\247\11s"
+ "\262(\313\241AI\275&\203\2\367\16\227\11\363\322\65\307\206!\307\322\65\3\370\23\227\371\262\223A\311"
+ "\22-\221\42%S\262dPb\0\371\23\267\11s\362:\26&a\22&a\22&a\64$\1\372\22"
+ "\267\11s\333\261\60\11\223\60\11\223\60\11\243!\11\373\24\267\11\363\322$\253#a\22&a\22&a"
+ "\22FC\22\374\24\247\11s\242,\312\241\60\11\223\60\11\223\60\11\243!\11\375\17\346\331\62\333\241\320"
+ "\67eI\305dH\0\376\20\307\331\62\342\226!\21UuT\206$.\3\377\17\326\331r\242\366\320\67"
+ "eI\305dH\0\0\0\0\4\377\377\0";
diff --git a/flip_social/font/font.h b/flip_social/font/font.h
new file mode 100644
index 000000000..f70711c3c
--- /dev/null
+++ b/flip_social/font/font.h
@@ -0,0 +1,13 @@
+#pragma once
+#include
+#include
+#include
+typedef enum
+{
+ FONT_SIZE_SMALL = 1,
+ FONT_SIZE_MEDIUM = 2,
+ FONT_SIZE_LARGE = 3,
+ FONT_SIZE_XLARGE = 4
+} FontSize;
+extern bool canvas_set_font_custom(Canvas *canvas, FontSize font_size);
+extern void canvas_draw_str_multi(Canvas *canvas, uint8_t x, uint8_t y, const char *str);
\ No newline at end of file
diff --git a/flip_social/friends/flip_social_friends.c b/flip_social/friends/flip_social_friends.c
index e564afab0..bb5108ef3 100644
--- a/flip_social/friends/flip_social_friends.c
+++ b/flip_social/friends/flip_social_friends.c
@@ -4,17 +4,10 @@ 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 == NULL)
{
- 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
- }
- }
+ FURI_LOG_E(TAG, "Failed to allocate memory for friends usernames.");
+ return NULL;
}
return friends;
}
@@ -28,11 +21,6 @@ bool flip_social_get_friends()
FURI_LOG_E(TAG, "App instance is NULL");
return false;
}
- if (fhttp.state == INACTIVE)
- {
- FURI_LOG_E(TAG, "HTTP state is INACTIVE");
- return false;
- }
// will return true unless the devboard is not connected
char url[100];
snprintf(
@@ -55,7 +43,7 @@ bool flip_social_get_friends()
bool flip_social_update_friends()
{
- if (!app_instance->submenu_friends)
+ if (!app_instance->submenu)
{
FURI_LOG_E(TAG, "Friends submenu is NULL");
return false;
@@ -66,11 +54,11 @@ bool flip_social_update_friends()
return false;
}
// Add submenu items for the users
- submenu_reset(app_instance->submenu_friends);
- submenu_set_header(app_instance->submenu_friends, "Friends");
+ submenu_reset(app_instance->submenu);
+ submenu_set_header(app_instance->submenu, "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);
+ submenu_add_item(app_instance->submenu, flip_social_friends->usernames[i], FlipSocialSubmenuLoggedInIndexFriendsStart + i, flip_social_callback_submenu_choices, app_instance);
}
return true;
}
@@ -82,89 +70,42 @@ bool flip_social_parse_json_friends()
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);
+ flipper_http_deinit();
return false;
}
- // Allocate memory for each username only if not already allocated
+ // 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;
// Reset the friends submenu
- submenu_reset(app_instance->submenu_friends);
- submenu_set_header(app_instance->submenu_friends, "Friends");
+ submenu_reset(app_instance->submenu);
+ submenu_set_header(app_instance->submenu, "Friends");
// Extract the users array from the JSON
- char *json_users = get_json_value("friends", data_cstr, 128);
- if (json_users == NULL)
+ for (int i = 0; i < MAX_FRIENDS; i++)
{
- 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);
- submenu_add_item(app_instance->submenu_friends, flip_social_friends->usernames[flip_social_friends->count], FlipSocialSubmenuLoggedInIndexFriendsStart + flip_social_friends->count, flip_social_callback_submenu_choices, app_instance);
- 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);
- submenu_add_item(app_instance->submenu_friends, flip_social_friends->usernames[flip_social_friends->count], FlipSocialSubmenuLoggedInIndexFriendsStart + flip_social_friends->count, flip_social_callback_submenu_choices, app_instance);
+ FuriString *friend = get_json_array_value_furi("friends", i, friend_data);
+ if (friend == NULL)
+ {
+ FURI_LOG_E(TAG, "Failed to parse friend %d.", i);
+ furi_string_free(friend_data);
+ break;
+ }
+ snprintf(flip_social_friends->usernames[i], MAX_USER_LENGTH, "%s", furi_string_get_cstr(friend));
+ submenu_add_item(app_instance->submenu, flip_social_friends->usernames[i], FlipSocialSubmenuLoggedInIndexFriendsStart + i, flip_social_callback_submenu_choices, app_instance);
flip_social_friends->count++;
+ furi_string_free(friend);
}
-
furi_string_free(friend_data);
- free(data_cstr);
- free(json_users);
- free(start);
- free(end);
+ // flipper_http_deinit();
return true;
}
diff --git a/flip_social/jsmn/jsmn.c b/flip_social/jsmn/jsmn.c
index b85ab83b5..d101530c6 100644
--- a/flip_social/jsmn/jsmn.c
+++ b/flip_social/jsmn/jsmn.c
@@ -7,8 +7,6 @@
*/
#include
-#include
-#include
/**
* Allocates a fresh unused token from the token pool.
@@ -424,7 +422,7 @@ int jsmn_parse(jsmn_parser *parser, const char *js, const size_t len,
}
// Helper function to create a JSON object
-char *jsmn(const char *key, const char *value)
+char *get_json(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
@@ -448,14 +446,19 @@ 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)
+char *get_json_value(char *key, const char *json_data)
{
// Parse the JSON feed
if (json_data != NULL)
{
jsmn_parser parser;
jsmn_init(&parser);
-
+ uint32_t max_tokens = json_token_count(json_data);
+ if (!jsmn_memory_check(max_tokens))
+ {
+ FURI_LOG_E("JSMM.H", "Insufficient memory for JSON tokens.");
+ return NULL;
+ }
// Allocate tokens array on the heap
jsmntok_t *tokens = malloc(sizeof(jsmntok_t) * max_tokens);
if (tokens == NULL)
@@ -510,26 +513,79 @@ char *get_json_value(char *key, char *json_data, uint32_t max_tokens)
{
FURI_LOG_E("JSMM.H", "JSON data is NULL");
}
- FURI_LOG_E("JSMM.H", "Failed to find the key in the JSON.");
+ char warning[128];
+ snprintf(warning, sizeof(warning), "Failed to find the key \"%s\" in the JSON.", key);
+ FURI_LOG_E("JSMM.H", warning);
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)
+// Helper function to skip a token and all its descendants.
+// Returns the index of the next token after skipping this one.
+// On error or out of bounds, returns -1.
+static int skip_token(const jsmntok_t *tokens, int start, int total)
{
- // Retrieve the array string for the given key
- char *array_str = get_json_value(key, json_data, max_tokens);
+ if (start < 0 || start >= total)
+ return -1;
+
+ int i = start;
+ if (tokens[i].type == JSMN_OBJECT)
+ {
+ // For an object: size is number of key-value pairs
+ int pairs = tokens[i].size;
+ i++; // move to first key-value pair
+ for (int p = 0; p < pairs; p++)
+ {
+ // skip key (primitive/string)
+ i++;
+ if (i >= total)
+ return -1;
+ // skip value (which could be object/array and must be skipped recursively)
+ i = skip_token(tokens, i, total);
+ if (i == -1)
+ return -1;
+ }
+ return i; // i is now just past the object
+ }
+ else if (tokens[i].type == JSMN_ARRAY)
+ {
+ // For an array: size is number of elements
+ int elems = tokens[i].size;
+ i++; // move to first element
+ for (int e = 0; e < elems; e++)
+ {
+ i = skip_token(tokens, i, total);
+ if (i == -1)
+ return -1;
+ }
+ return i; // i is now just past the array
+ }
+ else
+ {
+ // Primitive or string token, just skip it
+ return i + 1;
+ }
+}
+
+// Revised get_json_array_value
+char *get_json_array_value(char *key, uint32_t index, const char *json_data)
+{
+ // Always extract the full array each time from the original json_data
+ char *array_str = get_json_value(key, json_data);
if (array_str == NULL)
{
FURI_LOG_E("JSMM.H", "Failed to get array for key: %s", key);
return NULL;
}
+ uint32_t max_tokens = json_token_count(array_str);
+ if (!jsmn_memory_check(max_tokens))
+ {
+ FURI_LOG_E("JSMM.H", "Insufficient memory for JSON tokens.");
+ free(array_str);
+ 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)
{
@@ -538,7 +594,6 @@ char *get_json_array_value(char *key, uint32_t index, char *json_data, uint32_t
return NULL;
}
- // Parse the JSON array
int ret = jsmn_parse(&parser, array_str, strlen(array_str), tokens, max_tokens);
if (ret < 0)
{
@@ -548,7 +603,6 @@ char *get_json_array_value(char *key, uint32_t index, char *json_data, uint32_t
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);
@@ -557,50 +611,33 @@ char *get_json_array_value(char *key, uint32_t index, char *json_data, uint32_t
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);
+ // FURI_LOG_E("JSMM.H", "Index %lu out of bounds for array with size %u.", 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
+ // Find the index-th element: start from token[1], which is the first element
+ int elem_token = 1;
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
+ elem_token = skip_token(tokens, elem_token, ret);
+ if (elem_token == -1 || elem_token >= ret)
{
- // 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.");
+ FURI_LOG_E("JSMM.H", "Error skipping tokens to reach element %lu.", i);
free(tokens);
free(array_str);
return NULL;
}
}
- // Extract the array element
- jsmntok_t element = tokens[current_token];
+ // Now elem_token should point to the token of the requested element
+ jsmntok_t element = tokens[elem_token];
int length = element.end - element.start;
char *value = malloc(length + 1);
- if (value == NULL)
+ if (!value)
{
FURI_LOG_E("JSMM.H", "Failed to allocate memory for array element.");
free(tokens);
@@ -608,11 +645,9 @@ char *get_json_array_value(char *key, uint32_t index, char *json_data, uint32_t
return NULL;
}
- // Copy the element value to a new string
strncpy(value, array_str + element.start, length);
- value[length] = '\0'; // Null-terminate the string
+ value[length] = '\0';
- // Clean up
free(tokens);
free(array_str);
@@ -620,16 +655,22 @@ char *get_json_array_value(char *key, uint32_t index, char *json_data, uint32_t
}
// 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)
+char **get_json_array_values(char *key, char *json_data, int *num_values)
{
// Retrieve the array string for the given key
- char *array_str = get_json_value(key, json_data, max_tokens);
+ char *array_str = get_json_value(key, json_data);
if (array_str == NULL)
{
FURI_LOG_E("JSMM.H", "Failed to get array for key: %s", key);
return NULL;
}
-
+ uint32_t max_tokens = json_token_count(array_str);
+ if (!jsmn_memory_check(max_tokens))
+ {
+ FURI_LOG_E("JSMM.H", "Insufficient memory for JSON tokens.");
+ free(array_str);
+ return NULL;
+ }
// Initialize the JSON parser
jsmn_parser parser;
jsmn_init(&parser);
@@ -745,3 +786,18 @@ char **get_json_array_values(char *key, char *json_data, uint32_t max_tokens, in
free(array_str);
return values;
}
+
+int json_token_count(const char *json)
+{
+ if (json == NULL)
+ {
+ return JSMN_ERROR_INVAL;
+ }
+
+ jsmn_parser parser;
+ jsmn_init(&parser);
+
+ // Pass NULL for tokens and 0 for num_tokens to get the token count only
+ int ret = jsmn_parse(&parser, json, strlen(json), NULL, 0);
+ return ret; // If ret >= 0, it represents the number of tokens needed.
+}
diff --git a/flip_social/jsmn/jsmn.h b/flip_social/jsmn/jsmn.h
index 74cdccf95..5f96e5596 100644
--- a/flip_social/jsmn/jsmn.h
+++ b/flip_social/jsmn/jsmn.h
@@ -17,6 +17,7 @@
#define JSMN_H
#include
+#include
#ifdef __cplusplus
extern "C"
@@ -28,61 +29,6 @@ extern "C"
#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
*/
@@ -110,23 +56,19 @@ extern "C"
#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);
+char *get_json(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);
+char *get_json_value(char *key, const char *json_data);
// Revised get_json_array_value function
-char *get_json_array_value(char *key, uint32_t index, char *json_data, uint32_t max_tokens);
+char *get_json_array_value(char *key, uint32_t index, const char *json_data);
// 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);
+char **get_json_array_values(char *key, char *json_data, int *num_values);
+
+int json_token_count(const char *json);
#endif /* JB_JSMN_EDIT */
diff --git a/flip_social/jsmn/jsmn_furi.c b/flip_social/jsmn/jsmn_furi.c
new file mode 100644
index 000000000..d3bde9365
--- /dev/null
+++ b/flip_social/jsmn/jsmn_furi.c
@@ -0,0 +1,736 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2010 Serge Zaitsev
+ *
+ * [License text continues...]
+ */
+
+#include
+
+// Forward declarations of helper functions
+static int jsoneq_furi(const FuriString *json, jsmntok_t *tok, const FuriString *s);
+static int skip_token(const jsmntok_t *tokens, int start, int total);
+
+/**
+ * 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)
+{
+ if (parser->toknext >= num_tokens)
+ {
+ return NULL;
+ }
+ jsmntok_t *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.
+ * Now uses FuriString to access characters.
+ */
+static int jsmn_parse_primitive(jsmn_parser *parser, const FuriString *js,
+ jsmntok_t *tokens, const size_t num_tokens)
+{
+ size_t len = furi_string_size(js);
+ int start = parser->pos;
+
+ for (; parser->pos < len; parser->pos++)
+ {
+ char c = furi_string_get_char(js, parser->pos);
+ switch (c)
+ {
+#ifndef JSMN_STRICT
+ case ':':
+#endif
+ case '\t':
+ case '\r':
+ case '\n':
+ case ' ':
+ case ',':
+ case ']':
+ case '}':
+ goto found;
+ default:
+ break;
+ }
+ if (c < 32 || c >= 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;
+ }
+ jsmntok_t *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.
+ * Now uses FuriString to access characters.
+ */
+static int jsmn_parse_string(jsmn_parser *parser, const FuriString *js,
+ jsmntok_t *tokens, const size_t num_tokens)
+{
+ size_t len = furi_string_size(js);
+ int start = parser->pos;
+ parser->pos++;
+
+ for (; parser->pos < len; parser->pos++)
+ {
+ char c = furi_string_get_char(js, parser->pos);
+ if (c == '\"')
+ {
+ if (tokens == NULL)
+ {
+ return 0;
+ }
+ jsmntok_t *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;
+ }
+
+ if (c == '\\' && (parser->pos + 1) < len)
+ {
+ parser->pos++;
+ char esc = furi_string_get_char(js, parser->pos);
+ switch (esc)
+ {
+ case '\"':
+ case '/':
+ case '\\':
+ case 'b':
+ case 'f':
+ case 'r':
+ case 'n':
+ case 't':
+ break;
+ case 'u':
+ {
+ parser->pos++;
+ for (int i = 0; i < 4 && parser->pos < len; i++)
+ {
+ char hex = furi_string_get_char(js, parser->pos);
+ if (!((hex >= '0' && hex <= '9') ||
+ (hex >= 'A' && hex <= 'F') ||
+ (hex >= 'a' && hex <= 'f')))
+ {
+ parser->pos = start;
+ return JSMN_ERROR_INVAL;
+ }
+ parser->pos++;
+ }
+ parser->pos--;
+ break;
+ }
+ default:
+ parser->pos = start;
+ return JSMN_ERROR_INVAL;
+ }
+ }
+ }
+ parser->pos = start;
+ return JSMN_ERROR_PART;
+}
+
+/**
+ * Create JSON parser
+ */
+void jsmn_init_furi(jsmn_parser *parser)
+{
+ parser->pos = 0;
+ parser->toknext = 0;
+ parser->toksuper = -1;
+}
+
+/**
+ * Parse JSON string and fill tokens.
+ * Now uses FuriString for the input JSON.
+ */
+int jsmn_parse_furi(jsmn_parser *parser, const FuriString *js,
+ jsmntok_t *tokens, const unsigned int num_tokens)
+{
+ size_t len = furi_string_size(js);
+ int r;
+ int i;
+ int count = parser->toknext;
+
+ for (; parser->pos < len; parser->pos++)
+ {
+ char c = furi_string_get_char(js, parser->pos);
+ jsmntype_t type;
+
+ switch (c)
+ {
+ case '{':
+ case '[':
+ {
+ count++;
+ if (tokens == NULL)
+ {
+ break;
+ }
+ jsmntok_t *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
+ 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;
+ }
+ {
+ jsmntok_t *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
+ {
+ jsmntok_t *token;
+ 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;
+ }
+ }
+ 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, 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 ' ':
+ // Whitespace - ignore
+ 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
+ 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':
+ 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
+ default:
+#endif
+ r = jsmn_parse_primitive(parser, js, tokens, num_tokens);
+ if (r < 0)
+ return r;
+ count++;
+ if (parser->toksuper != -1 && tokens != NULL)
+ {
+ tokens[parser->toksuper].size++;
+ }
+ break;
+#ifdef JSMN_STRICT
+ default:
+ return JSMN_ERROR_INVAL;
+#endif
+ }
+ }
+
+ if (tokens != NULL)
+ {
+ for (i = parser->toknext - 1; i >= 0; i--)
+ {
+ if (tokens[i].start != -1 && tokens[i].end == -1)
+ {
+ return JSMN_ERROR_PART;
+ }
+ }
+ }
+
+ return count;
+}
+
+// Helper function to create a JSON object: {"key":"value"}
+FuriString *get_json_furi(const FuriString *key, const FuriString *value)
+{
+ FuriString *result = furi_string_alloc();
+ furi_string_printf(result, "{\"%s\":\"%s\"}",
+ furi_string_get_cstr(key),
+ furi_string_get_cstr(value));
+ return result; // Caller responsible for furi_string_free
+}
+
+// Helper function to compare JSON keys
+static int jsoneq_furi(const FuriString *json, jsmntok_t *tok, const FuriString *s)
+{
+ size_t s_len = furi_string_size(s);
+ size_t tok_len = tok->end - tok->start;
+
+ if (tok->type != JSMN_STRING)
+ return -1;
+ if (s_len != tok_len)
+ return -1;
+
+ FuriString *sub = furi_string_alloc_set(json);
+ furi_string_mid(sub, tok->start, tok_len);
+
+ int res = furi_string_cmp(sub, s);
+ furi_string_free(sub);
+
+ return (res == 0) ? 0 : -1;
+}
+
+// Skip a token and its descendants
+static int skip_token(const jsmntok_t *tokens, int start, int total)
+{
+ if (start < 0 || start >= total)
+ return -1;
+
+ int i = start;
+ if (tokens[i].type == JSMN_OBJECT)
+ {
+ int pairs = tokens[i].size;
+ i++;
+ for (int p = 0; p < pairs; p++)
+ {
+ i++; // skip key
+ if (i >= total)
+ return -1;
+ i = skip_token(tokens, i, total); // skip value
+ if (i == -1)
+ return -1;
+ }
+ return i;
+ }
+ else if (tokens[i].type == JSMN_ARRAY)
+ {
+ int elems = tokens[i].size;
+ i++;
+ for (int e = 0; e < elems; e++)
+ {
+ i = skip_token(tokens, i, total);
+ if (i == -1)
+ return -1;
+ }
+ return i;
+ }
+ else
+ {
+ return i + 1;
+ }
+}
+
+/**
+ * Parse JSON and return the value associated with a given char* key.
+ */
+FuriString *get_json_value_furi(const char *key, const FuriString *json_data)
+{
+ if (json_data == NULL)
+ {
+ FURI_LOG_E("JSMM.H", "JSON data is NULL");
+ return NULL;
+ }
+ uint32_t max_tokens = json_token_count_furi(json_data);
+ if (!jsmn_memory_check(max_tokens))
+ {
+ FURI_LOG_E("JSMM.H", "Insufficient memory for JSON tokens.");
+ return NULL;
+ }
+ // Create a temporary FuriString from key
+ FuriString *key_str = furi_string_alloc();
+ furi_string_cat_str(key_str, key);
+
+ jsmn_parser parser;
+ jsmn_init_furi(&parser);
+
+ jsmntok_t *tokens = (jsmntok_t *)malloc(sizeof(jsmntok_t) * max_tokens);
+ if (tokens == NULL)
+ {
+ FURI_LOG_E("JSMM.H", "Failed to allocate memory for JSON tokens.");
+ furi_string_free(key_str);
+ return NULL;
+ }
+
+ int ret = jsmn_parse_furi(&parser, json_data, tokens, max_tokens);
+ if (ret < 0)
+ {
+ FURI_LOG_E("JSMM.H", "Failed to parse JSON: %d", ret);
+ free(tokens);
+ furi_string_free(key_str);
+ return NULL;
+ }
+
+ if (ret < 1 || tokens[0].type != JSMN_OBJECT)
+ {
+ FURI_LOG_E("JSMM.H", "Root element is not an object.");
+ free(tokens);
+ furi_string_free(key_str);
+ return NULL;
+ }
+
+ for (int i = 1; i < ret; i++)
+ {
+ if (jsoneq_furi(json_data, &tokens[i], key_str) == 0)
+ {
+ int length = tokens[i + 1].end - tokens[i + 1].start;
+ FuriString *value = furi_string_alloc_set(json_data);
+ furi_string_mid(value, tokens[i + 1].start, length);
+ free(tokens);
+ furi_string_free(key_str);
+ return value;
+ }
+ }
+
+ free(tokens);
+ furi_string_free(key_str);
+ char warning[128];
+ snprintf(warning, sizeof(warning), "Failed to find the key \"%s\" in the JSON.", key);
+ FURI_LOG_E("JSMM.H", warning);
+ return NULL;
+}
+
+/**
+ * Return the value at a given index in a JSON array for a given char* key.
+ */
+FuriString *get_json_array_value_furi(const char *key, uint32_t index, const FuriString *json_data)
+{
+ FuriString *array_str = get_json_value_furi(key, json_data);
+ if (array_str == NULL)
+ {
+ FURI_LOG_E("JSMM.H", "Failed to get array for key");
+ return NULL;
+ }
+ uint32_t max_tokens = json_token_count_furi(array_str);
+ if (!jsmn_memory_check(max_tokens))
+ {
+ FURI_LOG_E("JSMM.H", "Insufficient memory for JSON tokens.");
+ furi_string_free(array_str);
+ return NULL;
+ }
+ jsmn_parser parser;
+ jsmn_init_furi(&parser);
+
+ jsmntok_t *tokens = (jsmntok_t *)malloc(sizeof(jsmntok_t) * max_tokens);
+ if (tokens == NULL)
+ {
+ FURI_LOG_E("JSMM.H", "Failed to allocate memory for JSON tokens.");
+ furi_string_free(array_str);
+ return NULL;
+ }
+
+ int ret = jsmn_parse_furi(&parser, array_str, tokens, max_tokens);
+ if (ret < 0)
+ {
+ FURI_LOG_E("JSMM.H", "Failed to parse JSON array: %d", ret);
+ free(tokens);
+ furi_string_free(array_str);
+ return NULL;
+ }
+
+ if (ret < 1 || tokens[0].type != JSMN_ARRAY)
+ {
+ FURI_LOG_E("JSMM.H", "Value for key is not an array.");
+ free(tokens);
+ furi_string_free(array_str);
+ return NULL;
+ }
+
+ if (index >= (uint32_t)tokens[0].size)
+ {
+ // FURI_LOG_E("JSMM.H", "Index %lu out of bounds for array with size %u.", index, tokens[0].size);
+ free(tokens);
+ furi_string_free(array_str);
+ return NULL;
+ }
+
+ int elem_token = 1;
+ for (uint32_t i = 0; i < index; i++)
+ {
+ elem_token = skip_token(tokens, elem_token, ret);
+ if (elem_token == -1 || elem_token >= ret)
+ {
+ FURI_LOG_E("JSMM.H", "Error skipping tokens to reach element %lu.", i);
+ free(tokens);
+ furi_string_free(array_str);
+ return NULL;
+ }
+ }
+
+ jsmntok_t element = tokens[elem_token];
+ int length = element.end - element.start;
+
+ FuriString *value = furi_string_alloc_set(array_str);
+ furi_string_mid(value, element.start, length);
+
+ free(tokens);
+ furi_string_free(array_str);
+
+ return value;
+}
+
+/**
+ * Extract all object values from a JSON array associated with a given char* key.
+ */
+FuriString **get_json_array_values_furi(const char *key, const FuriString *json_data, int *num_values)
+{
+ *num_values = 0;
+ // Convert key to FuriString and call get_json_value_furi
+ FuriString *array_str = get_json_value_furi(key, json_data);
+ if (array_str == NULL)
+ {
+ FURI_LOG_E("JSMM.H", "Failed to get array for key");
+ return NULL;
+ }
+
+ uint32_t max_tokens = json_token_count_furi(array_str);
+ if (!jsmn_memory_check(max_tokens))
+ {
+ FURI_LOG_E("JSMM.H", "Insufficient memory for JSON tokens.");
+ furi_string_free(array_str);
+ return NULL;
+ }
+ jsmn_parser parser;
+ jsmn_init_furi(&parser);
+
+ jsmntok_t *tokens = (jsmntok_t *)malloc(sizeof(jsmntok_t) * max_tokens);
+ if (tokens == NULL)
+ {
+ FURI_LOG_E("JSMM.H", "Failed to allocate memory for JSON tokens.");
+ furi_string_free(array_str);
+ return NULL;
+ }
+
+ int ret = jsmn_parse_furi(&parser, array_str, tokens, max_tokens);
+ if (ret < 0)
+ {
+ FURI_LOG_E("JSMM.H", "Failed to parse JSON array: %d", ret);
+ free(tokens);
+ furi_string_free(array_str);
+ return NULL;
+ }
+
+ if (tokens[0].type != JSMN_ARRAY)
+ {
+ FURI_LOG_E("JSMM.H", "Value for key is not an array.");
+ free(tokens);
+ furi_string_free(array_str);
+ return NULL;
+ }
+
+ int array_size = tokens[0].size;
+ FuriString **values = (FuriString **)malloc(array_size * sizeof(FuriString *));
+ if (values == NULL)
+ {
+ FURI_LOG_E("JSMM.H", "Failed to allocate memory for array of values.");
+ free(tokens);
+ furi_string_free(array_str);
+ return NULL;
+ }
+
+ int actual_num_values = 0;
+ int current_token = 1;
+ 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];
+
+ int length = element.end - element.start;
+ FuriString *value = furi_string_alloc_set(array_str);
+ furi_string_mid(value, element.start, length);
+
+ values[actual_num_values] = value;
+ actual_num_values++;
+
+ // Skip this element and its descendants
+ current_token = skip_token(tokens, current_token, ret);
+ if (current_token == -1)
+ {
+ FURI_LOG_E("JSMM.H", "Error skipping tokens after element %d.", i);
+ break;
+ }
+ }
+
+ *num_values = actual_num_values;
+ if (actual_num_values < array_size)
+ {
+ FuriString **reduced_values = (FuriString **)realloc(values, actual_num_values * sizeof(FuriString *));
+ if (reduced_values != NULL)
+ {
+ values = reduced_values;
+ }
+ }
+
+ free(tokens);
+ furi_string_free(array_str);
+ return values;
+}
+
+uint32_t json_token_count_furi(const FuriString *json)
+{
+ if (json == NULL)
+ {
+ return JSMN_ERROR_INVAL;
+ }
+
+ jsmn_parser parser;
+ jsmn_init_furi(&parser);
+
+ // Pass NULL for tokens and 0 for num_tokens to get the token count only
+ int ret = jsmn_parse_furi(&parser, json, NULL, 0);
+ return ret; // If ret >= 0, it represents the number of tokens needed.
+}
diff --git a/flip_social/jsmn/jsmn_furi.h b/flip_social/jsmn/jsmn_furi.h
new file mode 100644
index 000000000..cb01f38ca
--- /dev/null
+++ b/flip_social/jsmn/jsmn_furi.h
@@ -0,0 +1,74 @@
+/*
+ * 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_FURI_H
+#define JSMN_FURI_H
+
+#include
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+#ifdef JSMN_STATIC
+#define JSMN_API static
+#else
+#define JSMN_API extern
+#endif
+
+ JSMN_API void jsmn_init_furi(jsmn_parser *parser);
+ JSMN_API int jsmn_parse_furi(jsmn_parser *parser, const FuriString *js,
+ jsmntok_t *tokens, const unsigned int num_tokens);
+
+#ifndef JSMN_HEADER
+/* Implementation in jsmn_furi.c */
+#endif /* JSMN_HEADER */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* JSMN_FURI_H */
+
+#ifndef JB_JSMN_FURI_EDIT
+#define JB_JSMN_FURI_EDIT
+
+// Helper function to create a JSON object
+FuriString *get_json_furi(const FuriString *key, const FuriString *value);
+
+// Updated signatures to accept const char* key
+FuriString *get_json_value_furi(const char *key, const FuriString *json_data);
+FuriString *get_json_array_value_furi(const char *key, uint32_t index, const FuriString *json_data);
+FuriString **get_json_array_values_furi(const char *key, const FuriString *json_data, int *num_values);
+
+uint32_t json_token_count_furi(const FuriString *json);
+/* Example usage:
+char *json = "{\"key1\":\"value1\",\"key2\":\"value2\"}";
+FuriString *json_data = char_to_furi_string(json);
+if (!json_data)
+{
+ FURI_LOG_E(TAG, "Failed to allocate FuriString");
+ return -1;
+}
+FuriString *value = get_json_value_furi("key1", json_data, json_token_count_furi(json_data));
+if (value)
+{
+ FURI_LOG_I(TAG, "Value: %s", furi_string_get_cstr(value));
+ furi_string_free(value);
+}
+furi_string_free(json_data);
+*/
+#endif /* JB_JSMN_EDIT */
diff --git a/flip_social/jsmn/jsmn_h.c b/flip_social/jsmn/jsmn_h.c
new file mode 100644
index 000000000..59480e6e6
--- /dev/null
+++ b/flip_social/jsmn/jsmn_h.c
@@ -0,0 +1,15 @@
+#include
+FuriString *char_to_furi_string(const char *str)
+{
+ FuriString *furi_str = furi_string_alloc();
+ if (!furi_str)
+ {
+ return NULL;
+ }
+ for (size_t i = 0; i < strlen(str); i++)
+ {
+ furi_string_push_back(furi_str, str[i]);
+ }
+ return furi_str;
+}
+bool jsmn_memory_check(size_t heap_size) { return memmgr_get_free_heap() > (heap_size + 1024); }
\ No newline at end of file
diff --git a/flip_social/jsmn/jsmn_h.h b/flip_social/jsmn/jsmn_h.h
new file mode 100644
index 000000000..97d53e7ff
--- /dev/null
+++ b/flip_social/jsmn/jsmn_h.h
@@ -0,0 +1,56 @@
+#pragma once
+#include
+#include
+#include
+#include
+#include
+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
+{
+ JSMN_ERROR_NOMEM = -1,
+ JSMN_ERROR_INVAL = -2,
+ JSMN_ERROR_PART = -3
+};
+
+typedef struct
+{
+ jsmntype_t type;
+ int start;
+ int end;
+ int size;
+#ifdef JSMN_PARENT_LINKS
+ int parent;
+#endif
+} jsmntok_t;
+
+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;
+
+typedef struct
+{
+ char *key;
+ char *value;
+} JSON;
+
+typedef struct
+{
+ FuriString *key;
+ FuriString *value;
+} FuriJSON;
+
+FuriString *char_to_furi_string(const char *str);
+
+// check memory
+bool jsmn_memory_check(size_t heap_size);
diff --git a/flip_social/messages/flip_social_messages.c b/flip_social/messages/flip_social_messages.c
index c1ba402cb..9db309129 100644
--- a/flip_social/messages/flip_social_messages.c
+++ b/flip_social/messages/flip_social_messages.c
@@ -2,13 +2,6 @@
FlipSocialModel2 *flip_social_messages_alloc()
{
- if (!app_instance->submenu_messages)
- {
- if (!easy_flipper_set_submenu(&app_instance->submenu_messages, FlipSocialViewLoggedInMessagesSubmenu, "Messages", flip_social_callback_to_submenu_logged_in, &app_instance->view_dispatcher))
- {
- return NULL;
- }
- }
// Allocate memory for each username only if not already allocated
FlipSocialModel2 *users = malloc(sizeof(FlipSocialModel2));
if (users == NULL)
@@ -16,18 +9,6 @@ FlipSocialModel2 *flip_social_messages_alloc()
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;
}
@@ -40,27 +21,6 @@ FlipSocialMessage *flip_social_user_messages_alloc()
FURI_LOG_E(TAG, "Failed to allocate memory for messages");
return NULL;
}
- for (size_t i = 0; i < MAX_MESSAGES; 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;
}
@@ -76,12 +36,6 @@ void flip_social_free_message_users()
void flip_social_free_messages()
{
- if (app_instance->submenu_messages)
- {
- submenu_free(app_instance->submenu_messages);
- app_instance->submenu_messages = NULL;
- view_dispatcher_remove_view(app_instance->view_dispatcher, FlipSocialViewLoggedInMessagesSubmenu);
- }
if (flip_social_messages == NULL)
{
return;
@@ -92,7 +46,12 @@ void flip_social_free_messages()
bool flip_social_update_messages_submenu()
{
- if (app_instance->submenu_messages == NULL)
+ if (!app_instance)
+ {
+ FURI_LOG_E(TAG, "App instance is NULL");
+ return false;
+ }
+ if (app_instance->submenu == NULL)
{
FURI_LOG_E(TAG, "Submenu is NULL");
return false;
@@ -102,19 +61,24 @@ bool flip_social_update_messages_submenu()
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);
+ submenu_reset(app_instance->submenu);
+ submenu_set_header(app_instance->submenu, "Messages");
+ submenu_add_item(app_instance->submenu, "[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);
+ submenu_add_item(app_instance->submenu, 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)
+ if (app_instance == NULL)
+ {
+ FURI_LOG_E(TAG, "App instance is NULL");
+ return false;
+ }
+ if (app_instance->submenu == NULL)
{
FURI_LOG_E(TAG, "Submenu is NULL");
return false;
@@ -124,11 +88,11 @@ bool flip_social_update_submenu_user_choices()
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");
+ submenu_reset(app_instance->submenu);
+ submenu_set_header(app_instance->submenu, "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);
+ submenu_add_item(app_instance->submenu, flip_social_explore->usernames[i], FlipSocialSubmenuLoggedInIndexMessagesUserChoicesIndexStart + i, flip_social_callback_submenu_choices, app_instance);
}
return true;
}
@@ -141,16 +105,22 @@ bool flip_social_get_message_users()
FURI_LOG_E(TAG, "Username is NULL");
return false;
}
- if (fhttp.state == INACTIVE)
+ if (!flipper_http_init(flipper_http_rx_callback, app_instance))
{
- FURI_LOG_E(TAG, "HTTP state is INACTIVE");
+ FURI_LOG_E(TAG, "Failed to initialize FlipperHTTP");
return false;
}
+ char directory[128];
+ snprintf(directory, sizeof(directory), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_social/messages");
+
+ // Create the directory
+ Storage *storage = furi_record_open(RECORD_STORAGE);
+ storage_common_mkdir(storage, directory);
char command[128];
snprintf(
fhttp.file_path,
sizeof(fhttp.file_path),
- STORAGE_EXT_PATH_PREFIX "/apps_data/flip_social/message_users.json");
+ STORAGE_EXT_PATH_PREFIX "/apps_data/flip_social/messages/message_users.json");
fhttp.save_received_data = true;
auth_headers_alloc();
@@ -159,6 +129,7 @@ bool flip_social_get_message_users()
{
FURI_LOG_E(TAG, "Failed to send HTTP request for messages");
fhttp.state = ISSUE;
+ flipper_http_deinit();
return false;
}
fhttp.state = RECEIVING;
@@ -168,9 +139,9 @@ 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()
{
- if (fhttp.state == INACTIVE)
+ if (!flipper_http_init(flipper_http_rx_callback, app_instance))
{
- FURI_LOG_E(TAG, "HTTP state is INACTIVE");
+ FURI_LOG_E(TAG, "Failed to initialize FlipperHTTP");
return false;
}
if (app_instance->login_username_logged_out == NULL)
@@ -178,16 +149,23 @@ bool flip_social_get_messages_with_user()
FURI_LOG_E(TAG, "Username is NULL");
return false;
}
- if (!flip_social_message_users->usernames[flip_social_message_users->index] || strlen(flip_social_message_users->usernames[flip_social_message_users->index]) == 0)
+ if (strlen(flip_social_message_users->usernames[flip_social_message_users->index]) == 0)
{
FURI_LOG_E(TAG, "Username is NULL");
return false;
}
+ char directory[128];
+ snprintf(directory, sizeof(directory), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_social/messages");
+
+ // Create the directory
+ Storage *storage = furi_record_open(RECORD_STORAGE);
+ storage_common_mkdir(storage, directory);
+
char command[256];
snprintf(
fhttp.file_path,
sizeof(fhttp.file_path),
- STORAGE_EXT_PATH_PREFIX "/apps_data/flip_social/%s_messages.json",
+ STORAGE_EXT_PATH_PREFIX "/apps_data/flip_social/messages/%s_messages.json",
flip_social_message_users->usernames[flip_social_message_users->index]);
fhttp.save_received_data = true;
@@ -211,15 +189,10 @@ bool flip_social_parse_json_message_users()
if (message_data == NULL)
{
FURI_LOG_E(TAG, "Failed to load received data from file.");
+ flipper_http_deinit();
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;
- }
+ flipper_http_deinit();
// Allocate memory for each username only if not already allocated
flip_social_message_users = flip_social_messages_alloc();
@@ -227,65 +200,29 @@ bool flip_social_parse_json_message_users()
{
FURI_LOG_E(TAG, "Failed to allocate memory for message users.");
furi_string_free(message_data);
- free(data_cstr);
return false;
}
// 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, 64);
- 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)
+ for (int i = 0; i < MAX_MESSAGE_USERS; i++)
{
- *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);
+ FuriString *user = get_json_array_value_furi("users", i, message_data);
+ if (user == NULL)
+ {
+ break;
+ }
+ snprintf(flip_social_message_users->usernames[i], MAX_USER_LENGTH, "%s", furi_string_get_cstr(user));
flip_social_message_users->count++;
+ furi_string_free(user);
}
// 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;
}
@@ -297,82 +234,42 @@ bool flip_social_parse_json_message_user_choices()
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);
+ flipper_http_deinit();
return false;
}
+ flipper_http_deinit();
+
// 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;
}
// Initialize explore count
flip_social_explore->count = 0;
- // Extract the users array from the JSON
- char *json_users = get_json_value("users", data_cstr, 64);
- if (json_users == NULL)
+ for (int i = 0; i < MAX_MESSAGE_USERS; i++)
{
- 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);
+ FuriString *user = get_json_array_value_furi("users", i, user_data);
+ if (user == NULL)
+ {
+ break;
+ }
+ snprintf(flip_social_explore->usernames[i], MAX_USER_LENGTH, "%s", furi_string_get_cstr(user));
flip_social_explore->count++;
+ furi_string_free(user);
}
// 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;
+ return flip_social_explore->count > 0;
}
// parse messages between the logged in user and the selected user
@@ -383,15 +280,10 @@ bool flip_social_parse_json_messages()
if (message_data == NULL)
{
FURI_LOG_E(TAG, "Failed to load received data from file.");
+ flipper_http_deinit();
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;
- }
+ flipper_http_deinit();
// Allocate memory for each message only if not already allocated
flip_social_messages = flip_social_user_messages_alloc();
@@ -399,7 +291,6 @@ bool flip_social_parse_json_messages()
{
FURI_LOG_E(TAG, "Failed to allocate memory for messages.");
furi_string_free(message_data);
- free(data_cstr);
return false;
}
@@ -410,40 +301,38 @@ bool flip_social_parse_json_messages()
for (int i = 0; i < MAX_MESSAGES; i++)
{
// Parse each item in the array
- char *item = get_json_array_value("conversations", i, data_cstr, 64);
+ FuriString *item = get_json_array_value_furi("conversations", i, message_data);
if (item == NULL)
{
break;
}
// Extract individual fields from the JSON object
- char *sender = get_json_value("sender", item, 8);
- char *content = get_json_value("content", item, 8);
+ FuriString *sender = get_json_value_furi("sender", item);
+ FuriString *content = get_json_value_furi("content", item);
if (sender == NULL || content == NULL)
{
FURI_LOG_E(TAG, "Failed to parse item fields.");
- free(item);
+ furi_string_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);
+ snprintf(flip_social_messages->usernames[i], MAX_USER_LENGTH, "%s", furi_string_get_cstr(sender));
+ snprintf(flip_social_messages->messages[i], MAX_MESSAGE_LENGTH, "%s", furi_string_get_cstr(content));
flip_social_messages->count++;
- free(item);
- free(sender);
- free(content);
+ furi_string_free(item);
+ furi_string_free(sender);
+ furi_string_free(content);
}
if (!messages_dialog_alloc(true))
{
FURI_LOG_E(TAG, "Failed to allocate and set messages dialog.");
furi_string_free(message_data);
- free(data_cstr);
return false;
}
furi_string_free(message_data);
- free(data_cstr);
return true;
}
\ No newline at end of file
diff --git a/flip_store/.gitignore b/flip_store/.gitignore
deleted file mode 100644
index 9bea4330f..000000000
--- a/flip_store/.gitignore
+++ /dev/null
@@ -1,2 +0,0 @@
-
-.DS_Store
diff --git a/flip_store/README.md b/flip_store/README.md
index c53c6bdec..9db991dc4 100644
--- a/flip_store/README.md
+++ b/flip_store/README.md
@@ -1,12 +1,13 @@
# FlipStore
-Download Flipper Zero apps directly to your Flipper Zero using WiFi. You no longer need another device to install apps. FlipStore uses the FlipperHTTP flash for the WiFi Devboard, first introduced in the WebCrawler app: https://github.com/jblanked/WebCrawler-FlipperZero/tree/main/assets/FlipperHTTP
+Download Flipper Zero apps directly to your Flipper Zero using WiFi.
## Features
- App Catalog
- Install Apps
- Delete Apps
-- Install Developer Board Flashes
-- Install Custom Apps (coming soon)
+- Install WiFi Developer Board Firmware
+- Install Video Game Module Firmware
+- Install GitHub Repositories (Beta)
- Install Official Firmware (coming soon)
## Installation
@@ -38,18 +39,7 @@ Download Flipper Zero apps directly to your Flipper Zero using WiFi. You no long
- UX Improvements
**v0.8**
-- Download custom apps/assets from a GitHub URL
+- Download GitHub repositories
**1.0**
-- Download Official Firmware/Firmware Updates
-
-## Contribution
-This is a big task, and I welcome all contributors, especially developers interested in animations and graphics. Fork the repository, create a pull request, and I will review your edits.
-
-## Known Bugs
-1. Clicking a category in the app catalog results in an "Out of Memory" error.
- - This issue has been addressed, but it may still occur. If it does, restart the app.
-2. The app file is corrupted.
- - This is likely due to an error parsing the data. Restart the app and wait until the green LED light turns off after downloading the app before exiting the view. If this happens more than three times, the current version of FlipStore may not be able to download that app successfully.
-3. The app is frozen on the "Installing", "Loading", or "Receiving data" screen.
- - If it there LED is not on and it's been more than 5 seconds, restart your Flipper Zero with the devboard plugged in.
+- Download Official Flipper Zero Firmware
\ No newline at end of file
diff --git a/flip_store/alloc/flip_store_alloc.c b/flip_store/alloc/flip_store_alloc.c
index 953f08246..25ea48a69 100644
--- a/flip_store/alloc/flip_store_alloc.c
+++ b/flip_store/alloc/flip_store_alloc.c
@@ -6,133 +6,27 @@ FlipStoreApp *flip_store_app_alloc()
Gui *gui = furi_record_open(RECORD_GUI);
- if (!flipper_http_init(flipper_http_rx_callback, app))
- {
- FURI_LOG_E(TAG, "Failed to initialize flipper http");
- return NULL;
- }
-
- // Allocate the text input buffer
- app->uart_text_input_buffer_size_ssid = 64;
- app->uart_text_input_buffer_size_pass = 64;
- if (!easy_flipper_set_buffer(&app->uart_text_input_buffer_ssid, app->uart_text_input_buffer_size_ssid))
- {
- return NULL;
- }
- if (!easy_flipper_set_buffer(&app->uart_text_input_temp_buffer_ssid, app->uart_text_input_buffer_size_ssid))
- {
- return NULL;
- }
- if (!easy_flipper_set_buffer(&app->uart_text_input_buffer_pass, app->uart_text_input_buffer_size_pass))
- {
- return NULL;
- }
- if (!easy_flipper_set_buffer(&app->uart_text_input_temp_buffer_pass, app->uart_text_input_buffer_size_pass))
- {
- return NULL;
- }
-
// Allocate ViewDispatcher
if (!easy_flipper_set_view_dispatcher(&app->view_dispatcher, gui, app))
{
return NULL;
}
view_dispatcher_set_custom_event_callback(app->view_dispatcher, flip_store_custom_event_callback);
+
// Main view
if (!easy_flipper_set_view(&app->view_loader, FlipStoreViewLoader, flip_store_loader_draw_callback, NULL, callback_to_submenu_options, &app->view_dispatcher, app))
{
return NULL;
}
flip_store_loader_init(app->view_loader);
- if (!easy_flipper_set_widget(&app->widget_result, FlipStoreViewWidgetResult, "Error, try again.", callback_to_submenu_options, &app->view_dispatcher))
- {
- return NULL;
- }
-
- // Main view
- if (!easy_flipper_set_view(&app->view_app_info, FlipStoreViewAppInfo, flip_store_view_draw_callback_app_list, flip_store_input_callback, callback_to_app_list, &app->view_dispatcher, app))
- {
- return NULL;
- }
-
- // Widget
- if (!easy_flipper_set_widget(
- &app->widget,
- FlipStoreViewAbout,
- "Welcome to the FlipStore!\n------\nDownload apps via WiFi and\nrun them on your Flipper!\n------\nwww.github.com/jblanked",
- callback_to_submenu,
- &app->view_dispatcher))
- {
- return NULL;
- }
-
- // Popup
- if (!easy_flipper_set_popup(&app->popup, FlipStoreViewPopup, "Failed", 0, 0, "You are not connected to Wifi.\n\nIf you have the FlipperHTTP\nflash installed, then update\nyour WiFi credentials.", 0, 10, popup_callback, callback_to_submenu, &app->view_dispatcher, app))
- {
- FURI_LOG_E(TAG, "Failed to create popup");
- }
- // Dialog
- if (!easy_flipper_set_dialog_ex(
- &app->dialog_delete,
- FlipStoreViewAppDelete,
- "Delete App",
- 0,
- 0,
- "Are you sure you want to delete this app?",
- 0,
- 10,
- "No",
- "Yes",
- NULL,
- dialog_delete_callback,
- callback_to_app_list,
- &app->view_dispatcher,
- app))
- {
- return NULL;
- }
-
- if (!easy_flipper_set_dialog_ex(
- &app->dialog_firmware,
- FlipStoreViewFirmwareDialog,
- "Download Firmware",
- 0,
- 0,
- "Are you sure you want to\ndownload this firmware?",
- 0,
- 10,
- "No",
- "Yes",
- NULL,
- dialog_firmware_callback,
- callback_to_firmware_list,
- &app->view_dispatcher,
- app))
- {
- return NULL;
- }
-
- // Text Input
- if (!easy_flipper_set_uart_text_input(&app->uart_text_input_ssid, FlipStoreViewTextInputSSID, "Enter SSID", app->uart_text_input_temp_buffer_ssid, app->uart_text_input_buffer_size_ssid, flip_store_text_updated_ssid, callback_to_submenu, &app->view_dispatcher, app))
- {
- return NULL;
- }
- if (!easy_flipper_set_uart_text_input(&app->uart_text_input_pass, FlipStoreViewTextInputPass, "Enter Password", app->uart_text_input_temp_buffer_pass, app->uart_text_input_buffer_size_pass, flip_store_text_updated_pass, callback_to_submenu, &app->view_dispatcher, app))
- {
- return NULL;
- }
-
- // Variable Item List
- if (!easy_flipper_set_variable_item_list(&app->variable_item_list, FlipStoreViewSettings, settings_item_selected, callback_to_submenu, &app->view_dispatcher, app))
+ if (!easy_flipper_set_widget(&app->widget_result, FlipStoreViewWidgetResult, "Error, try again.", callback_to_submenu_options, &app->view_dispatcher))
{
return NULL;
}
- app->variable_item_ssid = variable_item_list_add(app->variable_item_list, "SSID", 0, NULL, NULL);
- app->variable_item_pass = variable_item_list_add(app->variable_item_list, "Password", 0, NULL, NULL);
// Submenu
- if (!easy_flipper_set_submenu(&app->submenu_main, FlipStoreViewSubmenu, "FlipStore v0.7", callback_exit_app, &app->view_dispatcher))
+ if (!easy_flipper_set_submenu(&app->submenu_main, FlipStoreViewSubmenu, VERSION_TAG, callback_exit_app, &app->view_dispatcher))
{
return NULL;
}
@@ -144,7 +38,11 @@ FlipStoreApp *flip_store_app_alloc()
{
return NULL;
}
- if (!easy_flipper_set_submenu(&app->submenu_firmwares, FlipStoreViewFirmwares, "ESP32 Firmwares", callback_to_submenu_options, &app->view_dispatcher))
+ if (!easy_flipper_set_submenu(&app->submenu_firmwares, FlipStoreViewFirmwares, "ESP32 Firmware", callback_to_submenu_options, &app->view_dispatcher))
+ {
+ return NULL;
+ }
+ if (!easy_flipper_set_submenu(&app->submenu_vgm_firmwares, FlipStoreViewVGMFirmwares, "VGM Firmware", callback_to_submenu_options, &app->view_dispatcher))
{
return NULL;
}
@@ -155,8 +53,9 @@ FlipStoreApp *flip_store_app_alloc()
submenu_add_item(app->submenu_main, "Settings", FlipStoreSubmenuIndexSettings, callback_submenu_choices, app);
//
submenu_add_item(app->submenu_options, "App Catalog", FlipStoreSubmenuIndexAppList, callback_submenu_choices, app);
- submenu_add_item(app->submenu_options, "ESP32 Firmwares", FlipStoreSubmenuIndexFirmwares, callback_submenu_choices, app);
-
+ submenu_add_item(app->submenu_options, "ESP32 Firmware", FlipStoreSubmenuIndexFirmwares, callback_submenu_choices, app);
+ submenu_add_item(app->submenu_options, "VGM Firmware", FlipStoreSubmenuIndexVGMFirmwares, callback_submenu_choices, app);
+ submenu_add_item(app->submenu_options, "GitHub Repository", FlipStoreSubmenuIndexGitHub, callback_submenu_choices, app);
//
submenu_add_item(app->submenu_app_list, "Bluetooth", FlipStoreSubmenuIndexAppListBluetooth, callback_submenu_choices, app);
submenu_add_item(app->submenu_app_list, "Games", FlipStoreSubmenuIndexAppListGames, callback_submenu_choices, app);
@@ -170,28 +69,6 @@ FlipStoreApp *flip_store_app_alloc()
submenu_add_item(app->submenu_app_list, "Tools", FlipStoreSubmenuIndexAppListTools, callback_submenu_choices, app);
submenu_add_item(app->submenu_app_list, "USB", FlipStoreSubmenuIndexAppListUSB, callback_submenu_choices, app);
//
- // dont add any items to the app list submenu of each category yet
-
- // load settings
- if (load_settings(app->uart_text_input_buffer_ssid, app->uart_text_input_buffer_size_ssid, app->uart_text_input_buffer_pass, app->uart_text_input_buffer_size_pass))
- {
- // Update variable items
- if (app->variable_item_ssid)
- variable_item_set_current_value_text(app->variable_item_ssid, app->uart_text_input_buffer_ssid);
- // do not display the password
-
- // Copy items into their temp buffers with safety checks
- if (app->uart_text_input_buffer_ssid && app->uart_text_input_temp_buffer_ssid)
- {
- strncpy(app->uart_text_input_temp_buffer_ssid, app->uart_text_input_buffer_ssid, app->uart_text_input_buffer_size_ssid - 1);
- app->uart_text_input_temp_buffer_ssid[app->uart_text_input_buffer_size_ssid - 1] = '\0';
- }
- if (app->uart_text_input_buffer_pass && app->uart_text_input_temp_buffer_pass)
- {
- strncpy(app->uart_text_input_temp_buffer_pass, app->uart_text_input_buffer_pass, app->uart_text_input_buffer_size_pass - 1);
- app->uart_text_input_temp_buffer_pass[app->uart_text_input_buffer_size_pass - 1] = '\0';
- }
- }
// Switch to the main view
view_dispatcher_switch_to_view(app->view_dispatcher, FlipStoreViewSubmenu);
diff --git a/flip_store/app.c b/flip_store/app.c
index 4252d18f1..5c036aba9 100644
--- a/flip_store/app.c
+++ b/flip_store/app.c
@@ -1,6 +1,5 @@
#include
#include
-#include
// Entry point for the Hello World application
int32_t main_flip_store(void *p)
@@ -9,25 +8,47 @@ int32_t main_flip_store(void *p)
UNUSED(p);
// Initialize the Hello World application
- app_instance = flip_store_app_alloc();
- if (!app_instance)
+ FlipStoreApp *app = flip_store_app_alloc();
+ if (!app)
{
FURI_LOG_E(TAG, "Failed to allocate FlipStoreApp");
return -1;
}
- if (!flipper_http_ping())
+ // check if board is connected (Derek Jamison)
+ FlipperHTTP *fhttp = flipper_http_alloc();
+ if (!fhttp)
+ {
+ easy_flipper_dialog("FlipperHTTP Error", "The UART is likely busy.\nEnsure you have the correct\nflash for your board then\nrestart your Flipper Zero.");
+ return -1;
+ }
+
+ if (!flipper_http_ping(fhttp))
{
FURI_LOG_E(TAG, "Failed to ping the device");
+ flipper_http_free(fhttp);
return -1;
}
+ // Try to wait for pong response.
+ uint32_t counter = 10;
+ while (fhttp->state == INACTIVE && --counter > 0)
+ {
+ FURI_LOG_D(TAG, "Waiting for PONG");
+ furi_delay_ms(100);
+ }
+
+ flipper_http_free(fhttp);
+ if (counter == 0)
+ {
+ easy_flipper_dialog("FlipperHTTP Error", "Ensure your WiFi Developer\nBoard or Pico W is connected\nand the latest FlipperHTTP\nfirmware is installed.");
+ }
+
// Run the view dispatcher
- view_dispatcher_run(app_instance->view_dispatcher);
+ view_dispatcher_run(app->view_dispatcher);
// Free the resources used by the Hello World application
- flip_store_app_free(app_instance);
- flip_catalog_free();
+ flip_store_app_free(app);
// Return 0 to indicate success
return 0;
diff --git a/flip_store/application.fam b/flip_store/application.fam
index 8f8df947d..abd9f3d24 100644
--- a/flip_store/application.fam
+++ b/flip_store/application.fam
@@ -10,5 +10,5 @@ App(
fap_description="Download apps via WiFi directly to your Flipper Zero",
fap_author="JBlanked",
fap_weburl="https://github.com/jblanked/FlipStore",
- fap_version="0.7",
+ fap_version="0.8",
)
diff --git a/flip_store/apps/flip_store_apps.c b/flip_store/apps/flip_store_apps.c
index d2f3dd9d8..8202fd120 100644
--- a/flip_store/apps/flip_store_apps.c
+++ b/flip_store/apps/flip_store_apps.c
@@ -3,80 +3,72 @@
FlipStoreAppInfo *flip_catalog = NULL;
uint32_t app_selected_index = 0;
-bool flip_store_sent_request = false;
-bool flip_store_success = false;
-bool flip_store_saved_data = false;
-bool flip_store_saved_success = false;
uint32_t flip_store_category_index = 0;
+int catalog_iteration = 0;
// define the list of categories
+char *category_ids[] = {
+ "64a69817effe1f448a4053b4", // "Bluetooth",
+ "64971d11be1a76c06747de2f", // "Games",
+ "64971d106617ba37a4bc79b9", // "GPIO",
+ "64971d106617ba37a4bc79b6", // "Infrared",
+ "64971d11be1a76c06747de29", // "iButton",
+ "64971d116617ba37a4bc79bc", // "Media",
+ "64971d10be1a76c06747de26", // "NFC",
+ "64971d10577d519190ede5c2", // "RFID",
+ "64971d0f6617ba37a4bc79b3", // "Sub-GHz",
+ "64971d11577d519190ede5c5", // "Tools",
+ "64971d11be1a76c06747de2c", // "USB",
+};
+
char *categories[] = {
- "Bluetooth",
- "Games",
- "GPIO",
- "Infrared",
- "iButton",
- "Media",
- "NFC",
- "RFID",
- "Sub-GHz",
- "Tools",
- "USB",
+ "Bluetooth", // "64a69817effe1f448a4053b4"
+ "Games", // "64971d11be1a76c06747de2f"
+ "GPIO", // "64971d106617ba37a4bc79b9"
+ "Infrared", // "64971d106617ba37a4bc79b6"
+ "iButton", // "64971d11be1a76c06747de29"
+ "Media", // "64971d116617ba37a4bc79bc"
+ "NFC", // "64971d10be1a76c06747de26"
+ "RFID", // "64971d10577d519190ede5c2"
+ "Sub-GHz", // "64971d0f6617ba37a4bc79b3"
+ "Tools", // "64971d11577d519190ede5c5"
+ "USB", // "64971d11be1a76c06747de2c"
};
FlipStoreAppInfo *flip_catalog_alloc()
{
- FlipStoreAppInfo *app_catalog = (FlipStoreAppInfo *)malloc(MAX_APP_COUNT * sizeof(FlipStoreAppInfo));
- if (!app_catalog)
+ if (memmgr_get_free_heap() < MAX_APP_COUNT * sizeof(FlipStoreAppInfo))
{
- FURI_LOG_E(TAG, "Failed to allocate memory for flip_catalog.");
+ FURI_LOG_E(TAG, "Not enough memory to allocate flip_catalog.");
return NULL;
}
- for (int i = 0; i < MAX_APP_COUNT; i++)
+ FlipStoreAppInfo *app_catalog = malloc(MAX_APP_COUNT * sizeof(FlipStoreAppInfo));
+ if (!app_catalog)
{
- app_catalog[i].app_name = (char *)malloc(MAX_APP_NAME_LENGTH * sizeof(char));
- if (!app_catalog[i].app_name)
- {
- FURI_LOG_E(TAG, "Failed to allocate memory for app_name.");
- return NULL;
- }
- app_catalog[i].app_id = (char *)malloc(MAX_APP_NAME_LENGTH * sizeof(char));
- if (!app_catalog[i].app_id)
- {
- FURI_LOG_E(TAG, "Failed to allocate memory for app_id.");
- return NULL;
- }
- app_catalog[i].app_build_id = (char *)malloc(MAX_ID_LENGTH * sizeof(char));
- if (!app_catalog[i].app_build_id)
- {
- FURI_LOG_E(TAG, "Failed to allocate memory for app_build_id.");
- return NULL;
- }
- app_catalog[i].app_version = (char *)malloc(MAX_APP_VERSION_LENGTH * sizeof(char));
- if (!app_catalog[i].app_version)
- {
- FURI_LOG_E(TAG, "Failed to allocate memory for app_version.");
- return NULL;
- }
- app_catalog[i].app_description = (char *)malloc(MAX_APP_DESCRIPTION_LENGTH * sizeof(char));
- if (!app_catalog[i].app_description)
- {
- FURI_LOG_E(TAG, "Failed to allocate memory for app_description.");
- return NULL;
- }
+ FURI_LOG_E(TAG, "Failed to allocate memory for flip_catalog.");
+ return NULL;
}
+ app_catalog->count = 0;
+ app_catalog->iteration = catalog_iteration;
return app_catalog;
}
+
void flip_catalog_free()
{
if (flip_catalog)
{
free(flip_catalog);
+ flip_catalog = NULL;
}
}
-bool flip_store_process_app_list()
+bool flip_store_process_app_list(FlipperHTTP *fhttp)
{
+ if (!fhttp)
+ {
+ FURI_LOG_E(TAG, "FlipperHTTP is NULL.");
+ return false;
+ }
// Initialize the flip_catalog
flip_catalog = flip_catalog_alloc();
if (!flip_catalog)
@@ -85,244 +77,137 @@ bool flip_store_process_app_list()
return false;
}
- FuriString *feed_data = flipper_http_load_from_file(fhttp.file_path);
+ 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)
+ FuriString *json_data_str = furi_string_alloc();
+ if (!json_data_str)
+ {
+ FURI_LOG_E("Game", "Failed to allocate json_data string");
+ return NULL;
+ }
+ furi_string_cat_str(json_data_str, "{\"json_data\":");
+ if (memmgr_get_free_heap() < furi_string_size(feed_data) + furi_string_size(json_data_str) + 2)
{
- FURI_LOG_E(TAG, "Failed to get C-string from FuriString.");
+ FURI_LOG_E(TAG, "Not enough memory to allocate json_data_str.");
furi_string_free(feed_data);
+ furi_string_free(json_data_str);
return false;
}
+ furi_string_cat(json_data_str, feed_data);
+ furi_string_free(feed_data);
+ furi_string_cat_str(json_data_str, "}");
- // Parser state variables
- bool in_string = false;
- bool is_escaped = false;
- bool reading_key = false;
- bool reading_value = false;
- bool inside_app_object = false;
- bool found_name = false, found_id = false, found_build_id = false, found_version = false, found_description = false;
- char current_key[MAX_KEY_LENGTH] = {0};
- size_t key_index = 0;
- char current_value[MAX_VALUE_LENGTH] = {0};
- size_t value_index = 0;
- int app_count = 0;
- enum ObjectState object_state = OBJECT_EXPECT_KEY;
- enum
- {
- STATE_SEARCH_APPS_KEY,
- STATE_SEARCH_ARRAY_START,
- STATE_READ_ARRAY_ELEMENTS,
- STATE_DONE
- } state = STATE_SEARCH_APPS_KEY;
+ flip_catalog->count = 0;
- // Iterate through the data
- for (size_t i = 0; data_cstr[i] != '\0' && state != STATE_DONE; ++i)
+ // parse the JSON data
+ for (int i = 0; i < MAX_APP_COUNT; i++)
{
- char c = data_cstr[i];
-
- if (is_escaped)
+ FuriString *json_data_array = get_json_array_value_furi("json_data", i, json_data_str);
+ if (!json_data_array)
{
- is_escaped = false;
- if (reading_key && key_index < MAX_KEY_LENGTH - 1)
- {
- current_key[key_index++] = c;
- }
- else if (reading_value && value_index < MAX_VALUE_LENGTH - 1)
- {
- current_value[value_index++] = c;
- }
- continue;
+ break;
}
- if (c == '\\')
+ FuriString *app_id = get_json_value_furi("alias", json_data_array);
+ if (!app_id)
{
- is_escaped = true;
- continue;
+ FURI_LOG_E(TAG, "Failed to get app_id.");
+ furi_string_free(json_data_array);
+ break;
}
+ snprintf(flip_catalog[i].app_id, MAX_ID_LENGTH, "%s", furi_string_get_cstr(app_id));
+ furi_string_free(app_id);
- if (c == '\"')
+ FuriString *current_version = get_json_value_furi("current_version", json_data_array);
+ if (!current_version)
{
- in_string = !in_string;
- if (in_string)
- {
- if (!reading_key && !reading_value)
- {
- if (state == STATE_SEARCH_APPS_KEY || object_state == OBJECT_EXPECT_KEY)
- {
- reading_key = true;
- key_index = 0;
- current_key[0] = '\0';
- }
- else if (object_state == OBJECT_EXPECT_VALUE)
- {
- reading_value = true;
- value_index = 0;
- current_value[0] = '\0';
- }
- }
- }
- else
- {
- if (reading_key)
- {
- reading_key = false;
- current_key[key_index] = '\0';
- if (state == STATE_SEARCH_APPS_KEY && strcmp(current_key, "apps") == 0)
- {
- state = STATE_SEARCH_ARRAY_START;
- }
- else if (inside_app_object)
- {
- object_state = OBJECT_EXPECT_COLON;
- }
- }
- else if (reading_value)
- {
- reading_value = false;
- current_value[value_index] = '\0';
-
- if (inside_app_object)
- {
- if (strcmp(current_key, "name") == 0)
- {
- snprintf(flip_catalog[app_count].app_name, MAX_APP_NAME_LENGTH, "%.31s", current_value);
- found_name = true;
- }
- else if (strcmp(current_key, "id") == 0)
- {
- snprintf(flip_catalog[app_count].app_id, MAX_ID_LENGTH, "%.31s", current_value);
- found_id = true;
- }
- else if (strcmp(current_key, "build_id") == 0)
- {
- snprintf(flip_catalog[app_count].app_build_id, MAX_ID_LENGTH, "%.31s", current_value);
- found_build_id = true;
- }
- else if (strcmp(current_key, "version") == 0)
- {
- snprintf(flip_catalog[app_count].app_version, MAX_APP_VERSION_LENGTH, "%.3s", current_value);
- found_version = true;
- }
- else if (strcmp(current_key, "description") == 0)
- {
- snprintf(flip_catalog[app_count].app_description, MAX_APP_DESCRIPTION_LENGTH, "%.99s", current_value);
- found_description = true;
- }
-
- if (found_name && found_id && found_build_id && found_version && found_description)
- {
- app_count++;
- if (app_count >= MAX_APP_COUNT)
- {
- FURI_LOG_I(TAG, "Reached maximum app count.");
- state = STATE_DONE;
- break;
- }
-
- found_name = found_id = found_build_id = found_version = found_description = false;
- }
+ FURI_LOG_E(TAG, "Failed to get current_version.");
+ furi_string_free(json_data_array);
+ break;
+ }
- object_state = OBJECT_EXPECT_COMMA_OR_END;
- }
- }
- }
- continue;
+ FuriString *app_name = get_json_value_furi("name", current_version);
+ if (!app_name)
+ {
+ FURI_LOG_E(TAG, "Failed to get app_name.");
+ furi_string_free(json_data_array);
+ furi_string_free(current_version);
+ break;
}
+ snprintf(flip_catalog[i].app_name, MAX_APP_NAME_LENGTH, "%s", furi_string_get_cstr(app_name));
+ furi_string_free(app_name);
- if (in_string)
+ FuriString *app_description = get_json_value_furi("short_description", current_version);
+ if (!app_description)
{
- if (reading_key && key_index < MAX_KEY_LENGTH - 1)
- {
- current_key[key_index++] = c;
- }
- else if (reading_value && value_index < MAX_VALUE_LENGTH - 1)
- {
- current_value[value_index++] = c;
- }
- continue;
+ FURI_LOG_E(TAG, "Failed to get app_description.");
+ furi_string_free(json_data_array);
+ furi_string_free(current_version);
+ break;
}
+ snprintf(flip_catalog[i].app_description, MAX_APP_DESCRIPTION_LENGTH, "%s", furi_string_get_cstr(app_description));
+ furi_string_free(app_description);
- if (state == STATE_SEARCH_ARRAY_START && c == '[')
+ FuriString *app_version = get_json_value_furi("version", current_version);
+ if (!app_version)
{
- state = STATE_READ_ARRAY_ELEMENTS;
- continue;
+ FURI_LOG_E(TAG, "Failed to get app_version.");
+ furi_string_free(json_data_array);
+ furi_string_free(current_version);
+ break;
}
+ snprintf(flip_catalog[i].app_version, MAX_APP_VERSION_LENGTH, "%s", furi_string_get_cstr(app_version));
+ furi_string_free(app_version);
- if (state == STATE_READ_ARRAY_ELEMENTS)
+ FuriString *_id = get_json_value_furi("_id", current_version);
+ if (!_id)
{
- if (c == '{')
- {
- inside_app_object = true;
- object_state = OBJECT_EXPECT_KEY;
- }
- else if (c == '}')
- {
- inside_app_object = false;
- }
- else if (c == ':')
- {
- object_state = OBJECT_EXPECT_VALUE;
- }
- else if (c == ',')
- {
- object_state = OBJECT_EXPECT_KEY;
- }
- else if (c == ']')
- {
- state = STATE_DONE;
- break;
- }
+ FURI_LOG_E(TAG, "Failed to get _id.");
+ furi_string_free(json_data_array);
+ furi_string_free(current_version);
+ break;
}
+ snprintf(flip_catalog[i].app_build_id, MAX_ID_LENGTH, "%s", furi_string_get_cstr(_id));
+ furi_string_free(_id);
+
+ flip_catalog->count++;
+ furi_string_free(json_data_array);
+ furi_string_free(current_version);
}
- // Clean up
- furi_string_free(feed_data);
- free(data_cstr);
- return app_count > 0;
+ furi_string_free(json_data_str);
+ return flip_catalog->count > 0;
}
-bool flip_store_get_fap_file(char *build_id, uint8_t target, uint16_t api_major, uint16_t api_minor)
+static bool flip_store_get_fap_file(FlipperHTTP *fhttp, char *build_id, uint8_t target, uint16_t api_major, uint16_t api_minor)
{
- char url[128];
- fhttp.save_received_data = false;
- fhttp.is_bytes_request = true;
+ if (!fhttp || !build_id)
+ {
+ FURI_LOG_E(TAG, "FlipperHTTP or build_id is NULL.");
+ return false;
+ }
+ char url[256];
+ fhttp->save_received_data = false;
+ fhttp->is_bytes_request = true;
snprintf(url, sizeof(url), "https://catalog.flipperzero.one/api/v0/application/version/%s/build/compatible?target=f%d&api=%d.%d", build_id, target, api_major, api_minor);
- char *headers = jsmn("Content-Type", "application/octet-stream");
- bool sent_request = flipper_http_get_request_bytes(url, headers);
- free(headers);
- return sent_request;
+ return flipper_http_get_request_bytes(fhttp, url, "{\"Content-Type\": \"application/octet-stream\"}");
}
-bool flip_store_install_app(char *category)
+bool flip_store_install_app(FlipperHTTP *fhttp, char *category)
{
- // create /apps/FlipStore directory if it doesn't exist
- char directory_path[128];
- snprintf(directory_path, sizeof(directory_path), STORAGE_EXT_PATH_PREFIX "/apps/%s", category);
-
- // Create the directory
- Storage *storage = furi_record_open(RECORD_STORAGE);
- storage_common_mkdir(storage, directory_path);
-
- snprintf(fhttp.file_path, sizeof(fhttp.file_path), STORAGE_EXT_PATH_PREFIX "/apps/%s/%s.fap", category, flip_catalog[app_selected_index].app_id);
-
- uint8_t target = furi_hal_version_get_hw_target();
- uint16_t api_major, api_minor;
- furi_hal_info_get_api_version(&api_major, &api_minor);
- if (fhttp.state != INACTIVE && flip_store_get_fap_file(flip_catalog[app_selected_index].app_build_id, target, api_major, api_minor))
- {
- fhttp.state = RECEIVING;
- return true;
- }
- else
+ if (!fhttp || !category)
{
- FURI_LOG_E(TAG, "Failed to send the request");
- flip_store_success = false;
+ FURI_LOG_E(TAG, "FlipperHTTP or category is NULL.");
return false;
}
+ snprintf(fhttp->file_path, sizeof(fhttp->file_path), STORAGE_EXT_PATH_PREFIX "/apps/%s/%s.fap", category, flip_catalog[app_selected_index].app_id);
+ uint8_t target = furi_hal_version_get_hw_target();
+ uint16_t api_major, api_minor;
+ furi_hal_info_get_api_version(&api_major, &api_minor);
+ return flip_store_get_fap_file(fhttp, flip_catalog[app_selected_index].app_build_id, target, api_major, api_minor);
}
\ No newline at end of file
diff --git a/flip_store/apps/flip_store_apps.h b/flip_store/apps/flip_store_apps.h
index 4b5de73e7..e9d66c9d0 100644
--- a/flip_store/apps/flip_store_apps.h
+++ b/flip_store/apps/flip_store_apps.h
@@ -8,29 +8,28 @@
// Define maximum limits
#define MAX_APP_NAME_LENGTH 32
#define MAX_ID_LENGTH 32
-#define MAX_APP_COUNT 100
+#define MAX_APP_COUNT 50
#define MAX_APP_DESCRIPTION_LENGTH 100
#define MAX_APP_VERSION_LENGTH 5
// define the list of categories
+extern char *category_ids[];
extern char *categories[];
+extern int catalog_iteration;
typedef struct
{
- char *app_name;
- char *app_id;
- char *app_build_id;
- char *app_version;
- char *app_description;
+ char app_name[MAX_APP_NAME_LENGTH];
+ char app_id[MAX_APP_NAME_LENGTH];
+ char app_build_id[MAX_ID_LENGTH];
+ char app_version[MAX_APP_VERSION_LENGTH];
+ char app_description[MAX_APP_DESCRIPTION_LENGTH];
+ size_t count;
+ int iteration;
} FlipStoreAppInfo;
extern FlipStoreAppInfo *flip_catalog;
-
extern uint32_t app_selected_index;
-extern bool flip_store_sent_request;
-extern bool flip_store_success;
-extern bool flip_store_saved_data;
-extern bool flip_store_saved_success;
extern uint32_t flip_store_category_index;
enum ObjectState
@@ -46,10 +45,8 @@ FlipStoreAppInfo *flip_catalog_alloc();
void flip_catalog_free();
// Utility function to parse JSON incrementally from a file
-bool flip_store_process_app_list();
-
-bool flip_store_get_fap_file(char *build_id, uint8_t target, uint16_t api_major, uint16_t api_minor);
+bool flip_store_process_app_list(FlipperHTTP *fhttp);
// function to handle the entire installation process "asynchronously"
-bool flip_store_install_app(char *category);
+bool flip_store_install_app(FlipperHTTP *fhttp, char *category);
#endif // FLIP_STORE_APPS_H
\ No newline at end of file
diff --git a/flip_store/assets/01-main-menu.png b/flip_store/assets/01-main-menu.png
index 30367ebc7..f5a589c69 100644
Binary files a/flip_store/assets/01-main-menu.png and b/flip_store/assets/01-main-menu.png differ
diff --git a/flip_store/assets/CHANGELOG.md b/flip_store/assets/CHANGELOG.md
index adb0a3ad6..a17451f74 100644
--- a/flip_store/assets/CHANGELOG.md
+++ b/flip_store/assets/CHANGELOG.md
@@ -1,3 +1,17 @@
+## v0.8
+- Updated FlipperHTTP to the latest library.
+- Switched to use Flipper catalog API.
+- Added an option to download Video Game Module firmware (FlipperHTTP)
+- Added an option to download Github repositories.
+- Updated Marauder to the version 1.2
+
+## v0.7.2
+- Final memory allocation improvements
+
+## v0.7.1
+- Improved memory allocation
+- Fixed a crash when re-entering the same app list
+
## v0.7
- Improved memory allocation
- Added updates from Derek Jamison
diff --git a/flip_store/assets/README.md b/flip_store/assets/README.md
index 7e3e7e4de..00ac695c5 100644
--- a/flip_store/assets/README.md
+++ b/flip_store/assets/README.md
@@ -1,12 +1,12 @@
-# FlipStore
-Download Flipper Zero apps directly to your Flipper Zero using WiFi. You no longer need another device to install apps. FlipStore uses the FlipperHTTP flash for the WiFi Devboard, first introduced in the WebCrawler app: https://github.com/jblanked/WebCrawler-FlipperZero/tree/main/assets/FlipperHTTP
+Download Flipper Zero apps directly to your Flipper Zero using WiFi.
-## Features
+## Features
- App Catalog
- Install Apps
- Delete Apps
-- Install Developer Board Flashes
-- Install Custom Apps (coming soon)
+- Install WiFi Developer Board Firmware
+- Install Video Game Module Firmware
+- Install GitHub Repositories (Beta)
- Install Official Firmware (coming soon)
## Installation
@@ -38,18 +38,7 @@ Download Flipper Zero apps directly to your Flipper Zero using WiFi. You no long
- UX Improvements
**v0.8**
-- Download custom apps from a GitHub URL
+- Download GitHub repositories
**1.0**
-- Download Official Firmware/Firmware Updates
-
-## Contribution
-This is a big task, and I welcome all contributors, especially developers interested in animations and graphics. Fork the repository, create a pull request, and I will review your edits.
-
-## Known Bugs
-1. Clicking a category in the app catalog results in an "Out of Memory" error.
- - This issue has been addressed, but it may still occur. If it does, restart the app.
-2. The app file is corrupted.
- - This is likely due to an error parsing the data. Restart the app and wait until the green LED light turns off after downloading the app before exiting the view. If this happens more than three times, the current version of FlipStore may not be able to download that app successfully.
-3. The app is frozen on the "Installing", "Loading", or "Receiving data" screen.
- - If it there LED is not on and it's been more than 5 seconds, restart your Flipper Zero with the devboard plugged in.
+- Download Official Flipper Zero Firmware
\ No newline at end of file
diff --git a/flip_store/callback/flip_store_callback.c b/flip_store/callback/flip_store_callback.c
index a44ef370f..6550b3220 100644
--- a/flip_store/callback/flip_store_callback.c
+++ b/flip_store/callback/flip_store_callback.c
@@ -1,4 +1,5 @@
#include
+#include
// Below added by Derek Jamison
// FURI_LOG_DEV will log only during app development. Be sure that Settings/System/Log Device is "LPUART"; so we dont use serial port.
@@ -12,138 +13,39 @@
bool flip_store_app_does_exist = false;
uint32_t selected_firmware_index = 0;
+static uint32_t callback_to_app_category_list(void *context);
static bool flip_store_dl_app_fetch(DataLoaderModel *model)
{
- UNUSED(model);
- return flip_store_install_app(categories[flip_store_category_index]);
+ if (!model->fhttp)
+ {
+ FURI_LOG_E(TAG, "FlipperHTTP is NULL");
+ return false;
+ }
+ return flip_store_install_app(model->fhttp, categories[flip_store_category_index]);
}
static char *flip_store_dl_app_parse(DataLoaderModel *model)
{
- UNUSED(model);
- if (fhttp.state != IDLE)
+ if (!model->fhttp || model->fhttp->state != IDLE)
{
- return NULL;
+ FURI_LOG_E(TAG, "FlipperHTTP is NULL or not IDLE");
+ return "Failed to install app.";
}
return "App installed successfully.";
}
static void flip_store_dl_app_switch_to_view(FlipStoreApp *app)
{
- flip_store_generic_switch_to_view(app, flip_catalog[app_selected_index].app_name, flip_store_dl_app_fetch, flip_store_dl_app_parse, 1, callback_to_app_list, FlipStoreViewLoader);
+ flip_store_generic_switch_to_view(app, flip_catalog[app_selected_index].app_name, flip_store_dl_app_fetch, flip_store_dl_app_parse, 1, callback_to_app_category_list, FlipStoreViewLoader);
}
//
-static bool flip_store_fetch_app_list(DataLoaderModel *model)
-{
- UNUSED(model);
- flip_catalog_free();
- snprintf(
- fhttp.file_path,
- sizeof(fhttp.file_path),
- STORAGE_EXT_PATH_PREFIX "/apps_data/flip_store/%s.json", categories[flip_store_category_index]);
- fhttp.save_received_data = true;
- fhttp.is_bytes_request = false;
- char url[128];
- snprintf(url, sizeof(url), "https://www.flipsocial.net/api/flipper/apps/%s/max/", categories[flip_store_category_index]);
- return fhttp.state != INACTIVE && flipper_http_get_request_with_headers(url, "{\"Content-Type\":\"application/json\"}");
-}
-static char *flip_store_parse_app_list(DataLoaderModel *model)
+static bool flip_store_fetch_firmware(DataLoaderModel *model)
{
- UNUSED(model);
- if (!app_instance)
- {
- FURI_LOG_E(TAG, "FlipStoreApp is NULL");
- return "Failed to fetch app list.";
- }
- Submenu **submenu = NULL;
- uint32_t view_id = 0;
- switch (flip_store_category_index)
+ if (!model->fhttp)
{
- case 0:
- submenu = &app_instance->submenu_app_list_bluetooth;
- view_id = FlipStoreViewAppListBluetooth;
- break;
- case 1:
- submenu = &app_instance->submenu_app_list_games;
- view_id = FlipStoreViewAppListGames;
- break;
- case 2:
- submenu = &app_instance->submenu_app_list_gpio;
- view_id = FlipStoreViewAppListGPIO;
- break;
- case 3:
- submenu = &app_instance->submenu_app_list_infrared;
- view_id = FlipStoreViewAppListInfrared;
- break;
- case 4:
- submenu = &app_instance->submenu_app_list_ibutton;
- view_id = FlipStoreViewAppListiButton;
- break;
- case 5:
- submenu = &app_instance->submenu_app_list_media;
- view_id = FlipStoreViewAppListMedia;
- break;
- case 6:
- submenu = &app_instance->submenu_app_list_nfc;
- view_id = FlipStoreViewAppListNFC;
- break;
- case 7:
- submenu = &app_instance->submenu_app_list_rfid;
- view_id = FlipStoreViewAppListRFID;
- break;
- case 8:
- submenu = &app_instance->submenu_app_list_subghz;
- view_id = FlipStoreViewAppListSubGHz;
- break;
- case 9:
- submenu = &app_instance->submenu_app_list_tools;
- view_id = FlipStoreViewAppListTools;
- break;
- case 10:
- submenu = &app_instance->submenu_app_list_usb;
- view_id = FlipStoreViewAppListUSB;
- break;
- }
- if (!submenu)
- {
- FURI_LOG_E(TAG, "Submenu is NULL");
- return "Failed to fetch app list.";
- }
- if (!easy_flipper_set_submenu(submenu, view_id, categories[flip_store_category_index], callback_to_app_list, &app_instance->view_dispatcher))
- {
- return NULL;
- }
-
- if (flip_store_process_app_list() && submenu && flip_catalog)
- {
- submenu_reset(*submenu);
- // add each app name to submenu
- for (int i = 0; i < MAX_APP_COUNT; i++)
- {
- if (strlen(flip_catalog[i].app_name) > 0)
- {
- submenu_add_item(*submenu, flip_catalog[i].app_name, FlipStoreSubmenuIndexStartAppList + i, callback_submenu_choices, app_instance);
- }
- else
- {
- break;
- }
- }
- view_dispatcher_switch_to_view(app_instance->view_dispatcher, view_id);
- return "Fetched app list successfully.";
- }
- else
- {
- FURI_LOG_E(TAG, "Failed to process the app list");
- return "Failed to fetch app list.";
+ FURI_LOG_E(TAG, "FlipperHTTP is NULL");
+ return false;
}
-}
-static void flip_store_switch_to_app_list(FlipStoreApp *app)
-{
- flip_store_generic_switch_to_view(app, categories[flip_store_category_index], flip_store_fetch_app_list, flip_store_parse_app_list, 1, callback_to_submenu, FlipStoreViewLoader);
-}
-//
-static bool flip_store_fetch_firmware(DataLoaderModel *model)
-{
+ model->fhttp->state = IDLE;
if (model->request_index == 0)
{
firmware_free();
@@ -152,27 +54,27 @@ static bool flip_store_fetch_firmware(DataLoaderModel *model)
{
return false;
}
- firmware_request_success = flip_store_get_firmware_file(
+ return flip_store_get_firmware_file(
+ model->fhttp,
firmwares[selected_firmware_index].links[0],
firmwares[selected_firmware_index].name,
strrchr(firmwares[selected_firmware_index].links[0], '/') + 1);
- return firmware_request_success;
}
else if (model->request_index == 1)
{
- firmware_request_success_2 = flip_store_get_firmware_file(
+ return flip_store_get_firmware_file(
+ model->fhttp,
firmwares[selected_firmware_index].links[1],
firmwares[selected_firmware_index].name,
strrchr(firmwares[selected_firmware_index].links[1], '/') + 1);
- return firmware_request_success_2;
}
else if (model->request_index == 2)
{
- firmware_request_success_3 = flip_store_get_firmware_file(
+ return flip_store_get_firmware_file(
+ model->fhttp,
firmwares[selected_firmware_index].links[2],
firmwares[selected_firmware_index].name,
strrchr(firmwares[selected_firmware_index].links[2], '/') + 1);
- return firmware_request_success_3;
}
return false;
}
@@ -180,34 +82,59 @@ static char *flip_store_parse_firmware(DataLoaderModel *model)
{
if (model->request_index == 0)
{
- if (firmware_request_success)
- {
- return "File 1 installed.";
- }
+ return "File 1 installed.";
}
else if (model->request_index == 1)
{
- if (firmware_request_success_2)
- {
- return "File 2 installed.";
- }
+ return "File 2 installed.";
}
else if (model->request_index == 2)
{
- if (firmware_request_success_3)
+ return "Firmware downloaded successfully";
+ }
+ return "Failed to download firmware.";
+}
+static void flip_store_switch_to_firmware_list(FlipStoreApp *app)
+{
+ flip_store_generic_switch_to_view(app, firmwares[selected_firmware_index].name, flip_store_fetch_firmware, flip_store_parse_firmware, FIRMWARE_LINKS, callback_to_firmware_list, FlipStoreViewLoader);
+}
+//
+static bool flip_store_fetch_vgm_firmware(DataLoaderModel *model)
+{
+ if (!model->fhttp)
+ {
+ FURI_LOG_E(TAG, "FlipperHTTP is NULL");
+ return false;
+ }
+ model->fhttp->state = IDLE;
+ if (model->request_index == 0)
+ {
+ vgm_firmware_free();
+ vgm_firmwares = vgm_firmware_alloc();
+ if (!vgm_firmwares)
{
- return "Firmware downloaded successfully";
+ return false;
}
+ return flip_store_get_firmware_file(
+ model->fhttp,
+ vgm_firmwares[selected_firmware_index].link,
+ vgm_firmwares[selected_firmware_index].name,
+ strrchr(vgm_firmwares[selected_firmware_index].link, '/') + 1);
}
- return NULL;
+ return false;
}
-static void flip_store_switch_to_firmware_list(FlipStoreApp *app)
+static char *flip_store_parse_vgm_firmware(DataLoaderModel *model)
{
- flip_store_generic_switch_to_view(app, firmwares[selected_firmware_index].name, flip_store_fetch_firmware, flip_store_parse_firmware, FIRMWARE_LINKS, callback_to_submenu, FlipStoreViewLoader);
+ UNUSED(model);
+ return "Firmware downloaded successfully";
+}
+static void flip_store_switch_to_vgm_firmware_list(FlipStoreApp *app)
+{
+ flip_store_generic_switch_to_view(app, vgm_firmwares[selected_firmware_index].name, flip_store_fetch_vgm_firmware, flip_store_parse_vgm_firmware, 1, callback_to_vgm_firmware_list, FlipStoreViewLoader);
}
// Function to draw the message on the canvas with word wrapping
-void draw_description(Canvas *canvas, const char *description, int x, int y)
+static void draw_description(Canvas *canvas, const char *description, int x, int y)
{
if (description == NULL || strlen(description) == 0)
{
@@ -267,13 +194,13 @@ void draw_description(Canvas *canvas, const char *description, int x, int y)
}
}
-void flip_store_view_draw_callback_app_list(Canvas *canvas, void *model)
+static void flip_store_view_draw_callback_app_list(Canvas *canvas, void *model)
{
UNUSED(model);
canvas_clear(canvas);
canvas_set_font(canvas, FontPrimary);
- char title[30];
- snprintf(title, 30, "%s (v.%s)", flip_catalog[app_selected_index].app_name, flip_catalog[app_selected_index].app_version);
+ char title[64];
+ snprintf(title, 64, "%s (v.%s)", flip_catalog[app_selected_index].app_name, flip_catalog[app_selected_index].app_version);
canvas_draw_str(canvas, 0, 10, title);
canvas_set_font(canvas, FontSecondary);
draw_description(canvas, flip_catalog[app_selected_index].app_description, 0, 13);
@@ -293,7 +220,7 @@ void flip_store_view_draw_callback_app_list(Canvas *canvas, void *model)
canvas_draw_str_aligned(canvas, 97, 54, AlignLeft, AlignTop, "Install");
}
-bool flip_store_input_callback(InputEvent *event, void *context)
+static bool flip_store_input_callback(InputEvent *event, void *context)
{
FlipStoreApp *app = (FlipStoreApp *)context;
if (!app)
@@ -320,244 +247,752 @@ bool flip_store_input_callback(InputEvent *event, void *context)
{
if (event->key == InputKeyBack)
{
- // Back button clicked, switch to the previous view.
- view_dispatcher_switch_to_view(app->view_dispatcher, FlipStoreViewAppList);
- return true;
+ // Back button clicked, switch to the previous view.
+ view_dispatcher_switch_to_view(app->view_dispatcher, FlipStoreViewAppListCategory);
+ return true;
+ }
+ }
+
+ return false;
+}
+static void free_text_input_view(FlipStoreApp *app);
+static bool alloc_text_input_view(void *context, char *title);
+static void flip_store_text_updated_ssid(void *context)
+{
+ FlipStoreApp *app = (FlipStoreApp *)context;
+ if (!app)
+ {
+ FURI_LOG_E(TAG, "FlipStoreApp is NULL");
+ return;
+ }
+
+ // store the entered text
+ strncpy(app->uart_text_input_buffer, app->uart_text_input_temp_buffer, app->uart_text_input_buffer_size);
+
+ // Ensure null-termination
+ app->uart_text_input_buffer[app->uart_text_input_buffer_size - 1] = '\0';
+
+ // save the setting
+ save_char("WiFi-SSID", app->uart_text_input_buffer);
+
+ // update the variable item text
+ if (app->variable_item_ssid)
+ {
+ variable_item_set_current_value_text(app->variable_item_ssid, app->uart_text_input_buffer);
+
+ // get value of password
+ char pass[64];
+ if (load_char("WiFi-Password", pass, sizeof(pass)))
+ {
+ if (strlen(pass) > 0 && strlen(app->uart_text_input_buffer) > 0)
+ {
+ // save the settings
+ save_settings(app->uart_text_input_buffer, pass);
+
+ // initialize the http
+ FlipperHTTP *fhttp = flipper_http_alloc();
+ if (fhttp)
+ {
+ // save the wifi if the device is connected
+ if (!flipper_http_save_wifi(fhttp, app->uart_text_input_buffer, pass))
+ {
+ easy_flipper_dialog("FlipperHTTP Error", "Ensure your WiFi Developer\nBoard or Pico W is connected\nand the latest FlipperHTTP\nfirmware is installed.");
+ }
+
+ // free the resources
+ flipper_http_free(fhttp);
+ }
+ else
+ {
+ easy_flipper_dialog("FlipperHTTP Error", "The UART is likely busy.\nEnsure you have the correct\nflash for your board then\nrestart your Flipper Zero.");
+ }
+ }
+ }
+ }
+
+ // switch to the settings view
+ view_dispatcher_switch_to_view(app->view_dispatcher, FlipStoreViewSettings);
+}
+static void flip_store_text_updated_pass(void *context)
+{
+ FlipStoreApp *app = (FlipStoreApp *)context;
+ if (!app)
+ {
+ FURI_LOG_E(TAG, "FlipStoreApp is NULL");
+ return;
+ }
+
+ // store the entered text
+ strncpy(app->uart_text_input_buffer, app->uart_text_input_temp_buffer, app->uart_text_input_buffer_size);
+
+ // Ensure null-termination
+ app->uart_text_input_buffer[app->uart_text_input_buffer_size - 1] = '\0';
+
+ // save the setting
+ save_char("WiFi-Password", app->uart_text_input_buffer);
+
+ // update the variable item text
+ if (app->variable_item_pass)
+ {
+ // variable_item_set_current_value_text(app->variable_item_pass, app->uart_text_input_buffer);
+ }
+
+ // get value of ssid
+ char ssid[64];
+ if (load_char("WiFi-SSID", ssid, sizeof(ssid)))
+ {
+ if (strlen(ssid) > 0 && strlen(app->uart_text_input_buffer) > 0)
+ {
+ // save the settings
+ save_settings(ssid, app->uart_text_input_buffer);
+
+ // initialize the http
+ FlipperHTTP *fhttp = flipper_http_alloc();
+ if (fhttp)
+ {
+ // save the wifi if the device is connected
+ if (!flipper_http_save_wifi(fhttp, ssid, app->uart_text_input_buffer))
+ {
+ easy_flipper_dialog("FlipperHTTP Error", "Ensure your WiFi Developer\nBoard or Pico W is connected\nand the latest FlipperHTTP\nfirmware is installed.");
+ }
+
+ // free the resources
+ flipper_http_free(fhttp);
+ }
+ else
+ {
+ easy_flipper_dialog("FlipperHTTP Error", "The UART is likely busy.\nEnsure you have the correct\nflash for your board then\nrestart your Flipper Zero.");
+ }
+ }
+ }
+
+ // switch to the settings view
+ view_dispatcher_switch_to_view(app->view_dispatcher, FlipStoreViewSettings);
+}
+static void flip_store_text_updated_repo(void *context);
+static void flip_store_text_updated_author(void *context)
+{
+ FlipStoreApp *app = (FlipStoreApp *)context;
+ if (!app)
+ {
+ FURI_LOG_E(TAG, "FlipStoreApp is NULL");
+ return;
+ }
+
+ // store the entered text
+ strncpy(app->uart_text_input_buffer, app->uart_text_input_temp_buffer, app->uart_text_input_buffer_size);
+
+ // Ensure null-termination
+ app->uart_text_input_buffer[app->uart_text_input_buffer_size - 1] = '\0';
+
+ // save the setting
+ save_char("Github-Author", app->uart_text_input_buffer);
+
+ view_dispatcher_switch_to_view(app->view_dispatcher, FlipStoreViewSubmenu);
+ text_input_reset(app->uart_text_input);
+ text_input_set_header_text(app->uart_text_input, "Repository");
+ app->uart_text_input_buffer_size = 64;
+ free(app->uart_text_input_buffer);
+ free(app->uart_text_input_temp_buffer);
+ easy_flipper_set_buffer(&app->uart_text_input_buffer, app->uart_text_input_buffer_size);
+ easy_flipper_set_buffer(&app->uart_text_input_temp_buffer, app->uart_text_input_buffer_size);
+ text_input_set_result_callback(app->uart_text_input, flip_store_text_updated_repo, app, app->uart_text_input_temp_buffer, app->uart_text_input_buffer_size, false);
+ view_dispatcher_switch_to_view(app->view_dispatcher, FlipStoreViewTextInput);
+}
+
+static bool flip_store_fetch_github(FlipperHTTP *fhttp)
+{
+ if (!fhttp)
+ {
+ FURI_LOG_E(TAG, "FlipperHTTP is NULL");
+ return false;
+ }
+ char author[64];
+ char repo[64];
+ if (!load_char("Github-Author", author, sizeof(author)) || !load_char("Github-Repo", repo, sizeof(repo)))
+ {
+ FURI_LOG_E(TAG, "Failed to load Github author or repo");
+ return false;
+ }
+ return flip_store_get_github_contents(fhttp, author, repo);
+}
+
+static bool flip_store_parse_github(FlipperHTTP *fhttp)
+{
+ if (!fhttp)
+ {
+ FURI_LOG_E(TAG, "FlipperHTTP is NULL");
+ return false;
+ }
+ char author[64];
+ char repo[64];
+ if (!load_char("Github-Author", author, sizeof(author)) || !load_char("Github-Repo", repo, sizeof(repo)))
+ {
+ FURI_LOG_E(TAG, "Failed to load Github author or repo");
+ return false;
+ }
+ if (!flip_store_parse_github_contents(fhttp->file_path, author, repo))
+ {
+ return false;
+ }
+ return flip_store_install_all_github_files(fhttp, author, repo);
+}
+static bool github_success = false;
+static void flip_store_get_github_repository(FlipStoreApp *app)
+{
+ FlipperHTTP *fhttp = flipper_http_alloc();
+ if (!fhttp)
+ {
+ FURI_LOG_E(TAG, "Failed to allocate FlipperHTTP");
+ return;
+ }
+ if (!app)
+ {
+ FURI_LOG_E(TAG, "FlipStoreApp is NULL");
+ return;
+ }
+ bool http_request()
+ {
+ github_success = flip_store_fetch_github(fhttp);
+ return github_success;
+ }
+ bool http_parse()
+ {
+ github_success = flip_store_parse_github(fhttp);
+ return github_success;
+ }
+ flipper_http_loading_task(fhttp, http_request, http_parse, FlipStoreViewSubmenuOptions, FlipStoreViewWidgetResult, &app->view_dispatcher);
+ flipper_http_free(fhttp);
+ if (github_success)
+ {
+ easy_flipper_dialog("Success", "Repository downloaded\nsuccessfully.");
+ }
+ else
+ {
+ easy_flipper_dialog("Failure", "Failed to download\nrepository.");
+ }
+}
+static void flip_store_text_updated_repo(void *context)
+{
+ FlipStoreApp *app = (FlipStoreApp *)context;
+ if (!app)
+ {
+ FURI_LOG_E(TAG, "FlipStoreApp is NULL");
+ return;
+ }
+
+ // store the entered text
+ strncpy(app->uart_text_input_buffer, app->uart_text_input_temp_buffer, app->uart_text_input_buffer_size);
+
+ // Ensure null-termination
+ app->uart_text_input_buffer[app->uart_text_input_buffer_size - 1] = '\0';
+
+ // save the setting
+ save_char("Github-Repo", app->uart_text_input_buffer);
+ view_dispatcher_switch_to_view(app->view_dispatcher, FlipStoreViewSubmenuOptions);
+ flip_store_get_github_repository(app);
+}
+static void free_category_submenu(FlipStoreApp *app)
+{
+ if (!app)
+ {
+ FURI_LOG_E(TAG, "FlipStoreApp is NULL");
+ return;
+ }
+ if (app->submenu_app_list_category)
+ {
+ view_dispatcher_remove_view(app->view_dispatcher, FlipStoreViewAppListCategory);
+ submenu_free(app->submenu_app_list_category);
+ app->submenu_app_list_category = NULL;
+ }
+}
+static void free_variable_item_list(FlipStoreApp *app);
+
+uint32_t callback_to_submenu(void *context)
+{
+ UNUSED(context);
+ firmware_free();
+ return FlipStoreViewSubmenu;
+}
+
+uint32_t callback_to_firmware_list(void *context)
+{
+ UNUSED(context);
+ return FlipStoreViewFirmwares;
+}
+uint32_t callback_to_vgm_firmware_list(void *context)
+{
+ UNUSED(context);
+ return FlipStoreViewVGMFirmwares;
+}
+static uint32_t callback_to_app_category_list(void *context)
+{
+ UNUSED(context);
+ return FlipStoreViewAppListCategory;
+}
+uint32_t callback_to_app_list(void *context)
+{
+ UNUSED(context);
+ return FlipStoreViewAppList;
+}
+
+static uint32_t callback_to_wifi_settings(void *context)
+{
+ UNUSED(context);
+ return FlipStoreViewSettings;
+}
+static void dialog_firmware_callback(DialogExResult result, void *context)
+{
+ FlipStoreApp *app = (FlipStoreApp *)context;
+ if (!app)
+ {
+ FURI_LOG_E(TAG, "FlipStoreApp is NULL");
+ return;
+ }
+ if (result == DialogExResultLeft) // No
+ {
+ // switch to the firmware list
+ if (is_esp32_firmware)
+ {
+ view_dispatcher_switch_to_view(app->view_dispatcher, FlipStoreViewFirmwares);
+ }
+ else
+ {
+ view_dispatcher_switch_to_view(app->view_dispatcher, FlipStoreViewVGMFirmwares);
+ }
+ }
+ else if (result == DialogExResultRight)
+ {
+ // download the firmware then return to the firmware list
+ if (is_esp32_firmware)
+ {
+ flip_store_switch_to_firmware_list(app);
+ }
+ else
+ {
+ flip_store_switch_to_vgm_firmware_list(app);
+ }
+ }
+}
+
+static bool alloc_about_view(FlipStoreApp *app)
+{
+ if (!app)
+ {
+ FURI_LOG_E(TAG, "FlipStoreApp is NULL");
+ return false;
+ }
+ if (!app->widget_about)
+ {
+ if (!easy_flipper_set_widget(
+ &app->widget_about,
+ FlipStoreViewAbout,
+ "Welcome to the FlipStore!\n------\nDownload apps via WiFi and\nrun them on your Flipper!\n------\nwww.github.com/jblanked",
+ callback_to_submenu,
+ &app->view_dispatcher))
+ {
+ return false;
+ }
+ if (!app->widget_about)
+ {
+ return false;
+ }
+ }
+ return true;
+}
+
+static void free_about_view(FlipStoreApp *app)
+{
+ if (app && app->widget_about != NULL)
+ {
+ view_dispatcher_remove_view(app->view_dispatcher, FlipStoreViewAbout);
+ widget_free(app->widget_about);
+ app->widget_about = NULL;
+ }
+}
+static bool alloc_text_input_view(void *context, char *title)
+{
+ FlipStoreApp *app = (FlipStoreApp *)context;
+ if (!app)
+ {
+ FURI_LOG_E(TAG, "FlipStoreApp is NULL");
+ return false;
+ }
+ if (!title)
+ {
+ FURI_LOG_E(TAG, "Title is NULL");
+ return false;
+ }
+ app->uart_text_input_buffer_size = 64;
+ if (!app->uart_text_input_buffer)
+ {
+ if (!easy_flipper_set_buffer(&app->uart_text_input_buffer, app->uart_text_input_buffer_size))
+ {
+ return false;
+ }
+ }
+ if (!app->uart_text_input_temp_buffer)
+ {
+ if (!easy_flipper_set_buffer(&app->uart_text_input_temp_buffer, app->uart_text_input_buffer_size))
+ {
+ return false;
+ }
+ }
+ if (!app->uart_text_input)
+ {
+ if (strcmp(title, "SSID") != 0 && strcmp(title, "Password") != 0)
+ {
+ // Github repository download
+ if (!easy_flipper_set_uart_text_input(
+ &app->uart_text_input,
+ FlipStoreViewTextInput,
+ title,
+ app->uart_text_input_temp_buffer,
+ app->uart_text_input_buffer_size,
+ strcmp(title, "Author") == 0 ? flip_store_text_updated_author : flip_store_text_updated_repo,
+ callback_to_submenu_options,
+ &app->view_dispatcher,
+ app))
+ {
+ return false;
+ }
+ if (!app->uart_text_input)
+ {
+ return false;
+ }
+ }
+ else
+ {
+ if (!easy_flipper_set_uart_text_input(
+ &app->uart_text_input,
+ FlipStoreViewTextInput,
+ title,
+ app->uart_text_input_temp_buffer,
+ app->uart_text_input_buffer_size,
+ strcmp(title, "SSID") == 0 ? flip_store_text_updated_ssid : flip_store_text_updated_pass,
+ callback_to_wifi_settings,
+ &app->view_dispatcher,
+ app))
+ {
+ return false;
+ }
+ if (!app->uart_text_input)
+ {
+ return false;
+ }
+ char ssid[64];
+ char pass[64];
+ if (load_settings(ssid, sizeof(ssid), pass, sizeof(pass)))
+ {
+ if (strcmp(title, "SSID") == 0)
+ {
+ strncpy(app->uart_text_input_temp_buffer, ssid, app->uart_text_input_buffer_size);
+ }
+ else
+ {
+ strncpy(app->uart_text_input_temp_buffer, pass, app->uart_text_input_buffer_size);
+ }
+ }
+ }
+ }
+ return true;
+}
+static void free_text_input_view(FlipStoreApp *app)
+{
+ if (!app)
+ {
+ FURI_LOG_E(TAG, "FlipStoreApp is NULL");
+ return;
+ }
+ if (app->uart_text_input)
+ {
+ view_dispatcher_remove_view(app->view_dispatcher, FlipStoreViewTextInput);
+ text_input_free(app->uart_text_input);
+ app->uart_text_input = NULL;
+ }
+ if (app->uart_text_input_buffer)
+ {
+ free(app->uart_text_input_buffer);
+ app->uart_text_input_buffer = NULL;
+ }
+ if (app->uart_text_input_temp_buffer)
+ {
+ free(app->uart_text_input_temp_buffer);
+ app->uart_text_input_temp_buffer = NULL;
+ }
+}
+static void settings_item_selected(void *context, uint32_t index);
+static bool alloc_variable_item_list(void *context)
+{
+ FlipStoreApp *app = (FlipStoreApp *)context;
+ if (!app)
+ {
+ FURI_LOG_E(TAG, "FlipStoreApp is NULL");
+ return false;
+ }
+ if (!app->variable_item_list)
+ {
+ if (!easy_flipper_set_variable_item_list(&app->variable_item_list, FlipStoreViewSettings, settings_item_selected, callback_to_submenu, &app->view_dispatcher, app))
+ return false;
+
+ if (!app->variable_item_list)
+ return false;
+
+ if (!app->variable_item_ssid)
+ {
+ app->variable_item_ssid = variable_item_list_add(app->variable_item_list, "SSID", 0, NULL, NULL);
+ variable_item_set_current_value_text(app->variable_item_ssid, "");
+ }
+ if (!app->variable_item_pass)
+ {
+ app->variable_item_pass = variable_item_list_add(app->variable_item_list, "Password", 0, NULL, NULL);
+ variable_item_set_current_value_text(app->variable_item_pass, "");
+ }
+ char ssid[64];
+ char pass[64];
+ if (load_settings(ssid, sizeof(ssid), pass, sizeof(pass)))
+ {
+ variable_item_set_current_value_text(app->variable_item_ssid, ssid);
+ // variable_item_set_current_value_text(app->variable_item_pass, pass);
+ save_char("WiFi-SSID", ssid);
+ save_char("WiFi-Password", pass);
}
}
-
- return false;
+ return true;
}
-
-void flip_store_text_updated_ssid(void *context)
+static void free_variable_item_list(FlipStoreApp *app)
{
- FlipStoreApp *app = (FlipStoreApp *)context;
if (!app)
{
FURI_LOG_E(TAG, "FlipStoreApp is NULL");
return;
}
-
- // store the entered text
- strncpy(app->uart_text_input_buffer_ssid, app->uart_text_input_temp_buffer_ssid, app->uart_text_input_buffer_size_ssid);
-
- // Ensure null-termination
- app->uart_text_input_buffer_ssid[app->uart_text_input_buffer_size_ssid - 1] = '\0';
-
- // update the variable item text
+ if (app->variable_item_list)
+ {
+ view_dispatcher_remove_view(app->view_dispatcher, FlipStoreViewSettings);
+ variable_item_list_free(app->variable_item_list);
+ app->variable_item_list = NULL;
+ }
if (app->variable_item_ssid)
{
- variable_item_set_current_value_text(app->variable_item_ssid, app->uart_text_input_buffer_ssid);
+ free(app->variable_item_ssid);
+ app->variable_item_ssid = NULL;
}
-
- // save the settings
- save_settings(app->uart_text_input_buffer_ssid, app->uart_text_input_buffer_pass);
-
- // if SSID and PASS are not empty, connect to the WiFi
- if (strlen(app->uart_text_input_buffer_ssid) > 0 && strlen(app->uart_text_input_buffer_pass) > 0)
+ if (app->variable_item_pass)
{
- // save wifi settings
- if (!flipper_http_save_wifi(app->uart_text_input_buffer_ssid, app->uart_text_input_buffer_pass))
- {
- FURI_LOG_E(TAG, "Failed to save WiFi settings");
- }
+ free(app->variable_item_pass);
+ app->variable_item_pass = NULL;
}
-
- // switch to the settings view
- view_dispatcher_switch_to_view(app->view_dispatcher, FlipStoreViewSettings);
}
-void flip_store_text_updated_pass(void *context)
+static bool alloc_dialog_firmware(void *context)
{
FlipStoreApp *app = (FlipStoreApp *)context;
if (!app)
{
FURI_LOG_E(TAG, "FlipStoreApp is NULL");
- return;
- }
-
- // store the entered text
- strncpy(app->uart_text_input_buffer_pass, app->uart_text_input_temp_buffer_pass, app->uart_text_input_buffer_size_pass);
-
- // Ensure null-termination
- app->uart_text_input_buffer_pass[app->uart_text_input_buffer_size_pass - 1] = '\0';
-
- // update the variable item text
- if (app->variable_item_pass)
- {
- variable_item_set_current_value_text(app->variable_item_pass, app->uart_text_input_buffer_pass);
+ return false;
}
- // save the settings
- save_settings(app->uart_text_input_buffer_ssid, app->uart_text_input_buffer_pass);
-
- // if SSID and PASS are not empty, connect to the WiFi
- if (strlen(app->uart_text_input_buffer_ssid) > 0 && strlen(app->uart_text_input_buffer_pass) > 0)
- {
- // save wifi settings
- if (!flipper_http_save_wifi(app->uart_text_input_buffer_ssid, app->uart_text_input_buffer_pass))
+ if (!app->dialog_firmware)
+ {
+ if (!easy_flipper_set_dialog_ex(
+ &app->dialog_firmware,
+ FlipStoreViewFirmwareDialog,
+ is_esp32_firmware ? "Download ESP32 Firmware" : "Download VGM Firmware",
+ 0,
+ 0,
+ "Are you sure you want to\ndownload this firmware?",
+ 0,
+ 10,
+ "No",
+ "Yes",
+ NULL,
+ dialog_firmware_callback,
+ callback_to_firmware_list,
+ &app->view_dispatcher,
+ app))
+ {
+ return false;
+ }
+ if (!app->dialog_firmware)
+ {
+ return false;
+ }
+ if (is_esp32_firmware)
{
- FURI_LOG_E(TAG, "Failed to save WiFi settings");
+ dialog_ex_set_header(app->dialog_firmware, firmwares[selected_firmware_index].name, 0, 0, AlignLeft, AlignTop);
+ }
+ else
+ {
+ dialog_ex_set_header(app->dialog_firmware, vgm_firmwares[selected_firmware_index].name, 0, 0, AlignLeft, AlignTop);
}
}
-
- // switch to the settings view
- view_dispatcher_switch_to_view(app->view_dispatcher, FlipStoreViewSettings);
+ return true;
}
-
-uint32_t callback_to_submenu(void *context)
+static void free_dialog_firmware(FlipStoreApp *app)
{
- if (!context)
+ if (app && app->dialog_firmware)
{
- FURI_LOG_E(TAG, "Context is NULL");
- return VIEW_NONE;
+ view_dispatcher_remove_view(app->view_dispatcher, FlipStoreViewFirmwareDialog);
+ dialog_ex_free(app->dialog_firmware);
+ app->dialog_firmware = NULL;
}
- UNUSED(context);
- firmware_free();
- return FlipStoreViewSubmenu;
}
-
-uint32_t callback_to_submenu_options(void *context)
+static bool alloc_app_info_view(FlipStoreApp *app)
{
- if (!context)
+ if (!app)
{
- FURI_LOG_E(TAG, "Context is NULL");
- return VIEW_NONE;
+ FURI_LOG_E(TAG, "FlipStoreApp is NULL");
+ return false;
}
- UNUSED(context);
- firmware_free();
- return FlipStoreViewSubmenuOptions;
-}
-
-uint32_t callback_to_firmware_list(void *context)
-{
- if (!context)
+ if (!app->view_app_info)
{
- FURI_LOG_E(TAG, "Context is NULL");
- return VIEW_NONE;
+ if (!easy_flipper_set_view(
+ &app->view_app_info,
+ FlipStoreViewAppInfo,
+ flip_store_view_draw_callback_app_list,
+ flip_store_input_callback,
+ callback_to_app_category_list,
+ &app->view_dispatcher,
+ app))
+ {
+ return false;
+ }
+ if (!app->view_app_info)
+ {
+ return false;
+ }
}
- UNUSED(context);
- sent_firmware_request = false;
- sent_firmware_request_2 = false;
- sent_firmware_request_3 = false;
- //
- firmware_request_success = false;
- firmware_request_success_2 = false;
- firmware_request_success_3 = false;
- //
- firmware_download_success = false;
- firmware_download_success_2 = false;
- firmware_download_success_3 = false;
- return FlipStoreViewFirmwares;
+ return true;
}
-
-uint32_t callback_to_app_list(void *context)
+static void free_app_info_view(FlipStoreApp *app)
{
- if (!context)
+ if (!app)
+ {
+ FURI_LOG_E(TAG, "FlipStoreApp is NULL");
+ return;
+ }
+ if (app->view_app_info)
{
- FURI_LOG_E(TAG, "Context is NULL");
- return VIEW_NONE;
+ view_dispatcher_remove_view(app->view_dispatcher, FlipStoreViewAppInfo);
+ view_free(app->view_app_info);
+ app->view_app_info = NULL;
}
- UNUSED(context);
- flip_store_sent_request = false;
- flip_store_success = false;
- flip_store_saved_data = false;
- flip_store_saved_success = false;
- flip_store_app_does_exist = false;
- sent_firmware_request = false;
- return FlipStoreViewAppList;
}
-
-void settings_item_selected(void *context, uint32_t index)
+uint32_t callback_to_submenu_options(void *context)
{
FlipStoreApp *app = (FlipStoreApp *)context;
if (!app)
{
FURI_LOG_E(TAG, "FlipStoreApp is NULL");
- return;
- }
- switch (index)
- {
- case 0: // Input SSID
- view_dispatcher_switch_to_view(app->view_dispatcher, FlipStoreViewTextInputSSID);
- break;
- case 1: // Input Password
- view_dispatcher_switch_to_view(app->view_dispatcher, FlipStoreViewTextInputPass);
- break;
- default:
- FURI_LOG_E(TAG, "Unknown configuration item index");
- break;
+ return FlipStoreViewSubmenuOptions;
}
+ firmware_free();
+ vgm_firmware_free();
+ flip_catalog_free();
+ free_category_submenu(app);
+ return FlipStoreViewSubmenuOptions;
}
-
-void dialog_delete_callback(DialogExResult result, void *context)
+void free_all_views(FlipStoreApp *app, bool should_free_variable_item_list)
{
- furi_assert(context);
- FlipStoreApp *app = (FlipStoreApp *)context;
- if (result == DialogExResultLeft) // No
+ if (!app)
{
- view_dispatcher_switch_to_view(app->view_dispatcher, FlipStoreViewAppList);
+ FURI_LOG_E(TAG, "FlipStoreApp is NULL");
+ return;
}
- else if (result == DialogExResultRight)
+ free_about_view(app);
+ flip_catalog_free();
+ if (should_free_variable_item_list)
{
- // delete the app then return to the app list
- if (!delete_app(flip_catalog[app_selected_index].app_id, categories[flip_store_category_index]))
- {
- // pop up a message
- popup_set_header(app->popup, "[ERROR]", 0, 0, AlignLeft, AlignTop);
- popup_set_text(app->popup, "Issue deleting app.", 0, 50, AlignLeft, AlignTop);
- view_dispatcher_switch_to_view(app->view_dispatcher, FlipStoreViewPopup);
- furi_delay_ms(2000); // delay for 2 seconds
- view_dispatcher_switch_to_view(app->view_dispatcher, FlipStoreViewAppList);
- }
- else
- {
- // pop up a message
- popup_set_header(app->popup, "[SUCCESS]", 0, 0, AlignLeft, AlignTop);
- popup_set_text(app->popup, "App deleted successfully.", 0, 50, AlignLeft, AlignTop);
- view_dispatcher_switch_to_view(app->view_dispatcher, FlipStoreViewPopup);
- furi_delay_ms(2000); // delay for 2 seconds
- view_dispatcher_switch_to_view(app->view_dispatcher, FlipStoreViewAppList);
- }
+ free_variable_item_list(app);
}
+ free_category_submenu(app);
+ free_text_input_view(app);
+ free_dialog_firmware(app);
+ free_app_info_view(app);
+ firmware_free();
+ vgm_firmware_free();
+}
+uint32_t callback_exit_app(void *context)
+{
+ UNUSED(context);
+ return VIEW_NONE; // Return VIEW_NONE to exit the app
}
-void dialog_firmware_callback(DialogExResult result, void *context)
+static bool set_appropriate_list(FlipperHTTP *fhttp, FlipStoreApp *app)
{
- furi_assert(context);
- FlipStoreApp *app = (FlipStoreApp *)context;
- if (result == DialogExResultLeft) // No
+ if (!fhttp || !app)
{
- // switch to the firmware list
- view_dispatcher_switch_to_view(app->view_dispatcher, FlipStoreViewFirmwares);
+ FURI_LOG_E(TAG, "FlipperHTTP oor app is NULL");
+ return false;
}
- else if (result == DialogExResultRight)
+
+ if (!easy_flipper_set_submenu(&app->submenu_app_list_category, FlipStoreViewAppListCategory, categories[flip_store_category_index], callback_to_app_list, &app->view_dispatcher))
{
- // download the firmware then return to the firmware list
- flip_store_switch_to_firmware_list(app);
+ FURI_LOG_E(TAG, "Failed to set submenu");
+ return false;
}
-}
-void popup_callback(void *context)
-{
- FlipStoreApp *app = (FlipStoreApp *)context;
- if (!app)
+ if (flip_store_process_app_list(fhttp) && app->submenu_app_list_category && flip_catalog)
{
- FURI_LOG_E(TAG, "FlipStoreApp is NULL");
- return;
+ submenu_reset(app->submenu_app_list_category);
+ submenu_set_header(app->submenu_app_list_category, categories[flip_store_category_index]);
+ // add each app name to submenu
+ for (size_t i = 0; i < flip_catalog->count; i++)
+ {
+ if (strlen(flip_catalog[i].app_name) > 0)
+ {
+ submenu_add_item(app->submenu_app_list_category, flip_catalog[i].app_name, FlipStoreSubmenuIndexStartAppList + i, callback_submenu_choices, app);
+ }
+ else
+ {
+ break;
+ }
+ }
+ // add [LOAD NEXT] to submenu
+ submenu_add_item(app->submenu_app_list_category, "[LOAD NEXT]", FlipStoreSubmenuIndexStartAppList + flip_catalog->count, callback_submenu_choices, app);
+ return true;
}
- view_dispatcher_switch_to_view(app->view_dispatcher, FlipStoreViewSubmenu);
+ FURI_LOG_E(TAG, "Failed to process the app list");
+ return false;
}
-uint32_t callback_exit_app(void *context)
+// we'll have to loop 8 at a time due to memory constraints
+static void fetch_appropiate_app_list(FlipStoreApp *app, int iteration)
{
- // Exit the application
- if (!context)
+ FlipperHTTP *fhttp = flipper_http_alloc();
+ if (!fhttp)
{
- FURI_LOG_E(TAG, "Context is NULL");
- return VIEW_NONE;
+ FURI_LOG_E(TAG, "Failed to allocate FlipperHTTP");
+ return;
}
- UNUSED(context);
- return VIEW_NONE; // Return VIEW_NONE to exit the app
+ bool fetch_app_list()
+ {
+ // ensure /apps_data/flip_store/data exists
+ Storage *storage = furi_record_open(RECORD_STORAGE);
+ char dir[256];
+ snprintf(dir, sizeof(dir), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_store/data");
+ storage_common_mkdir(storage, dir);
+ furi_record_close(RECORD_STORAGE);
+ fhttp->state = IDLE;
+ flip_catalog_free();
+ snprintf(
+ fhttp->file_path,
+ sizeof(fhttp->file_path),
+ STORAGE_EXT_PATH_PREFIX "/apps_data/flip_store/data/%s.json", categories[flip_store_category_index]);
+ fhttp->save_received_data = true;
+ fhttp->is_bytes_request = false;
+ char url[256];
+ // load 8 at a time
+ snprintf(url, sizeof(url), "https://catalog.flipperzero.one/api/v0/0/application?limit=8&is_latest_release_version=true&offset=%d&sort_by=updated_at&sort_order=-1&category_id=%s", iteration, category_ids[flip_store_category_index]);
+ return flipper_http_get_request_with_headers(fhttp, url, "{\"Content-Type\":\"application/json\"}");
+ }
+ bool parse_app_list()
+ {
+ return set_appropriate_list(fhttp, app);
+ }
+ flipper_http_loading_task(fhttp, fetch_app_list, parse_app_list, FlipStoreViewAppListCategory, FlipStoreViewSubmenuOptions, &app->view_dispatcher);
+ flipper_http_free(fhttp);
}
void callback_submenu_choices(void *context, uint32_t index)
@@ -568,15 +1003,29 @@ void callback_submenu_choices(void *context, uint32_t index)
FURI_LOG_E(TAG, "FlipStoreApp is NULL");
return;
}
+
switch (index)
{
case FlipStoreSubmenuIndexAbout:
+ free_all_views(app, true);
+ if (!alloc_about_view(app))
+ {
+ FURI_LOG_E(TAG, "Failed to set about view");
+ return;
+ }
view_dispatcher_switch_to_view(app->view_dispatcher, FlipStoreViewAbout);
break;
case FlipStoreSubmenuIndexSettings:
+ free_all_views(app, true);
+ if (!alloc_variable_item_list(app))
+ {
+ FURI_LOG_E(TAG, "Failed to allocate variable item list");
+ return;
+ }
view_dispatcher_switch_to_view(app->view_dispatcher, FlipStoreViewSettings);
break;
case FlipStoreSubmenuIndexOptions:
+ free_all_views(app, true);
view_dispatcher_switch_to_view(app->view_dispatcher, FlipStoreViewSubmenuOptions);
break;
case FlipStoreSubmenuIndexAppList:
@@ -584,12 +1033,7 @@ void callback_submenu_choices(void *context, uint32_t index)
flip_store_app_does_exist = false;
view_dispatcher_switch_to_view(app->view_dispatcher, FlipStoreViewAppList);
break;
- case FlipStoreSubmenuIndexFirmwares:
- if (!app->submenu_firmwares)
- {
- FURI_LOG_E(TAG, "Submenu firmwares is NULL");
- return;
- }
+ case FlipStoreSubmenuIndexFirmwares: // esp32 firmwares
firmwares = firmware_alloc();
if (firmwares == NULL)
{
@@ -604,64 +1048,99 @@ void callback_submenu_choices(void *context, uint32_t index)
}
view_dispatcher_switch_to_view(app->view_dispatcher, FlipStoreViewFirmwares);
break;
+ case FlipStoreSubmenuIndexVGMFirmwares: // vgm firmwares
+ vgm_firmwares = vgm_firmware_alloc();
+ if (vgm_firmwares == NULL)
+ {
+ FURI_LOG_E(TAG, "Failed to allocate memory for vgm firmwares");
+ return;
+ }
+ submenu_reset(app->submenu_vgm_firmwares);
+ submenu_set_header(app->submenu_vgm_firmwares, "VGM Firmwares");
+ for (int i = 0; i < VGM_FIRMWARE_COUNT; i++)
+ {
+ submenu_add_item(app->submenu_vgm_firmwares, vgm_firmwares[i].name, FlipStoreSubmenuIndexStartVGMFirmwares + i, callback_submenu_choices, app);
+ }
+ view_dispatcher_switch_to_view(app->view_dispatcher, FlipStoreViewVGMFirmwares);
+ break;
+ case FlipStoreSubmenuIndexGitHub: // github
+ free_all_views(app, true);
+ if (!alloc_text_input_view(app, "Author"))
+ {
+ FURI_LOG_E(TAG, "Failed to allocate text input view");
+ return;
+ }
+ view_dispatcher_switch_to_view(app->view_dispatcher, FlipStoreViewTextInput);
+ break;
case FlipStoreSubmenuIndexAppListBluetooth:
+ free_all_views(app, true);
flip_store_category_index = 0;
flip_store_app_does_exist = false;
- flip_store_switch_to_app_list(app);
+ fetch_appropiate_app_list(app, 0);
break;
case FlipStoreSubmenuIndexAppListGames:
+ free_all_views(app, true);
flip_store_category_index = 1;
flip_store_app_does_exist = false;
- flip_store_switch_to_app_list(app);
+ fetch_appropiate_app_list(app, 0);
break;
case FlipStoreSubmenuIndexAppListGPIO:
+ free_all_views(app, true);
flip_store_category_index = 2;
flip_store_app_does_exist = false;
- flip_store_switch_to_app_list(app);
+ fetch_appropiate_app_list(app, 0);
break;
case FlipStoreSubmenuIndexAppListInfrared:
+ free_all_views(app, true);
flip_store_category_index = 3;
flip_store_app_does_exist = false;
- flip_store_switch_to_app_list(app);
+ fetch_appropiate_app_list(app, 0);
break;
case FlipStoreSubmenuIndexAppListiButton:
+ free_all_views(app, true);
flip_store_category_index = 4;
flip_store_app_does_exist = false;
- flip_store_switch_to_app_list(app);
+ fetch_appropiate_app_list(app, 0);
break;
case FlipStoreSubmenuIndexAppListMedia:
+ free_all_views(app, true);
flip_store_category_index = 5;
flip_store_app_does_exist = false;
- flip_store_switch_to_app_list(app);
+ fetch_appropiate_app_list(app, 0);
break;
case FlipStoreSubmenuIndexAppListNFC:
+ free_all_views(app, true);
flip_store_category_index = 6;
flip_store_app_does_exist = false;
- flip_store_switch_to_app_list(app);
+ fetch_appropiate_app_list(app, 0);
break;
case FlipStoreSubmenuIndexAppListRFID:
+ free_all_views(app, true);
flip_store_category_index = 7;
flip_store_app_does_exist = false;
- flip_store_switch_to_app_list(app);
+ fetch_appropiate_app_list(app, 0);
break;
case FlipStoreSubmenuIndexAppListSubGHz:
+ free_all_views(app, true);
flip_store_category_index = 8;
flip_store_app_does_exist = false;
- flip_store_switch_to_app_list(app);
+ fetch_appropiate_app_list(app, 0);
break;
case FlipStoreSubmenuIndexAppListTools:
+ free_all_views(app, true);
flip_store_category_index = 9;
flip_store_app_does_exist = false;
- flip_store_switch_to_app_list(app);
+ fetch_appropiate_app_list(app, 0);
break;
case FlipStoreSubmenuIndexAppListUSB:
+ free_all_views(app, true);
flip_store_category_index = 10;
flip_store_app_does_exist = false;
- flip_store_switch_to_app_list(app);
+ fetch_appropiate_app_list(app, 0);
break;
default:
- // Check if the index is within the firmwares list range
- if (index >= FlipStoreSubmenuIndexStartFirmwares && index < FlipStoreSubmenuIndexStartFirmwares + 3)
+ // Check if the index is within the ESP32 firmwares list range
+ if (index >= FlipStoreSubmenuIndexStartFirmwares && index < FlipStoreSubmenuIndexStartFirmwares + FIRMWARE_COUNT)
{
// Get the firmware index
uint32_t firmware_index = index - FlipStoreSubmenuIndexStartFirmwares;
@@ -671,27 +1150,59 @@ void callback_submenu_choices(void *context, uint32_t index)
{
// Get the firmware name
selected_firmware_index = firmware_index;
+ is_esp32_firmware = true;
+
+ // Switch to the firmware download view
+ free_dialog_firmware(app);
+ if (!alloc_dialog_firmware(app))
+ {
+ FURI_LOG_E(TAG, "Failed to allocate dialog firmware");
+ return;
+ }
+ view_dispatcher_switch_to_view(app->view_dispatcher, FlipStoreViewFirmwareDialog);
+ }
+ else
+ {
+ FURI_LOG_E(TAG, "Invalid firmware index");
+ easy_flipper_dialog("Error", "Issue parsing firmware.");
+ }
+ }
+ // Check if the index is within the VGM firmwares list range
+ else if (index >= FlipStoreSubmenuIndexStartVGMFirmwares && index < FlipStoreSubmenuIndexStartVGMFirmwares + VGM_FIRMWARE_COUNT)
+ {
+ // Get the firmware index
+ uint32_t firmware_index = index - FlipStoreSubmenuIndexStartVGMFirmwares;
+
+ // Check if the firmware index is valid
+ if ((int)firmware_index >= 0 && firmware_index < VGM_FIRMWARE_COUNT)
+ {
+ // Get the firmware name
+ selected_firmware_index = firmware_index;
+ is_esp32_firmware = false;
// Switch to the firmware download view
- dialog_ex_set_header(app->dialog_firmware, firmwares[firmware_index].name, 0, 0, AlignLeft, AlignTop);
+ free_dialog_firmware(app);
+ if (!alloc_dialog_firmware(app))
+ {
+ FURI_LOG_E(TAG, "Failed to allocate dialog firmware");
+ return;
+ }
view_dispatcher_switch_to_view(app->view_dispatcher, FlipStoreViewFirmwareDialog);
}
else
{
FURI_LOG_E(TAG, "Invalid firmware index");
- popup_set_header(app->popup, "[ERROR]", 0, 0, AlignLeft, AlignTop);
- popup_set_text(app->popup, "Issue parsing firmwarex", 0, 50, AlignLeft, AlignTop);
- view_dispatcher_switch_to_view(app->view_dispatcher, FlipStoreViewPopup);
+ easy_flipper_dialog("Error", "Issue parsing firmware.");
}
}
// Check if the index is within the app list range
- else if (index >= FlipStoreSubmenuIndexStartAppList && index < FlipStoreSubmenuIndexStartAppList + MAX_APP_COUNT)
+ else if (index >= FlipStoreSubmenuIndexStartAppList && index < FlipStoreSubmenuIndexStartAppList + flip_catalog->count)
{
// Get the app index
uint32_t app_index = index - FlipStoreSubmenuIndexStartAppList;
// Check if the app index is valid
- if ((int)app_index >= 0 && app_index < MAX_APP_COUNT)
+ if ((int)app_index >= 0 && app_index < flip_catalog->count)
{
// Get the app name
char *app_name = flip_catalog[app_index].app_name;
@@ -701,6 +1212,12 @@ void callback_submenu_choices(void *context, uint32_t index)
{
app_selected_index = app_index;
flip_store_app_does_exist = app_exists(flip_catalog[app_selected_index].app_id, categories[flip_store_category_index]);
+ free_app_info_view(app);
+ if (!alloc_app_info_view(app))
+ {
+ FURI_LOG_E(TAG, "Failed to allocate app info view");
+ return;
+ }
view_dispatcher_switch_to_view(app->view_dispatcher, FlipStoreViewAppInfo);
}
else
@@ -713,6 +1230,15 @@ void callback_submenu_choices(void *context, uint32_t index)
FURI_LOG_E(TAG, "Invalid app index");
}
}
+ // Check if the index is for loading the next set of apps
+ else if (index == FlipStoreSubmenuIndexStartAppList + flip_catalog->count)
+ {
+ catalog_iteration = flip_catalog->iteration + 8;
+ view_dispatcher_switch_to_view(app->view_dispatcher, FlipStoreViewWidgetResult);
+ free_category_submenu(app);
+ flip_catalog_free();
+ fetch_appropiate_app_list(app, catalog_iteration);
+ }
else
{
FURI_LOG_E(TAG, "Unknown submenu index");
@@ -721,6 +1247,55 @@ void callback_submenu_choices(void *context, uint32_t index)
}
}
+static void settings_item_selected(void *context, uint32_t index)
+{
+ FlipStoreApp *app = (FlipStoreApp *)context;
+ if (!app)
+ {
+ FURI_LOG_E(TAG, "FlipStoreApp is NULL");
+ return;
+ }
+ char ssid[64];
+ char pass[64];
+ switch (index)
+ {
+ case 0: // Input SSID
+ // Text Input
+ free_all_views(app, false);
+ if (!alloc_text_input_view(app, "SSID"))
+ {
+ FURI_LOG_E(TAG, "Failed to allocate text input view");
+ return;
+ }
+ // load SSID
+ if (load_settings(ssid, sizeof(ssid), pass, sizeof(pass)))
+ {
+ strncpy(app->uart_text_input_temp_buffer, ssid, app->uart_text_input_buffer_size - 1);
+ app->uart_text_input_temp_buffer[app->uart_text_input_buffer_size - 1] = '\0';
+ }
+ view_dispatcher_switch_to_view(app->view_dispatcher, FlipStoreViewTextInput);
+ break;
+ case 1: // Input Password
+ free_all_views(app, false);
+ if (!alloc_text_input_view(app, "Password"))
+ {
+ FURI_LOG_E(TAG, "Failed to allocate text input view");
+ return;
+ }
+ // load password
+ if (load_settings(ssid, sizeof(ssid), pass, sizeof(pass)))
+ {
+ strncpy(app->uart_text_input_temp_buffer, pass, app->uart_text_input_buffer_size - 1);
+ app->uart_text_input_temp_buffer[app->uart_text_input_buffer_size - 1] = '\0';
+ }
+ view_dispatcher_switch_to_view(app->view_dispatcher, FlipStoreViewTextInput);
+ break;
+ default:
+ FURI_LOG_E(TAG, "Unknown configuration item index");
+ break;
+ }
+}
+
static void flip_store_widget_set_text(char *message, Widget **widget)
{
if (widget == NULL)
@@ -823,6 +1398,50 @@ static void flip_store_widget_set_text(char *message, Widget **widget)
// Add the formatted message to the widget
widget_add_text_scroll_element(*widget, 0, 0, 128, 64, formatted_message);
}
+static void flip_store_request_error(Canvas *canvas, FlipperHTTP *fhttp)
+{
+ if (!canvas)
+ {
+ FURI_LOG_E(TAG, "Canvas is NULL");
+ return;
+ }
+ if (!fhttp)
+ {
+ FURI_LOG_E(TAG, "FlipperHTTP is NULL");
+ return;
+ }
+ 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
+ {
+ FURI_LOG_E(TAG, "Received an error: %s", fhttp->last_response);
+ canvas_draw_str(canvas, 0, 42, "Unusual error...");
+ canvas_draw_str(canvas, 0, 50, "Update your WiFi settings.");
+ canvas_draw_str(canvas, 0, 60, "Press BACK to return.");
+ }
+ }
+ else
+ {
+ canvas_clear(canvas);
+ canvas_draw_str(canvas, 0, 10, "[ERROR] Unknown error.");
+ canvas_draw_str(canvas, 0, 50, "Update your WiFi settings.");
+ canvas_draw_str(canvas, 0, 60, "Press BACK to return.");
+ }
+}
void flip_store_loader_draw_callback(Canvas *canvas, void *model)
{
@@ -832,8 +1451,8 @@ void flip_store_loader_draw_callback(Canvas *canvas, void *model)
return;
}
- SerialState http_state = fhttp.state;
DataLoaderModel *data_loader_model = (DataLoaderModel *)model;
+ SerialState http_state = data_loader_model->fhttp->state;
DataState data_state = data_loader_model->data_state;
char *title = data_loader_model->title;
@@ -841,7 +1460,7 @@ void flip_store_loader_draw_callback(Canvas *canvas, void *model)
if (http_state == INACTIVE)
{
- canvas_draw_str(canvas, 0, 7, "WiFi Dev Board disconnected.");
+ 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");
@@ -852,7 +1471,7 @@ void flip_store_loader_draw_callback(Canvas *canvas, void *model)
if (data_state == DataStateError || data_state == DataStateParseError)
{
- flip_store_request_error(canvas);
+ flip_store_request_error(canvas, data_loader_model->fhttp);
return;
}
@@ -902,7 +1521,14 @@ static void flip_store_loader_process_callback(void *context)
View *view = app->view_loader;
DataState current_data_state;
- with_view_model(view, DataLoaderModel * model, { current_data_state = model->data_state; }, false);
+ DataLoaderModel *loader_model = NULL;
+ with_view_model(view, DataLoaderModel * model, { current_data_state = model->data_state; loader_model = model; }, false);
+ if (!loader_model || !loader_model->fhttp)
+ {
+ FURI_LOG_E(TAG, "Model or fhttp is NULL");
+ DEV_CRASH();
+ return;
+ }
if (current_data_state == DataStateInitial)
{
@@ -920,7 +1546,7 @@ static void flip_store_loader_process_callback(void *context)
}
// Clear any previous responses
- strncpy(fhttp.last_response, "", 1);
+ strncpy(model->fhttp->last_response, "", 1);
bool request_status = fetch(model);
if (!request_status)
{
@@ -931,21 +1557,21 @@ static void flip_store_loader_process_callback(void *context)
}
else if (current_data_state == DataStateRequested || current_data_state == DataStateError)
{
- if (fhttp.state == IDLE && fhttp.last_response != NULL)
+ if (loader_model->fhttp->state == IDLE && loader_model->fhttp->last_response != NULL)
{
- if (strstr(fhttp.last_response, "[PONG]") != NULL)
+ if (strstr(loader_model->fhttp->last_response, "[PONG]") != NULL)
{
FURI_LOG_DEV(TAG, "PONG received.");
}
- else if (strncmp(fhttp.last_response, "[SUCCESS]", 9) == 0)
+ else if (strncmp(loader_model->fhttp->last_response, "[SUCCESS]", 9) == 0)
{
- FURI_LOG_DEV(TAG, "SUCCESS received. %s", fhttp.last_response ? fhttp.last_response : "NULL");
+ FURI_LOG_DEV(TAG, "SUCCESS received. %s", loader_model->fhttp->last_response ? loader_model->fhttp->last_response : "NULL");
}
- else if (strncmp(fhttp.last_response, "[ERROR]", 9) == 0)
+ else if (strncmp(loader_model->fhttp->last_response, "[ERROR]", 9) == 0)
{
- FURI_LOG_DEV(TAG, "ERROR received. %s", fhttp.last_response ? fhttp.last_response : "NULL");
+ FURI_LOG_DEV(TAG, "ERROR received. %s", loader_model->fhttp->last_response ? loader_model->fhttp->last_response : "NULL");
}
- else if (strlen(fhttp.last_response) == 0)
+ else if (strlen(loader_model->fhttp->last_response) == 0)
{
// Still waiting on response
}
@@ -954,21 +1580,21 @@ static void flip_store_loader_process_callback(void *context)
with_view_model(view, DataLoaderModel * model, { model->data_state = DataStateReceived; }, true);
}
}
- else if (fhttp.state == SENDING || fhttp.state == RECEIVING)
+ else if (loader_model->fhttp->state == SENDING || loader_model->fhttp->state == RECEIVING)
{
// continue waiting
}
- else if (fhttp.state == INACTIVE)
+ else if (loader_model->fhttp->state == INACTIVE)
{
// inactive. try again
}
- else if (fhttp.state == ISSUE)
+ else if (loader_model->fhttp->state == ISSUE)
{
with_view_model(view, DataLoaderModel * model, { model->data_state = DataStateError; }, true);
}
else
{
- FURI_LOG_DEV(TAG, "Unexpected state: %d lastresp: %s", fhttp.state, fhttp.last_response ? fhttp.last_response : "NULL");
+ FURI_LOG_DEV(TAG, "Unexpected state: %d lastresp: %s", loader_model->fhttp->state, loader_model->fhttp->last_response ? loader_model->fhttp->last_response : "NULL");
DEV_CRASH();
}
}
@@ -989,7 +1615,7 @@ static void flip_store_loader_process_callback(void *context)
{
data_text = model->parser(model);
}
- FURI_LOG_DEV(TAG, "Parsed data: %s\r\ntext: %s", fhttp.last_response ? fhttp.last_response : "NULL", data_text ? data_text : "NULL");
+ FURI_LOG_DEV(TAG, "Parsed data: %s\r\ntext: %s", model->fhttp->last_response ? model->fhttp->last_response : "NULL", data_text ? data_text : "NULL");
model->data_text = data_text;
if (data_text == NULL)
{
@@ -1014,7 +1640,7 @@ static void flip_store_loader_process_callback(void *context)
}
else
{
- flip_store_widget_set_text(model->data_text != NULL ? model->data_text : "Empty result", &app->widget_result);
+ flip_store_widget_set_text(model->data_text != NULL ? model->data_text : "", &app->widget_result);
if (model->data_text != NULL)
{
free(model->data_text);
@@ -1118,8 +1744,14 @@ void flip_store_loader_free_model(View *view)
}
if (model->parser_context)
{
- free(model->parser_context);
- model->parser_context = NULL;
+ // do not free the context here, it is the app context
+ // free(model->parser_context);
+ // model->parser_context = NULL;
+ }
+ if (model->fhttp)
+ {
+ flipper_http_free(model->fhttp);
+ model->fhttp = NULL;
}
},
false);
@@ -1174,6 +1806,12 @@ void flip_store_generic_switch_to_view(FlipStoreApp *app, char *title, DataLoade
model->back_callback = back;
model->data_state = DataStateInitial;
model->data_text = NULL;
+ //
+ model->parser_context = app;
+ if (!model->fhttp)
+ {
+ model->fhttp = flipper_http_alloc();
+ }
},
true);
diff --git a/flip_store/callback/flip_store_callback.h b/flip_store/callback/flip_store_callback.h
index 68b2d0d41..1d40d6b2d 100644
--- a/flip_store/callback/flip_store_callback.h
+++ b/flip_store/callback/flip_store_callback.h
@@ -15,35 +15,20 @@
extern bool flip_store_app_does_exist;
extern uint32_t selected_firmware_index;
-// Function to draw the description on the canvas with word wrapping
-void draw_description(Canvas *canvas, const char *user_message, int x, int y);
-
-void flip_store_view_draw_callback_app_list(Canvas *canvas, void *model);
-
-bool flip_store_input_callback(InputEvent *event, void *context);
-
-void flip_store_text_updated_ssid(void *context);
-
-void flip_store_text_updated_pass(void *context);
-
uint32_t callback_to_submenu(void *context);
uint32_t callback_to_submenu_options(void *context);
uint32_t callback_to_firmware_list(void *context);
+uint32_t callback_to_vgm_firmware_list(void *context);
uint32_t callback_to_app_list(void *context);
-void settings_item_selected(void *context, uint32_t index);
-
-void dialog_delete_callback(DialogExResult result, void *context);
-void dialog_firmware_callback(DialogExResult result, void *context);
-
-void popup_callback(void *context);
-
uint32_t callback_exit_app(void *context);
void callback_submenu_choices(void *context, uint32_t index);
+void free_all_views(FlipStoreApp *app, bool should_free_variable_item_list);
+
// Add edits by Derek Jamison
typedef enum DataState DataState;
enum DataState
@@ -77,6 +62,7 @@ struct DataLoaderModel
size_t request_count;
ViewNavigationCallback back_callback;
FuriTimer *timer;
+ FlipperHTTP *fhttp;
};
void flip_store_generic_switch_to_view(FlipStoreApp *app, char *title, DataLoaderFetch fetcher, DataLoaderParser parser, size_t request_count, ViewNavigationCallback back, uint32_t view_id);
diff --git a/flip_store/easy_flipper/easy_flipper.c b/flip_store/easy_flipper/easy_flipper.c
index f61b7d231..c2ce949e4 100644
--- a/flip_store/easy_flipper/easy_flipper.c
+++ b/flip_store/easy_flipper/easy_flipper.c
@@ -1,5 +1,25 @@
#include
+void easy_flipper_dialog(
+ char *header,
+ char *text)
+{
+ DialogsApp *dialogs = furi_record_open(RECORD_DIALOGS);
+ DialogMessage *message = dialog_message_alloc();
+ dialog_message_set_header(
+ message, header, 64, 0, AlignCenter, AlignTop);
+ dialog_message_set_text(
+ message,
+ text,
+ 0,
+ 63,
+ AlignLeft,
+ AlignBottom);
+ dialog_message_show(dialogs, message);
+ dialog_message_free(message);
+ furi_record_close(RECORD_DIALOGS);
+}
+
/**
* @brief Navigation callback for exiting the application
* @param context The context - unused
diff --git a/flip_store/easy_flipper/easy_flipper.h b/flip_store/easy_flipper/easy_flipper.h
index 3ccfe66b8..875cca896 100644
--- a/flip_store/easy_flipper/easy_flipper.h
+++ b/flip_store/easy_flipper/easy_flipper.h
@@ -8,6 +8,7 @@
#include
#include
#include
+#include
#include
#include
#include
@@ -15,6 +16,8 @@
#include
#include
#include
+#include
+#include
#include
#include
#include
@@ -23,6 +26,10 @@
#define EASY_TAG "EasyFlipper"
+void easy_flipper_dialog(
+ char *header,
+ char *text);
+
/**
* @brief Navigation callback for exiting the application
* @param context The context - unused
diff --git a/flip_store/firmwares/flip_store_firmwares.c b/flip_store/firmwares/flip_store_firmwares.c
index 5977d2a52..1b089414b 100644
--- a/flip_store/firmwares/flip_store_firmwares.c
+++ b/flip_store/firmwares/flip_store_firmwares.c
@@ -1,17 +1,8 @@
#include
Firmware *firmwares = NULL;
-bool sent_firmware_request = false;
-bool sent_firmware_request_2 = false;
-bool sent_firmware_request_3 = false;
-//
-bool firmware_request_success = false;
-bool firmware_request_success_2 = false;
-bool firmware_request_success_3 = false;
-//
-bool firmware_download_success = false;
-bool firmware_download_success_2 = false;
-bool firmware_download_success_3 = false;
+VGMFirmware *vgm_firmwares = NULL;
+bool is_esp32_firmware = true;
Firmware *firmware_alloc()
{
@@ -21,93 +12,95 @@ Firmware *firmware_alloc()
FURI_LOG_E(TAG, "Failed to allocate memory for Firmware");
return NULL;
}
- for (int i = 0; i < FIRMWARE_COUNT; i++)
- {
- if (fw[i].name == NULL)
- {
- fw[i].name = (char *)malloc(16);
- if (!fw[i].name)
- {
- FURI_LOG_E(TAG, "Failed to allocate memory for Firmware name");
- return NULL;
- }
- }
- for (int j = 0; j < FIRMWARE_LINKS; j++)
- {
- if (fw[i].links[j] == NULL)
- {
- fw[i].links[j] = (char *)malloc(256);
- if (!fw[i].links[j])
- {
- FURI_LOG_E(TAG, "Failed to allocate memory for Firmware links");
- return NULL;
- }
- }
- }
- }
// Black Magic
- fw[0].name = "Black Magic";
- fw[0].links[0] = "https://raw.githubusercontent.com/FZEEFlasher/fzeeflasher.github.io/main/resources/STATIC/BM/bootloader.bin";
- fw[0].links[1] = "https://raw.githubusercontent.com/FZEEFlasher/fzeeflasher.github.io/main/resources/STATIC/BM/partition-table.bin";
- fw[0].links[2] = "https://raw.githubusercontent.com/FZEEFlasher/fzeeflasher.github.io/main/resources/STATIC/BM/blackmagic.bin";
+ snprintf(fw[0].name, sizeof(fw[0].name), "%s", "Black Magic");
+ snprintf(fw[0].links[0], sizeof(fw[0].links[0]), "%s", "https://raw.githubusercontent.com/FZEEFlasher/fzeeflasher.github.io/main/resources/STATIC/BM/bootloader.bin");
+ snprintf(fw[0].links[1], sizeof(fw[0].links[1]), "%s", "https://raw.githubusercontent.com/FZEEFlasher/fzeeflasher.github.io/main/resources/STATIC/BM/partition-table.bin");
+ snprintf(fw[0].links[2], sizeof(fw[0].links[2]), "%s", "https://raw.githubusercontent.com/FZEEFlasher/fzeeflasher.github.io/main/resources/STATIC/BM/blackmagic.bin");
// FlipperHTTP
- fw[1].name = "FlipperHTTP";
- fw[1].links[0] = "https://raw.githubusercontent.com/jblanked/FlipperHTTP/main/WiFi%20Developer%20Board%20(ESP32S2)/flipper_http_bootloader.bin";
- fw[1].links[1] = "https://raw.githubusercontent.com/jblanked/FlipperHTTP/main/WiFi%20Developer%20Board%20(ESP32S2)/flipper_http_firmware_a.bin";
- fw[1].links[2] = "https://raw.githubusercontent.com/jblanked/FlipperHTTP/main/WiFi%20Developer%20Board%20(ESP32S2)/flipper_http_partitions.bin";
+ snprintf(fw[1].name, sizeof(fw[1].name), "%s", "FlipperHTTP");
+ snprintf(fw[1].links[0], sizeof(fw[1].links[0]), "%s", "https://raw.githubusercontent.com/jblanked/FlipperHTTP/main/WiFi%20Developer%20Board%20(ESP32S2)/flipper_http_bootloader.bin");
+ snprintf(fw[1].links[1], sizeof(fw[1].links[1]), "%s", "https://raw.githubusercontent.com/jblanked/FlipperHTTP/main/WiFi%20Developer%20Board%20(ESP32S2)/flipper_http_firmware_a.bin");
+ snprintf(fw[1].links[2], sizeof(fw[1].links[2]), "%s", "https://raw.githubusercontent.com/jblanked/FlipperHTTP/main/WiFi%20Developer%20Board%20(ESP32S2)/flipper_http_partitions.bin");
// Marauder
- fw[2].name = "Marauder";
- fw[2].links[0] = "https://raw.githubusercontent.com/FZEEFlasher/fzeeflasher.github.io/main/resources/STATIC/M/FLIPDEV/esp32_marauder.ino.bootloader.bin";
- fw[2].links[1] = "https://raw.githubusercontent.com/FZEEFlasher/fzeeflasher.github.io/main/resources/STATIC/M/FLIPDEV/esp32_marauder.ino.partitions.bin";
- fw[2].links[2] = "https://raw.githubusercontent.com/FZEEFlasher/fzeeflasher.github.io/main/resources/CURRENT/esp32_marauder_v1_1_0_20241128_flipper.bin";
+ snprintf(fw[2].name, sizeof(fw[2].name), "%s", "Marauder");
+ snprintf(fw[2].links[0], sizeof(fw[2].links[0]), "%s", "https://raw.githubusercontent.com/FZEEFlasher/fzeeflasher.github.io/main/resources/STATIC/M/FLIPDEV/esp32_marauder.ino.bootloader.bin");
+ snprintf(fw[2].links[1], sizeof(fw[2].links[1]), "%s", "https://raw.githubusercontent.com/FZEEFlasher/fzeeflasher.github.io/main/resources/STATIC/M/FLIPDEV/esp32_marauder.ino.partitions.bin");
+ snprintf(fw[2].links[2], sizeof(fw[2].links[2]), "%s", "https://raw.githubusercontent.com/FZEEFlasher/fzeeflasher.github.io/main/resources/CURRENT/esp32_marauder_v1_2_0_12192024_flipper.bin");
+
+ return fw;
+}
- // https://api.github.com/repos/FZEEFlasher/fzeeflasher.github.io/contents/resources/STATIC/BM/bootloader.bin
- // https://api.github.com/repos/FZEEFlasher/fzeeflasher.github.io/contents/resources/STATIC/BM/partition-table.bin
- // https://api.github.com/repos/FZEEFlasher/fzeeflasher.github.io/contents/resources/STATIC/BM/blackmagic.bin
+VGMFirmware *vgm_firmware_alloc()
+{
+ VGMFirmware *fw = (VGMFirmware *)malloc(VGM_FIRMWARE_COUNT * sizeof(VGMFirmware));
+ if (!fw)
+ {
+ FURI_LOG_E(TAG, "Failed to allocate memory for VGM Firmware");
+ return NULL;
+ }
- // https://api.github.com/repos/jblanked/FlipperHTTP/contents/flipper_http_bootloader.bin
- // https://api.github.com/repos/jblanked/FlipperHTTP/contents/flipper_http_firmware_a.bin
- // https://api.github.com/repos/jblanked/FlipperHTTP/contents/flipper_http_partitions.bin
+ // FlipperHTTP
+ snprintf(fw[0].name, sizeof(fw[0].name), "%s", "FlipperHTTP");
+ snprintf(fw[0].link, sizeof(fw[0].link), "%s", "https://raw.githubusercontent.com/jblanked/FlipperHTTP/main/Video%20Game%20Module/MicroPython/flipper_http_vgm_micro_python.uf2");
- // https://api.github.com/repos/FZEEFlasher/fzeeflasher.github.io/contents/resources/STATIC/M/FLIPDEV/esp32_marauder.ino.bootloader.bin
- // https://api.github.com/repos/FZEEFlasher/fzeeflasher.github.io/contents/resources/STATIC/M/FLIPDEV/esp32_marauder.ino.partitions.bin
- // https://api.github.com/repos/FZEEFlasher/fzeeflasher.github.io/contents/resources/CURRENT/esp32_marauder_v1_0_0_20240626_flipper.bin
return fw;
}
+
void firmware_free()
{
if (firmwares)
{
free(firmwares);
+ firmwares = NULL;
+ }
+}
+void vgm_firmware_free()
+{
+ if (vgm_firmwares)
+ {
+ free(vgm_firmwares);
+ vgm_firmwares = NULL;
}
}
-bool flip_store_get_firmware_file(char *link, char *name, char *filename)
+bool flip_store_get_firmware_file(FlipperHTTP *fhttp, char *link, char *name, char *filename)
{
- if (fhttp.state == INACTIVE)
+ if (!fhttp)
{
+ FURI_LOG_E(TAG, "FlipperHTTP is NULL");
return false;
}
+ if (fhttp->state == INACTIVE)
+ {
+ return false;
+ }
+
Storage *storage = furi_record_open(RECORD_STORAGE);
+
char directory_path[64];
- snprintf(directory_path, sizeof(directory_path), STORAGE_EXT_PATH_PREFIX "/apps_data/esp_flasher");
- storage_common_mkdir(storage, directory_path);
- snprintf(directory_path, sizeof(directory_path), STORAGE_EXT_PATH_PREFIX "/apps_data/esp_flasher/%s", firmwares[selected_firmware_index].name);
- storage_common_mkdir(storage, directory_path);
- snprintf(fhttp.file_path, sizeof(fhttp.file_path), STORAGE_EXT_PATH_PREFIX "/apps_data/esp_flasher/%s/%s", name, filename);
- fhttp.save_received_data = false;
- fhttp.is_bytes_request = true;
- char *headers = jsmn("Content-Type", "application/octet-stream");
- bool sent_request = flipper_http_get_request_bytes(link, headers);
- free(headers);
- if (sent_request)
+ // save in ESP32 flasher directory
+ if (is_esp32_firmware)
+ {
+ snprintf(directory_path, sizeof(directory_path), STORAGE_EXT_PATH_PREFIX "/apps_data/esp_flasher");
+ storage_common_mkdir(storage, directory_path);
+ snprintf(directory_path, sizeof(directory_path), STORAGE_EXT_PATH_PREFIX "/apps_data/esp_flasher/%s", firmwares[selected_firmware_index].name);
+ storage_common_mkdir(storage, directory_path);
+ snprintf(fhttp->file_path, sizeof(fhttp->file_path), STORAGE_EXT_PATH_PREFIX "/apps_data/esp_flasher/%s/%s", name, filename);
+ }
+ else // install in app_data directory
{
- fhttp.state = RECEIVING;
- return true;
+ snprintf(directory_path, sizeof(directory_path), STORAGE_EXT_PATH_PREFIX "/apps_data/vgm");
+ storage_common_mkdir(storage, directory_path);
+ snprintf(directory_path, sizeof(directory_path), STORAGE_EXT_PATH_PREFIX "/apps_data/vgm/%s", name);
+ storage_common_mkdir(storage, directory_path);
+ snprintf(fhttp->file_path, sizeof(fhttp->file_path), STORAGE_EXT_PATH_PREFIX "/apps_data/vgm/%s/%s", name, filename);
}
- fhttp.state = ISSUE;
- return false;
+ furi_record_close(RECORD_STORAGE);
+ fhttp->save_received_data = false;
+ fhttp->is_bytes_request = true;
+ return flipper_http_get_request_bytes(fhttp, link, "{\"Content-Type\":\"application/octet-stream\"}");
}
diff --git a/flip_store/firmwares/flip_store_firmwares.h b/flip_store/firmwares/flip_store_firmwares.h
index 923c07c21..6b5770fb5 100644
--- a/flip_store/firmwares/flip_store_firmwares.h
+++ b/flip_store/firmwares/flip_store_firmwares.h
@@ -7,25 +7,25 @@
typedef struct
{
- char *name;
- char *links[FIRMWARE_LINKS];
+ char name[16];
+ char links[FIRMWARE_LINKS][256];
} Firmware;
+typedef struct
+{
+ char name[16];
+ char link[256];
+} VGMFirmware;
+
extern Firmware *firmwares;
+extern VGMFirmware *vgm_firmwares;
Firmware *firmware_alloc();
+VGMFirmware *vgm_firmware_alloc();
void firmware_free();
+void vgm_firmware_free();
+extern bool is_esp32_firmware;
// download and waiting process
-bool flip_store_get_firmware_file(char *link, char *name, char *filename);
-
-extern bool sent_firmware_request;
-extern bool sent_firmware_request_2;
-extern bool sent_firmware_request_3;
-extern bool firmware_request_success;
-extern bool firmware_request_success_2;
-extern bool firmware_request_success_3;
-extern bool firmware_download_success;
-extern bool firmware_download_success_2;
-extern bool firmware_download_success_3;
+bool flip_store_get_firmware_file(FlipperHTTP *fhttp, char *link, char *name, char *filename);
#endif // FLIP_STORE_FIRMWARES_H
\ No newline at end of file
diff --git a/flip_store/flip_storage/flip_store_storage.c b/flip_store/flip_storage/flip_store_storage.c
index 4e382b786..1db29732b 100644
--- a/flip_store/flip_storage/flip_store_storage.c
+++ b/flip_store/flip_storage/flip_store_storage.c
@@ -315,4 +315,132 @@ bool parse_json_incrementally(const char *file_path, const char *target_key, cha
furi_record_close(RECORD_STORAGE);
}
return false;
+}
+
+bool save_char(
+ const char *path_name, const char *value)
+{
+ if (!value)
+ {
+ return false;
+ }
+ // Create the directory for saving settings
+ char directory_path[256];
+ snprintf(directory_path, sizeof(directory_path), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_store/data");
+
+ // 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);
+ char file_path[256];
+ snprintf(file_path, sizeof(file_path), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_store/data/%s.txt", path_name);
+
+ // 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;
+ }
+
+ // Write the data to the file
+ size_t data_size = strlen(value) + 1; // Include null terminator
+ if (storage_file_write(file, value, 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;
+}
+
+bool load_char(
+ const char *path_name,
+ char *value,
+ size_t value_size)
+{
+ if (!value)
+ {
+ return false;
+ }
+ Storage *storage = furi_record_open(RECORD_STORAGE);
+ File *file = storage_file_alloc(storage);
+
+ char file_path[256];
+ snprintf(file_path, sizeof(file_path), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_store/data/%s.txt", path_name);
+
+ // 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
+ }
+
+ // Read data into the buffer
+ size_t read_count = storage_file_read(file, value, value_size);
+ if (storage_file_get_error(file) != FSE_OK)
+ {
+ FURI_LOG_E(HTTP_TAG, "Error reading from file.");
+ storage_file_close(file);
+ storage_file_free(file);
+ furi_record_close(RECORD_STORAGE);
+ return false;
+ }
+
+ // Ensure null-termination
+ value[read_count - 1] = '\0';
+
+ storage_file_close(file);
+ storage_file_free(file);
+ furi_record_close(RECORD_STORAGE);
+
+ return true;
+}
+
+bool save_char_with_path(const char *full_path, const char *value)
+{
+ if (!value)
+ {
+ return false;
+ }
+
+ Storage *storage = furi_record_open(RECORD_STORAGE);
+ File *file = storage_file_alloc(storage);
+
+ // Open the file in write mode
+ if (!storage_file_open(file, full_path, FSAM_WRITE, FSOM_CREATE_ALWAYS))
+ {
+ FURI_LOG_E(HTTP_TAG, "Failed to open file for writing: %s", full_path);
+ storage_file_free(file);
+ furi_record_close(RECORD_STORAGE);
+ return false;
+ }
+
+ // Write the data to the file
+ size_t data_size = strlen(value) + 1; // Include null terminator
+ if (storage_file_write(file, value, 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;
}
\ No newline at end of file
diff --git a/flip_store/flip_storage/flip_store_storage.h b/flip_store/flip_storage/flip_store_storage.h
index d62443507..bf27ae374 100644
--- a/flip_store/flip_storage/flip_store_storage.h
+++ b/flip_store/flip_storage/flip_store_storage.h
@@ -27,4 +27,14 @@ bool app_exists(const char *app_id, const char *app_category);
// Function to parse JSON incrementally from a file
bool parse_json_incrementally(const char *file_path, const char *target_key, char *value_buffer, size_t value_buffer_size);
+bool save_char(
+ const char *path_name, const char *value);
+
+bool load_char(
+ const char *path_name,
+ char *value,
+ size_t value_size);
+
+bool save_char_with_path(const char *full_path, const char *value);
+
#endif
\ No newline at end of file
diff --git a/flip_store/flip_store.c b/flip_store/flip_store.c
index 37cc03e26..17728615d 100644
--- a/flip_store/flip_store.c
+++ b/flip_store/flip_store.c
@@ -1,7 +1,5 @@
#include
-
-void flip_store_loader_free_model(View *view);
-FlipStoreApp *app_instance = NULL;
+#include
// Function to free the resources used by FlipStoreApp
void flip_store_app_free(FlipStoreApp *app)
@@ -27,12 +25,6 @@ void flip_store_app_free(FlipStoreApp *app)
view_free(app->view_loader);
}
- if (app->view_app_info)
- {
- view_dispatcher_remove_view(app->view_dispatcher, FlipStoreViewAppInfo);
- view_free(app->view_app_info);
- }
-
// Free Submenu(s)
if (app->submenu_main)
{
@@ -54,151 +46,22 @@ void flip_store_app_free(FlipStoreApp *app)
view_dispatcher_remove_view(app->view_dispatcher, FlipStoreViewFirmwares);
submenu_free(app->submenu_firmwares);
}
- if (app->submenu_app_list_bluetooth)
- {
- view_dispatcher_remove_view(app->view_dispatcher, FlipStoreViewAppListBluetooth);
- submenu_free(app->submenu_app_list_bluetooth);
- }
- if (app->submenu_app_list_games)
- {
- view_dispatcher_remove_view(app->view_dispatcher, FlipStoreViewAppListGames);
- submenu_free(app->submenu_app_list_games);
- }
- if (app->submenu_app_list_gpio)
- {
- view_dispatcher_remove_view(app->view_dispatcher, FlipStoreViewAppListGPIO);
- submenu_free(app->submenu_app_list_gpio);
- }
- if (app->submenu_app_list_infrared)
- {
- view_dispatcher_remove_view(app->view_dispatcher, FlipStoreViewAppListInfrared);
- submenu_free(app->submenu_app_list_infrared);
- }
- if (app->submenu_app_list_ibutton)
- {
- view_dispatcher_remove_view(app->view_dispatcher, FlipStoreViewAppListiButton);
- submenu_free(app->submenu_app_list_ibutton);
- }
- if (app->submenu_app_list_media)
- {
- view_dispatcher_remove_view(app->view_dispatcher, FlipStoreViewAppListMedia);
- submenu_free(app->submenu_app_list_media);
- }
- if (app->submenu_app_list_nfc)
- {
- view_dispatcher_remove_view(app->view_dispatcher, FlipStoreViewAppListNFC);
- submenu_free(app->submenu_app_list_nfc);
- }
- if (app->submenu_app_list_rfid)
- {
- view_dispatcher_remove_view(app->view_dispatcher, FlipStoreViewAppListRFID);
- submenu_free(app->submenu_app_list_rfid);
- }
- if (app->submenu_app_list_subghz)
- {
- view_dispatcher_remove_view(app->view_dispatcher, FlipStoreViewAppListSubGHz);
- submenu_free(app->submenu_app_list_subghz);
- }
- if (app->submenu_app_list_tools)
- {
- view_dispatcher_remove_view(app->view_dispatcher, FlipStoreViewAppListTools);
- submenu_free(app->submenu_app_list_tools);
- }
- if (app->submenu_app_list_usb)
- {
- view_dispatcher_remove_view(app->view_dispatcher, FlipStoreViewAppListUSB);
- submenu_free(app->submenu_app_list_usb);
- }
-
- // Free Widget(s)
- if (app->widget)
+ if (app->submenu_vgm_firmwares)
{
- view_dispatcher_remove_view(app->view_dispatcher, FlipStoreViewAbout);
- widget_free(app->widget);
+ view_dispatcher_remove_view(app->view_dispatcher, FlipStoreViewVGMFirmwares);
+ submenu_free(app->submenu_vgm_firmwares);
}
- // Free Variable Item List(s)
- if (app->variable_item_list)
- {
- view_dispatcher_remove_view(app->view_dispatcher, FlipStoreViewSettings);
- variable_item_list_free(app->variable_item_list);
- }
-
- // Free Text Input(s)
- if (app->uart_text_input_ssid)
- {
- view_dispatcher_remove_view(app->view_dispatcher, FlipStoreViewTextInputSSID);
- text_input_free(app->uart_text_input_ssid);
- }
- if (app->uart_text_input_pass)
- {
- view_dispatcher_remove_view(app->view_dispatcher, FlipStoreViewTextInputPass);
- text_input_free(app->uart_text_input_pass);
- }
-
- // Free popup
- if (app->popup)
- {
- view_dispatcher_remove_view(app->view_dispatcher, FlipStoreViewPopup);
- popup_free(app->popup);
- }
-
- // Free dialog
- if (app->dialog_delete)
- {
- view_dispatcher_remove_view(app->view_dispatcher, FlipStoreViewAppDelete);
- dialog_ex_free(app->dialog_delete);
- }
- if (app->dialog_firmware)
- {
- view_dispatcher_remove_view(app->view_dispatcher, FlipStoreViewFirmwareDialog);
- dialog_ex_free(app->dialog_firmware);
- }
-
- // deinitalize flipper http
- flipper_http_deinit();
+ free_all_views(app, true);
// free the view dispatcher
- view_dispatcher_free(app->view_dispatcher);
+ if (app->view_dispatcher)
+ view_dispatcher_free(app->view_dispatcher);
// close the gui
furi_record_close(RECORD_GUI);
// free the app
- free(app);
-}
-
-void flip_store_request_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
- {
- FURI_LOG_E(TAG, "Received an error: %s", fhttp.last_response);
- canvas_draw_str(canvas, 0, 42, "Unusual error...");
- canvas_draw_str(canvas, 0, 50, "Update your WiFi settings.");
- canvas_draw_str(canvas, 0, 60, "Press BACK to return.");
- }
- }
- else
- {
- canvas_clear(canvas);
- canvas_draw_str(canvas, 0, 10, "[ERROR] Unknown error.");
- canvas_draw_str(canvas, 0, 50, "Update your WiFi settings.");
- canvas_draw_str(canvas, 0, 60, "Press BACK to return.");
- }
+ if (app)
+ free(app);
}
\ No newline at end of file
diff --git a/flip_store/flip_store.h b/flip_store/flip_store.h
index 6e73fc443..84ce56c2f 100644
--- a/flip_store/flip_store.h
+++ b/flip_store/flip_store.h
@@ -11,11 +11,20 @@
#include
#include
#include
+#include
#include
+
#define TAG "FlipStore"
+#define VERSION_TAG "FlipStore v0.8"
+
#define FIRMWARE_COUNT 3
#define FIRMWARE_LINKS 3
+#define VGM_FIRMWARE_COUNT 1
+#define VGM_FIRMWARE_LINKS 1
+
+#define MAX_GITHUB_FILES 30
+
// Define the submenu items for our FlipStore application
typedef enum
{
@@ -25,8 +34,10 @@ typedef enum
//
FlipStoreSubmenuIndexOptions, // Click to view the options
//
- FlipStoreSubmenuIndexAppList,
- FlipStoreSubmenuIndexFirmwares,
+ FlipStoreSubmenuIndexAppList, // Click to view the app list
+ FlipStoreSubmenuIndexFirmwares, // Click to view the ESP32 firmwares
+ FlipStoreSubmenuIndexVGMFirmwares, // Click to view the VGM firmwares
+ FlipStoreSubmenuIndexGitHub, // Click to view the GitHub repository view
//
FlipStoreSubmenuIndexAppListBluetooth,
FlipStoreSubmenuIndexAppListGames,
@@ -40,7 +51,8 @@ typedef enum
FlipStoreSubmenuIndexAppListTools,
FlipStoreSubmenuIndexAppListUSB,
//
- FlipStoreSubmenuIndexStartFirmwares,
+ FlipStoreSubmenuIndexStartFirmwares = 50,
+ FlipStoreSubmenuIndexStartVGMFirmwares = 60,
//
FlipStoreSubmenuIndexStartAppList = 100,
} FlipStoreSubmenuIndex;
@@ -49,39 +61,29 @@ typedef enum
typedef enum
{
//
- FlipStoreViewSubmenu, // The submenu
- FlipStoreViewSubmenuOptions, // The submenu options
- //
- FlipStoreViewAbout, // The about screen
- FlipStoreViewSettings, // The settings screen
- FlipStoreViewTextInputSSID, // The text input screen for SSID
- FlipStoreViewTextInputPass, // The text input screen for password
- //
- FlipStoreViewPopup, // The popup screen
- //
- FlipStoreViewAppList, // The app list screen
- FlipStoreViewFirmwares, // The firmwares screen (submenu)
- FlipStoreViewFirmwareDialog, // The firmware view (DialogEx) of the selected firmware
- //
- FlipStoreViewAppInfo, // The app info screen (widget) of the selected app
- FlipStoreViewAppDownload, // The app download screen (widget) of the selected app
- FlipStoreViewAppDelete, // The app delete screen (DialogEx) of the selected app
- //
- FlipStoreViewAppListBluetooth, // the app list screen for Bluetooth
- FlipStoreViewAppListGames, // the app list screen for Games
- FlipStoreViewAppListGPIO, // the app list screen for GPIO
- FlipStoreViewAppListInfrared, // the app list screen for Infrared
- FlipStoreViewAppListiButton, // the app list screen for iButton
- FlipStoreViewAppListMedia, // the app list screen for Media
- FlipStoreViewAppListNFC, // the app list screen for NFC
- FlipStoreViewAppListRFID, // the app list screen for RFID
- FlipStoreViewAppListSubGHz, // the app list screen for Sub-GHz
- FlipStoreViewAppListTools, // the app list screen for Tools
- FlipStoreViewAppListUSB, // the app list screen for USB
- //
- //
- FlipStoreViewWidgetResult, // The text box that displays the random fact
- FlipStoreViewLoader, // The loader screen retrieves data from the internet
+ FlipStoreViewSubmenu, // The submenu
+ FlipStoreViewSubmenuOptions, // The submenu options
+ //
+ FlipStoreViewAbout, // The about screen
+ FlipStoreViewSettings, // The settings screen
+ FlipStoreViewTextInput, // The text input screen
+ //
+ FlipStoreViewAppList, // The app list screen
+ FlipStoreViewFirmwares, // The firmwares screen (submenu)
+ FlipStoreViewVGMFirmwares, // The VGM firmwares screen (submenu)
+ FlipStoreViewAGithub, // The GitHub repository screen
+ //
+ FlipStoreViewFirmwareDialog, // The firmware view (DialogEx) of the selected firmware
+ //
+ FlipStoreViewAppInfo, // The app info screen (widget) of the selected app
+ FlipStoreViewAppDownload, // The app download screen (widget) of the selected app
+ FlipStoreViewAppDelete, // The app delete screen (DialogEx) of the selected app
+ //
+ FlipStoreViewAppListCategory, // the app list screen for each category
+ //
+ //
+ FlipStoreViewWidgetResult, // The text box that displays the random fact
+ FlipStoreViewLoader, // The loader screen retrieves data from the internet
} FlipStoreView;
// Each screen will have its own view
@@ -93,48 +95,26 @@ typedef struct
ViewDispatcher *view_dispatcher; // Switches between our views
View *view_app_info; // The app info screen (view) of the selected app
//
- DialogEx *dialog_firmware; // The dialog for installing a firmware
- View *view_firmware_download; // The firmware download screen (view) of the selected firmware
+ DialogEx *dialog_firmware; // The dialog for installing a firmware
//
Submenu *submenu_main; // The submenu (main)
//
- Submenu *submenu_options; // The submenu (options)
- Submenu *submenu_app_list; // The submenu (app list) for the selected category
- Submenu *submenu_firmwares; // The submenu (firmwares)
- //
- Submenu *submenu_app_list_bluetooth; // The submenu (app list) for Bluetooth
- Submenu *submenu_app_list_games; // The submenu (app list) for Games
- Submenu *submenu_app_list_gpio; // The submenu (app list) for GPIO
- Submenu *submenu_app_list_infrared; // The submenu (app list) for Infrared
- Submenu *submenu_app_list_ibutton; // The submenu (app list) for iButton
- Submenu *submenu_app_list_media; // The submenu (app list) for Media
- Submenu *submenu_app_list_nfc; // The submenu (app list) for NFC
- Submenu *submenu_app_list_rfid; // The submenu (app list) for RFID
- Submenu *submenu_app_list_subghz; // The submenu (app list) for Sub-GHz
- Submenu *submenu_app_list_tools; // The submenu (app list) for Tools
- Submenu *submenu_app_list_usb; // The submenu (app list) for USB
- //
- Widget *widget; // The widget
- Popup *popup; // The popup
- DialogEx *dialog_delete; // The dialog for deleting an app
+ Submenu *submenu_options; // The submenu (options)
+ Submenu *submenu_app_list; // The submenu (app list) for the selected category
+ Submenu *submenu_firmwares; // The submenu (firmwares)
+ Submenu *submenu_vgm_firmwares; // The submenu (VGM firmwares)
+ Submenu *submenu_app_list_category; // The submenu (app list) for each category
+ Widget *widget_about; // The widget
VariableItemList *variable_item_list; // The variable item list (settngs)
VariableItem *variable_item_ssid; // The variable item
VariableItem *variable_item_pass; // The variable item
- TextInput *uart_text_input_ssid; // The text input
- TextInput *uart_text_input_pass; // The text input
-
- char *uart_text_input_buffer_ssid; // Buffer for the text input
- char *uart_text_input_temp_buffer_ssid; // Temporary buffer for the text input
- uint32_t uart_text_input_buffer_size_ssid; // Size of the text input buffer
-
- char *uart_text_input_buffer_pass; // Buffer for the text input
- char *uart_text_input_temp_buffer_pass; // Temporary buffer for the text input
- uint32_t uart_text_input_buffer_size_pass; // Size of the text input buffer
+ //
+ TextInput *uart_text_input; // The text input
+ char *uart_text_input_buffer; // Buffer for the text input
+ char *uart_text_input_temp_buffer; // Temporary buffer for the text input
+ uint32_t uart_text_input_buffer_size; // Size of the text input buffer
} FlipStoreApp;
void flip_store_app_free(FlipStoreApp *app);
-void flip_store_request_error(Canvas *canvas);
-extern FlipStoreApp *app_instance;
-
#endif // FLIP_STORE_E_H
\ No newline at end of file
diff --git a/flip_store/flipper_http/flipper_http.c b/flip_store/flipper_http/flipper_http.c
index 361dc535c..e2ec2eaa6 100644
--- a/flip_store/flipper_http/flipper_http.c
+++ b/flip_store/flipper_http/flipper_http.c
@@ -1,8 +1,9 @@
-#include
-FlipperHTTP fhttp;
-char rx_line_buffer[RX_LINE_BUFFER_SIZE];
-uint8_t file_buffer[FILE_BUFFER_SIZE];
-size_t file_buffer_len = 0;
+// Description: Flipper HTTP API (For use with Flipper Zero and the FlipperHTTP flash: https://github.com/jblanked/FlipperHTTP)
+// License: MIT
+// Author: JBlanked
+// File: flipper_http.c
+#include // change this to where flipper_http.h is located
+
// Function to append received data to file
// make sure to initialize the file path before calling this function
bool flipper_http_append_to_file(
@@ -88,7 +89,8 @@ FuriString *flipper_http_load_from_file(char *file_path)
{
storage_file_free(file);
furi_record_close(RECORD_STORAGE);
- return NULL; // Return false if the file does not exist
+ FURI_LOG_E(HTTP_TAG, "Failed to open file for reading: %s", file_path);
+ return NULL;
}
// Allocate a FuriString to hold the received data
@@ -105,6 +107,16 @@ FuriString *flipper_http_load_from_file(char *file_path)
// Reset the FuriString to ensure it's empty before reading
furi_string_reset(str_result);
+ if (memmgr_get_free_heap() < MAX_FILE_SHOW)
+ {
+ FURI_LOG_E(HTTP_TAG, "Not enough heap to read file.");
+ furi_string_free(str_result);
+ storage_file_close(file);
+ storage_file_free(file);
+ furi_record_close(RECORD_STORAGE);
+ return NULL;
+ }
+
// Define a buffer to hold the read data
uint8_t *buffer = (uint8_t *)malloc(MAX_FILE_SHOW);
if (!buffer)
@@ -135,9 +147,100 @@ FuriString *flipper_http_load_from_file(char *file_path)
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;
+}
+
+FuriString *flipper_http_load_from_file_with_limit(char *file_path, size_t limit)
+{
+ // 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);
+ FURI_LOG_E(HTTP_TAG, "Failed to open file for reading: %s", file_path);
+ return NULL;
+ }
+
+ if (memmgr_get_free_heap() < limit)
+ {
+ FURI_LOG_E(HTTP_TAG, "Not enough heap to read file.");
+ storage_file_close(file);
+ storage_file_free(file);
+ furi_record_close(RECORD_STORAGE);
+ return NULL;
+ }
+
+ // Allocate a buffer to hold the read data
+ uint8_t *buffer = (uint8_t *)malloc(limit);
+ if (!buffer)
+ {
+ FURI_LOG_E(HTTP_TAG, "Failed to allocate buffer");
+ storage_file_close(file);
+ storage_file_free(file);
+ furi_record_close(RECORD_STORAGE);
+ return NULL;
+ }
+
+ // Allocate a FuriString with preallocated capacity
+ FuriString *str_result = furi_string_alloc();
+ if (!str_result)
+ {
+ FURI_LOG_E(HTTP_TAG, "Failed to allocate FuriString");
+ free(buffer);
+ storage_file_close(file);
+ storage_file_free(file);
+ furi_record_close(RECORD_STORAGE);
+ return NULL;
+ }
+ furi_string_reserve(str_result, limit);
+
+ // Read data into the buffer
+ size_t read_count = storage_file_read(file, buffer, limit);
+ if (storage_file_get_error(file) != FSE_OK)
+ {
+ FURI_LOG_E(HTTP_TAG, "Error reading from file.");
+ furi_string_free(str_result);
+ free(buffer);
+ storage_file_close(file);
+ storage_file_free(file);
+ furi_record_close(RECORD_STORAGE);
+ return NULL;
+ }
+ if (read_count == 0)
+ {
+ FURI_LOG_E(HTTP_TAG, "No data read from file.");
+ furi_string_free(str_result);
+ free(buffer);
+ storage_file_close(file);
+ storage_file_free(file);
+ furi_record_close(RECORD_STORAGE);
+ return NULL;
+ }
+
+ // Append the entire buffer to FuriString in one operation
+ furi_string_cat_str(str_result, (char *)buffer);
// Clean up
storage_file_close(file);
@@ -151,13 +254,23 @@ FuriString *flipper_http_load_from_file(char *file_path)
/**
* @brief Worker thread to handle UART data asynchronously.
* @return 0
- * @param context The context to pass to the callback.
+ * @param context The FlipperHTTP context.
* @note This function will handle received data asynchronously via the callback.
*/
// UART worker thread
int32_t flipper_http_worker(void *context)
{
- UNUSED(context);
+ if (!context)
+ {
+ FURI_LOG_E(HTTP_TAG, "Failed to get context.");
+ return -1;
+ }
+ FlipperHTTP *fhttp = (FlipperHTTP *)context;
+ if (!fhttp)
+ {
+ FURI_LOG_E(HTTP_TAG, "Failed to get context.");
+ return -1;
+ }
size_t rx_line_pos = 0;
while (1)
@@ -171,11 +284,11 @@ int32_t flipper_http_worker(void *context)
if (events & WorkerEvtRxDone)
{
// Continuously read from the stream buffer until it's empty
- while (!furi_stream_buffer_is_empty(fhttp.flipper_http_stream))
+ 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);
+ size_t received = furi_stream_buffer_receive(fhttp->flipper_http_stream, &c, 1, 0);
if (received == 0)
{
@@ -184,43 +297,43 @@ int32_t flipper_http_worker(void *context)
}
// Append the received byte to the file if saving is enabled
- if (fhttp.save_bytes)
+ if (fhttp->save_bytes)
{
// Add byte to the buffer
- file_buffer[file_buffer_len++] = c;
+ fhttp->file_buffer[fhttp->file_buffer_len++] = c;
// Write to file if buffer is full
- if (file_buffer_len >= FILE_BUFFER_SIZE)
+ if (fhttp->file_buffer_len >= FILE_BUFFER_SIZE)
{
if (!flipper_http_append_to_file(
- file_buffer,
- file_buffer_len,
- fhttp.just_started_bytes,
- fhttp.file_path))
+ fhttp->file_buffer,
+ fhttp->file_buffer_len,
+ fhttp->just_started_bytes,
+ fhttp->file_path))
{
FURI_LOG_E(HTTP_TAG, "Failed to append data to file");
}
- file_buffer_len = 0;
- fhttp.just_started_bytes = false;
+ fhttp->file_buffer_len = 0;
+ fhttp->just_started_bytes = false;
}
}
// Handle line buffering only if callback is set (text data)
- if (fhttp.handle_rx_line_cb)
+ 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
+ fhttp->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);
+ fhttp->handle_rx_line_cb(fhttp->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
+ fhttp->rx_line_buffer[rx_line_pos++] = c; // Add character to the line buffer
}
}
}
@@ -233,22 +346,27 @@ int32_t flipper_http_worker(void *context)
/**
* @brief Callback function for the GET timeout timer.
* @return 0
- * @param context The context to pass to the callback.
+ * @param context The FlipperHTTP context.
* @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.");
+ FlipperHTTP *fhttp = (FlipperHTTP *)context;
+ if (!fhttp)
+ {
+ FURI_LOG_E(HTTP_TAG, "Failed to get context.");
+ return;
+ }
+ FURI_LOG_E(HTTP_TAG, "Timeout reached 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;
+ 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;
+ fhttp->state = ISSUE;
}
// UART RX Handler Callback (Interrupt Context)
@@ -257,7 +375,7 @@ void get_timeout_timer_callback(void *context)
* @return void
* @param handle The UART handle.
* @param event The event type.
- * @param context The context to pass to the callback.
+ * @param context The FlipperHTTP context.
* @note This function will handle received data asynchronously via the callback.
*/
void _flipper_http_rx_callback(
@@ -265,171 +383,208 @@ void _flipper_http_rx_callback(
FuriHalSerialRxEvent event,
void *context)
{
- UNUSED(context);
+ FlipperHTTP *fhttp = (FlipperHTTP *)context;
+ if (!fhttp)
+ {
+ FURI_LOG_E(HTTP_TAG, "Failed to get context.");
+ return;
+ }
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);
+ 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.
+ * @return FlipperHTTP context if the UART was initialized successfully, NULL otherwise.
* @note The received data will be handled asynchronously via the callback.
*/
-bool flipper_http_init(FlipperHTTP_Callback callback, void *context)
+FlipperHTTP *flipper_http_alloc()
{
- if (!context)
- {
- FURI_LOG_E(HTTP_TAG, "Invalid context provided to flipper_http_init.");
- return false;
- }
- if (!callback)
+ FlipperHTTP *fhttp = (FlipperHTTP *)malloc(sizeof(FlipperHTTP));
+ if (!fhttp)
{
- FURI_LOG_E(HTTP_TAG, "Invalid callback provided to flipper_http_init.");
- return false;
+ FURI_LOG_E(HTTP_TAG, "Failed to allocate FlipperHTTP.");
+ return NULL;
}
- fhttp.flipper_http_stream = furi_stream_buffer_alloc(RX_BUF_SIZE, 1);
- if (!fhttp.flipper_http_stream)
+ memset(fhttp, 0, sizeof(FlipperHTTP)); // Initialize allocated memory to zero
+
+ 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;
+ free(fhttp);
+ return NULL;
}
- fhttp.rx_thread = furi_thread_alloc();
- if (!fhttp.rx_thread)
+ 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_stream_buffer_free(fhttp->flipper_http_stream);
+ free(fhttp);
+ return NULL;
}
- 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);
+ 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); // Corrected context
+ furi_thread_set_callback(fhttp->rx_thread, flipper_http_worker);
- fhttp.handle_rx_line_cb = callback;
- fhttp.callback_context = context;
+ fhttp->handle_rx_line_cb = flipper_http_rx_callback;
+ fhttp->callback_context = fhttp;
- furi_thread_start(fhttp.rx_thread);
- fhttp.rx_thread_id = furi_thread_get_id(fhttp.rx_thread);
+ 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
+ // 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;
+ // Cleanup resources
+ 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);
+ free(fhttp);
+ return NULL;
}
- fhttp.serial_handle = furi_hal_serial_control_acquire(UART_CH);
- if (!fhttp.serial_handle)
+ 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;
+ 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);
+ free(fhttp);
+ return NULL;
}
// Initialize UART with acquired handle
- furi_hal_serial_init(fhttp.serial_handle, BAUDRATE);
+ furi_hal_serial_init(fhttp->serial_handle, BAUDRATE);
// Enable RX direction
- furi_hal_serial_enable_direction(fhttp.serial_handle, FuriHalSerialDirectionRx);
+ 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);
+ // Start asynchronous RX with the corrected callback and context
+ furi_hal_serial_async_rx_start(fhttp->serial_handle, _flipper_http_rx_callback, fhttp, false); // Corrected context
// Wait for the TX to complete to ensure UART is ready
- furi_hal_serial_tx_wait_complete(fhttp.serial_handle);
+ furi_hal_serial_tx_wait_complete(fhttp->serial_handle);
// Allocate the timer for handling timeouts
- fhttp.get_timeout_timer = furi_timer_alloc(
+ fhttp->get_timeout_timer = furi_timer_alloc(
get_timeout_timer_callback, // Callback function
FuriTimerTypeOnce, // One-shot timer
- &fhttp // Context passed to callback
+ fhttp // Corrected context
);
- if (!fhttp.get_timeout_timer)
+ 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;
+ 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);
+ free(fhttp);
+ return NULL;
}
// 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)
+ 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;
+ // Cleanup resources
+ furi_timer_free(fhttp->get_timeout_timer);
+ 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);
+ free(fhttp);
+ return NULL;
}
+ memset(fhttp->last_response, 0, RX_BUF_SIZE); // Initialize last_response
+
+ fhttp->state = IDLE;
// FURI_LOG_I(HTTP_TAG, "UART initialized successfully.");
- return true;
+ return fhttp;
}
// Deinitialize UART
/**
* @brief Deinitialize UART.
* @return void
+ * @param fhttp The FlipperHTTP context
* @note This function will stop the asynchronous RX, release the serial handle, and free the resources.
*/
-void flipper_http_deinit()
+void flipper_http_free(FlipperHTTP *fhttp)
{
- if (fhttp.serial_handle == NULL)
+ if (!fhttp)
+ {
+ FURI_LOG_E(HTTP_TAG, "Failed to get context.");
+ return;
+ }
+ 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);
+ 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);
+ 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);
+ furi_thread_flags_set(fhttp->rx_thread_id, WorkerEvtStop);
// Wait for the thread to finish
- furi_thread_join(fhttp.rx_thread);
+ furi_thread_join(fhttp->rx_thread);
// Free the thread resources
- furi_thread_free(fhttp.rx_thread);
+ furi_thread_free(fhttp->rx_thread);
// Free the stream buffer
- furi_stream_buffer_free(fhttp.flipper_http_stream);
+ furi_stream_buffer_free(fhttp->flipper_http_stream);
// Free the timer
- if (fhttp.get_timeout_timer)
+ if (fhttp->get_timeout_timer)
{
- furi_timer_free(fhttp.get_timeout_timer);
- fhttp.get_timeout_timer = NULL;
+ furi_timer_free(fhttp->get_timeout_timer);
+ fhttp->get_timeout_timer = NULL;
}
// Free the last response
- if (fhttp.last_response)
+ if (fhttp->last_response)
{
- free(fhttp.last_response);
- fhttp.last_response = NULL;
+ free(fhttp->last_response);
+ fhttp->last_response = NULL;
}
+ // Free the FlipperHTTP context
+ free(fhttp);
+ fhttp = NULL;
+
// FURI_LOG_I("FlipperHTTP", "UART deinitialized successfully.");
}
@@ -437,11 +592,17 @@ void flipper_http_deinit()
/**
* @brief Send data over UART with newline termination.
* @return true if the data was sent successfully, false otherwise.
+ * @param fhttp The FlipperHTTP context
* @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)
+bool flipper_http_send_data(FlipperHTTP *fhttp, const char *data)
{
+ if (!fhttp)
+ {
+ FURI_LOG_E(HTTP_TAG, "Failed to get context.");
+ return false;
+ }
size_t data_length = strlen(data);
if (data_length == 0)
{
@@ -453,7 +614,7 @@ bool flipper_http_send_data(const char *data)
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.");
+ FURI_LOG_E("FlipperHTTP", "Data too long to send over FHTTP->");
return false;
}
@@ -462,20 +623,20 @@ bool flipper_http_send_data(const char *data)
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)))
+ 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.";
+ 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);
+ 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;
+ fhttp->state = IDLE;
return true;
}
@@ -483,20 +644,26 @@ bool flipper_http_send_data(const char *data)
/**
* @brief Send a PING request to check if the Wifi Dev Board is connected.
* @return true if the request was successful, false otherwise.
+ * @param fhttp The FlipperHTTP context
* @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()
+bool flipper_http_ping(FlipperHTTP *fhttp)
{
+ if (!fhttp)
+ {
+ FURI_LOG_E(HTTP_TAG, "Failed to get context.");
+ return false;
+ }
const char *command = "[PING]";
- if (!flipper_http_send_data(command))
+ if (!flipper_http_send_data(fhttp, 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;
+ fhttp->state = INACTIVE;
// The response will be handled asynchronously via the callback
return true;
}
@@ -505,12 +672,18 @@ bool flipper_http_ping()
/**
* @brief Send a command to list available commands.
* @return true if the request was successful, false otherwise.
+ * @param fhttp The FlipperHTTP context
* @note The received data will be handled asynchronously via the callback.
*/
-bool flipper_http_list_commands()
+bool flipper_http_list_commands(FlipperHTTP *fhttp)
{
+ if (!fhttp)
+ {
+ FURI_LOG_E(HTTP_TAG, "Failed to get context.");
+ return false;
+ }
const char *command = "[LIST]";
- if (!flipper_http_send_data(command))
+ if (!flipper_http_send_data(fhttp, command))
{
FURI_LOG_E("FlipperHTTP", "Failed to send LIST command.");
return false;
@@ -524,12 +697,18 @@ bool flipper_http_list_commands()
/**
* @brief Allow the LED to display while processing.
* @return true if the request was successful, false otherwise.
+ * @param fhttp The FlipperHTTP context
* @note The received data will be handled asynchronously via the callback.
*/
-bool flipper_http_led_on()
+bool flipper_http_led_on(FlipperHTTP *fhttp)
{
+ if (!fhttp)
+ {
+ FURI_LOG_E(HTTP_TAG, "Failed to get context.");
+ return false;
+ }
const char *command = "[LED/ON]";
- if (!flipper_http_send_data(command))
+ if (!flipper_http_send_data(fhttp, command))
{
FURI_LOG_E("FlipperHTTP", "Failed to send LED ON command.");
return false;
@@ -543,12 +722,18 @@ bool flipper_http_led_on()
/**
* @brief Disable the LED from displaying while processing.
* @return true if the request was successful, false otherwise.
+ * @param fhttp The FlipperHTTP context
* @note The received data will be handled asynchronously via the callback.
*/
-bool flipper_http_led_off()
+bool flipper_http_led_off(FlipperHTTP *fhttp)
{
+ if (!fhttp)
+ {
+ FURI_LOG_E(HTTP_TAG, "Failed to get context.");
+ return false;
+ }
const char *command = "[LED/OFF]";
- if (!flipper_http_send_data(command))
+ if (!flipper_http_send_data(fhttp, command))
{
FURI_LOG_E("FlipperHTTP", "Failed to send LED OFF command.");
return false;
@@ -562,12 +747,18 @@ bool flipper_http_led_off()
/**
* @brief Parse JSON data.
* @return true if the JSON data was parsed successfully, false otherwise.
+ * @param fhttp The FlipperHTTP context
* @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)
+bool flipper_http_parse_json(FlipperHTTP *fhttp, const char *key, const char *json_data)
{
+ if (!fhttp)
+ {
+ FURI_LOG_E(HTTP_TAG, "Failed to get context.");
+ return false;
+ }
if (!key || !json_data)
{
FURI_LOG_E("FlipperHTTP", "Invalid arguments provided to flipper_http_parse_json.");
@@ -583,7 +774,7 @@ bool flipper_http_parse_json(const char *key, const char *json_data)
return false;
}
- if (!flipper_http_send_data(buffer))
+ if (!flipper_http_send_data(fhttp, buffer))
{
FURI_LOG_E("FlipperHTTP", "Failed to send JSON parse command.");
return false;
@@ -597,13 +788,19 @@ bool flipper_http_parse_json(const char *key, const char *json_data)
/**
* @brief Parse JSON array data.
* @return true if the JSON array data was parsed successfully, false otherwise.
+ * @param fhttp The FlipperHTTP context
* @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)
+bool flipper_http_parse_json_array(FlipperHTTP *fhttp, const char *key, int index, const char *json_data)
{
+ if (!fhttp)
+ {
+ FURI_LOG_E(HTTP_TAG, "Failed to get context.");
+ return false;
+ }
if (!key || !json_data)
{
FURI_LOG_E("FlipperHTTP", "Invalid arguments provided to flipper_http_parse_json_array.");
@@ -624,7 +821,7 @@ bool flipper_http_parse_json_array(const char *key, int index, const char *json_
return false;
}
- if (!flipper_http_send_data(buffer))
+ if (!flipper_http_send_data(fhttp, buffer))
{
FURI_LOG_E("FlipperHTTP", "Failed to send JSON parse array command.");
return false;
@@ -638,12 +835,18 @@ bool flipper_http_parse_json_array(const char *key, int index, const char *json_
/**
* @brief Send a command to scan for WiFi networks.
* @return true if the request was successful, false otherwise.
+ * @param fhttp The FlipperHTTP context
* @note The received data will be handled asynchronously via the callback.
*/
-bool flipper_http_scan_wifi()
+bool flipper_http_scan_wifi(FlipperHTTP *fhttp)
{
+ if (!fhttp)
+ {
+ FURI_LOG_E(HTTP_TAG, "Failed to get context.");
+ return false;
+ }
const char *command = "[WIFI/SCAN]";
- if (!flipper_http_send_data(command))
+ if (!flipper_http_send_data(fhttp, command))
{
FURI_LOG_E("FlipperHTTP", "Failed to send WiFi scan command.");
return false;
@@ -657,10 +860,16 @@ bool flipper_http_scan_wifi()
/**
* @brief Send a command to save WiFi settings.
* @return true if the request was successful, false otherwise.
+ * @param fhttp The FlipperHTTP context
* @note The received data will be handled asynchronously via the callback.
*/
-bool flipper_http_save_wifi(const char *ssid, const char *password)
+bool flipper_http_save_wifi(FlipperHTTP *fhttp, const char *ssid, const char *password)
{
+ if (!fhttp)
+ {
+ FURI_LOG_E(HTTP_TAG, "Failed to get context.");
+ return false;
+ }
if (!ssid || !password)
{
FURI_LOG_E("FlipperHTTP", "Invalid arguments provided to flipper_http_save_wifi.");
@@ -675,7 +884,7 @@ bool flipper_http_save_wifi(const char *ssid, const char *password)
return false;
}
- if (!flipper_http_send_data(buffer))
+ if (!flipper_http_send_data(fhttp, buffer))
{
FURI_LOG_E("FlipperHTTP", "Failed to send WiFi save command.");
return false;
@@ -689,12 +898,18 @@ bool flipper_http_save_wifi(const char *ssid, const char *password)
/**
* @brief Send a command to get the IP address of the WiFi Devboard
* @return true if the request was successful, false otherwise.
+ * @param fhttp The FlipperHTTP context
* @note The received data will be handled asynchronously via the callback.
*/
-bool flipper_http_ip_address()
+bool flipper_http_ip_address(FlipperHTTP *fhttp)
{
+ if (!fhttp)
+ {
+ FURI_LOG_E(HTTP_TAG, "Failed to get context.");
+ return false;
+ }
const char *command = "[IP/ADDRESS]";
- if (!flipper_http_send_data(command))
+ if (!flipper_http_send_data(fhttp, command))
{
FURI_LOG_E("FlipperHTTP", "Failed to send IP address command.");
return false;
@@ -708,12 +923,18 @@ bool flipper_http_ip_address()
/**
* @brief Send a command to get the IP address of the connected WiFi network.
* @return true if the request was successful, false otherwise.
+ * @param fhttp The FlipperHTTP context
* @note The received data will be handled asynchronously via the callback.
*/
-bool flipper_http_ip_wifi()
+bool flipper_http_ip_wifi(FlipperHTTP *fhttp)
{
+ if (!fhttp)
+ {
+ FURI_LOG_E(HTTP_TAG, "Failed to get context.");
+ return false;
+ }
const char *command = "[WIFI/IP]";
- if (!flipper_http_send_data(command))
+ if (!flipper_http_send_data(fhttp, command))
{
FURI_LOG_E("FlipperHTTP", "Failed to send WiFi IP command.");
return false;
@@ -727,12 +948,18 @@ bool flipper_http_ip_wifi()
/**
* @brief Send a command to disconnect from WiFi.
* @return true if the request was successful, false otherwise.
+ * @param fhttp The FlipperHTTP context
* @note The received data will be handled asynchronously via the callback.
*/
-bool flipper_http_disconnect_wifi()
+bool flipper_http_disconnect_wifi(FlipperHTTP *fhttp)
{
+ if (!fhttp)
+ {
+ FURI_LOG_E(HTTP_TAG, "Failed to get context.");
+ return false;
+ }
const char *command = "[WIFI/DISCONNECT]";
- if (!flipper_http_send_data(command))
+ if (!flipper_http_send_data(fhttp, command))
{
FURI_LOG_E("FlipperHTTP", "Failed to send WiFi disconnect command.");
return false;
@@ -746,12 +973,18 @@ bool flipper_http_disconnect_wifi()
/**
* @brief Send a command to connect to WiFi.
* @return true if the request was successful, false otherwise.
+ * @param fhttp The FlipperHTTP context
* @note The received data will be handled asynchronously via the callback.
*/
-bool flipper_http_connect_wifi()
+bool flipper_http_connect_wifi(FlipperHTTP *fhttp)
{
+ if (!fhttp)
+ {
+ FURI_LOG_E(HTTP_TAG, "Failed to get context.");
+ return false;
+ }
const char *command = "[WIFI/CONNECT]";
- if (!flipper_http_send_data(command))
+ if (!flipper_http_send_data(fhttp, command))
{
FURI_LOG_E("FlipperHTTP", "Failed to send WiFi connect command.");
return false;
@@ -765,11 +998,17 @@ bool flipper_http_connect_wifi()
/**
* @brief Send a GET request to the specified URL.
* @return true if the request was successful, false otherwise.
+ * @param fhttp The FlipperHTTP context
* @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)
+bool flipper_http_get_request(FlipperHTTP *fhttp, const char *url)
{
+ if (!fhttp)
+ {
+ FURI_LOG_E(HTTP_TAG, "Failed to get context.");
+ return false;
+ }
if (!url)
{
FURI_LOG_E("FlipperHTTP", "Invalid arguments provided to flipper_http_get_request.");
@@ -786,7 +1025,7 @@ bool flipper_http_get_request(const char *url)
}
// Send GET request via UART
- if (!flipper_http_send_data(command))
+ if (!flipper_http_send_data(fhttp, command))
{
FURI_LOG_E("FlipperHTTP", "Failed to send GET request command.");
return false;
@@ -795,16 +1034,23 @@ bool flipper_http_get_request(const char *url)
// 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 fhttp The FlipperHTTP context
* @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)
+bool flipper_http_get_request_with_headers(FlipperHTTP *fhttp, const char *url, const char *headers)
{
+ if (!fhttp)
+ {
+ FURI_LOG_E(HTTP_TAG, "Failed to get context.");
+ return false;
+ }
if (!url || !headers)
{
FURI_LOG_E(
@@ -823,7 +1069,7 @@ bool flipper_http_get_request_with_headers(const char *url, const char *headers)
}
// Send GET request via UART
- if (!flipper_http_send_data(command))
+ if (!flipper_http_send_data(fhttp, command))
{
FURI_LOG_E("FlipperHTTP", "Failed to send GET request command with headers.");
return false;
@@ -832,16 +1078,23 @@ bool flipper_http_get_request_with_headers(const char *url, const char *headers)
// 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 fhttp The FlipperHTTP context
* @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)
+bool flipper_http_get_request_bytes(FlipperHTTP *fhttp, const char *url, const char *headers)
{
+ if (!fhttp)
+ {
+ FURI_LOG_E(HTTP_TAG, "Failed to get context.");
+ return false;
+ }
if (!url || !headers)
{
FURI_LOG_E("FlipperHTTP", "Invalid arguments provided to flipper_http_get_request_bytes.");
@@ -859,7 +1112,7 @@ bool flipper_http_get_request_bytes(const char *url, const char *headers)
}
// Send GET request via UART
- if (!flipper_http_send_data(command))
+ if (!flipper_http_send_data(fhttp, command))
{
FURI_LOG_E("FlipperHTTP", "Failed to send GET request command with headers.");
return false;
@@ -868,20 +1121,28 @@ bool flipper_http_get_request_bytes(const char *url, const char *headers)
// 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 fhttp The FlipperHTTP context
* @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(
+ FlipperHTTP *fhttp,
const char *url,
const char *headers,
const char *payload)
{
+ if (!fhttp)
+ {
+ FURI_LOG_E(HTTP_TAG, "Failed to get context.");
+ return false;
+ }
if (!url || !headers || !payload)
{
FURI_LOG_E(
@@ -906,7 +1167,7 @@ bool flipper_http_post_request_with_headers(
}
// Send POST request via UART
- if (!flipper_http_send_data(command))
+ if (!flipper_http_send_data(fhttp, command))
{
FURI_LOG_E("FlipperHTTP", "Failed to send POST request command with headers and data.");
return false;
@@ -915,17 +1176,24 @@ bool flipper_http_post_request_with_headers(
// 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 fhttp The FlipperHTTP context
* @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)
+bool flipper_http_post_request_bytes(FlipperHTTP *fhttp, const char *url, const char *headers, const char *payload)
{
+ if (!fhttp)
+ {
+ FURI_LOG_E(HTTP_TAG, "Failed to get context.");
+ return false;
+ }
if (!url || !headers || !payload)
{
FURI_LOG_E(
@@ -949,7 +1217,7 @@ bool flipper_http_post_request_bytes(const char *url, const char *headers, const
}
// Send POST request via UART
- if (!flipper_http_send_data(command))
+ if (!flipper_http_send_data(fhttp, command))
{
FURI_LOG_E("FlipperHTTP", "Failed to send POST request command with headers and data.");
return false;
@@ -958,20 +1226,28 @@ bool flipper_http_post_request_bytes(const char *url, const char *headers, const
// 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 fhttp The FlipperHTTP context
* @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(
+ FlipperHTTP *fhttp,
const char *url,
const char *headers,
const char *payload)
{
+ if (!fhttp)
+ {
+ FURI_LOG_E(HTTP_TAG, "Failed to get context.");
+ return false;
+ }
if (!url || !headers || !payload)
{
FURI_LOG_E(
@@ -995,7 +1271,7 @@ bool flipper_http_put_request_with_headers(
}
// Send PUT request via UART
- if (!flipper_http_send_data(command))
+ if (!flipper_http_send_data(fhttp, command))
{
FURI_LOG_E("FlipperHTTP", "Failed to send PUT request command with headers and data.");
return false;
@@ -1004,20 +1280,28 @@ bool flipper_http_put_request_with_headers(
// 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 fhttp The FlipperHTTP context
* @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(
+ FlipperHTTP *fhttp,
const char *url,
const char *headers,
const char *payload)
{
+ if (!fhttp)
+ {
+ FURI_LOG_E(HTTP_TAG, "Failed to get context.");
+ return false;
+ }
if (!url || !headers || !payload)
{
FURI_LOG_E(
@@ -1043,7 +1327,7 @@ bool flipper_http_delete_request_with_headers(
}
// Send DELETE request via UART
- if (!flipper_http_send_data(command))
+ if (!flipper_http_send_data(fhttp, command))
{
FURI_LOG_E("FlipperHTTP", "Failed to send DELETE request command with headers and data.");
return false;
@@ -1052,17 +1336,60 @@ bool flipper_http_delete_request_with_headers(
// The response will be handled asynchronously via the callback
return true;
}
+// Function to trim leading and trailing spaces and newlines from a constant string
+static 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;
+}
+
// 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.
+ * @param context The FlipperHTTP context.
* @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)
+ FlipperHTTP *fhttp = (FlipperHTTP *)context;
+ if (!fhttp)
+ {
+ FURI_LOG_E(HTTP_TAG, "Failed to get context.");
+ return;
+ }
+ if (!line)
{
FURI_LOG_E(HTTP_TAG, "Invalid arguments provided to flipper_http_rx_callback.");
return;
@@ -1078,233 +1405,233 @@ void flipper_http_rx_callback(const char *line, void *context)
strstr(trimmed_line, "[PUT/END]") == NULL &&
strstr(trimmed_line, "[DELETE/END]") == NULL)
{
- strncpy(fhttp.last_response, trimmed_line, RX_BUF_SIZE);
+ 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)
+ if (fhttp->state != INACTIVE && fhttp->state != ISSUE)
{
- fhttp.state = RECEIVING;
+ 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)
+ 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);
+ 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.save_received_data = false;
-
- if (fhttp.is_bytes_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->save_received_data = false;
+
+ if (fhttp->is_bytes_request)
{
// Search for the binary marker `[GET/END]` in the file buffer
const char marker[] = "[GET/END]";
const size_t marker_len = sizeof(marker) - 1; // Exclude null terminator
- for (size_t i = 0; i <= file_buffer_len - marker_len; i++)
+ for (size_t i = 0; i <= fhttp->file_buffer_len - marker_len; i++)
{
// Check if the marker is found
- if (memcmp(&file_buffer[i], marker, marker_len) == 0)
+ if (memcmp(&fhttp->file_buffer[i], marker, marker_len) == 0)
{
// Remove the marker by shifting the remaining data left
- size_t remaining_len = file_buffer_len - (i + marker_len);
- memmove(&file_buffer[i], &file_buffer[i + marker_len], remaining_len);
- file_buffer_len -= marker_len;
+ size_t remaining_len = fhttp->file_buffer_len - (i + marker_len);
+ memmove(&fhttp->file_buffer[i], &fhttp->file_buffer[i + marker_len], remaining_len);
+ fhttp->file_buffer_len -= marker_len;
break;
}
}
// If there is data left in the buffer, append it to the file
- if (file_buffer_len > 0)
+ if (fhttp->file_buffer_len > 0)
{
- if (!flipper_http_append_to_file(file_buffer, file_buffer_len, false, fhttp.file_path))
+ if (!flipper_http_append_to_file(fhttp->file_buffer, fhttp->file_buffer_len, false, fhttp->file_path))
{
FURI_LOG_E(HTTP_TAG, "Failed to append data to file.");
}
- file_buffer_len = 0;
+ fhttp->file_buffer_len = 0;
}
}
- fhttp.is_bytes_request = false;
+ fhttp->is_bytes_request = false;
return;
}
// Append the new line to the existing data
- if (fhttp.save_received_data &&
+ if (fhttp->save_received_data &&
!flipper_http_append_to_file(
- line, strlen(line), !fhttp.just_started_get, fhttp.file_path))
+ 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;
+ fhttp->started_receiving_get = false;
+ fhttp->just_started_get = false;
+ fhttp->state = IDLE;
return;
}
- if (!fhttp.just_started_get)
+ if (!fhttp->just_started_get)
{
- fhttp.just_started_get = true;
+ fhttp->just_started_get = true;
}
return;
}
// Check if we've started receiving data from a POST request
- else if (fhttp.started_receiving_post)
+ 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);
+ 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.save_received_data = false;
-
- if (fhttp.is_bytes_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->save_received_data = false;
+
+ if (fhttp->is_bytes_request)
{
// Search for the binary marker `[POST/END]` in the file buffer
const char marker[] = "[POST/END]";
const size_t marker_len = sizeof(marker) - 1; // Exclude null terminator
- for (size_t i = 0; i <= file_buffer_len - marker_len; i++)
+ for (size_t i = 0; i <= fhttp->file_buffer_len - marker_len; i++)
{
// Check if the marker is found
- if (memcmp(&file_buffer[i], marker, marker_len) == 0)
+ if (memcmp(&fhttp->file_buffer[i], marker, marker_len) == 0)
{
// Remove the marker by shifting the remaining data left
- size_t remaining_len = file_buffer_len - (i + marker_len);
- memmove(&file_buffer[i], &file_buffer[i + marker_len], remaining_len);
- file_buffer_len -= marker_len;
+ size_t remaining_len = fhttp->file_buffer_len - (i + marker_len);
+ memmove(&fhttp->file_buffer[i], &fhttp->file_buffer[i + marker_len], remaining_len);
+ fhttp->file_buffer_len -= marker_len;
break;
}
}
// If there is data left in the buffer, append it to the file
- if (file_buffer_len > 0)
+ if (fhttp->file_buffer_len > 0)
{
- if (!flipper_http_append_to_file(file_buffer, file_buffer_len, false, fhttp.file_path))
+ if (!flipper_http_append_to_file(fhttp->file_buffer, fhttp->file_buffer_len, false, fhttp->file_path))
{
FURI_LOG_E(HTTP_TAG, "Failed to append data to file.");
}
- file_buffer_len = 0;
+ fhttp->file_buffer_len = 0;
}
}
- fhttp.is_bytes_request = false;
+ fhttp->is_bytes_request = false;
return;
}
// Append the new line to the existing data
- if (fhttp.save_received_data &&
+ if (fhttp->save_received_data &&
!flipper_http_append_to_file(
- line, strlen(line), !fhttp.just_started_post, fhttp.file_path))
+ 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;
+ fhttp->started_receiving_post = false;
+ fhttp->just_started_post = false;
+ fhttp->state = IDLE;
return;
}
- if (!fhttp.just_started_post)
+ if (!fhttp->just_started_post)
{
- fhttp.just_started_post = true;
+ fhttp->just_started_post = true;
}
return;
}
// Check if we've started receiving data from a PUT request
- else if (fhttp.started_receiving_put)
+ 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);
+ 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;
+ 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 &&
+ if (fhttp->save_received_data &&
!flipper_http_append_to_file(
- line, strlen(line), !fhttp.just_started_put, fhttp.file_path))
+ 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;
+ fhttp->started_receiving_put = false;
+ fhttp->just_started_put = false;
+ fhttp->state = IDLE;
return;
}
- if (!fhttp.just_started_put)
+ if (!fhttp->just_started_put)
{
- fhttp.just_started_put = true;
+ fhttp->just_started_put = true;
}
return;
}
// Check if we've started receiving data from a DELETE request
- else if (fhttp.started_receiving_delete)
+ 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);
+ 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;
+ 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 &&
+ if (fhttp->save_received_data &&
!flipper_http_append_to_file(
- line, strlen(line), !fhttp.just_started_delete, fhttp.file_path))
+ 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;
+ fhttp->started_receiving_delete = false;
+ fhttp->just_started_delete = false;
+ fhttp->state = IDLE;
return;
}
- if (!fhttp.just_started_delete)
+ if (!fhttp->just_started_delete)
{
- fhttp.just_started_delete = true;
+ fhttp->just_started_delete = true;
}
return;
}
@@ -1318,49 +1645,49 @@ void flipper_http_rx_callback(const char *line, void *context)
{
FURI_LOG_I(HTTP_TAG, "Received info: %s", line);
- if (fhttp.state == INACTIVE && strstr(line, "[INFO] Already connected to Wifi.") != NULL)
+ if (fhttp->state == INACTIVE && strstr(line, "[INFO] Already connected to Wifi.") != NULL)
{
- fhttp.state = IDLE;
+ 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->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;
- fhttp.just_started_bytes = true;
- file_buffer_len = 0;
+ fhttp->save_bytes = fhttp->is_bytes_request;
+ fhttp->just_started_bytes = true;
+ fhttp->file_buffer_len = 0;
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->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;
- fhttp.just_started_bytes = true;
- file_buffer_len = 0;
+ fhttp->save_bytes = fhttp->is_bytes_request;
+ fhttp->just_started_bytes = true;
+ fhttp->file_buffer_len = 0;
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->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;
+ 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)
@@ -1370,7 +1697,7 @@ void flipper_http_rx_callback(const char *line, void *context)
else if (strstr(line, "[ERROR]") != NULL)
{
FURI_LOG_E(HTTP_TAG, "Received error: %s", line);
- fhttp.state = ISSUE;
+ fhttp->state = ISSUE;
return;
}
else if (strstr(line, "[PONG]") != NULL)
@@ -1378,88 +1705,57 @@ void flipper_http_rx_callback(const char *line, void *context)
FURI_LOG_I(HTTP_TAG, "Received PONG response: Wifi Dev Board is still alive.");
// send command to connect to WiFi
- if (fhttp.state == INACTIVE)
+ if (fhttp->state == INACTIVE)
{
- fhttp.state = IDLE;
+ fhttp->state = IDLE;
return;
}
}
- if (fhttp.state == INACTIVE && strstr(line, "[PONG]") != NULL)
+ if (fhttp->state == INACTIVE && strstr(line, "[PONG]") != NULL)
{
- fhttp.state = IDLE;
+ fhttp->state = IDLE;
}
- else if (fhttp.state == INACTIVE && strstr(line, "[PONG]") == NULL)
+ else if (fhttp->state == INACTIVE && strstr(line, "[PONG]") == NULL)
{
- fhttp.state = INACTIVE;
+ fhttp->state = INACTIVE;
}
else
{
- fhttp.state = IDLE;
+ 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 fhttp The FlipperHTTP context
* @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))
+bool flipper_http_process_response_async(FlipperHTTP *fhttp, bool (*http_request)(void), bool (*parse_json)(void))
{
+ if (!fhttp)
+ {
+ FURI_LOG_E(HTTP_TAG, "Failed to get context.");
+ return false;
+ }
if (http_request()) // start the async request
{
- furi_timer_start(fhttp.get_timeout_timer, TIMEOUT_DURATION_TICKS);
- fhttp.state = RECEIVING;
+ 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)
+ 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);
+ 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...");
@@ -1470,6 +1766,7 @@ bool flipper_http_process_response_async(bool (*http_request)(void), bool (*pars
/**
* @brief Perform a task while displaying a loading screen
+ * @param fhttp The FlipperHTTP context
* @param http_request The function to send the request
* @param parse_response The function to parse the response
* @param success_view_id The view ID to switch to on success
@@ -1477,12 +1774,23 @@ bool flipper_http_process_response_async(bool (*http_request)(void), bool (*pars
* @param view_dispatcher The view dispatcher to use
* @return
*/
-void flipper_http_loading_task(bool (*http_request)(void),
+void flipper_http_loading_task(FlipperHTTP *fhttp,
+ bool (*http_request)(void),
bool (*parse_response)(void),
uint32_t success_view_id,
uint32_t failure_view_id,
ViewDispatcher **view_dispatcher)
{
+ if (!fhttp)
+ {
+ FURI_LOG_E(HTTP_TAG, "Failed to get context.");
+ return;
+ }
+ if (fhttp->state == INACTIVE)
+ {
+ view_dispatcher_switch_to_view(*view_dispatcher, failure_view_id);
+ return;
+ }
Loading *loading;
int32_t loading_view_id = 987654321; // Random ID
@@ -1501,7 +1809,7 @@ void flipper_http_loading_task(bool (*http_request)(void),
view_dispatcher_switch_to_view(*view_dispatcher, loading_view_id);
// Make the request
- if (!flipper_http_process_response_async(http_request, parse_response))
+ if (!flipper_http_process_response_async(fhttp, http_request, parse_response))
{
FURI_LOG_E(HTTP_TAG, "Failed to make request");
view_dispatcher_switch_to_view(*view_dispatcher, failure_view_id);
@@ -1514,5 +1822,5 @@ void flipper_http_loading_task(bool (*http_request)(void),
// Switch to the success view
view_dispatcher_switch_to_view(*view_dispatcher, success_view_id);
view_dispatcher_remove_view(*view_dispatcher, loading_view_id);
- loading_free(loading);
+ loading_free(loading); // comment this out if you experience a freeze
}
\ No newline at end of file
diff --git a/flip_store/flipper_http/flipper_http.h b/flip_store/flipper_http/flipper_http.h
index 02ba35961..6c778c3f2 100644
--- a/flip_store/flipper_http/flipper_http.h
+++ b/flip_store/flipper_http/flipper_http.h
@@ -1,6 +1,8 @@
-// flipper_http.h
-#ifndef FLIPPER_HTTP_H
-#define FLIPPER_HTTP_H
+// Description: Flipper HTTP API (For use with Flipper Zero and the FlipperHTTP flash: https://github.com/jblanked/FlipperHTTP)
+// License: MIT
+// Author: JBlanked
+// File: flipper_http.h
+#pragma once
#include
#include
@@ -20,9 +22,9 @@
#define UART_CH (momentum_settings.uart_esp_channel) // 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 RX_BUF_SIZE 2048 // UART RX buffer size
+#define RX_LINE_BUFFER_SIZE 2048 // UART RX line buffer size (increase for large responses)
+#define MAX_FILE_SHOW 12000 // Maximum data from file to show
#define FILE_BUFFER_SIZE 512 // File buffer size
// Forward declaration for callback
@@ -83,13 +85,11 @@ typedef struct
bool save_received_data; // Flag to save the received data to a file
bool just_started_bytes; // Indicates if bytes data reception has just started
-} 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];
-extern size_t file_buffer_len;
+ char rx_line_buffer[RX_LINE_BUFFER_SIZE];
+ uint8_t file_buffer[FILE_BUFFER_SIZE];
+ size_t file_buffer_len;
+} FlipperHTTP;
// fhttp.last_response holds the last received data from the UART
@@ -102,6 +102,7 @@ bool flipper_http_append_to_file(
char *file_path);
FuriString *flipper_http_load_from_file(char *file_path);
+FuriString *flipper_http_load_from_file_with_limit(char *file_path, size_t limit);
// UART worker thread
/**
@@ -139,172 +140,189 @@ void _flipper_http_rx_callback(
// 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.
+ * @return FlipperHTTP context if the UART was initialized successfully, NULL otherwise.
* @note The received data will be handled asynchronously via the callback.
*/
-bool flipper_http_init(FlipperHTTP_Callback callback, void *context);
+FlipperHTTP *flipper_http_alloc();
// Deinitialize UART
/**
* @brief Deinitialize UART.
* @return void
+ * @param fhttp The FlipperHTTP context
* @note This function will stop the asynchronous RX, release the serial handle, and free the resources.
*/
-void flipper_http_deinit();
+void flipper_http_free(FlipperHTTP *fhttp);
// 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 fhttp The FlipperHTTP context
* @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);
+bool flipper_http_send_data(FlipperHTTP *fhttp, 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.
+ * @param fhttp The FlipperHTTP context
* @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();
+bool flipper_http_ping(FlipperHTTP *fhttp);
// Function to list available commands
/**
* @brief Send a command to list available commands.
* @return true if the request was successful, false otherwise.
+ * @param fhttp The FlipperHTTP context
* @note The received data will be handled asynchronously via the callback.
*/
-bool flipper_http_list_commands();
+bool flipper_http_list_commands(FlipperHTTP *fhttp);
// Function to turn on the LED
/**
* @brief Allow the LED to display while processing.
* @return true if the request was successful, false otherwise.
+ * @param fhttp The FlipperHTTP context
* @note The received data will be handled asynchronously via the callback.
*/
-bool flipper_http_led_on();
+bool flipper_http_led_on(FlipperHTTP *fhttp);
// Function to turn off the LED
/**
* @brief Disable the LED from displaying while processing.
* @return true if the request was successful, false otherwise.
+ * @param fhttp The FlipperHTTP context
* @note The received data will be handled asynchronously via the callback.
*/
-bool flipper_http_led_off();
+bool flipper_http_led_off(FlipperHTTP *fhttp);
// Function to parse JSON data
/**
* @brief Parse JSON data.
* @return true if the JSON data was parsed successfully, false otherwise.
+ * @param fhttp The FlipperHTTP context
* @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);
+bool flipper_http_parse_json(FlipperHTTP *fhttp, 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 fhttp The FlipperHTTP context
* @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);
+bool flipper_http_parse_json_array(FlipperHTTP *fhttp, 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.
+ * @param fhttp The FlipperHTTP context
* @note The received data will be handled asynchronously via the callback.
*/
-bool flipper_http_scan_wifi();
+bool flipper_http_scan_wifi(FlipperHTTP *fhttp);
// 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.
+ * @param fhttp The FlipperHTTP context
* @note The received data will be handled asynchronously via the callback.
*/
-bool flipper_http_save_wifi(const char *ssid, const char *password);
+bool flipper_http_save_wifi(FlipperHTTP *fhttp, 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.
+ * @param fhttp The FlipperHTTP context
* @note The received data will be handled asynchronously via the callback.
*/
-bool flipper_http_ip_address();
+bool flipper_http_ip_address(FlipperHTTP *fhttp);
// 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.
+ * @param fhttp The FlipperHTTP context
* @note The received data will be handled asynchronously via the callback.
*/
-bool flipper_http_ip_wifi();
+bool flipper_http_ip_wifi(FlipperHTTP *fhttp);
// 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.
+ * @param fhttp The FlipperHTTP context
* @note The received data will be handled asynchronously via the callback.
*/
-bool flipper_http_disconnect_wifi();
+bool flipper_http_disconnect_wifi(FlipperHTTP *fhttp);
// 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.
+ * @param fhttp The FlipperHTTP context
* @note The received data will be handled asynchronously via the callback.
*/
-bool flipper_http_connect_wifi();
+bool flipper_http_connect_wifi(FlipperHTTP *fhttp);
// 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 fhttp The FlipperHTTP context
* @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);
+bool flipper_http_get_request(FlipperHTTP *fhttp, 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 fhttp The FlipperHTTP context
* @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);
+bool flipper_http_get_request_with_headers(FlipperHTTP *fhttp, 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 fhttp The FlipperHTTP context
* @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);
+bool flipper_http_get_request_bytes(FlipperHTTP *fhttp, 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 fhttp The FlipperHTTP context
* @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(
+ FlipperHTTP *fhttp,
const char *url,
const char *headers,
const char *payload);
@@ -313,23 +331,26 @@ bool flipper_http_post_request_with_headers(
/**
* @brief Send a POST request to the specified URL.
* @return true if the request was successful, false otherwise.
+ * @param fhttp The FlipperHTTP context
* @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);
+bool flipper_http_post_request_bytes(FlipperHTTP *fhttp, 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 fhttp The FlipperHTTP context
* @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(
+ FlipperHTTP *fhttp,
const char *url,
const char *headers,
const char *payload);
@@ -338,12 +359,14 @@ bool flipper_http_put_request_with_headers(
/**
* @brief Send a DELETE request to the specified URL.
* @return true if the request was successful, false otherwise.
+ * @param fhttp The FlipperHTTP context
* @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(
+ FlipperHTTP *fhttp,
const char *url,
const char *headers,
const char *payload);
@@ -353,23 +376,23 @@ bool flipper_http_delete_request_with_headers(
* @brief Callback function to handle received data asynchronously.
* @return void
* @param line The received line.
- * @param context The context passed to the callback.
+ * @param context The FlipperHTTP context.
* @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 fhttp The FlipperHTTP context
* @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));
+bool flipper_http_process_response_async(FlipperHTTP *fhttp, bool (*http_request)(void), bool (*parse_json)(void));
/**
* @brief Perform a task while displaying a loading screen
+ * @param fhttp The FlipperHTTP context
* @param http_request The function to send the request
* @param parse_response The function to parse the response
* @param success_view_id The view ID to switch to on success
@@ -377,10 +400,9 @@ bool flipper_http_process_response_async(bool (*http_request)(void), bool (*pars
* @param view_dispatcher The view dispatcher to use
* @return
*/
-void flipper_http_loading_task(bool (*http_request)(void),
+void flipper_http_loading_task(FlipperHTTP *fhttp,
+ bool (*http_request)(void),
bool (*parse_response)(void),
uint32_t success_view_id,
uint32_t failure_view_id,
ViewDispatcher **view_dispatcher);
-
-#endif // FLIPPER_HTTP_H
diff --git a/flip_store/github/flip_store_github.c b/flip_store/github/flip_store_github.c
new file mode 100644
index 000000000..d445a9b40
--- /dev/null
+++ b/flip_store/github/flip_store_github.c
@@ -0,0 +1,378 @@
+#include
+#include
+
+// Helper to download a file from Github and save it to the storage
+bool flip_store_download_github_file(
+ FlipperHTTP *fhttp,
+ const char *filename,
+ const char *author,
+ const char *repo,
+ const char *link)
+{
+ if (!fhttp || !filename || !author || !repo || !link)
+ {
+ FURI_LOG_E(TAG, "Invalid arguments.");
+ return false;
+ }
+
+ snprintf(fhttp->file_path, sizeof(fhttp->file_path), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_store/%s/%s/%s.txt", author, repo, filename);
+ fhttp->state = IDLE;
+ fhttp->save_received_data = false;
+ fhttp->is_bytes_request = true;
+
+ return flipper_http_get_request_bytes(fhttp, link, "{\"Content-Type\":\"application/octet-stream\"}");
+}
+
+bool flip_store_get_github_contents(FlipperHTTP *fhttp, const char *author, const char *repo)
+{
+ // Create Initial directory
+ Storage *storage = furi_record_open(RECORD_STORAGE);
+
+ char dir[256];
+
+ // create a data directory: /ext/apps_data/flip_store/data
+ snprintf(dir, sizeof(dir), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_store/data");
+ storage_common_mkdir(storage, dir);
+
+ // create a data directory for the author: /ext/apps_data/flip_store/data/author
+ snprintf(dir, sizeof(dir), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_store/data/%s", author);
+ storage_common_mkdir(storage, dir);
+
+ // example path: /ext/apps_data/flip_store/data/author/info.json
+ snprintf(fhttp->file_path, sizeof(fhttp->file_path), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_store/data/%s/info.json", author);
+
+ // create a data directory for the repo: /ext/apps_data/flip_store/data/author/repo
+ snprintf(dir, sizeof(dir), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_store/data/%s/%s", author, repo);
+ storage_common_mkdir(storage, dir);
+
+ // example path: /ext/apps_data/flip_store/author
+ snprintf(dir, sizeof(dir), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_store/%s", author);
+ storage_common_mkdir(storage, dir);
+
+ // example path: /ext/apps_data/flip_store/author/repo
+ snprintf(dir, sizeof(dir), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_store/%s/%s", author, repo);
+ storage_common_mkdir(storage, dir);
+
+ furi_record_close(RECORD_STORAGE);
+
+ // get the contents of the repo
+ char link[256];
+ snprintf(link, sizeof(link), "https://api.github.com/repos/%s/%s/contents", author, repo);
+ fhttp->save_received_data = true;
+ return flipper_http_get_request_with_headers(fhttp, link, "{\"Content-Type\":\"application/json\"}");
+}
+
+#include
+#include
+#include
+
+// Assuming necessary headers and definitions for FuriString, FURI_LOG, etc., are included.
+
+bool flip_store_parse_github_contents(char *file_path, const char *author, const char *repo)
+{
+ FURI_LOG_I(TAG, "Parsing Github contents from %s - %s.", author, repo);
+ if (!file_path || !author || !repo)
+ {
+ FURI_LOG_E(TAG, "Invalid arguments.");
+ return false;
+ }
+
+ // Load JSON data from file
+ FuriString *git_data = flipper_http_load_from_file(file_path);
+ if (git_data == NULL)
+ {
+ FURI_LOG_E(TAG, "Failed to load received data from file.");
+ return false;
+ }
+
+ // Allocate a new FuriString to hold the entire JSON structure
+ FuriString *git_data_str = furi_string_alloc();
+ if (!git_data_str)
+ {
+ FURI_LOG_E(TAG, "Failed to allocate git_data_str.");
+ furi_string_free(git_data);
+ return false;
+ }
+
+ // Construct the full JSON string
+ furi_string_cat_str(git_data_str, "{\"json_data\":");
+ furi_string_cat(git_data_str, git_data);
+ furi_string_cat_str(git_data_str, "}");
+ furi_string_free(git_data); // Free the original git_data as it's now part of git_data_str
+
+ // Check available memory
+ const size_t additional_bytes = strlen("{\"json_data\":") + 1; // +1 for the closing "}"
+ if (memmgr_get_free_heap() < furi_string_size(git_data_str) + additional_bytes)
+ {
+ FURI_LOG_E(TAG, "Not enough memory to allocate git_data_str.");
+ furi_string_free(git_data_str);
+ return false;
+ }
+
+ int file_count = 0;
+ char dir[512]; // Increased size to accommodate longer paths if necessary
+ FURI_LOG_I(TAG, "Looping through Github files.");
+ FURI_LOG_I(TAG, "Available memory: %d bytes", memmgr_get_free_heap());
+
+ // Get the C-string and its length for processing
+ char *data = (char *)furi_string_get_cstr(git_data_str);
+ size_t data_len = furi_string_size(git_data_str);
+
+ size_t pos = 0; // Current position in the data string
+ // Locate the start of the JSON array
+ char *array_start = strchr(data, '[');
+ if (!array_start)
+ {
+ FURI_LOG_E(TAG, "Invalid JSON format: '[' not found.");
+ furi_string_free(git_data_str);
+ return false;
+ }
+ pos = array_start - data; // Update position to the start of the array
+ size_t brace_count = 0;
+ size_t obj_start = 0;
+ bool in_string = false; // To handle braces inside strings
+
+ while (pos < data_len && file_count < MAX_GITHUB_FILES)
+ {
+ char current = data[pos];
+
+ // Toggle in_string flag if a quote is found (handling escaped quotes)
+ if (current == '"' && (pos == 0 || data[pos - 1] != '\\'))
+ {
+ in_string = !in_string;
+ }
+
+ if (!in_string)
+ {
+ if (current == '{')
+ {
+ if (brace_count == 0)
+ {
+ obj_start = pos; // Potential start of a JSON object
+ }
+ brace_count++;
+ }
+ else if (current == '}')
+ {
+ brace_count--;
+ if (brace_count == 0)
+ {
+ size_t obj_end = pos;
+ size_t obj_length = obj_end - obj_start + 1;
+ // Extract the JSON object substring
+ char *obj_str = malloc(obj_length + 1);
+ if (!obj_str)
+ {
+ FURI_LOG_E(TAG, "Memory allocation failed for obj_str.");
+ break;
+ }
+ strncpy(obj_str, data + obj_start, obj_length);
+ obj_str[obj_length] = '\0'; // Null-terminate
+
+ FuriString *json_data_array = furi_string_alloc();
+ furi_string_set(json_data_array, obj_str); // Set the string to the allocated memory
+ free(obj_str); // Free the temporary C-string
+
+ if (!json_data_array)
+ {
+ FURI_LOG_E(TAG, "Failed to initialize json_data_array.");
+ break;
+ }
+
+ FURI_LOG_I(TAG, "Loaded json data array value %d. Available memory: %d bytes", file_count, memmgr_get_free_heap());
+
+ // Extract "type" field
+ FuriString *type = get_json_value_furi("type", json_data_array);
+ if (!type)
+ {
+ FURI_LOG_E(TAG, "Failed to get type.");
+ furi_string_free(json_data_array);
+ break;
+ }
+
+ // Skip non-file types (e.g., directories)
+ if (strcmp(furi_string_get_cstr(type), "file") != 0)
+ {
+ furi_string_free(type);
+ furi_string_free(json_data_array);
+ pos = obj_end + 1; // Move past this object
+ continue;
+ }
+ furi_string_free(type);
+
+ // Extract "download_url" and "name"
+ FuriString *download_url = get_json_value_furi("download_url", json_data_array);
+ if (!download_url)
+ {
+ FURI_LOG_E(TAG, "Failed to get download_url.");
+ furi_string_free(json_data_array);
+ break;
+ }
+
+ FuriString *name = get_json_value_furi("name", json_data_array);
+ if (!name)
+ {
+ FURI_LOG_E(TAG, "Failed to get name.");
+ furi_string_free(json_data_array);
+ furi_string_free(download_url);
+ break;
+ }
+
+ furi_string_free(json_data_array);
+ FURI_LOG_I(TAG, "Received name and download_url. Available memory: %d bytes", memmgr_get_free_heap());
+
+ // Create JSON to save
+ FuriString *json = furi_string_alloc();
+ if (!json)
+ {
+ FURI_LOG_E(TAG, "Failed to allocate json.");
+ furi_string_free(download_url);
+ furi_string_free(name);
+ break;
+ }
+
+ furi_string_cat_str(json, "{\"name\":\"");
+ furi_string_cat(json, name);
+ furi_string_cat_str(json, "\",\"link\":\"");
+ furi_string_cat(json, download_url);
+ furi_string_cat_str(json, "\"}");
+
+ FURI_LOG_I(TAG, "Created json. Available memory: %d bytes", memmgr_get_free_heap());
+
+ // Save the JSON to the data folder: /ext/apps_data/flip_store/data/author/repo/fileX.json
+ snprintf(dir, sizeof(dir), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_store/data/%s/%s/file%d.json", author, repo, file_count);
+ if (!save_char_with_path(dir, furi_string_get_cstr(json)))
+ {
+ FURI_LOG_E(TAG, "Failed to save json.");
+ }
+
+ FURI_LOG_I(TAG, "Saved file %s.", furi_string_get_cstr(name));
+
+ // Free allocated resources
+ furi_string_free(name);
+ furi_string_free(download_url);
+ furi_string_free(json);
+
+ file_count++;
+
+ // This can be expensive for large strings; consider memory constraints
+ size_t remaining_length = data_len - (obj_end + 1);
+ memmove(data + obj_start, data + obj_end + 1, remaining_length + 1); // +1 to include null terminator
+ data_len -= (obj_end + 1 - obj_start);
+ pos = obj_start; // Reset position to the start of the modified string
+ continue;
+ }
+ }
+ }
+
+ pos++;
+ }
+
+ furi_string_free(git_data_str);
+
+ // Save file count
+ char file_count_str[16];
+ snprintf(file_count_str, sizeof(file_count_str), "%d", file_count);
+ char file_count_dir[512]; // Increased size for longer paths
+ snprintf(file_count_dir, sizeof(file_count_dir), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_store/data/%s/file_count.txt", author);
+
+ FURI_LOG_I(TAG, "Successfully parsed %d files.", file_count);
+ return save_char_with_path(file_count_dir, file_count_str);
+}
+
+bool flip_store_install_all_github_files(FlipperHTTP *fhttp, const char *author, const char *repo)
+{
+ FURI_LOG_I(TAG, "Installing all Github files.");
+ if (!fhttp || !author || !repo)
+ {
+ FURI_LOG_E(TAG, "Invalid arguments.");
+ return false;
+ }
+ fhttp->state = RECEIVING;
+ // get the file count
+ char file_count_dir[256]; // /ext/apps_data/flip_store/data/author/file_count.txt
+ snprintf(file_count_dir, sizeof(file_count_dir), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_store/data/%s/file_count.txt", author);
+ FuriString *file_count = flipper_http_load_from_file(file_count_dir);
+ if (file_count == NULL)
+ {
+ FURI_LOG_E(TAG, "Failed to load file count.");
+ return false;
+ }
+ int count = atoi(furi_string_get_cstr(file_count));
+ furi_string_free(file_count);
+
+ // install all files
+ char file_dir[256]; // /ext/apps_data/flip_store/data/author/repo/file.json
+ FURI_LOG_I(TAG, "Installing %d files.", count);
+ for (int i = 0; i < count; i++)
+ {
+ snprintf(file_dir, sizeof(file_dir), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_store/data/%s/%s/file%d.json", author, repo, i);
+ FURI_LOG_I(TAG, "Loading file %s. Available memory: %d bytes", file_dir, memmgr_get_free_heap());
+ FuriString *file = flipper_http_load_from_file_with_limit(file_dir, 512);
+ if (!file)
+ {
+ FURI_LOG_E(TAG, "Failed to load file.");
+ return false;
+ }
+ FURI_LOG_I(TAG, "Loaded file %s.", file_dir);
+ FuriString *name = get_json_value_furi("name", file);
+ if (!name)
+ {
+ FURI_LOG_E(TAG, "Failed to get name.");
+ furi_string_free(file);
+ return false;
+ }
+ FuriString *link = get_json_value_furi("link", file);
+ if (!link)
+ {
+ FURI_LOG_E(TAG, "Failed to get link.");
+ furi_string_free(file);
+ furi_string_free(name);
+ return false;
+ }
+ furi_string_free(file);
+
+ bool fetch_file()
+ {
+ return flip_store_download_github_file(fhttp, furi_string_get_cstr(name), author, repo, furi_string_get_cstr(link));
+ }
+
+ bool parse()
+ {
+ // remove .txt from the filename
+ char current_file_path[512];
+ char new_file_path[512];
+ snprintf(current_file_path, sizeof(current_file_path), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_store/%s/%s/%s.txt", author, repo, furi_string_get_cstr(name));
+ snprintf(new_file_path, sizeof(new_file_path), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_store/%s/%s/%s", author, repo, furi_string_get_cstr(name));
+ Storage *storage = furi_record_open(RECORD_STORAGE);
+ if (!storage_file_exists(storage, current_file_path))
+ {
+ FURI_LOG_E(TAG, "Failed to download file.");
+ furi_record_close(RECORD_STORAGE);
+ return false;
+ }
+ if (storage_common_rename(storage, current_file_path, new_file_path) != FSE_OK)
+ {
+ FURI_LOG_E(TAG, "Failed to rename file.");
+ furi_record_close(RECORD_STORAGE);
+ return false;
+ }
+ furi_record_close(RECORD_STORAGE);
+ return true;
+ }
+ // download the file and wait until it is downloaded
+ FURI_LOG_I(TAG, "Downloading file %s", furi_string_get_cstr(name));
+ if (!flipper_http_process_response_async(fhttp, fetch_file, parse))
+ {
+ FURI_LOG_E(TAG, "Failed to download file.");
+ furi_string_free(name);
+ furi_string_free(link);
+ return false;
+ }
+ FURI_LOG_I(TAG, "Downloaded file %s", furi_string_get_cstr(name));
+ furi_string_free(name);
+ furi_string_free(link);
+ }
+ fhttp->state = IDLE;
+ return true;
+}
\ No newline at end of file
diff --git a/flip_store/github/flip_store_github.h b/flip_store/github/flip_store_github.h
new file mode 100644
index 000000000..7f9d4564d
--- /dev/null
+++ b/flip_store/github/flip_store_github.h
@@ -0,0 +1,26 @@
+#pragma once
+#include
+
+/*
+I did try downloading a zip file from Github but a few issues
+- unsure how to unzip the file
+- either Github redirected us to a loading page, or their was no response on if using an IoT device
+- any contributions to this would be appreciated
+*/
+
+// Helper to download a file from Github and save it to the storage
+bool flip_store_download_github_file(
+ FlipperHTTP *fhttp,
+ const char *filename,
+ const char *author,
+ const char *repo,
+ const char *link);
+
+// Helper to get the contents of a Github repo (from https://api.github.com/repos/author/repo/contents)
+bool flip_store_get_github_contents(FlipperHTTP *fhttp, const char *author, const char *repo);
+
+// Helper to parse the contents of a Github repo (takes parts of the data and saves it for easy access later)
+bool flip_store_parse_github_contents(char *file_path, const char *author, const char *repo);
+
+// Helper to install all files from the parsed Github repo contents in flip_store_parse_github_contents
+bool flip_store_install_all_github_files(FlipperHTTP *fhttp, const char *author, const char *repo);
\ No newline at end of file
diff --git a/flip_store/jsmn/jsmn.c b/flip_store/jsmn/jsmn.c
index b85ab83b5..d101530c6 100644
--- a/flip_store/jsmn/jsmn.c
+++ b/flip_store/jsmn/jsmn.c
@@ -7,8 +7,6 @@
*/
#include
-#include
-#include
/**
* Allocates a fresh unused token from the token pool.
@@ -424,7 +422,7 @@ int jsmn_parse(jsmn_parser *parser, const char *js, const size_t len,
}
// Helper function to create a JSON object
-char *jsmn(const char *key, const char *value)
+char *get_json(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
@@ -448,14 +446,19 @@ 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)
+char *get_json_value(char *key, const char *json_data)
{
// Parse the JSON feed
if (json_data != NULL)
{
jsmn_parser parser;
jsmn_init(&parser);
-
+ uint32_t max_tokens = json_token_count(json_data);
+ if (!jsmn_memory_check(max_tokens))
+ {
+ FURI_LOG_E("JSMM.H", "Insufficient memory for JSON tokens.");
+ return NULL;
+ }
// Allocate tokens array on the heap
jsmntok_t *tokens = malloc(sizeof(jsmntok_t) * max_tokens);
if (tokens == NULL)
@@ -510,26 +513,79 @@ char *get_json_value(char *key, char *json_data, uint32_t max_tokens)
{
FURI_LOG_E("JSMM.H", "JSON data is NULL");
}
- FURI_LOG_E("JSMM.H", "Failed to find the key in the JSON.");
+ char warning[128];
+ snprintf(warning, sizeof(warning), "Failed to find the key \"%s\" in the JSON.", key);
+ FURI_LOG_E("JSMM.H", warning);
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)
+// Helper function to skip a token and all its descendants.
+// Returns the index of the next token after skipping this one.
+// On error or out of bounds, returns -1.
+static int skip_token(const jsmntok_t *tokens, int start, int total)
{
- // Retrieve the array string for the given key
- char *array_str = get_json_value(key, json_data, max_tokens);
+ if (start < 0 || start >= total)
+ return -1;
+
+ int i = start;
+ if (tokens[i].type == JSMN_OBJECT)
+ {
+ // For an object: size is number of key-value pairs
+ int pairs = tokens[i].size;
+ i++; // move to first key-value pair
+ for (int p = 0; p < pairs; p++)
+ {
+ // skip key (primitive/string)
+ i++;
+ if (i >= total)
+ return -1;
+ // skip value (which could be object/array and must be skipped recursively)
+ i = skip_token(tokens, i, total);
+ if (i == -1)
+ return -1;
+ }
+ return i; // i is now just past the object
+ }
+ else if (tokens[i].type == JSMN_ARRAY)
+ {
+ // For an array: size is number of elements
+ int elems = tokens[i].size;
+ i++; // move to first element
+ for (int e = 0; e < elems; e++)
+ {
+ i = skip_token(tokens, i, total);
+ if (i == -1)
+ return -1;
+ }
+ return i; // i is now just past the array
+ }
+ else
+ {
+ // Primitive or string token, just skip it
+ return i + 1;
+ }
+}
+
+// Revised get_json_array_value
+char *get_json_array_value(char *key, uint32_t index, const char *json_data)
+{
+ // Always extract the full array each time from the original json_data
+ char *array_str = get_json_value(key, json_data);
if (array_str == NULL)
{
FURI_LOG_E("JSMM.H", "Failed to get array for key: %s", key);
return NULL;
}
+ uint32_t max_tokens = json_token_count(array_str);
+ if (!jsmn_memory_check(max_tokens))
+ {
+ FURI_LOG_E("JSMM.H", "Insufficient memory for JSON tokens.");
+ free(array_str);
+ 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)
{
@@ -538,7 +594,6 @@ char *get_json_array_value(char *key, uint32_t index, char *json_data, uint32_t
return NULL;
}
- // Parse the JSON array
int ret = jsmn_parse(&parser, array_str, strlen(array_str), tokens, max_tokens);
if (ret < 0)
{
@@ -548,7 +603,6 @@ char *get_json_array_value(char *key, uint32_t index, char *json_data, uint32_t
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);
@@ -557,50 +611,33 @@ char *get_json_array_value(char *key, uint32_t index, char *json_data, uint32_t
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);
+ // FURI_LOG_E("JSMM.H", "Index %lu out of bounds for array with size %u.", 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
+ // Find the index-th element: start from token[1], which is the first element
+ int elem_token = 1;
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
+ elem_token = skip_token(tokens, elem_token, ret);
+ if (elem_token == -1 || elem_token >= ret)
{
- // 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.");
+ FURI_LOG_E("JSMM.H", "Error skipping tokens to reach element %lu.", i);
free(tokens);
free(array_str);
return NULL;
}
}
- // Extract the array element
- jsmntok_t element = tokens[current_token];
+ // Now elem_token should point to the token of the requested element
+ jsmntok_t element = tokens[elem_token];
int length = element.end - element.start;
char *value = malloc(length + 1);
- if (value == NULL)
+ if (!value)
{
FURI_LOG_E("JSMM.H", "Failed to allocate memory for array element.");
free(tokens);
@@ -608,11 +645,9 @@ char *get_json_array_value(char *key, uint32_t index, char *json_data, uint32_t
return NULL;
}
- // Copy the element value to a new string
strncpy(value, array_str + element.start, length);
- value[length] = '\0'; // Null-terminate the string
+ value[length] = '\0';
- // Clean up
free(tokens);
free(array_str);
@@ -620,16 +655,22 @@ char *get_json_array_value(char *key, uint32_t index, char *json_data, uint32_t
}
// 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)
+char **get_json_array_values(char *key, char *json_data, int *num_values)
{
// Retrieve the array string for the given key
- char *array_str = get_json_value(key, json_data, max_tokens);
+ char *array_str = get_json_value(key, json_data);
if (array_str == NULL)
{
FURI_LOG_E("JSMM.H", "Failed to get array for key: %s", key);
return NULL;
}
-
+ uint32_t max_tokens = json_token_count(array_str);
+ if (!jsmn_memory_check(max_tokens))
+ {
+ FURI_LOG_E("JSMM.H", "Insufficient memory for JSON tokens.");
+ free(array_str);
+ return NULL;
+ }
// Initialize the JSON parser
jsmn_parser parser;
jsmn_init(&parser);
@@ -745,3 +786,18 @@ char **get_json_array_values(char *key, char *json_data, uint32_t max_tokens, in
free(array_str);
return values;
}
+
+int json_token_count(const char *json)
+{
+ if (json == NULL)
+ {
+ return JSMN_ERROR_INVAL;
+ }
+
+ jsmn_parser parser;
+ jsmn_init(&parser);
+
+ // Pass NULL for tokens and 0 for num_tokens to get the token count only
+ int ret = jsmn_parse(&parser, json, strlen(json), NULL, 0);
+ return ret; // If ret >= 0, it represents the number of tokens needed.
+}
diff --git a/flip_store/jsmn/jsmn.h b/flip_store/jsmn/jsmn.h
index 74cdccf95..5f96e5596 100644
--- a/flip_store/jsmn/jsmn.h
+++ b/flip_store/jsmn/jsmn.h
@@ -17,6 +17,7 @@
#define JSMN_H
#include
+#include
#ifdef __cplusplus
extern "C"
@@ -28,61 +29,6 @@ extern "C"
#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
*/
@@ -110,23 +56,19 @@ extern "C"
#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);
+char *get_json(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);
+char *get_json_value(char *key, const char *json_data);
// Revised get_json_array_value function
-char *get_json_array_value(char *key, uint32_t index, char *json_data, uint32_t max_tokens);
+char *get_json_array_value(char *key, uint32_t index, const char *json_data);
// 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);
+char **get_json_array_values(char *key, char *json_data, int *num_values);
+
+int json_token_count(const char *json);
#endif /* JB_JSMN_EDIT */
diff --git a/flip_store/jsmn/jsmn_furi.c b/flip_store/jsmn/jsmn_furi.c
new file mode 100644
index 000000000..0eab40c99
--- /dev/null
+++ b/flip_store/jsmn/jsmn_furi.c
@@ -0,0 +1,736 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2010 Serge Zaitsev
+ *
+ * [License text continues...]
+ */
+
+#include
+
+// Forward declarations of helper functions
+static int jsoneq_furi(const FuriString *json, jsmntok_t *tok, const FuriString *s);
+static int skip_token(const jsmntok_t *tokens, int start, int total);
+
+/**
+ * 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)
+{
+ if (parser->toknext >= num_tokens)
+ {
+ return NULL;
+ }
+ jsmntok_t *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.
+ * Now uses FuriString to access characters.
+ */
+static int jsmn_parse_primitive(jsmn_parser *parser, const FuriString *js,
+ jsmntok_t *tokens, const size_t num_tokens)
+{
+ size_t len = furi_string_size(js);
+ int start = parser->pos;
+
+ for (; parser->pos < len; parser->pos++)
+ {
+ char c = furi_string_get_char(js, parser->pos);
+ switch (c)
+ {
+#ifndef JSMN_STRICT
+ case ':':
+#endif
+ case '\t':
+ case '\r':
+ case '\n':
+ case ' ':
+ case ',':
+ case ']':
+ case '}':
+ goto found;
+ default:
+ break;
+ }
+ if (c < 32 || c >= 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;
+ }
+ jsmntok_t *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.
+ * Now uses FuriString to access characters.
+ */
+static int jsmn_parse_string(jsmn_parser *parser, const FuriString *js,
+ jsmntok_t *tokens, const size_t num_tokens)
+{
+ size_t len = furi_string_size(js);
+ int start = parser->pos;
+ parser->pos++;
+
+ for (; parser->pos < len; parser->pos++)
+ {
+ char c = furi_string_get_char(js, parser->pos);
+ if (c == '\"')
+ {
+ if (tokens == NULL)
+ {
+ return 0;
+ }
+ jsmntok_t *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;
+ }
+
+ if (c == '\\' && (parser->pos + 1) < len)
+ {
+ parser->pos++;
+ char esc = furi_string_get_char(js, parser->pos);
+ switch (esc)
+ {
+ case '\"':
+ case '/':
+ case '\\':
+ case 'b':
+ case 'f':
+ case 'r':
+ case 'n':
+ case 't':
+ break;
+ case 'u':
+ {
+ parser->pos++;
+ for (int i = 0; i < 4 && parser->pos < len; i++)
+ {
+ char hex = furi_string_get_char(js, parser->pos);
+ if (!((hex >= '0' && hex <= '9') ||
+ (hex >= 'A' && hex <= 'F') ||
+ (hex >= 'a' && hex <= 'f')))
+ {
+ parser->pos = start;
+ return JSMN_ERROR_INVAL;
+ }
+ parser->pos++;
+ }
+ parser->pos--;
+ break;
+ }
+ default:
+ parser->pos = start;
+ return JSMN_ERROR_INVAL;
+ }
+ }
+ }
+ parser->pos = start;
+ return JSMN_ERROR_PART;
+}
+
+/**
+ * Create JSON parser
+ */
+void jsmn_init_furi(jsmn_parser *parser)
+{
+ parser->pos = 0;
+ parser->toknext = 0;
+ parser->toksuper = -1;
+}
+
+/**
+ * Parse JSON string and fill tokens.
+ * Now uses FuriString for the input JSON.
+ */
+int jsmn_parse_furi(jsmn_parser *parser, const FuriString *js,
+ jsmntok_t *tokens, const unsigned int num_tokens)
+{
+ size_t len = furi_string_size(js);
+ int r;
+ int i;
+ int count = parser->toknext;
+
+ for (; parser->pos < len; parser->pos++)
+ {
+ char c = furi_string_get_char(js, parser->pos);
+ jsmntype_t type;
+
+ switch (c)
+ {
+ case '{':
+ case '[':
+ {
+ count++;
+ if (tokens == NULL)
+ {
+ break;
+ }
+ jsmntok_t *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
+ 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;
+ }
+ {
+ jsmntok_t *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
+ {
+ jsmntok_t *token;
+ 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;
+ }
+ }
+ 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, 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 ' ':
+ // Whitespace - ignore
+ 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
+ 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':
+ 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
+ default:
+#endif
+ r = jsmn_parse_primitive(parser, js, tokens, num_tokens);
+ if (r < 0)
+ return r;
+ count++;
+ if (parser->toksuper != -1 && tokens != NULL)
+ {
+ tokens[parser->toksuper].size++;
+ }
+ break;
+#ifdef JSMN_STRICT
+ default:
+ return JSMN_ERROR_INVAL;
+#endif
+ }
+ }
+
+ if (tokens != NULL)
+ {
+ for (i = parser->toknext - 1; i >= 0; i--)
+ {
+ if (tokens[i].start != -1 && tokens[i].end == -1)
+ {
+ return JSMN_ERROR_PART;
+ }
+ }
+ }
+
+ return count;
+}
+
+// Helper function to create a JSON object: {"key":"value"}
+FuriString *get_json_furi(const FuriString *key, const FuriString *value)
+{
+ FuriString *result = furi_string_alloc();
+ furi_string_printf(result, "{\"%s\":\"%s\"}",
+ furi_string_get_cstr(key),
+ furi_string_get_cstr(value));
+ return result; // Caller responsible for furi_string_free
+}
+
+// Helper function to compare JSON keys
+static int jsoneq_furi(const FuriString *json, jsmntok_t *tok, const FuriString *s)
+{
+ size_t s_len = furi_string_size(s);
+ size_t tok_len = tok->end - tok->start;
+
+ if (tok->type != JSMN_STRING)
+ return -1;
+ if (s_len != tok_len)
+ return -1;
+
+ FuriString *sub = furi_string_alloc_set(json);
+ furi_string_mid(sub, tok->start, tok_len);
+
+ int res = furi_string_cmp(sub, s);
+ furi_string_free(sub);
+
+ return (res == 0) ? 0 : -1;
+}
+
+// Skip a token and its descendants
+static int skip_token(const jsmntok_t *tokens, int start, int total)
+{
+ if (start < 0 || start >= total)
+ return -1;
+
+ int i = start;
+ if (tokens[i].type == JSMN_OBJECT)
+ {
+ int pairs = tokens[i].size;
+ i++;
+ for (int p = 0; p < pairs; p++)
+ {
+ i++; // skip key
+ if (i >= total)
+ return -1;
+ i = skip_token(tokens, i, total); // skip value
+ if (i == -1)
+ return -1;
+ }
+ return i;
+ }
+ else if (tokens[i].type == JSMN_ARRAY)
+ {
+ int elems = tokens[i].size;
+ i++;
+ for (int e = 0; e < elems; e++)
+ {
+ i = skip_token(tokens, i, total);
+ if (i == -1)
+ return -1;
+ }
+ return i;
+ }
+ else
+ {
+ return i + 1;
+ }
+}
+
+/**
+ * Parse JSON and return the value associated with a given char* key.
+ */
+FuriString *get_json_value_furi(const char *key, const FuriString *json_data)
+{
+ if (json_data == NULL)
+ {
+ FURI_LOG_E("JSMM.H", "JSON data is NULL");
+ return NULL;
+ }
+ uint32_t max_tokens = json_token_count_furi(json_data);
+ if (!jsmn_memory_check(sizeof(jsmntok_t) * max_tokens))
+ {
+ FURI_LOG_E("JSMM.H", "Insufficient memory for JSON tokens.");
+ return NULL;
+ }
+ // Create a temporary FuriString from key
+ FuriString *key_str = furi_string_alloc();
+ furi_string_cat_str(key_str, key);
+
+ jsmn_parser parser;
+ jsmn_init_furi(&parser);
+
+ jsmntok_t *tokens = (jsmntok_t *)malloc(sizeof(jsmntok_t) * max_tokens);
+ if (tokens == NULL)
+ {
+ FURI_LOG_E("JSMM.H", "Failed to allocate memory for JSON tokens.");
+ furi_string_free(key_str);
+ return NULL;
+ }
+
+ int ret = jsmn_parse_furi(&parser, json_data, tokens, max_tokens);
+ if (ret < 0)
+ {
+ FURI_LOG_E("JSMM.H", "Failed to parse JSON: %d", ret);
+ free(tokens);
+ furi_string_free(key_str);
+ return NULL;
+ }
+
+ if (ret < 1 || tokens[0].type != JSMN_OBJECT)
+ {
+ FURI_LOG_E("JSMM.H", "Root element is not an object.");
+ free(tokens);
+ furi_string_free(key_str);
+ return NULL;
+ }
+
+ for (int i = 1; i < ret; i++)
+ {
+ if (jsoneq_furi(json_data, &tokens[i], key_str) == 0)
+ {
+ int length = tokens[i + 1].end - tokens[i + 1].start;
+ FuriString *value = furi_string_alloc_set(json_data);
+ furi_string_mid(value, tokens[i + 1].start, length);
+ free(tokens);
+ furi_string_free(key_str);
+ return value;
+ }
+ }
+
+ free(tokens);
+ furi_string_free(key_str);
+ char warning[128];
+ snprintf(warning, sizeof(warning), "Failed to find the key \"%s\" in the JSON.", key);
+ FURI_LOG_E("JSMM.H", warning);
+ return NULL;
+}
+
+/**
+ * Return the value at a given index in a JSON array for a given char* key.
+ */
+FuriString *get_json_array_value_furi(const char *key, uint32_t index, const FuriString *json_data)
+{
+ FuriString *array_str = get_json_value_furi(key, json_data);
+ if (array_str == NULL)
+ {
+ FURI_LOG_E("JSMM.H", "Failed to get array for key");
+ return NULL;
+ }
+ uint32_t max_tokens = json_token_count_furi(array_str);
+ if (!jsmn_memory_check(sizeof(jsmntok_t) * max_tokens))
+ {
+ FURI_LOG_E("JSMM.H", "Insufficient memory for JSON tokens.");
+ furi_string_free(array_str);
+ return NULL;
+ }
+ jsmn_parser parser;
+ jsmn_init_furi(&parser);
+
+ jsmntok_t *tokens = (jsmntok_t *)malloc(sizeof(jsmntok_t) * max_tokens);
+ if (tokens == NULL)
+ {
+ FURI_LOG_E("JSMM.H", "Failed to allocate memory for JSON tokens.");
+ furi_string_free(array_str);
+ return NULL;
+ }
+
+ int ret = jsmn_parse_furi(&parser, array_str, tokens, max_tokens);
+ if (ret < 0)
+ {
+ FURI_LOG_E("JSMM.H", "Failed to parse JSON array: %d", ret);
+ free(tokens);
+ furi_string_free(array_str);
+ return NULL;
+ }
+
+ if (ret < 1 || tokens[0].type != JSMN_ARRAY)
+ {
+ FURI_LOG_E("JSMM.H", "Value for key is not an array.");
+ free(tokens);
+ furi_string_free(array_str);
+ return NULL;
+ }
+
+ if (index >= (uint32_t)tokens[0].size)
+ {
+ // FURI_LOG_E("JSMM.H", "Index %lu out of bounds for array with size %u.", index, tokens[0].size);
+ free(tokens);
+ furi_string_free(array_str);
+ return NULL;
+ }
+
+ int elem_token = 1;
+ for (uint32_t i = 0; i < index; i++)
+ {
+ elem_token = skip_token(tokens, elem_token, ret);
+ if (elem_token == -1 || elem_token >= ret)
+ {
+ FURI_LOG_E("JSMM.H", "Error skipping tokens to reach element %lu.", i);
+ free(tokens);
+ furi_string_free(array_str);
+ return NULL;
+ }
+ }
+
+ jsmntok_t element = tokens[elem_token];
+ int length = element.end - element.start;
+
+ FuriString *value = furi_string_alloc_set(array_str);
+ furi_string_mid(value, element.start, length);
+
+ free(tokens);
+ furi_string_free(array_str);
+
+ return value;
+}
+
+/**
+ * Extract all object values from a JSON array associated with a given char* key.
+ */
+FuriString **get_json_array_values_furi(const char *key, const FuriString *json_data, int *num_values)
+{
+ *num_values = 0;
+ // Convert key to FuriString and call get_json_value_furi
+ FuriString *array_str = get_json_value_furi(key, json_data);
+ if (array_str == NULL)
+ {
+ FURI_LOG_E("JSMM.H", "Failed to get array for key");
+ return NULL;
+ }
+
+ uint32_t max_tokens = json_token_count_furi(array_str);
+ if (!jsmn_memory_check(sizeof(jsmntok_t) * max_tokens))
+ {
+ FURI_LOG_E("JSMM.H", "Insufficient memory for JSON tokens.");
+ furi_string_free(array_str);
+ return NULL;
+ }
+ jsmn_parser parser;
+ jsmn_init_furi(&parser);
+
+ jsmntok_t *tokens = (jsmntok_t *)malloc(sizeof(jsmntok_t) * max_tokens);
+ if (tokens == NULL)
+ {
+ FURI_LOG_E("JSMM.H", "Failed to allocate memory for JSON tokens.");
+ furi_string_free(array_str);
+ return NULL;
+ }
+
+ int ret = jsmn_parse_furi(&parser, array_str, tokens, max_tokens);
+ if (ret < 0)
+ {
+ FURI_LOG_E("JSMM.H", "Failed to parse JSON array: %d", ret);
+ free(tokens);
+ furi_string_free(array_str);
+ return NULL;
+ }
+
+ if (tokens[0].type != JSMN_ARRAY)
+ {
+ FURI_LOG_E("JSMM.H", "Value for key is not an array.");
+ free(tokens);
+ furi_string_free(array_str);
+ return NULL;
+ }
+
+ int array_size = tokens[0].size;
+ FuriString **values = (FuriString **)malloc(array_size * sizeof(FuriString *));
+ if (values == NULL)
+ {
+ FURI_LOG_E("JSMM.H", "Failed to allocate memory for array of values.");
+ free(tokens);
+ furi_string_free(array_str);
+ return NULL;
+ }
+
+ int actual_num_values = 0;
+ int current_token = 1;
+ 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];
+
+ int length = element.end - element.start;
+ FuriString *value = furi_string_alloc_set(array_str);
+ furi_string_mid(value, element.start, length);
+
+ values[actual_num_values] = value;
+ actual_num_values++;
+
+ // Skip this element and its descendants
+ current_token = skip_token(tokens, current_token, ret);
+ if (current_token == -1)
+ {
+ FURI_LOG_E("JSMM.H", "Error skipping tokens after element %d.", i);
+ break;
+ }
+ }
+
+ *num_values = actual_num_values;
+ if (actual_num_values < array_size)
+ {
+ FuriString **reduced_values = (FuriString **)realloc(values, actual_num_values * sizeof(FuriString *));
+ if (reduced_values != NULL)
+ {
+ values = reduced_values;
+ }
+ }
+
+ free(tokens);
+ furi_string_free(array_str);
+ return values;
+}
+
+uint32_t json_token_count_furi(const FuriString *json)
+{
+ if (json == NULL)
+ {
+ return JSMN_ERROR_INVAL;
+ }
+
+ jsmn_parser parser;
+ jsmn_init_furi(&parser);
+
+ // Pass NULL for tokens and 0 for num_tokens to get the token count only
+ int ret = jsmn_parse_furi(&parser, json, NULL, 0);
+ return ret; // If ret >= 0, it represents the number of tokens needed.
+}
diff --git a/flip_store/jsmn/jsmn_furi.h b/flip_store/jsmn/jsmn_furi.h
new file mode 100644
index 000000000..cb01f38ca
--- /dev/null
+++ b/flip_store/jsmn/jsmn_furi.h
@@ -0,0 +1,74 @@
+/*
+ * 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_FURI_H
+#define JSMN_FURI_H
+
+#include
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+#ifdef JSMN_STATIC
+#define JSMN_API static
+#else
+#define JSMN_API extern
+#endif
+
+ JSMN_API void jsmn_init_furi(jsmn_parser *parser);
+ JSMN_API int jsmn_parse_furi(jsmn_parser *parser, const FuriString *js,
+ jsmntok_t *tokens, const unsigned int num_tokens);
+
+#ifndef JSMN_HEADER
+/* Implementation in jsmn_furi.c */
+#endif /* JSMN_HEADER */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* JSMN_FURI_H */
+
+#ifndef JB_JSMN_FURI_EDIT
+#define JB_JSMN_FURI_EDIT
+
+// Helper function to create a JSON object
+FuriString *get_json_furi(const FuriString *key, const FuriString *value);
+
+// Updated signatures to accept const char* key
+FuriString *get_json_value_furi(const char *key, const FuriString *json_data);
+FuriString *get_json_array_value_furi(const char *key, uint32_t index, const FuriString *json_data);
+FuriString **get_json_array_values_furi(const char *key, const FuriString *json_data, int *num_values);
+
+uint32_t json_token_count_furi(const FuriString *json);
+/* Example usage:
+char *json = "{\"key1\":\"value1\",\"key2\":\"value2\"}";
+FuriString *json_data = char_to_furi_string(json);
+if (!json_data)
+{
+ FURI_LOG_E(TAG, "Failed to allocate FuriString");
+ return -1;
+}
+FuriString *value = get_json_value_furi("key1", json_data, json_token_count_furi(json_data));
+if (value)
+{
+ FURI_LOG_I(TAG, "Value: %s", furi_string_get_cstr(value));
+ furi_string_free(value);
+}
+furi_string_free(json_data);
+*/
+#endif /* JB_JSMN_EDIT */
diff --git a/flip_store/jsmn/jsmn_h.c b/flip_store/jsmn/jsmn_h.c
new file mode 100644
index 000000000..59480e6e6
--- /dev/null
+++ b/flip_store/jsmn/jsmn_h.c
@@ -0,0 +1,15 @@
+#include
+FuriString *char_to_furi_string(const char *str)
+{
+ FuriString *furi_str = furi_string_alloc();
+ if (!furi_str)
+ {
+ return NULL;
+ }
+ for (size_t i = 0; i < strlen(str); i++)
+ {
+ furi_string_push_back(furi_str, str[i]);
+ }
+ return furi_str;
+}
+bool jsmn_memory_check(size_t heap_size) { return memmgr_get_free_heap() > (heap_size + 1024); }
\ No newline at end of file
diff --git a/flip_store/jsmn/jsmn_h.h b/flip_store/jsmn/jsmn_h.h
new file mode 100644
index 000000000..97d53e7ff
--- /dev/null
+++ b/flip_store/jsmn/jsmn_h.h
@@ -0,0 +1,56 @@
+#pragma once
+#include
+#include
+#include
+#include
+#include
+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
+{
+ JSMN_ERROR_NOMEM = -1,
+ JSMN_ERROR_INVAL = -2,
+ JSMN_ERROR_PART = -3
+};
+
+typedef struct
+{
+ jsmntype_t type;
+ int start;
+ int end;
+ int size;
+#ifdef JSMN_PARENT_LINKS
+ int parent;
+#endif
+} jsmntok_t;
+
+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;
+
+typedef struct
+{
+ char *key;
+ char *value;
+} JSON;
+
+typedef struct
+{
+ FuriString *key;
+ FuriString *value;
+} FuriJSON;
+
+FuriString *char_to_furi_string(const char *str);
+
+// check memory
+bool jsmn_memory_check(size_t heap_size);
diff --git a/flip_trader/alloc/flip_trader_alloc.c b/flip_trader/alloc/flip_trader_alloc.c
index adfa6c02d..f919e898c 100644
--- a/flip_trader/alloc/flip_trader_alloc.c
+++ b/flip_trader/alloc/flip_trader_alloc.c
@@ -1,13 +1,15 @@
#include "alloc/flip_trader_alloc.h"
// Function to allocate resources for the FlipTraderApp
-FlipTraderApp* flip_trader_app_alloc() {
- FlipTraderApp* app = (FlipTraderApp*)malloc(sizeof(FlipTraderApp));
+FlipTraderApp *flip_trader_app_alloc()
+{
+ FlipTraderApp *app = (FlipTraderApp *)malloc(sizeof(FlipTraderApp));
- Gui* gui = furi_record_open(RECORD_GUI);
+ Gui *gui = furi_record_open(RECORD_GUI);
// initialize uart
- if(!flipper_http_init(flipper_http_rx_callback, app)) {
+ if (!flipper_http_init(flipper_http_rx_callback, app))
+ {
FURI_LOG_E(TAG, "Failed to initialize flipper http");
return NULL;
}
@@ -15,170 +17,108 @@ FlipTraderApp* flip_trader_app_alloc() {
// Allocate the text input buffer
app->uart_text_input_buffer_size_ssid = 64;
app->uart_text_input_buffer_size_password = 64;
- if(!easy_flipper_set_buffer(
- &app->uart_text_input_buffer_ssid, app->uart_text_input_buffer_size_ssid)) {
+ if (!easy_flipper_set_buffer(&app->uart_text_input_buffer_ssid, app->uart_text_input_buffer_size_ssid))
+ {
return NULL;
}
- if(!easy_flipper_set_buffer(
- &app->uart_text_input_temp_buffer_ssid, app->uart_text_input_buffer_size_ssid)) {
+ if (!easy_flipper_set_buffer(&app->uart_text_input_temp_buffer_ssid, app->uart_text_input_buffer_size_ssid))
+ {
return NULL;
}
- if(!easy_flipper_set_buffer(
- &app->uart_text_input_buffer_password, app->uart_text_input_buffer_size_password)) {
+ if (!easy_flipper_set_buffer(&app->uart_text_input_buffer_password, app->uart_text_input_buffer_size_password))
+ {
return NULL;
}
- if(!easy_flipper_set_buffer(
- &app->uart_text_input_temp_buffer_password,
- app->uart_text_input_buffer_size_password)) {
+ if (!easy_flipper_set_buffer(&app->uart_text_input_temp_buffer_password, app->uart_text_input_buffer_size_password))
+ {
return NULL;
}
asset_names = asset_names_alloc();
- if(!asset_names) {
+ if (!asset_names)
+ {
return NULL;
}
// Allocate ViewDispatcher
- if(!easy_flipper_set_view_dispatcher(&app->view_dispatcher, gui, app)) {
+ if (!easy_flipper_set_view_dispatcher(&app->view_dispatcher, gui, app))
+ {
return NULL;
}
- view_dispatcher_set_custom_event_callback(
- app->view_dispatcher, flip_trader_custom_event_callback);
+ view_dispatcher_set_custom_event_callback(app->view_dispatcher, flip_trader_custom_event_callback);
// Main view
- if(!easy_flipper_set_view(
- &app->view_loader,
- FlipTraderViewLoader,
- flip_trader_loader_draw_callback,
- NULL,
- callback_to_assets_submenu,
- &app->view_dispatcher,
- app)) {
+ if (!easy_flipper_set_view(&app->view_loader, FlipTraderViewLoader, flip_trader_loader_draw_callback, NULL, callback_to_assets_submenu, &app->view_dispatcher, app))
+ {
return NULL;
}
flip_trader_loader_init(app->view_loader);
// Widget
- if(!easy_flipper_set_widget(
- &app->widget_about,
- FlipTraderViewAbout,
- "FlipTrader v1.2\n-----\nUse WiFi to get the price of\nstocks and currency pairs.\n-----\nwww.github.com/jblanked",
- callback_to_submenu,
- &app->view_dispatcher)) {
+ if (!easy_flipper_set_widget(&app->widget_about, FlipTraderViewAbout, "FlipTrader v1.2\n-----\nUse WiFi to get the price of\nstocks and currency pairs.\n-----\nwww.github.com/jblanked", callback_to_submenu, &app->view_dispatcher))
+ {
return NULL;
}
- if(!easy_flipper_set_widget(
- &app->widget_result,
- FlipTraderViewWidgetResult,
- "Error, try again.",
- callback_to_assets_submenu,
- &app->view_dispatcher)) {
+ if (!easy_flipper_set_widget(&app->widget_result, FlipTraderViewWidgetResult, "Error, try again.", callback_to_assets_submenu, &app->view_dispatcher))
+ {
return NULL;
}
// Text Input
- if(!easy_flipper_set_uart_text_input(
- &app->uart_text_input_ssid,
- FlipTraderViewTextInputSSID,
- "Enter SSID",
- app->uart_text_input_temp_buffer_ssid,
- app->uart_text_input_buffer_size_ssid,
- text_updated_ssid,
- callback_to_wifi_settings,
- &app->view_dispatcher,
- app)) {
+ if (!easy_flipper_set_uart_text_input(&app->uart_text_input_ssid, FlipTraderViewTextInputSSID, "Enter SSID", app->uart_text_input_temp_buffer_ssid, app->uart_text_input_buffer_size_ssid, text_updated_ssid, callback_to_wifi_settings, &app->view_dispatcher, app))
+ {
return NULL;
}
- if(!easy_flipper_set_uart_text_input(
- &app->uart_text_input_password,
- FlipTraderViewTextInputPassword,
- "Enter password",
- app->uart_text_input_temp_buffer_password,
- app->uart_text_input_buffer_size_password,
- text_updated_password,
- callback_to_wifi_settings,
- &app->view_dispatcher,
- app)) {
+ if (!easy_flipper_set_uart_text_input(&app->uart_text_input_password, FlipTraderViewTextInputPassword, "Enter password", app->uart_text_input_temp_buffer_password, app->uart_text_input_buffer_size_password, text_updated_password, callback_to_wifi_settings, &app->view_dispatcher, app))
+ {
return NULL;
}
// Variable Item List
- if(!easy_flipper_set_variable_item_list(
- &app->variable_item_list_wifi,
- FlipTraderViewWiFiSettings,
- settings_item_selected,
- callback_to_submenu,
- &app->view_dispatcher,
- app)) {
+ if (!easy_flipper_set_variable_item_list(&app->variable_item_list_wifi, FlipTraderViewWiFiSettings, settings_item_selected, callback_to_submenu, &app->view_dispatcher, app))
+ {
return NULL;
}
- app->variable_item_ssid =
- variable_item_list_add(app->variable_item_list_wifi, "SSID", 0, NULL, NULL);
- app->variable_item_password =
- variable_item_list_add(app->variable_item_list_wifi, "Password", 0, NULL, NULL);
+ app->variable_item_ssid = variable_item_list_add(app->variable_item_list_wifi, "SSID", 0, NULL, NULL);
+ app->variable_item_password = variable_item_list_add(app->variable_item_list_wifi, "Password", 0, NULL, NULL);
variable_item_set_current_value_text(app->variable_item_ssid, "");
variable_item_set_current_value_text(app->variable_item_password, "");
// Submenu
- if(!easy_flipper_set_submenu(
- &app->submenu_main,
- FlipTraderViewMainSubmenu,
- "FlipTrader v1.2",
- easy_flipper_callback_exit_app,
- &app->view_dispatcher)) {
+ if (!easy_flipper_set_submenu(&app->submenu_main, FlipTraderViewMainSubmenu, "FlipTrader v1.2", easy_flipper_callback_exit_app, &app->view_dispatcher))
+ {
return NULL;
}
- if(!easy_flipper_set_submenu(
- &app->submenu_assets,
- FlipTraderViewAssetsSubmenu,
- "Assets",
- callback_to_submenu,
- &app->view_dispatcher)) {
+ if (!easy_flipper_set_submenu(&app->submenu_assets, FlipTraderViewAssetsSubmenu, "Assets", callback_to_submenu, &app->view_dispatcher))
+ {
return NULL;
}
- submenu_add_item(
- app->submenu_main, "Assets", FlipTradeSubmenuIndexAssets, callback_submenu_choices, app);
- submenu_add_item(
- app->submenu_main, "About", FlipTraderSubmenuIndexAbout, callback_submenu_choices, app);
- submenu_add_item(
- app->submenu_main, "WiFi", FlipTraderSubmenuIndexSettings, callback_submenu_choices, app);
+ submenu_add_item(app->submenu_main, "Assets", FlipTradeSubmenuIndexAssets, callback_submenu_choices, app);
+ submenu_add_item(app->submenu_main, "About", FlipTraderSubmenuIndexAbout, callback_submenu_choices, app);
+ submenu_add_item(app->submenu_main, "WiFi", FlipTraderSubmenuIndexSettings, callback_submenu_choices, app);
// add the assets
- for(uint32_t i = 0; i < ASSET_COUNT; i++) {
- submenu_add_item(
- app->submenu_assets,
- asset_names[i],
- FlipTraderSubmenuIndexAssetStartIndex + i,
- callback_submenu_choices,
- app);
+ for (uint32_t i = 0; i < ASSET_COUNT; i++)
+ {
+ submenu_add_item(app->submenu_assets, asset_names[i], FlipTraderSubmenuIndexAssetStartIndex + i, callback_submenu_choices, app);
}
// load settings
- if(load_settings(
- app->uart_text_input_buffer_ssid,
- app->uart_text_input_buffer_size_ssid,
- app->uart_text_input_buffer_password,
- app->uart_text_input_buffer_size_password)) {
+ if (load_settings(app->uart_text_input_buffer_ssid, app->uart_text_input_buffer_size_ssid, app->uart_text_input_buffer_password, app->uart_text_input_buffer_size_password))
+ {
// Update variable items
- if(app->variable_item_ssid)
- variable_item_set_current_value_text(
- app->variable_item_ssid, app->uart_text_input_buffer_ssid);
+ if (app->variable_item_ssid)
+ variable_item_set_current_value_text(app->variable_item_ssid, app->uart_text_input_buffer_ssid);
// dont show password
// Copy items into their temp buffers with safety checks
- if(app->uart_text_input_buffer_ssid && app->uart_text_input_temp_buffer_ssid) {
- strncpy(
- app->uart_text_input_temp_buffer_ssid,
- app->uart_text_input_buffer_ssid,
- app->uart_text_input_buffer_size_ssid - 1);
- app->uart_text_input_temp_buffer_ssid[app->uart_text_input_buffer_size_ssid - 1] =
- '\0';
+ if (app->uart_text_input_buffer_ssid && app->uart_text_input_temp_buffer_ssid)
+ {
+ strncpy(app->uart_text_input_temp_buffer_ssid, app->uart_text_input_buffer_ssid, app->uart_text_input_buffer_size_ssid - 1);
+ app->uart_text_input_temp_buffer_ssid[app->uart_text_input_buffer_size_ssid - 1] = '\0';
}
- if(app->uart_text_input_buffer_password && app->uart_text_input_temp_buffer_password) {
- strncpy(
- app->uart_text_input_temp_buffer_password,
- app->uart_text_input_buffer_password,
- app->uart_text_input_buffer_size_password - 1);
- app->uart_text_input_temp_buffer_password[app->uart_text_input_buffer_size_password - 1] =
- '\0';
+ if (app->uart_text_input_buffer_password && app->uart_text_input_temp_buffer_password)
+ {
+ strncpy(app->uart_text_input_temp_buffer_password, app->uart_text_input_buffer_password, app->uart_text_input_buffer_size_password - 1);
+ app->uart_text_input_temp_buffer_password[app->uart_text_input_buffer_size_password - 1] = '\0';
}
}
@@ -186,4 +126,4 @@ FlipTraderApp* flip_trader_app_alloc() {
view_dispatcher_switch_to_view(app->view_dispatcher, FlipTraderViewMainSubmenu);
return app;
-}
+}
\ No newline at end of file
diff --git a/flip_trader/alloc/flip_trader_alloc.h b/flip_trader/alloc/flip_trader_alloc.h
index 2108b6319..1381677a0 100644
--- a/flip_trader/alloc/flip_trader_alloc.h
+++ b/flip_trader/alloc/flip_trader_alloc.h
@@ -5,6 +5,6 @@
#include
// Function to allocate resources for the FlipTraderApp
-FlipTraderApp* flip_trader_app_alloc();
+FlipTraderApp *flip_trader_app_alloc();
-#endif // FLIP_TRADER_I_H
+#endif // FLIP_TRADER_I_H
\ No newline at end of file
diff --git a/flip_trader/app.c b/flip_trader/app.c
index ca631933d..e3d2d541a 100644
--- a/flip_trader/app.c
+++ b/flip_trader/app.c
@@ -2,34 +2,40 @@
#include
// Entry point for the FlipTrader application
-int32_t flip_trader_app(void* p) {
+int32_t flip_trader_app(void *p)
+{
// Suppress unused parameter warning
UNUSED(p);
// Initialize the FlipTrader application
app_instance = flip_trader_app_alloc();
- if(!app_instance) {
+ if (!app_instance)
+ {
FURI_LOG_E(TAG, "Failed to allocate FlipTraderApp");
return -1;
}
- if(!flipper_http_ping()) {
+ if (!flipper_http_ping())
+ {
FURI_LOG_E(TAG, "Failed to ping the device");
return -1;
}
- if(app_instance->uart_text_input_buffer_ssid != NULL &&
- app_instance->uart_text_input_buffer_password != NULL) {
+ if (app_instance->uart_text_input_buffer_ssid != NULL &&
+ app_instance->uart_text_input_buffer_password != NULL)
+ {
// Try to wait for pong response.
uint8_t counter = 10;
- while(fhttp.state == INACTIVE && --counter > 0) {
+ while (fhttp.state == INACTIVE && --counter > 0)
+ {
FURI_LOG_D(TAG, "Waiting for PONG");
furi_delay_ms(100);
}
- if(counter == 0) {
- DialogsApp* dialogs = furi_record_open(RECORD_DIALOGS);
- DialogMessage* message = dialog_message_alloc();
+ if (counter == 0)
+ {
+ DialogsApp *dialogs = furi_record_open(RECORD_DIALOGS);
+ DialogMessage *message = dialog_message_alloc();
dialog_message_set_header(
message, "[FlipperHTTP Error]", 64, 0, AlignCenter, AlignTop);
dialog_message_set_text(
diff --git a/flip_trader/callback/flip_trader_callback.c b/flip_trader/callback/flip_trader_callback.c
index 5b105d18c..28582f15f 100644
--- a/flip_trader/callback/flip_trader_callback.c
+++ b/flip_trader/callback/flip_trader_callback.c
@@ -3,8 +3,7 @@
// Below added by Derek Jamison
// FURI_LOG_DEV will log only during app development. Be sure that Settings/System/Log Device is "LPUART"; so we dont use serial port.
#ifdef DEVELOPMENT
-#define FURI_LOG_DEV(tag, format, ...) \
- furi_log_print_format(FuriLogLevelInfo, tag, format, ##__VA_ARGS__)
+#define FURI_LOG_DEV(tag, format, ...) furi_log_print_format(FuriLogLevelInfo, tag, format, ##__VA_ARGS__)
#define DEV_CRASH() furi_crash()
#else
#define FURI_LOG_DEV(tag, format, ...)
@@ -17,35 +16,46 @@ bool sent_get_request = false;
bool get_request_success = false;
bool request_processed = false;
-static void flip_trader_request_error_draw(Canvas* canvas) {
- if(canvas == NULL) {
+static void flip_trader_request_error_draw(Canvas *canvas)
+{
+ if (canvas == NULL)
+ {
FURI_LOG_E(TAG, "flip_trader_request_error_draw - canvas is NULL");
DEV_CRASH();
return;
}
- if(fhttp.last_response != NULL) {
- if(strstr(fhttp.last_response, "[ERROR] Not connected to Wifi. Failed to reconnect.") !=
- NULL) {
+ 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, 22, "Failed to reconnect.");
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) {
+ }
+ 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 if(strstr(fhttp.last_response, "[PONG]") != NULL) {
+ }
+ else if (strstr(fhttp.last_response, "[PONG]") != NULL)
+ {
canvas_clear(canvas);
canvas_draw_str(canvas, 0, 10, "[STATUS]Connecting to AP...");
- } else {
+ }
+ else
+ {
canvas_clear(canvas);
FURI_LOG_E(TAG, "Received an error: %s", fhttp.last_response);
canvas_draw_str(canvas, 0, 10, "[ERROR] Unusual error...");
canvas_draw_str(canvas, 0, 60, "Press BACK and retry.");
}
- } else {
+ }
+ else
+ {
canvas_clear(canvas);
canvas_draw_str(canvas, 0, 10, "Failed to receive data.");
canvas_draw_str(canvas, 0, 50, "Update your WiFi settings.");
@@ -53,29 +63,29 @@ static void flip_trader_request_error_draw(Canvas* canvas) {
}
}
-static bool send_price_request(AssetLoaderModel* model) {
+static bool send_price_request(AssetLoaderModel* model)
+{
UNUSED(model);
- if(fhttp.state == INACTIVE) {
+ if (fhttp.state == INACTIVE)
+ {
return false;
}
- if(!sent_get_request && fhttp.state == IDLE) {
+ if (!sent_get_request && fhttp.state == IDLE)
+ {
sent_get_request = true;
char url[128];
- char* headers = jsmn("Content-Type", "application/json");
+ char *headers = jsmn("Content-Type", "application/json");
snprintf(
fhttp.file_path,
sizeof(fhttp.file_path),
STORAGE_EXT_PATH_PREFIX "/apps_data/flip_trader/price.txt");
fhttp.save_received_data = true;
- snprintf(
- url,
- 128,
- "https://www.alphavantage.co/query?function=GLOBAL_QUOTE&symbol=%s&apikey=2X90WLEFMP43OJKE",
- asset_names[asset_index]);
+ snprintf(url, 128, "https://www.alphavantage.co/query?function=GLOBAL_QUOTE&symbol=%s&apikey=2X90WLEFMP43OJKE", asset_names[asset_index]);
get_request_success = flipper_http_get_request_with_headers(url, headers);
free(headers);
- if(!get_request_success) {
+ if (!get_request_success)
+ {
FURI_LOG_E(TAG, "Failed to send GET request");
fhttp.state = ISSUE;
return false;
@@ -85,26 +95,31 @@ static bool send_price_request(AssetLoaderModel* model) {
return true;
}
-static char* process_asset_price(AssetLoaderModel* model) {
+static char *process_asset_price(AssetLoaderModel* model)
+{
UNUSED(model);
- if(!request_processed) {
+ if (!request_processed)
+ {
// load the received data from the saved file
- FuriString* price_data = flipper_http_load_from_file(fhttp.file_path);
- if(price_data == NULL) {
+ FuriString *price_data = flipper_http_load_from_file(fhttp.file_path);
+ if (price_data == NULL)
+ {
FURI_LOG_E(TAG, "Failed to load received data from file.");
fhttp.state = ISSUE;
return NULL;
}
- char* data_cstr = (char*)furi_string_get_cstr(price_data);
- if(data_cstr == NULL) {
+ char *data_cstr = (char *)furi_string_get_cstr(price_data);
+ if (data_cstr == NULL)
+ {
FURI_LOG_E(TAG, "Failed to get C-string from FuriString.");
furi_string_free(price_data);
fhttp.state = ISSUE;
return NULL;
}
request_processed = true;
- char* global_quote = get_json_value("Global Quote", data_cstr, MAX_TOKENS);
- if(global_quote == NULL) {
+ char *global_quote = get_json_value("Global Quote", data_cstr, MAX_TOKENS);
+ if (global_quote == NULL)
+ {
FURI_LOG_E(TAG, "Failed to get Global Quote");
fhttp.state = ISSUE;
furi_string_free(price_data);
@@ -112,8 +127,9 @@ static char* process_asset_price(AssetLoaderModel* model) {
free(data_cstr);
return NULL;
}
- char* price = get_json_value("05. price", global_quote, MAX_TOKENS);
- if(price == NULL) {
+ char *price = get_json_value("05. price", global_quote, MAX_TOKENS);
+ if (price == NULL)
+ {
FURI_LOG_E(TAG, "Failed to get price");
fhttp.state = ISSUE;
furi_string_free(price_data);
@@ -134,24 +150,21 @@ static char* process_asset_price(AssetLoaderModel* model) {
return asset_price;
}
-static void flip_trader_asset_switch_to_view(FlipTraderApp* app) {
- flip_trader_generic_switch_to_view(
- app,
- "Fetching..",
- send_price_request,
- process_asset_price,
- 1,
- callback_to_assets_submenu,
- FlipTraderViewLoader);
+static void flip_trader_asset_switch_to_view(FlipTraderApp *app)
+{
+ flip_trader_generic_switch_to_view(app, "Fetching..", send_price_request, process_asset_price, 1, callback_to_assets_submenu, FlipTraderViewLoader);
}
-void callback_submenu_choices(void* context, uint32_t index) {
- FlipTraderApp* app = (FlipTraderApp*)context;
- if(!app) {
+void callback_submenu_choices(void *context, uint32_t index)
+{
+ FlipTraderApp *app = (FlipTraderApp *)context;
+ if (!app)
+ {
FURI_LOG_E(TAG, "FlipTraderApp is NULL");
return;
}
- switch(index) {
+ switch (index)
+ {
// view the assets submenu
case FlipTradeSubmenuIndexAssets:
view_dispatcher_switch_to_view(app->view_dispatcher, FlipTraderViewAssetsSubmenu);
@@ -166,46 +179,48 @@ void callback_submenu_choices(void* context, uint32_t index) {
break;
default:
// handle FlipTraderSubmenuIndexAssetStartIndex + index
- if(index >= FlipTraderSubmenuIndexAssetStartIndex) {
+ if (index >= FlipTraderSubmenuIndexAssetStartIndex)
+ {
asset_index = index - FlipTraderSubmenuIndexAssetStartIndex;
flip_trader_asset_switch_to_view(app);
- } else {
+ }
+ else
+ {
FURI_LOG_E(TAG, "Unknown submenu index");
}
break;
}
}
-void text_updated_ssid(void* context) {
- FlipTraderApp* app = (FlipTraderApp*)context;
- if(!app) {
+void text_updated_ssid(void *context)
+{
+ FlipTraderApp *app = (FlipTraderApp *)context;
+ if (!app)
+ {
FURI_LOG_E(TAG, "FlipTraderApp is NULL");
return;
}
// store the entered text
- strncpy(
- app->uart_text_input_buffer_ssid,
- app->uart_text_input_temp_buffer_ssid,
- app->uart_text_input_buffer_size_ssid);
+ strncpy(app->uart_text_input_buffer_ssid, app->uart_text_input_temp_buffer_ssid, app->uart_text_input_buffer_size_ssid);
// Ensure null-termination
app->uart_text_input_buffer_ssid[app->uart_text_input_buffer_size_ssid - 1] = '\0';
// update the variable item text
- if(app->variable_item_ssid) {
- variable_item_set_current_value_text(
- app->variable_item_ssid, app->uart_text_input_buffer_ssid);
+ if (app->variable_item_ssid)
+ {
+ variable_item_set_current_value_text(app->variable_item_ssid, app->uart_text_input_buffer_ssid);
}
// save settings
save_settings(app->uart_text_input_buffer_ssid, app->uart_text_input_buffer_password);
// save wifi settings to devboard
- if(strlen(app->uart_text_input_buffer_ssid) > 0 &&
- strlen(app->uart_text_input_buffer_password) > 0) {
- if(!flipper_http_save_wifi(
- app->uart_text_input_buffer_ssid, app->uart_text_input_buffer_password)) {
+ if (strlen(app->uart_text_input_buffer_ssid) > 0 && strlen(app->uart_text_input_buffer_password) > 0)
+ {
+ if (!flipper_http_save_wifi(app->uart_text_input_buffer_ssid, app->uart_text_input_buffer_password))
+ {
FURI_LOG_E(TAG, "Failed to save wifi settings");
}
}
@@ -214,36 +229,35 @@ void text_updated_ssid(void* context) {
view_dispatcher_switch_to_view(app->view_dispatcher, FlipTraderViewWiFiSettings);
}
-void text_updated_password(void* context) {
- FlipTraderApp* app = (FlipTraderApp*)context;
- if(!app) {
+void text_updated_password(void *context)
+{
+ FlipTraderApp *app = (FlipTraderApp *)context;
+ if (!app)
+ {
FURI_LOG_E(TAG, "FlipTraderApp is NULL");
return;
}
// store the entered text
- strncpy(
- app->uart_text_input_buffer_password,
- app->uart_text_input_temp_buffer_password,
- app->uart_text_input_buffer_size_password);
+ strncpy(app->uart_text_input_buffer_password, app->uart_text_input_temp_buffer_password, app->uart_text_input_buffer_size_password);
// Ensure null-termination
app->uart_text_input_buffer_password[app->uart_text_input_buffer_size_password - 1] = '\0';
// update the variable item text
- if(app->variable_item_password) {
- variable_item_set_current_value_text(
- app->variable_item_password, app->uart_text_input_buffer_password);
+ if (app->variable_item_password)
+ {
+ variable_item_set_current_value_text(app->variable_item_password, app->uart_text_input_buffer_password);
}
// save settings
save_settings(app->uart_text_input_buffer_ssid, app->uart_text_input_buffer_password);
// save wifi settings to devboard
- if(strlen(app->uart_text_input_buffer_ssid) > 0 &&
- strlen(app->uart_text_input_buffer_password) > 0) {
- if(!flipper_http_save_wifi(
- app->uart_text_input_buffer_ssid, app->uart_text_input_buffer_password)) {
+ if (strlen(app->uart_text_input_buffer_ssid) > 0 && strlen(app->uart_text_input_buffer_password) > 0)
+ {
+ if (!flipper_http_save_wifi(app->uart_text_input_buffer_ssid, app->uart_text_input_buffer_password))
+ {
FURI_LOG_E(TAG, "Failed to save wifi settings");
}
}
@@ -252,8 +266,10 @@ void text_updated_password(void* context) {
view_dispatcher_switch_to_view(app->view_dispatcher, FlipTraderViewWiFiSettings);
}
-uint32_t callback_to_submenu(void* context) {
- if(!context) {
+uint32_t callback_to_submenu(void *context)
+{
+ if (!context)
+ {
FURI_LOG_E(TAG, "Context is NULL");
return VIEW_NONE;
}
@@ -265,8 +281,10 @@ uint32_t callback_to_submenu(void* context) {
return FlipTraderViewMainSubmenu;
}
-uint32_t callback_to_wifi_settings(void* context) {
- if(!context) {
+uint32_t callback_to_wifi_settings(void *context)
+{
+ if (!context)
+ {
FURI_LOG_E(TAG, "Context is NULL");
return VIEW_NONE;
}
@@ -274,8 +292,10 @@ uint32_t callback_to_wifi_settings(void* context) {
return FlipTraderViewWiFiSettings;
}
-uint32_t callback_to_assets_submenu(void* context) {
- if(!context) {
+uint32_t callback_to_assets_submenu(void *context)
+{
+ if (!context)
+ {
FURI_LOG_E(TAG, "Context is NULL");
return VIEW_NONE;
}
@@ -287,13 +307,16 @@ uint32_t callback_to_assets_submenu(void* context) {
return FlipTraderViewAssetsSubmenu;
}
-void settings_item_selected(void* context, uint32_t index) {
- FlipTraderApp* app = (FlipTraderApp*)context;
- if(!app) {
+void settings_item_selected(void *context, uint32_t index)
+{
+ FlipTraderApp *app = (FlipTraderApp *)context;
+ if (!app)
+ {
FURI_LOG_E(TAG, "FlipTraderApp is NULL");
return;
}
- switch(index) {
+ switch (index)
+ {
case 0: // Input SSID
view_dispatcher_switch_to_view(app->view_dispatcher, FlipTraderViewTextInputSSID);
break;
@@ -306,13 +329,16 @@ void settings_item_selected(void* context, uint32_t index) {
}
}
-static void flip_trader_widget_set_text(char* message, Widget** widget) {
- if(widget == NULL) {
+static void flip_trader_widget_set_text(char *message, Widget **widget)
+{
+ if (widget == NULL)
+ {
FURI_LOG_E(TAG, "flip_trader_set_widget_text - widget is NULL");
DEV_CRASH();
return;
}
- if(message == NULL) {
+ if (message == NULL)
+ {
FURI_LOG_E(TAG, "flip_trader_set_widget_text - message is NULL");
DEV_CRASH();
return;
@@ -320,19 +346,20 @@ static void flip_trader_widget_set_text(char* message, Widget** widget) {
widget_reset(*widget);
uint32_t message_length = strlen(message); // Length of the message
- uint32_t i = 0; // Index tracker
- uint32_t formatted_index = 0; // Tracker for where we are in the formatted message
- char* formatted_message; // Buffer to hold the final formatted message
- if(!easy_flipper_set_buffer(&formatted_message, message_length * 2 + 1)) {
+ uint32_t i = 0; // Index tracker
+ uint32_t formatted_index = 0; // Tracker for where we are in the formatted message
+ char *formatted_message; // Buffer to hold the final formatted message
+ if (!easy_flipper_set_buffer(&formatted_message, message_length * 2 + 1))
+ {
return;
}
- while(i < message_length) {
+ while (i < message_length)
+ {
// TODO: Use canvas_glyph_width to calculate the maximum characters for the line
- uint32_t max_line_length = 29; // Maximum characters per line
+ uint32_t max_line_length = 29; // Maximum characters per line
uint32_t remaining_length = message_length - i; // Remaining characters
- uint32_t line_length = (remaining_length < max_line_length) ? remaining_length :
- max_line_length;
+ uint32_t line_length = (remaining_length < max_line_length) ? remaining_length : max_line_length;
// Temporary buffer to hold the current line
char line[30];
@@ -340,11 +367,12 @@ static void flip_trader_widget_set_text(char* message, Widget** widget) {
line[line_length] = '\0';
// Check if the line ends in the middle of a word and adjust accordingly
- if(line_length == 29 && message[i + line_length] != '\0' &&
- message[i + line_length] != ' ') {
+ if (line_length == 29 && message[i + line_length] != '\0' && message[i + line_length] != ' ')
+ {
// Find the last space within the 30-character segment
- char* last_space = strrchr(line, ' ');
- if(last_space != NULL) {
+ char *last_space = strrchr(line, ' ');
+ if (last_space != NULL)
+ {
// Adjust the line length to avoid cutting the word
line_length = last_space - line;
line[line_length] = '\0'; // Null-terminate at the space
@@ -352,7 +380,8 @@ static void flip_trader_widget_set_text(char* message, Widget** widget) {
}
// Manually copy the fixed line into the formatted_message buffer
- for(uint32_t j = 0; j < line_length; j++) {
+ for (uint32_t j = 0; j < line_length; j++)
+ {
formatted_message[formatted_index++] = line[j];
}
@@ -363,7 +392,8 @@ static void flip_trader_widget_set_text(char* message, Widget** widget) {
i += line_length;
// Skip spaces at the beginning of the next line
- while(message[i] == ' ') {
+ while (message[i] == ' ')
+ {
i++;
}
}
@@ -372,20 +402,23 @@ static void flip_trader_widget_set_text(char* message, Widget** widget) {
widget_add_text_scroll_element(*widget, 0, 0, 128, 64, formatted_message);
}
-void flip_trader_loader_draw_callback(Canvas* canvas, void* model) {
- if(!canvas || !model) {
+void flip_trader_loader_draw_callback(Canvas *canvas, void *model)
+{
+ if (!canvas || !model)
+ {
FURI_LOG_E(TAG, "flip_trader_loader_draw_callback - canvas or model is NULL");
return;
}
SerialState http_state = fhttp.state;
- AssetLoaderModel* asset_loader_model = (AssetLoaderModel*)model;
+ AssetLoaderModel *asset_loader_model = (AssetLoaderModel *)model;
AssetState asset_state = asset_loader_model->asset_state;
- char* title = asset_loader_model->title;
+ char *title = asset_loader_model->title;
canvas_set_font(canvas, FontSecondary);
- if(http_state == INACTIVE) {
+ if (http_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,");
@@ -395,7 +428,8 @@ void flip_trader_loader_draw_callback(Canvas* canvas, void* model) {
return;
}
- if(asset_state == AssetStateError || asset_state == AssetStateParseError) {
+ if (asset_state == AssetStateError || asset_state == AssetStateParseError)
+ {
flip_trader_request_error_draw(canvas);
return;
}
@@ -403,53 +437,61 @@ void flip_trader_loader_draw_callback(Canvas* canvas, void* model) {
canvas_draw_str(canvas, 0, 7, title);
canvas_draw_str(canvas, 0, 17, "Loading...");
- if(asset_state == AssetStateInitial) {
+ if (asset_state == AssetStateInitial)
+ {
return;
}
- if(http_state == SENDING) {
+ if (http_state == SENDING)
+ {
canvas_draw_str(canvas, 0, 27, "Sending...");
return;
}
- if(http_state == RECEIVING || asset_state == AssetStateRequested) {
+ if (http_state == RECEIVING || asset_state == AssetStateRequested)
+ {
canvas_draw_str(canvas, 0, 27, "Receiving...");
return;
}
- if(http_state == IDLE && asset_state == AssetStateReceived) {
+ if (http_state == IDLE && asset_state == AssetStateReceived)
+ {
canvas_draw_str(canvas, 0, 27, "Processing...");
return;
}
- if(http_state == IDLE && asset_state == AssetStateParsed) {
+ if (http_state == IDLE && asset_state == AssetStateParsed)
+ {
canvas_draw_str(canvas, 0, 27, "Processed...");
return;
}
}
-static void flip_trader_loader_process_callback(void* context) {
- if(context == NULL) {
+static void flip_trader_loader_process_callback(void *context)
+{
+ if (context == NULL)
+ {
FURI_LOG_E(TAG, "flip_trader_loader_process_callback - context is NULL");
DEV_CRASH();
return;
}
- FlipTraderApp* app = (FlipTraderApp*)context;
- View* view = app->view_loader;
+ FlipTraderApp *app = (FlipTraderApp *)context;
+ View *view = app->view_loader;
AssetState current_asset_state;
- with_view_model(
- view, AssetLoaderModel * model, { current_asset_state = model->asset_state; }, false);
+ with_view_model(view, AssetLoaderModel * model, { current_asset_state = model->asset_state; }, false);
- if(current_asset_state == AssetStateInitial) {
+ if (current_asset_state == AssetStateInitial)
+ {
with_view_model(
view,
AssetLoaderModel * model,
{
model->asset_state = AssetStateRequested;
AssetLoaderFetch fetch = model->fetcher;
- if(fetch == NULL) {
+ if (fetch == NULL)
+ {
FURI_LOG_E(TAG, "Model doesn't have Fetch function assigned.");
model->asset_state = AssetStateError;
return;
@@ -458,151 +500,174 @@ static void flip_trader_loader_process_callback(void* context) {
// Clear any previous responses
strncpy(fhttp.last_response, "", 1);
bool request_status = fetch(model);
- if(!request_status) {
+ if (!request_status)
+ {
model->asset_state = AssetStateError;
}
},
true);
- } else if(current_asset_state == AssetStateRequested || current_asset_state == AssetStateError) {
- if(fhttp.state == IDLE && fhttp.last_response != NULL) {
- if(strstr(fhttp.last_response, "[PONG]") != NULL) {
+ }
+ else if (current_asset_state == AssetStateRequested || current_asset_state == AssetStateError)
+ {
+ if (fhttp.state == IDLE && fhttp.last_response != NULL)
+ {
+ if (strstr(fhttp.last_response, "[PONG]") != NULL)
+ {
FURI_LOG_DEV(TAG, "PONG received.");
- } else if(strncmp(fhttp.last_response, "[SUCCESS]", 9) == 0) {
- FURI_LOG_DEV(
- TAG,
- "SUCCESS received. %s",
- fhttp.last_response ? fhttp.last_response : "NULL");
- } else if(strncmp(fhttp.last_response, "[ERROR]", 9) == 0) {
- FURI_LOG_DEV(
- TAG, "ERROR received. %s", fhttp.last_response ? fhttp.last_response : "NULL");
- } else if(strlen(fhttp.last_response) == 0) {
+ }
+ else if (strncmp(fhttp.last_response, "[SUCCESS]", 9) == 0)
+ {
+ FURI_LOG_DEV(TAG, "SUCCESS received. %s", fhttp.last_response ? fhttp.last_response : "NULL");
+ }
+ else if (strncmp(fhttp.last_response, "[ERROR]", 9) == 0)
+ {
+ FURI_LOG_DEV(TAG, "ERROR received. %s", fhttp.last_response ? fhttp.last_response : "NULL");
+ }
+ else if (strlen(fhttp.last_response) == 0)
+ {
// Still waiting on response
- } else {
- with_view_model(
- view,
- AssetLoaderModel * model,
- { model->asset_state = AssetStateReceived; },
- true);
}
- } else if(fhttp.state == SENDING || fhttp.state == RECEIVING) {
+ else
+ {
+ with_view_model(view, AssetLoaderModel * model, { model->asset_state = AssetStateReceived; }, true);
+ }
+ }
+ else if (fhttp.state == SENDING || fhttp.state == RECEIVING)
+ {
// continue waiting
- } else if(fhttp.state == INACTIVE) {
+ }
+ else if (fhttp.state == INACTIVE)
+ {
// inactive. try again
- } else if(fhttp.state == ISSUE) {
- with_view_model(
- view, AssetLoaderModel * model, { model->asset_state = AssetStateError; }, true);
- } else {
- FURI_LOG_DEV(
- TAG,
- "Unexpected state: %d lastresp: %s",
- fhttp.state,
- fhttp.last_response ? fhttp.last_response : "NULL");
+ }
+ else if (fhttp.state == ISSUE)
+ {
+ with_view_model(view, AssetLoaderModel * model, { model->asset_state = AssetStateError; }, true);
+ }
+ else
+ {
+ FURI_LOG_DEV(TAG, "Unexpected state: %d lastresp: %s", fhttp.state, fhttp.last_response ? fhttp.last_response : "NULL");
DEV_CRASH();
}
- } else if(current_asset_state == AssetStateReceived) {
+ }
+ else if (current_asset_state == AssetStateReceived)
+ {
with_view_model(
view,
AssetLoaderModel * model,
{
- char* asset_text;
- if(model->parser == NULL) {
+ char *asset_text;
+ if (model->parser == NULL)
+ {
asset_text = NULL;
FURI_LOG_DEV(TAG, "Parser is NULL");
DEV_CRASH();
- } else {
+ }
+ else
+ {
asset_text = model->parser(model);
}
- FURI_LOG_DEV(
- TAG,
- "Parsed asset: %s\r\ntext: %s",
- fhttp.last_response ? fhttp.last_response : "NULL",
- asset_text ? asset_text : "NULL");
+ FURI_LOG_DEV(TAG, "Parsed asset: %s\r\ntext: %s", fhttp.last_response ? fhttp.last_response : "NULL", asset_text ? asset_text : "NULL");
model->asset_text = asset_text;
- if(asset_text == NULL) {
+ if (asset_text == NULL)
+ {
model->asset_state = AssetStateParseError;
- } else {
+ }
+ else
+ {
model->asset_state = AssetStateParsed;
}
},
true);
- } else if(current_asset_state == AssetStateParsed) {
+ }
+ else if (current_asset_state == AssetStateParsed)
+ {
with_view_model(
view,
AssetLoaderModel * model,
{
- if(++model->request_index < model->request_count) {
+ if (++model->request_index < model->request_count)
+ {
model->asset_state = AssetStateInitial;
- } else {
- flip_trader_widget_set_text(
- model->asset_text != NULL ? model->asset_text : "Empty result",
- &app_instance->widget_result);
- if(model->asset_text != NULL) {
+ }
+ else
+ {
+ flip_trader_widget_set_text(model->asset_text != NULL ? model->asset_text : "Empty result", &app_instance->widget_result);
+ if (model->asset_text != NULL)
+ {
free(model->asset_text);
model->asset_text = NULL;
}
- view_set_previous_callback(
- widget_get_view(app_instance->widget_result), model->back_callback);
- view_dispatcher_switch_to_view(
- app_instance->view_dispatcher, FlipTraderViewWidgetResult);
+ view_set_previous_callback(widget_get_view(app_instance->widget_result), model->back_callback);
+ view_dispatcher_switch_to_view(app_instance->view_dispatcher, FlipTraderViewWidgetResult);
}
},
true);
}
}
-static void flip_trader_loader_timer_callback(void* context) {
- if(context == NULL) {
+static void flip_trader_loader_timer_callback(void *context)
+{
+ if (context == NULL)
+ {
FURI_LOG_E(TAG, "flip_trader_loader_timer_callback - context is NULL");
DEV_CRASH();
return;
}
- FlipTraderApp* app = (FlipTraderApp*)context;
+ FlipTraderApp *app = (FlipTraderApp *)context;
view_dispatcher_send_custom_event(app->view_dispatcher, FlipTraderCustomEventProcess);
}
-static void flip_trader_loader_on_enter(void* context) {
- if(context == NULL) {
+static void flip_trader_loader_on_enter(void *context)
+{
+ if (context == NULL)
+ {
FURI_LOG_E(TAG, "flip_trader_loader_on_enter - context is NULL");
DEV_CRASH();
return;
}
- FlipTraderApp* app = (FlipTraderApp*)context;
- View* view = app->view_loader;
+ FlipTraderApp *app = (FlipTraderApp *)context;
+ View *view = app->view_loader;
with_view_model(
view,
AssetLoaderModel * model,
{
view_set_previous_callback(view, model->back_callback);
- if(model->timer == NULL) {
- model->timer = furi_timer_alloc(
- flip_trader_loader_timer_callback, FuriTimerTypePeriodic, app);
+ if (model->timer == NULL)
+ {
+ model->timer = furi_timer_alloc(flip_trader_loader_timer_callback, FuriTimerTypePeriodic, app);
}
furi_timer_start(model->timer, 250);
},
true);
}
-static void flip_trader_loader_on_exit(void* context) {
- if(context == NULL) {
+static void flip_trader_loader_on_exit(void *context)
+{
+ if (context == NULL)
+ {
FURI_LOG_E(TAG, "flip_trader_loader_on_exit - context is NULL");
DEV_CRASH();
return;
}
- FlipTraderApp* app = (FlipTraderApp*)context;
- View* view = app->view_loader;
+ FlipTraderApp *app = (FlipTraderApp *)context;
+ View *view = app->view_loader;
with_view_model(
view,
AssetLoaderModel * model,
{
- if(model->timer) {
+ if (model->timer)
+ {
furi_timer_stop(model->timer);
}
},
false);
}
-void flip_trader_loader_init(View* view) {
- if(view == NULL) {
+void flip_trader_loader_init(View *view)
+{
+ if (view == NULL)
+ {
FURI_LOG_E(TAG, "flip_trader_loader_init - view is NULL");
DEV_CRASH();
return;
@@ -612,8 +677,10 @@ void flip_trader_loader_init(View* view) {
view_set_exit_callback(view, flip_trader_loader_on_exit);
}
-void flip_trader_loader_free_model(View* view) {
- if(view == NULL) {
+void flip_trader_loader_free_model(View *view)
+{
+ if (view == NULL)
+ {
FURI_LOG_E(TAG, "flip_trader_loader_free_model - view is NULL");
DEV_CRASH();
return;
@@ -622,11 +689,13 @@ void flip_trader_loader_free_model(View* view) {
view,
AssetLoaderModel * model,
{
- if(model->timer) {
+ if (model->timer)
+ {
furi_timer_free(model->timer);
model->timer = NULL;
}
- if(model->parser_context) {
+ if (model->parser_context)
+ {
free(model->parser_context);
model->parser_context = NULL;
}
@@ -634,14 +703,17 @@ void flip_trader_loader_free_model(View* view) {
false);
}
-bool flip_trader_custom_event_callback(void* context, uint32_t index) {
- if(context == NULL) {
+bool flip_trader_custom_event_callback(void *context, uint32_t index)
+{
+ if (context == NULL)
+ {
FURI_LOG_E(TAG, "flip_trader_custom_event_callback - context is NULL");
DEV_CRASH();
return false;
}
- switch(index) {
+ switch (index)
+ {
case FlipTraderCustomEventProcess:
flip_trader_loader_process_callback(context);
return true;
@@ -651,22 +723,18 @@ bool flip_trader_custom_event_callback(void* context, uint32_t index) {
}
}
-void flip_trader_generic_switch_to_view(
- FlipTraderApp* app,
- char* title,
- AssetLoaderFetch fetcher,
- AssetLoaderParser parser,
- size_t request_count,
- ViewNavigationCallback back,
- uint32_t view_id) {
- if(app == NULL) {
+void flip_trader_generic_switch_to_view(FlipTraderApp *app, char *title, AssetLoaderFetch fetcher, AssetLoaderParser parser, size_t request_count, ViewNavigationCallback back, uint32_t view_id)
+{
+ if (app == NULL)
+ {
FURI_LOG_E(TAG, "flip_trader_generic_switch_to_view - app is NULL");
DEV_CRASH();
return;
}
- View* view = app->view_loader;
- if(view == NULL) {
+ View *view = app->view_loader;
+ if (view == NULL)
+ {
FURI_LOG_E(TAG, "flip_trader_generic_switch_to_view - view is NULL");
DEV_CRASH();
return;
diff --git a/flip_trader/callback/flip_trader_callback.h b/flip_trader/callback/flip_trader_callback.h
index b69ecdd94..8c74f5836 100644
--- a/flip_trader/callback/flip_trader_callback.h
+++ b/flip_trader/callback/flip_trader_callback.h
@@ -11,20 +11,21 @@ extern bool sent_get_request;
extern bool get_request_success;
extern bool request_processed;
-void callback_submenu_choices(void* context, uint32_t index);
-void text_updated_ssid(void* context);
+void callback_submenu_choices(void *context, uint32_t index);
+void text_updated_ssid(void *context);
-void text_updated_password(void* context);
+void text_updated_password(void *context);
-uint32_t callback_to_submenu(void* context);
+uint32_t callback_to_submenu(void *context);
-uint32_t callback_to_wifi_settings(void* context);
-uint32_t callback_to_assets_submenu(void* context);
-void settings_item_selected(void* context, uint32_t index);
+uint32_t callback_to_wifi_settings(void *context);
+uint32_t callback_to_assets_submenu(void *context);
+void settings_item_selected(void *context, uint32_t index);
// Add edits by Derek Jamison
typedef enum AssetState AssetState;
-enum AssetState {
+enum AssetState
+{
AssetStateInitial,
AssetStateRequested,
AssetStateReceived,
@@ -34,40 +35,35 @@ enum AssetState {
};
typedef enum FlipTraderCustomEvent FlipTraderCustomEvent;
-enum FlipTraderCustomEvent {
+enum FlipTraderCustomEvent
+{
FlipTraderCustomEventProcess,
};
typedef struct AssetLoaderModel AssetLoaderModel;
-typedef bool (*AssetLoaderFetch)(AssetLoaderModel* model);
-typedef char* (*AssetLoaderParser)(AssetLoaderModel* model);
-struct AssetLoaderModel {
- char* title;
- char* asset_text;
+typedef bool (*AssetLoaderFetch)(AssetLoaderModel *model);
+typedef char *(*AssetLoaderParser)(AssetLoaderModel *model);
+struct AssetLoaderModel
+{
+ char *title;
+ char *asset_text;
AssetState asset_state;
AssetLoaderFetch fetcher;
AssetLoaderParser parser;
- void* parser_context;
+ void *parser_context;
size_t request_index;
size_t request_count;
ViewNavigationCallback back_callback;
- FuriTimer* timer;
+ FuriTimer *timer;
};
-void flip_trader_generic_switch_to_view(
- FlipTraderApp* app,
- char* title,
- AssetLoaderFetch fetcher,
- AssetLoaderParser parser,
- size_t request_count,
- ViewNavigationCallback back,
- uint32_t view_id);
+void flip_trader_generic_switch_to_view(FlipTraderApp *app, char *title, AssetLoaderFetch fetcher, AssetLoaderParser parser, size_t request_count, ViewNavigationCallback back, uint32_t view_id);
-void flip_trader_loader_draw_callback(Canvas* canvas, void* model);
+void flip_trader_loader_draw_callback(Canvas *canvas, void *model);
-void flip_trader_loader_init(View* view);
+void flip_trader_loader_init(View *view);
-void flip_trader_loader_free_model(View* view);
+void flip_trader_loader_free_model(View *view);
-bool flip_trader_custom_event_callback(void* context, uint32_t index);
-#endif // FLIP_TRADER_CALLBACK_H
+bool flip_trader_custom_event_callback(void *context, uint32_t index);
+#endif // FLIP_TRADER_CALLBACK_H
\ No newline at end of file
diff --git a/flip_trader/easy_flipper/easy_flipper.c b/flip_trader/easy_flipper/easy_flipper.c
index 8b98e1a1b..f61b7d231 100644
--- a/flip_trader/easy_flipper/easy_flipper.c
+++ b/flip_trader/easy_flipper/easy_flipper.c
@@ -5,9 +5,11 @@
* @param context The context - unused
* @return next view id (VIEW_NONE to exit the app)
*/
-uint32_t easy_flipper_callback_exit_app(void* context) {
+uint32_t easy_flipper_callback_exit_app(void *context)
+{
// Exit the application
- if(!context) {
+ if (!context)
+ {
FURI_LOG_E(EASY_TAG, "Context is NULL");
return VIEW_NONE;
}
@@ -21,13 +23,16 @@ uint32_t easy_flipper_callback_exit_app(void* context) {
* @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) {
+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) {
+ *buffer = (char *)malloc(buffer_size);
+ if (!*buffer)
+ {
FURI_LOG_E(EASY_TAG, "Failed to allocate buffer");
return false;
}
@@ -46,32 +51,39 @@ bool easy_flipper_set_buffer(char** buffer, uint32_t buffer_size) {
* @return true if successful, false otherwise
*/
bool easy_flipper_set_view(
- View** 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) {
+ 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) {
+ if (!*view)
+ {
FURI_LOG_E(EASY_TAG, "Failed to allocate View");
return false;
}
- if(draw_callback) {
+ if (draw_callback)
+ {
view_set_draw_callback(*view, draw_callback);
}
- if(input_callback) {
+ if (input_callback)
+ {
view_set_input_callback(*view, input_callback);
}
- if(context) {
+ if (context)
+ {
view_set_context(*view, context);
}
- if(previous_callback) {
+ if (previous_callback)
+ {
view_set_previous_callback(*view, previous_callback);
}
view_dispatcher_add_view(*view_dispatcher, view_id, *view);
@@ -85,18 +97,22 @@ bool easy_flipper_set_view(
* @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) {
+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) {
+ 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) {
+ if (context)
+ {
view_dispatcher_set_event_callback_context(*view_dispatcher, context);
}
return true;
@@ -113,24 +129,29 @@ bool easy_flipper_set_view_dispatcher(ViewDispatcher** view_dispatcher, Gui* gui
* @return true if successful, false otherwise
*/
bool easy_flipper_set_submenu(
- Submenu** submenu,
+ Submenu **submenu,
int32_t view_id,
- char* title,
- uint32_t(previous_callback)(void*),
- ViewDispatcher** view_dispatcher) {
- if(!submenu) {
+ 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) {
+ if (!*submenu)
+ {
FURI_LOG_E(EASY_TAG, "Failed to allocate Submenu");
return false;
}
- if(title) {
+ if (title)
+ {
submenu_set_header(*submenu, title);
}
- if(previous_callback) {
+ 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));
@@ -147,20 +168,24 @@ bool easy_flipper_set_submenu(
* @return true if successful, false otherwise
*/
bool easy_flipper_set_menu(
- Menu** menu,
+ Menu **menu,
int32_t view_id,
- uint32_t(previous_callback)(void*),
- ViewDispatcher** view_dispatcher) {
- if(!menu) {
+ 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) {
+ if (!*menu)
+ {
FURI_LOG_E(EASY_TAG, "Failed to allocate Menu");
return false;
}
- if(previous_callback) {
+ 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));
@@ -177,24 +202,29 @@ bool easy_flipper_set_menu(
* @return true if successful, false otherwise
*/
bool easy_flipper_set_widget(
- Widget** widget,
+ Widget **widget,
int32_t view_id,
- char* text,
- uint32_t(previous_callback)(void*),
- ViewDispatcher** view_dispatcher) {
- if(!widget) {
+ 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) {
+ if (!*widget)
+ {
FURI_LOG_E(EASY_TAG, "Failed to allocate Widget");
return false;
}
- if(text) {
+ if (text)
+ {
widget_add_text_scroll_element(*widget, 0, 0, 128, 64, text);
}
- if(previous_callback) {
+ 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));
@@ -213,30 +243,33 @@ bool easy_flipper_set_widget(
* @return true if successful, false otherwise
*/
bool easy_flipper_set_variable_item_list(
- VariableItemList** 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) {
+ 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) {
+ if (!*variable_item_list)
+ {
FURI_LOG_E(EASY_TAG, "Failed to allocate VariableItemList");
return false;
}
- if(enter_callback) {
+ 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);
+ 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));
+ view_dispatcher_add_view(*view_dispatcher, view_id, variable_item_list_get_view(*variable_item_list));
return true;
}
@@ -249,38 +282,38 @@ bool easy_flipper_set_variable_item_list(
* @return true if successful, false otherwise
*/
bool easy_flipper_set_text_input(
- TextInput** text_input,
+ TextInput **text_input,
int32_t view_id,
- char* header_text,
- char* text_input_temp_buffer,
+ 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) {
+ 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) {
+ if (!*text_input)
+ {
FURI_LOG_E(EASY_TAG, "Failed to allocate TextInput");
return false;
}
- if(previous_callback) {
+ if (previous_callback)
+ {
view_set_previous_callback(text_input_get_view(*text_input), previous_callback);
}
- if(header_text) {
+ 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);
+ 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;
@@ -295,38 +328,38 @@ bool easy_flipper_set_text_input(
* @return true if successful, false otherwise
*/
bool easy_flipper_set_uart_text_input(
- TextInput** uart_text_input,
+ TextInput **uart_text_input,
int32_t view_id,
- char* header_text,
- char* uart_text_input_temp_buffer,
+ 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) {
+ 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 = text_input_alloc();
- if(!*uart_text_input) {
+ if (!*uart_text_input)
+ {
FURI_LOG_E(EASY_TAG, "Failed to allocate UART_TextInput");
return false;
}
- if(previous_callback) {
+ if (previous_callback)
+ {
view_set_previous_callback(text_input_get_view(*uart_text_input), previous_callback);
}
- if(header_text) {
+ if (header_text)
+ {
text_input_set_header_text(*uart_text_input, header_text);
}
- if(uart_text_input_temp_buffer && uart_text_input_buffer_size && result_callback) {
- text_input_set_result_callback(
- *uart_text_input,
- result_callback,
- context,
- uart_text_input_temp_buffer,
- uart_text_input_buffer_size,
- false);
+ if (uart_text_input_temp_buffer && uart_text_input_buffer_size && result_callback)
+ {
+ text_input_set_result_callback(*uart_text_input, result_callback, context, uart_text_input_temp_buffer, uart_text_input_buffer_size, false);
}
text_input_show_illegal_symbols(*uart_text_input, true);
view_dispatcher_add_view(*view_dispatcher, view_id, text_input_get_view(*uart_text_input));
@@ -353,52 +386,63 @@ bool easy_flipper_set_uart_text_input(
* @return true if successful, false otherwise
*/
bool easy_flipper_set_dialog_ex(
- DialogEx** dialog_ex,
+ DialogEx **dialog_ex,
int32_t view_id,
- char* header,
+ char *header,
uint16_t header_x,
uint16_t header_y,
- char* text,
+ 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) {
+ 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) {
+ if (!*dialog_ex)
+ {
FURI_LOG_E(EASY_TAG, "Failed to allocate DialogEx");
return false;
}
- if(header) {
+ if (header)
+ {
dialog_ex_set_header(*dialog_ex, header, header_x, header_y, AlignLeft, AlignTop);
}
- if(text) {
+ if (text)
+ {
dialog_ex_set_text(*dialog_ex, text, text_x, text_y, AlignLeft, AlignTop);
}
- if(left_button_text) {
+ if (left_button_text)
+ {
dialog_ex_set_left_button_text(*dialog_ex, left_button_text);
}
- if(right_button_text) {
+ if (right_button_text)
+ {
dialog_ex_set_right_button_text(*dialog_ex, right_button_text);
}
- if(center_button_text) {
+ if (center_button_text)
+ {
dialog_ex_set_center_button_text(*dialog_ex, center_button_text);
}
- if(result_callback) {
+ if (result_callback)
+ {
dialog_ex_set_result_callback(*dialog_ex, result_callback);
}
- if(previous_callback) {
+ if (previous_callback)
+ {
view_set_previous_callback(dialog_ex_get_view(*dialog_ex), previous_callback);
}
- if(context) {
+ if (context)
+ {
dialog_ex_set_context(*dialog_ex, context);
}
view_dispatcher_add_view(*view_dispatcher, view_id, dialog_ex_get_view(*dialog_ex));
@@ -422,40 +466,48 @@ bool easy_flipper_set_dialog_ex(
* @return true if successful, false otherwise
*/
bool easy_flipper_set_popup(
- Popup** popup,
+ Popup **popup,
int32_t view_id,
- char* header,
+ char *header,
uint16_t header_x,
uint16_t header_y,
- char* text,
+ 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) {
+ 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) {
+ if (!*popup)
+ {
FURI_LOG_E(EASY_TAG, "Failed to allocate Popup");
return false;
}
- if(header) {
+ if (header)
+ {
popup_set_header(*popup, header, header_x, header_y, AlignLeft, AlignTop);
}
- if(text) {
+ if (text)
+ {
popup_set_text(*popup, text, text_x, text_y, AlignLeft, AlignTop);
}
- if(result_callback) {
+ if (result_callback)
+ {
popup_set_callback(*popup, result_callback);
}
- if(previous_callback) {
+ if (previous_callback)
+ {
view_set_previous_callback(popup_get_view(*popup), previous_callback);
}
- if(context) {
+ if (context)
+ {
popup_set_context(*popup, context);
}
view_dispatcher_add_view(*view_dispatcher, view_id, popup_get_view(*popup));
@@ -471,20 +523,24 @@ bool easy_flipper_set_popup(
* @return true if successful, false otherwise
*/
bool easy_flipper_set_loading(
- Loading** loading,
+ Loading **loading,
int32_t view_id,
- uint32_t(previous_callback)(void*),
- ViewDispatcher** view_dispatcher) {
- if(!loading) {
+ 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) {
+ if (!*loading)
+ {
FURI_LOG_E(EASY_TAG, "Failed to allocate Loading");
return false;
}
- if(previous_callback) {
+ 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));
@@ -497,16 +553,19 @@ bool easy_flipper_set_loading(
* @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) {
+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) {
+ 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/flip_trader/easy_flipper/easy_flipper.h b/flip_trader/easy_flipper/easy_flipper.h
index 1d6dbe430..3ccfe66b8 100644
--- a/flip_trader/easy_flipper/easy_flipper.h
+++ b/flip_trader/easy_flipper/easy_flipper.h
@@ -28,14 +28,14 @@
* @param context The context - unused
* @return next view id (VIEW_NONE to exit the app)
*/
-uint32_t easy_flipper_callback_exit_app(void* context);
+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);
+bool easy_flipper_set_buffer(char **buffer, uint32_t buffer_size);
/**
* @brief Initialize a View object
* @param view The View object to initialize
@@ -47,13 +47,13 @@ bool easy_flipper_set_buffer(char** buffer, uint32_t buffer_size);
* @return true if successful, false otherwise
*/
bool easy_flipper_set_view(
- View** 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);
+ 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
@@ -62,7 +62,7 @@ bool easy_flipper_set_view(
* @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);
+bool easy_flipper_set_view_dispatcher(ViewDispatcher **view_dispatcher, Gui *gui, void *context);
/**
* @brief Initialize a Submenu object
@@ -75,11 +75,11 @@ bool easy_flipper_set_view_dispatcher(ViewDispatcher** view_dispatcher, Gui* gui
* @return true if successful, false otherwise
*/
bool easy_flipper_set_submenu(
- Submenu** submenu,
+ Submenu **submenu,
int32_t view_id,
- char* title,
- uint32_t(previous_callback)(void*),
- ViewDispatcher** view_dispatcher);
+ char *title,
+ uint32_t(previous_callback)(void *),
+ ViewDispatcher **view_dispatcher);
/**
* @brief Initialize a Menu object
@@ -92,10 +92,10 @@ bool easy_flipper_set_submenu(
* @return true if successful, false otherwise
*/
bool easy_flipper_set_menu(
- Menu** menu,
+ Menu **menu,
int32_t view_id,
- uint32_t(previous_callback)(void*),
- ViewDispatcher** view_dispatcher);
+ uint32_t(previous_callback)(void *),
+ ViewDispatcher **view_dispatcher);
/**
* @brief Initialize a Widget object
@@ -107,11 +107,11 @@ bool easy_flipper_set_menu(
* @return true if successful, false otherwise
*/
bool easy_flipper_set_widget(
- Widget** widget,
+ Widget **widget,
int32_t view_id,
- char* text,
- uint32_t(previous_callback)(void*),
- ViewDispatcher** view_dispatcher);
+ char *text,
+ uint32_t(previous_callback)(void *),
+ ViewDispatcher **view_dispatcher);
/**
* @brief Initialize a VariableItemList object
@@ -125,12 +125,12 @@ bool easy_flipper_set_widget(
* @return true if successful, false otherwise
*/
bool easy_flipper_set_variable_item_list(
- VariableItemList** 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);
+ void (*enter_callback)(void *, uint32_t),
+ uint32_t(previous_callback)(void *),
+ ViewDispatcher **view_dispatcher,
+ void *context);
/**
* @brief Initialize a TextInput object
@@ -141,15 +141,15 @@ bool easy_flipper_set_variable_item_list(
* @return true if successful, false otherwise
*/
bool easy_flipper_set_text_input(
- TextInput** text_input,
+ TextInput **text_input,
int32_t view_id,
- char* header_text,
- char* text_input_temp_buffer,
+ 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);
+ void (*result_callback)(void *),
+ uint32_t(previous_callback)(void *),
+ ViewDispatcher **view_dispatcher,
+ void *context);
/**
* @brief Initialize a TextInput object with extra symbols
@@ -160,15 +160,15 @@ bool easy_flipper_set_text_input(
* @return true if successful, false otherwise
*/
bool easy_flipper_set_uart_text_input(
- TextInput** uart_text_input,
+ TextInput **uart_text_input,
int32_t view_id,
- char* header_text,
- char* uart_text_input_temp_buffer,
+ 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);
+ void (*result_callback)(void *),
+ uint32_t(previous_callback)(void *),
+ ViewDispatcher **view_dispatcher,
+ void *context);
/**
* @brief Initialize a DialogEx object
@@ -190,21 +190,21 @@ bool easy_flipper_set_uart_text_input(
* @return true if successful, false otherwise
*/
bool easy_flipper_set_dialog_ex(
- DialogEx** dialog_ex,
+ DialogEx **dialog_ex,
int32_t view_id,
- char* header,
+ char *header,
uint16_t header_x,
uint16_t header_y,
- char* text,
+ 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);
+ 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
@@ -223,18 +223,18 @@ bool easy_flipper_set_dialog_ex(
* @return true if successful, false otherwise
*/
bool easy_flipper_set_popup(
- Popup** popup,
+ Popup **popup,
int32_t view_id,
- char* header,
+ char *header,
uint16_t header_x,
uint16_t header_y,
- char* text,
+ char *text,
uint16_t text_x,
uint16_t text_y,
- void (*result_callback)(void*),
- uint32_t(previous_callback)(void*),
- ViewDispatcher** view_dispatcher,
- void* context);
+ void (*result_callback)(void *),
+ uint32_t(previous_callback)(void *),
+ ViewDispatcher **view_dispatcher,
+ void *context);
/**
* @brief Initialize a Loading object
@@ -245,10 +245,10 @@ bool easy_flipper_set_popup(
* @return true if successful, false otherwise
*/
bool easy_flipper_set_loading(
- Loading** loading,
+ Loading **loading,
int32_t view_id,
- uint32_t(previous_callback)(void*),
- ViewDispatcher** view_dispatcher);
+ uint32_t(previous_callback)(void *),
+ ViewDispatcher **view_dispatcher);
/**
* @brief Set a char butter to a FuriString
@@ -256,6 +256,6 @@ bool easy_flipper_set_loading(
* @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);
+bool easy_flipper_set_char_to_furi_string(FuriString **furi_string, char *buffer);
-#endif
+#endif
\ No newline at end of file
diff --git a/flip_trader/flip_storage/flip_trader_storage.c b/flip_trader/flip_storage/flip_trader_storage.c
index ce863f84e..ed8a03a98 100644
--- a/flip_trader/flip_storage/flip_trader_storage.c
+++ b/flip_trader/flip_storage/flip_trader_storage.c
@@ -1,18 +1,21 @@
#include "flip_storage/flip_trader_storage.h"
-void save_settings(const char* ssid, const char* password) {
+void save_settings(
+ const char *ssid,
+ const char *password)
+{
// Create the directory for saving settings
char directory_path[256];
- snprintf(
- directory_path, sizeof(directory_path), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_trader");
+ snprintf(directory_path, sizeof(directory_path), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_trader");
// Create the directory
- Storage* storage = furi_record_open(RECORD_STORAGE);
+ 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)) {
+ 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);
@@ -21,15 +24,17 @@ void save_settings(const char* ssid, const char* password) {
// 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) {
+ 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) {
+ 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");
}
@@ -38,11 +43,17 @@ void save_settings(const char* ssid, const char* password) {
furi_record_close(RECORD_STORAGE);
}
-bool load_settings(char* ssid, size_t ssid_size, char* password, size_t password_size) {
- Storage* storage = furi_record_open(RECORD_STORAGE);
- File* file = storage_file_alloc(storage);
+bool load_settings(
+ char *ssid,
+ size_t ssid_size,
+ char *password,
+ size_t password_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)) {
+ 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);
@@ -51,8 +62,9 @@ bool load_settings(char* ssid, size_t ssid_size, char* password, size_t password
// 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) {
+ 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);
@@ -63,9 +75,9 @@ bool load_settings(char* ssid, size_t ssid_size, char* password, size_t password
// 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) {
+ 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);
@@ -79,4 +91,4 @@ bool load_settings(char* ssid, size_t ssid_size, char* password, size_t password
furi_record_close(RECORD_STORAGE);
return true;
-}
+}
\ No newline at end of file
diff --git a/flip_trader/flip_storage/flip_trader_storage.h b/flip_trader/flip_storage/flip_trader_storage.h
index b2edac06a..3b147bb05 100644
--- a/flip_trader/flip_storage/flip_trader_storage.h
+++ b/flip_trader/flip_storage/flip_trader_storage.h
@@ -7,8 +7,14 @@
#define SETTINGS_PATH STORAGE_EXT_PATH_PREFIX "/apps_data/flip_trader/settings.bin"
-void save_settings(const char* ssid, const char* password);
+void save_settings(
+ const char *ssid,
+ const char *password);
-bool load_settings(char* ssid, size_t ssid_size, char* password, size_t password_size);
+bool load_settings(
+ char *ssid,
+ size_t ssid_size,
+ char *password,
+ size_t password_size);
-#endif // FLIP_TRADER_STORAGE_H
+#endif // FLIP_TRADER_STORAGE_H
\ No newline at end of file
diff --git a/flip_trader/flip_trader.c b/flip_trader/flip_trader.c
index cc078af04..adfa83efb 100644
--- a/flip_trader/flip_trader.c
+++ b/flip_trader/flip_trader.c
@@ -1,19 +1,24 @@
#include
-void flip_trader_loader_free_model(View* view);
+void flip_trader_loader_free_model(View *view);
-void asset_names_free(char** names) {
- if(names) {
- for(int i = 0; i < ASSET_COUNT; i++) {
+void asset_names_free(char **names)
+{
+ if (names)
+ {
+ for (int i = 0; i < ASSET_COUNT; i++)
+ {
free(names[i]);
}
free(names);
}
}
-char** asset_names_alloc() {
+char **asset_names_alloc()
+{
// Allocate memory for an array of 42 string pointers
- char** names = malloc(ASSET_COUNT * sizeof(char*));
- if(!names) {
+ char **names = malloc(ASSET_COUNT * sizeof(char *));
+ if (!names)
+ {
FURI_LOG_E(TAG, "Failed to allocate memory for asset names");
return NULL;
}
@@ -65,56 +70,66 @@ char** asset_names_alloc() {
return names;
}
-char** asset_names = NULL;
+char **asset_names = NULL;
// index
uint32_t asset_index = 0;
-FlipTraderApp* app_instance = NULL;
+FlipTraderApp *app_instance = NULL;
// Function to free the resources used by FlipTraderApp
-void flip_trader_app_free(FlipTraderApp* app) {
- if(!app) {
+void flip_trader_app_free(FlipTraderApp *app)
+{
+ if (!app)
+ {
FURI_LOG_E(TAG, "FlipTraderApp is NULL");
return;
}
// Free View(s)
- if(app->view_loader) {
+ if (app->view_loader)
+ {
view_dispatcher_remove_view(app->view_dispatcher, FlipTraderViewLoader);
flip_trader_loader_free_model(app->view_loader);
view_free(app->view_loader);
}
// Free Submenu(s)
- if(app->submenu_main) {
+ if (app->submenu_main)
+ {
view_dispatcher_remove_view(app->view_dispatcher, FlipTraderViewMainSubmenu);
submenu_free(app->submenu_main);
}
- if(app->submenu_assets) {
+ if (app->submenu_assets)
+ {
view_dispatcher_remove_view(app->view_dispatcher, FlipTraderViewAssetsSubmenu);
submenu_free(app->submenu_assets);
}
// Free Widget(s)
- if(app->widget_about) {
+ if (app->widget_about)
+ {
view_dispatcher_remove_view(app->view_dispatcher, FlipTraderViewAbout);
widget_free(app->widget_about);
}
- if(app->widget_result) {
+ if (app->widget_result)
+ {
view_dispatcher_remove_view(app->view_dispatcher, FlipTraderViewWidgetResult);
widget_free(app->widget_result);
}
// Free Variable Item List(s)
- if(app->variable_item_list_wifi) {
+ if (app->variable_item_list_wifi)
+ {
view_dispatcher_remove_view(app->view_dispatcher, FlipTraderViewWiFiSettings);
variable_item_list_free(app->variable_item_list_wifi);
}
// Free Text Input(s)
- if(app->uart_text_input_ssid) {
+ if (app->uart_text_input_ssid)
+ {
view_dispatcher_remove_view(app->view_dispatcher, FlipTraderViewTextInputSSID);
text_input_free(app->uart_text_input_ssid);
}
- if(app->uart_text_input_password) {
+ if (app->uart_text_input_password)
+ {
view_dispatcher_remove_view(app->view_dispatcher, FlipTraderViewTextInputPassword);
text_input_free(app->uart_text_input_password);
}
@@ -132,4 +147,4 @@ void flip_trader_app_free(FlipTraderApp* app) {
// free the app
free(app);
-}
+}
\ No newline at end of file
diff --git a/flip_trader/flip_trader.h b/flip_trader/flip_trader.h
index 35f28e828..f208790f6 100644
--- a/flip_trader/flip_trader.h
+++ b/flip_trader/flip_trader.h
@@ -16,62 +16,65 @@
#define TAG "FlipTrader"
// Define the submenu items for our FlipTrader application
-typedef enum {
+typedef enum
+{
// FlipTraderSubmenuIndexMain, // Click to run get the info of the selected pair
- FlipTradeSubmenuIndexAssets, // Click to view the assets screen (ETHUSD, BTCUSD, etc.)
- FlipTraderSubmenuIndexAbout, // Click to view the about screen
- FlipTraderSubmenuIndexSettings, // Click to view the WiFi settings screen
- //
+ FlipTradeSubmenuIndexAssets, // Click to view the assets screen (ETHUSD, BTCUSD, etc.)
+ FlipTraderSubmenuIndexAbout, // Click to view the about screen
+ FlipTraderSubmenuIndexSettings, // Click to view the WiFi settings screen
+ //
FlipTraderSubmenuIndexAssetStartIndex, // Start of the submenu items for the assets
} FlipTraderSubmenuIndex;
// Define a single view for our FlipTrader application
-typedef enum {
- FlipTraderViewMain, // The screen that displays the info of the selected pair
- FlipTraderViewMainSubmenu, // The main submenu of the FlipTrader app
- FlipTraderViewAbout, // The about screen
- FlipTraderViewWiFiSettings, // The WiFi settings screen
- FlipTraderViewTextInputSSID, // The text input screen for the SSID
+typedef enum
+{
+ FlipTraderViewMain, // The screen that displays the info of the selected pair
+ FlipTraderViewMainSubmenu, // The main submenu of the FlipTrader app
+ FlipTraderViewAbout, // The about screen
+ FlipTraderViewWiFiSettings, // The WiFi settings screen
+ FlipTraderViewTextInputSSID, // The text input screen for the SSID
FlipTraderViewTextInputPassword, // The text input screen for the password
//
FlipTraderViewAssetsSubmenu, // The submenu for the assets
- FlipTraderViewWidgetResult, // The text box that displays the random fact
- FlipTraderViewLoader, // The loader screen retrieves data from the internet
+ FlipTraderViewWidgetResult, // The text box that displays the random fact
+ FlipTraderViewLoader, // The loader screen retrieves data from the internet
} FlipTraderView;
// Each screen will have its own view
-typedef struct {
- ViewDispatcher* view_dispatcher; // Switches between our views
- View* view_loader; // The screen that loads data from internet
- Submenu* submenu_main; // The submenu
- Submenu* submenu_assets; // The submenu for the assets
- Widget* widget_about; // The widget
- Widget* widget_result; // The widget that displays the result
- VariableItemList* variable_item_list_wifi; // The variable item list (settngs)
- VariableItem* variable_item_ssid; // The variable item for the SSID
- VariableItem* variable_item_password; // The variable item for the password
- TextInput* uart_text_input_ssid; // The text input for the SSID
- TextInput* uart_text_input_password; // The text input for the password
+typedef struct
+{
+ ViewDispatcher *view_dispatcher; // Switches between our views
+ View *view_loader; // The screen that loads data from internet
+ Submenu *submenu_main; // The submenu
+ Submenu *submenu_assets; // The submenu for the assets
+ Widget *widget_about; // The widget
+ Widget *widget_result; // The widget that displays the result
+ VariableItemList *variable_item_list_wifi; // The variable item list (settngs)
+ VariableItem *variable_item_ssid; // The variable item for the SSID
+ VariableItem *variable_item_password; // The variable item for the password
+ TextInput *uart_text_input_ssid; // The text input for the SSID
+ TextInput *uart_text_input_password; // The text input for the password
- char* uart_text_input_buffer_ssid; // Buffer for the text input (SSID)
- char* uart_text_input_temp_buffer_ssid; // Temporary buffer for the text input (SSID)
+ char *uart_text_input_buffer_ssid; // Buffer for the text input (SSID)
+ char *uart_text_input_temp_buffer_ssid; // Temporary buffer for the text input (SSID)
uint32_t uart_text_input_buffer_size_ssid; // Size of the text input buffer (SSID)
- char* uart_text_input_buffer_password; // Buffer for the text input (password)
- char* uart_text_input_temp_buffer_password; // Temporary buffer for the text input (password)
+ char *uart_text_input_buffer_password; // Buffer for the text input (password)
+ char *uart_text_input_temp_buffer_password; // Temporary buffer for the text input (password)
uint32_t uart_text_input_buffer_size_password; // Size of the text input buffer (password)
} FlipTraderApp;
#define ASSET_COUNT 42
-extern char** asset_names;
+extern char **asset_names;
// index
extern uint32_t asset_index;
// Function to free the resources used by FlipTraderApp
-void flip_trader_app_free(FlipTraderApp* app);
-char** asset_names_alloc();
-void asset_names_free(char** names);
-extern FlipTraderApp* app_instance;
-#endif // FLIP_TRADE_E_H
+void flip_trader_app_free(FlipTraderApp *app);
+char **asset_names_alloc();
+void asset_names_free(char **names);
+extern FlipTraderApp *app_instance;
+#endif // FLIP_TRADE_E_H
\ No newline at end of file
diff --git a/flip_trader/flipper_http/flipper_http.c b/flip_trader/flipper_http/flipper_http.c
index ed3b216de..a13dc5135 100644
--- a/flip_trader/flipper_http/flipper_http.c
+++ b/flip_trader/flipper_http/flipper_http.c
@@ -5,17 +5,21 @@ 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,
+ 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);
+ char *file_path)
+{
+ Storage *storage = furi_record_open(RECORD_STORAGE);
+ File *file = storage_file_alloc(storage);
- if(start_new_file) {
+ if (start_new_file)
+ {
// Delete the file if it already exists
- if(storage_file_exists(storage, file_path)) {
- if(!storage_simply_remove_recursive(storage, file_path)) {
+ if (storage_file_exists(storage, file_path))
+ {
+ if (!storage_simply_remove_recursive(storage, file_path))
+ {
FURI_LOG_E(HTTP_TAG, "Failed to delete file: %s", file_path);
storage_file_free(file);
furi_record_close(RECORD_STORAGE);
@@ -23,15 +27,19 @@ bool flipper_http_append_to_file(
}
}
// Open the file in write mode
- if(!storage_file_open(file, file_path, FSAM_WRITE, FSOM_CREATE_ALWAYS)) {
+ 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 {
+ }
+ else
+ {
// Open the file in append mode
- if(!storage_file_open(file, file_path, FSAM_WRITE, FSOM_OPEN_APPEND)) {
+ 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);
@@ -40,7 +48,8 @@ bool flipper_http_append_to_file(
}
// Write the data to the file
- if(storage_file_write(file, data, data_size) != data_size) {
+ 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);
@@ -54,32 +63,37 @@ bool flipper_http_append_to_file(
return true;
}
-FuriString* flipper_http_load_from_file(char* file_path) {
+FuriString *flipper_http_load_from_file(char *file_path)
+{
// Open the storage record
- Storage* storage = furi_record_open(RECORD_STORAGE);
- if(!storage) {
+ 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) {
+ 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)) {
+ 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) {
+ 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);
@@ -91,8 +105,9 @@ FuriString* flipper_http_load_from_file(char* file_path) {
furi_string_reset(str_result);
// Define a buffer to hold the read data
- uint8_t* buffer = (uint8_t*)malloc(MAX_FILE_SHOW);
- if(!buffer) {
+ 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);
@@ -103,7 +118,8 @@ FuriString* flipper_http_load_from_file(char* file_path) {
// 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) {
+ 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);
@@ -113,7 +129,8 @@ FuriString* flipper_http_load_from_file(char* file_path) {
}
// Append each byte to the FuriString
- for(size_t i = 0; i < read_count; i++) {
+ for (size_t i = 0; i < read_count; i++)
+ {
furi_string_push_back(str_result, buffer[i]);
}
@@ -137,38 +154,47 @@ FuriString* flipper_http_load_from_file(char* file_path) {
* @note This function will handle received data asynchronously via the callback.
*/
// UART worker thread
-int32_t flipper_http_worker(void* context) {
+int32_t flipper_http_worker(void *context)
+{
UNUSED(context);
size_t rx_line_pos = 0;
static size_t file_buffer_len = 0;
- while(1) {
+ while (1)
+ {
uint32_t events = furi_thread_flags_wait(
WorkerEvtStop | WorkerEvtRxDone, FuriFlagWaitAny, FuriWaitForever);
- if(events & WorkerEvtStop) break;
- if(events & WorkerEvtRxDone) {
+ 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)) {
+ 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) {
+ if (received == 0)
+ {
// No more data to read
break;
}
// Append the received byte to the file if saving is enabled
- if(fhttp.save_bytes) {
+ 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,
- fhttp.just_started_bytes,
- fhttp.file_path)) {
+ if (file_buffer_len >= FILE_BUFFER_SIZE)
+ {
+ if (!flipper_http_append_to_file(
+ file_buffer,
+ file_buffer_len,
+ fhttp.just_started_bytes,
+ fhttp.file_path))
+ {
FURI_LOG_E(HTTP_TAG, "Failed to append data to file");
}
file_buffer_len = 0;
@@ -177,9 +203,11 @@ int32_t flipper_http_worker(void* context) {
}
// Handle line buffering only if callback is set (text data)
- if(fhttp.handle_rx_line_cb) {
+ if (fhttp.handle_rx_line_cb)
+ {
// Handle line buffering
- if(c == '\n' || rx_line_pos >= RX_LINE_BUFFER_SIZE - 1) {
+ 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
@@ -187,7 +215,9 @@ int32_t flipper_http_worker(void* context) {
// Reset the line buffer position
rx_line_pos = 0;
- } else {
+ }
+ else
+ {
rx_line_buffer[rx_line_pos++] = c; // Add character to the line buffer
}
}
@@ -195,29 +225,38 @@ int32_t flipper_http_worker(void* context) {
}
}
- if(fhttp.save_bytes) {
+ 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)) {
+ 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) {
+ 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) {
+ }
+ 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) {
+ if (fhttp.save_bytes)
+ {
+ char *end = NULL;
+ if ((end = strstr(fhttp.file_path, "\n")) != NULL)
+ {
*end = '\0';
}
}
@@ -234,7 +273,8 @@ int32_t flipper_http_worker(void* context) {
* @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) {
+void get_timeout_timer_callback(void *context)
+{
UNUSED(context);
FURI_LOG_E(HTTP_TAG, "Timeout reached: 2 seconds without receiving the end.");
@@ -258,11 +298,13 @@ void get_timeout_timer_callback(void* context) {
* @note This function will handle received data asynchronously via the callback.
*/
void _flipper_http_rx_callback(
- FuriHalSerialHandle* handle,
+ FuriHalSerialHandle *handle,
FuriHalSerialRxEvent event,
- void* context) {
+ void *context)
+{
UNUSED(context);
- if(event == FuriHalSerialRxEventData) {
+ 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);
@@ -277,23 +319,28 @@ void _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) {
+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) {
+ 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) {
+ 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) {
+ if (!fhttp.rx_thread)
+ {
FURI_LOG_E(HTTP_TAG, "Failed to allocate UART thread.");
furi_stream_buffer_free(fhttp.flipper_http_stream);
return false;
@@ -311,13 +358,15 @@ bool flipper_http_init(FlipperHTTP_Callback callback, void* context) {
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)) {
+ 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) {
+ 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);
@@ -340,11 +389,12 @@ bool flipper_http_init(FlipperHTTP_Callback callback, void* context) {
// 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
+ FuriTimerTypeOnce, // One-shot timer
+ &fhttp // Context passed to callback
);
- if(!fhttp.get_timeout_timer) {
+ 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);
@@ -361,8 +411,9 @@ bool flipper_http_init(FlipperHTTP_Callback callback, void* context) {
// 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) {
+ 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;
}
@@ -377,8 +428,10 @@ bool flipper_http_init(FlipperHTTP_Callback callback, void* context) {
* @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) {
+void flipper_http_deinit()
+{
+ if (fhttp.serial_handle == NULL)
+ {
FURI_LOG_E(HTTP_TAG, "UART handle is NULL. Already deinitialized?");
return;
}
@@ -401,13 +454,15 @@ void flipper_http_deinit() {
furi_stream_buffer_free(fhttp.flipper_http_stream);
// Free the timer
- if(fhttp.get_timeout_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) {
+ if (fhttp.last_response)
+ {
free(fhttp.last_response);
fhttp.last_response = NULL;
}
@@ -422,34 +477,38 @@ void flipper_http_deinit() {
* @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) {
+bool flipper_http_send_data(const char *data)
+{
size_t data_length = strlen(data);
- if(data_length == 0) {
+ 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 > 512) { // Ensure buffer size is sufficient
+ if (send_length > 512)
+ { // Ensure buffer size is sufficient
FURI_LOG_E("FlipperHTTP", "Data too long to send over FHTTP.");
return false;
}
char send_buffer[513]; // 512 + 1 for safety
strncpy(send_buffer, data, 512);
- send_buffer[data_length] = '\n'; // Append newline
+ 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))) {
+ 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);
+ 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);
@@ -465,9 +524,11 @@ bool flipper_http_send_data(const char* data) {
* @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)) {
+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;
}
@@ -483,9 +544,11 @@ bool flipper_http_ping() {
* @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)) {
+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;
}
@@ -500,9 +563,11 @@ bool flipper_http_list_commands() {
* @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)) {
+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;
}
@@ -517,9 +582,11 @@ bool flipper_http_led_on() {
* @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)) {
+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;
}
@@ -536,8 +603,10 @@ bool flipper_http_led_off() {
* @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) {
+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;
}
@@ -545,12 +614,14 @@ bool flipper_http_parse_json(const char* key, const char* json_data) {
char buffer[256];
int ret =
snprintf(buffer, sizeof(buffer), "[PARSE]{\"key\":\"%s\",\"json\":%s}", key, json_data);
- if(ret < 0 || ret >= (int)sizeof(buffer)) {
+ 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)) {
+ if (!flipper_http_send_data(buffer))
+ {
FURI_LOG_E("FlipperHTTP", "Failed to send JSON parse command.");
return false;
}
@@ -568,8 +639,10 @@ bool flipper_http_parse_json(const char* key, const char* json_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) {
+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;
}
@@ -582,12 +655,14 @@ bool flipper_http_parse_json_array(const char* key, int index, const char* json_
key,
index,
json_data);
- if(ret < 0 || ret >= (int)sizeof(buffer)) {
+ 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)) {
+ if (!flipper_http_send_data(buffer))
+ {
FURI_LOG_E("FlipperHTTP", "Failed to send JSON parse array command.");
return false;
}
@@ -602,9 +677,11 @@ bool flipper_http_parse_json_array(const char* key, int index, const char* json_
* @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)) {
+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;
}
@@ -619,20 +696,24 @@ bool flipper_http_scan_wifi() {
* @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) {
+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)) {
+ 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)) {
+ if (!flipper_http_send_data(buffer))
+ {
FURI_LOG_E("FlipperHTTP", "Failed to send WiFi save command.");
return false;
}
@@ -647,9 +728,11 @@ bool flipper_http_save_wifi(const char* ssid, const char* password) {
* @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)) {
+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;
}
@@ -664,9 +747,11 @@ bool flipper_http_ip_address() {
* @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)) {
+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;
}
@@ -681,9 +766,11 @@ bool flipper_http_ip_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)) {
+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;
}
@@ -698,9 +785,11 @@ bool flipper_http_disconnect_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)) {
+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;
}
@@ -716,8 +805,10 @@ bool flipper_http_connect_wifi() {
* @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) {
+bool flipper_http_get_request(const char *url)
+{
+ if (!url)
+ {
FURI_LOG_E("FlipperHTTP", "Invalid arguments provided to flipper_http_get_request.");
return false;
}
@@ -725,13 +816,15 @@ bool flipper_http_get_request(const char* url) {
// Prepare GET request command
char command[256];
int ret = snprintf(command, sizeof(command), "[GET]%s", url);
- if(ret < 0 || ret >= (int)sizeof(command)) {
+ 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)) {
+ if (!flipper_http_send_data(command))
+ {
FURI_LOG_E("FlipperHTTP", "Failed to send GET request command.");
return false;
}
@@ -747,8 +840,10 @@ bool flipper_http_get_request(const char* url) {
* @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) {
+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;
@@ -758,13 +853,15 @@ bool flipper_http_get_request_with_headers(const char* url, const char* headers)
char command[512];
int ret = snprintf(
command, sizeof(command), "[GET/HTTP]{\"url\":\"%s\",\"headers\":%s}", url, headers);
- if(ret < 0 || ret >= (int)sizeof(command)) {
+ 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)) {
+ if (!flipper_http_send_data(command))
+ {
FURI_LOG_E("FlipperHTTP", "Failed to send GET request command with headers.");
return false;
}
@@ -780,8 +877,10 @@ bool flipper_http_get_request_with_headers(const char* url, const char* headers)
* @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) {
+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;
}
@@ -790,13 +889,15 @@ bool flipper_http_get_request_bytes(const char* url, const char* 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)) {
+ 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)) {
+ if (!flipper_http_send_data(command))
+ {
FURI_LOG_E("FlipperHTTP", "Failed to send GET request command with headers.");
return false;
}
@@ -814,10 +915,12 @@ bool flipper_http_get_request_bytes(const char* url, const char* headers) {
* @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) {
+ 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.");
@@ -833,13 +936,15 @@ bool flipper_http_post_request_with_headers(
url,
headers,
payload);
- if(ret < 0 || ret >= (int)sizeof(command)) {
+ 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)) {
+ if (!flipper_http_send_data(command))
+ {
FURI_LOG_E("FlipperHTTP", "Failed to send POST request command with headers and data.");
return false;
}
@@ -856,8 +961,10 @@ bool flipper_http_post_request_with_headers(
* @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) {
+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;
@@ -872,13 +979,15 @@ bool flipper_http_post_request_bytes(const char* url, const char* headers, const
url,
headers,
payload);
- if(ret < 0 || ret >= (int)sizeof(command)) {
+ 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)) {
+ if (!flipper_http_send_data(command))
+ {
FURI_LOG_E("FlipperHTTP", "Failed to send POST request command with headers and data.");
return false;
}
@@ -896,10 +1005,12 @@ bool flipper_http_post_request_bytes(const char* url, const char* headers, const
* @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) {
+ 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;
@@ -914,13 +1025,15 @@ bool flipper_http_put_request_with_headers(
url,
headers,
payload);
- if(ret < 0 || ret >= (int)sizeof(command)) {
+ 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)) {
+ if (!flipper_http_send_data(command))
+ {
FURI_LOG_E("FlipperHTTP", "Failed to send PUT request command with headers and data.");
return false;
}
@@ -938,10 +1051,12 @@ bool flipper_http_put_request_with_headers(
* @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) {
+ 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.");
@@ -957,14 +1072,16 @@ bool flipper_http_delete_request_with_headers(
url,
headers,
payload);
- if(ret < 0 || ret >= (int)sizeof(command)) {
+ 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)) {
+ if (!flipper_http_send_data(command))
+ {
FURI_LOG_E("FlipperHTTP", "Failed to send DELETE request command with headers and data.");
return false;
}
@@ -980,26 +1097,31 @@ bool flipper_http_delete_request_with_headers(
* @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) {
+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') {
+ 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) {
+ 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) {
+ if (fhttp.state != INACTIVE && fhttp.state != ISSUE)
+ {
fhttp.state = RECEIVING;
}
@@ -1007,11 +1129,13 @@ void flipper_http_rx_callback(const char* line, void* context) {
// 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) {
+ 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) {
+ 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);
@@ -1025,9 +1149,10 @@ void flipper_http_rx_callback(const char* line, void* context) {
}
// 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)) {
+ 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;
@@ -1035,18 +1160,21 @@ void flipper_http_rx_callback(const char* line, void* context) {
return;
}
- if(!fhttp.just_started_get) {
+ 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) {
+ 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) {
+ 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);
@@ -1060,9 +1188,10 @@ void flipper_http_rx_callback(const char* line, void* context) {
}
// 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)) {
+ 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;
@@ -1070,18 +1199,21 @@ void flipper_http_rx_callback(const char* line, void* context) {
return;
}
- if(!fhttp.just_started_post) {
+ 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) {
+ 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) {
+ 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);
@@ -1095,9 +1227,10 @@ void flipper_http_rx_callback(const char* line, void* context) {
}
// 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)) {
+ 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;
@@ -1105,18 +1238,21 @@ void flipper_http_rx_callback(const char* line, void* context) {
return;
}
- if(!fhttp.just_started_put) {
+ 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) {
+ 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) {
+ 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);
@@ -1130,9 +1266,10 @@ void flipper_http_rx_callback(const char* line, void* context) {
}
// 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)) {
+ 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;
@@ -1140,22 +1277,29 @@ void flipper_http_rx_callback(const char* line, void* context) {
return;
}
- if(!fhttp.just_started_delete) {
+ 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) {
+ if (strstr(line, "[SUCCESS]") != NULL || strstr(line, "[CONNECTED]") != NULL)
+ {
FURI_LOG_I(HTTP_TAG, "Operation succeeded.");
- } else if(strstr(line, "[INFO]") != NULL) {
+ }
+ 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) {
+ if (fhttp.state == INACTIVE && strstr(line, "[INFO] Already connected to Wifi.") != NULL)
+ {
fhttp.state = IDLE;
}
- } else if(strstr(line, "[GET/SUCCESS]") != NULL) {
+ }
+ 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);
@@ -1164,7 +1308,9 @@ void flipper_http_rx_callback(const char* line, void* context) {
fhttp.save_bytes = fhttp.is_bytes_request;
fhttp.just_started_bytes = true;
return;
- } else if(strstr(line, "[POST/SUCCESS]") != NULL) {
+ }
+ 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);
@@ -1173,67 +1319,86 @@ void flipper_http_rx_callback(const char* line, void* context) {
fhttp.save_bytes = fhttp.is_bytes_request;
fhttp.just_started_bytes = true;
return;
- } else if(strstr(line, "[PUT/SUCCESS]") != NULL) {
+ }
+ 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) {
+ }
+ 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) {
+ }
+ else if (strstr(line, "[DISCONNECTED]") != NULL)
+ {
FURI_LOG_I(HTTP_TAG, "WiFi disconnected successfully.");
- } else if(strstr(line, "[ERROR]") != NULL) {
+ }
+ 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) {
+ }
+ 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) {
+ if (fhttp.state == INACTIVE)
+ {
fhttp.state = IDLE;
return;
}
}
- if(fhttp.state == INACTIVE && strstr(line, "[PONG]") != NULL) {
+ if (fhttp.state == INACTIVE && strstr(line, "[PONG]") != NULL)
+ {
fhttp.state = IDLE;
- } else if(fhttp.state == INACTIVE && strstr(line, "[PONG]") == NULL) {
+ }
+ else if (fhttp.state == INACTIVE && strstr(line, "[PONG]") == NULL)
+ {
fhttp.state = INACTIVE;
- } else {
+ }
+ 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;
+char *trim(const char *str)
+{
+ const char *end;
+ char *trimmed_str;
size_t len;
// Trim leading space
- while(isspace((unsigned char)*str))
+ while (isspace((unsigned char)*str))
str++;
// All spaces?
- if(*str == 0) return strdup(""); // Return an empty string if 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))
+ 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) {
+ trimmed_str = (char *)malloc(len + 1);
+ if (trimmed_str == NULL)
+ {
return NULL; // Handle memory allocation failure
}
@@ -1250,24 +1415,28 @@ char* trim(const char* str) {
* @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
+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 {
+ }
+ 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) {
+ 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)
+ 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/flip_trader/flipper_http/flipper_http.h b/flip_trader/flipper_http/flipper_http.h
index 6b8f9e068..d0e431107 100644
--- a/flip_trader/flipper_http/flipper_http.h
+++ b/flip_trader/flipper_http/flipper_http.h
@@ -11,69 +11,72 @@
// STORAGE_EXT_PATH_PREFIX is defined in the Furi SDK as /ext
-#define HTTP_TAG "FlipTrader" // change this to your app name
-#define http_tag "flip_trader" // change this to your app id
-#define UART_CH (momentum_settings.uart_esp_channel) // UART channel
+#define HTTP_TAG "FlipTrader" // change this to your app name
+#define http_tag "flip_trader" // change this to your app id
+#define UART_CH (momentum_settings.uart_esp_channel) // 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 4096 // UART RX line buffer size (increase for large responses)
-#define MAX_FILE_SHOW 4096 // Maximum data from file to show
-#define FILE_BUFFER_SIZE 512 // File buffer size
+#define BAUDRATE (115200) // UART baudrate
+#define RX_BUF_SIZE 1024 // UART RX buffer size
+#define RX_LINE_BUFFER_SIZE 4096 // UART RX line buffer size (increase for large responses)
+#define MAX_FILE_SHOW 4096 // 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);
+typedef void (*FlipperHTTP_Callback)(const char *line, void *context);
// State variable to track the UART state
-typedef enum {
- INACTIVE, // Inactive state
- IDLE, // Default state
+typedef enum
+{
+ INACTIVE, // Inactive state
+ IDLE, // Default state
RECEIVING, // Receiving data
- SENDING, // Sending data
- ISSUE, // Issue with connection
+ SENDING, // Sending data
+ ISSUE, // Issue with connection
} SerialState;
// Event Flags for UART Worker Thread
-typedef enum {
+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
+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
+ 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 *last_response;
char file_path[256]; // Path to save the received data
// Timer-related members
- FuriTimer* get_timeout_timer; // Timer for HTTP request timeout
+ 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 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 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 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
+ 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;
+ 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
+ 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
bool just_started_bytes; // Indicates if bytes data reception has just started
} FlipperHTTP;
@@ -88,12 +91,12 @@ extern 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,
+ const void *data,
size_t data_size,
bool start_new_file,
- char* file_path);
+ char *file_path);
-FuriString* flipper_http_load_from_file(char* file_path);
+FuriString *flipper_http_load_from_file(char *file_path);
// UART worker thread
/**
@@ -103,7 +106,7 @@ FuriString* flipper_http_load_from_file(char* file_path);
* @note This function will handle received data asynchronously via the callback.
*/
// UART worker thread
-int32_t flipper_http_worker(void* context);
+int32_t flipper_http_worker(void *context);
// Timer callback function
/**
@@ -112,7 +115,7 @@ int32_t flipper_http_worker(void* context);
* @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);
+void get_timeout_timer_callback(void *context);
// UART RX Handler Callback (Interrupt Context)
/**
@@ -124,9 +127,9 @@ void get_timeout_timer_callback(void* context);
* @note This function will handle received data asynchronously via the callback.
*/
void _flipper_http_rx_callback(
- FuriHalSerialHandle* handle,
+ FuriHalSerialHandle *handle,
FuriHalSerialRxEvent event,
- void* context);
+ void *context);
// UART initialization function
/**
@@ -136,7 +139,7 @@ void _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);
+bool flipper_http_init(FlipperHTTP_Callback callback, void *context);
// Deinitialize UART
/**
@@ -153,7 +156,7 @@ void flipper_http_deinit();
* @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);
+bool flipper_http_send_data(const char *data);
// Function to send a PING request
/**
@@ -197,7 +200,7 @@ bool flipper_http_led_off();
* @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);
+bool flipper_http_parse_json(const char *key, const char *json_data);
// Function to parse JSON array data
/**
@@ -208,7 +211,7 @@ bool flipper_http_parse_json(const char* key, const char* json_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);
+bool flipper_http_parse_json_array(const char *key, int index, const char *json_data);
// Function to scan for WiFi networks
/**
@@ -224,7 +227,7 @@ bool flipper_http_scan_wifi();
* @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);
+bool flipper_http_save_wifi(const char *ssid, const char *password);
// Function to get IP address of WiFi Devboard
/**
@@ -265,7 +268,7 @@ bool flipper_http_connect_wifi();
* @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);
+bool flipper_http_get_request(const char *url);
// Function to send a GET request with headers
/**
@@ -275,7 +278,7 @@ bool flipper_http_get_request(const char* url);
* @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);
+bool flipper_http_get_request_with_headers(const char *url, const char *headers);
// Function to send a GET request with headers and return bytes
/**
@@ -285,7 +288,7 @@ bool flipper_http_get_request_with_headers(const char* url, const char* headers)
* @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);
+bool flipper_http_get_request_bytes(const char *url, const char *headers);
// Function to send a POST request with headers
/**
@@ -297,9 +300,9 @@ bool flipper_http_get_request_bytes(const char* url, const char* headers);
* @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);
+ const char *url,
+ const char *headers,
+ const char *payload);
// Function to send a POST request with headers and return bytes
/**
@@ -310,7 +313,7 @@ bool flipper_http_post_request_with_headers(
* @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);
+bool flipper_http_post_request_bytes(const char *url, const char *headers, const char *payload);
// Function to send a PUT request with headers
/**
@@ -322,9 +325,9 @@ bool flipper_http_post_request_bytes(const char* url, const char* headers, const
* @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);
+ const char *url,
+ const char *headers,
+ const char *payload);
// Function to send a DELETE request with headers
/**
@@ -336,9 +339,9 @@ bool flipper_http_put_request_with_headers(
* @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);
+ const char *url,
+ const char *headers,
+ const char *payload);
// Function to handle received data asynchronously
/**
@@ -348,10 +351,10 @@ bool flipper_http_delete_request_with_headers(
* @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);
+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);
+char *trim(const char *str);
/**
* @brief Process requests and parse JSON data asynchronously
* @param http_request The function to send the request
diff --git a/flip_trader/jsmn/jsmn.c b/flip_trader/jsmn/jsmn.c
index eb33b3cc7..b85ab83b5 100644
--- a/flip_trader/jsmn/jsmn.c
+++ b/flip_trader/jsmn/jsmn.c
@@ -13,11 +13,13 @@
/**
* 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;
+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) {
+ if (parser->toknext >= num_tokens)
+ {
return NULL;
}
tok = &tokens[parser->toknext++];
@@ -32,8 +34,9 @@ static jsmntok_t*
/**
* Fills token type and boundaries.
*/
-static void
- jsmn_fill_token(jsmntok_t* token, const jsmntype_t type, const int start, const int end) {
+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;
@@ -43,19 +46,19 @@ static void
/**
* 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;
+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]) {
+ 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 ':':
@@ -72,7 +75,8 @@ static int jsmn_parse_primitive(
/* to quiet a warning from gcc*/
break;
}
- if(js[parser->pos] < 32 || js[parser->pos] >= 127) {
+ if (js[parser->pos] < 32 || js[parser->pos] >= 127)
+ {
parser->pos = start;
return JSMN_ERROR_INVAL;
}
@@ -84,12 +88,14 @@ static int jsmn_parse_primitive(
#endif
found:
- if(tokens == NULL) {
+ if (tokens == NULL)
+ {
parser->pos--;
return 0;
}
token = jsmn_alloc_token(parser, tokens, num_tokens);
- if(token == NULL) {
+ if (token == NULL)
+ {
parser->pos = start;
return JSMN_ERROR_NOMEM;
}
@@ -104,29 +110,31 @@ static int jsmn_parse_primitive(
/**
* 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;
+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++) {
+ for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++)
+ {
char c = js[parser->pos];
/* Quote: end of string */
- if(c == '\"') {
- if(tokens == NULL) {
+ if (c == '\"')
+ {
+ if (tokens == NULL)
+ {
return 0;
}
token = jsmn_alloc_token(parser, tokens, num_tokens);
- if(token == NULL) {
+ if (token == NULL)
+ {
parser->pos = start;
return JSMN_ERROR_NOMEM;
}
@@ -138,10 +146,12 @@ static int jsmn_parse_string(
}
/* Backslash: Quoted symbol expected */
- if(c == '\\' && parser->pos + 1 < len) {
+ if (c == '\\' && parser->pos + 1 < len)
+ {
int i;
parser->pos++;
- switch(js[parser->pos]) {
+ switch (js[parser->pos])
+ {
/* Allowed escaped symbols */
case '\"':
case '/':
@@ -155,11 +165,13 @@ static int jsmn_parse_string(
/* Allows escaped symbol \uXXXX */
case 'u':
parser->pos++;
- for(i = 0; i < 4 && parser->pos < len && js[parser->pos] != '\0'; i++) {
+ 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 */
+ 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;
}
@@ -181,7 +193,8 @@ static int jsmn_parse_string(
/**
* Create JSON parser over an array of tokens
*/
-void jsmn_init(jsmn_parser* parser) {
+void jsmn_init(jsmn_parser *parser)
+{
parser->pos = 0;
parser->toknext = 0;
parser->toksuper = -1;
@@ -190,38 +203,41 @@ void jsmn_init(jsmn_parser* parser) {
/**
* 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 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;
+ jsmntok_t *token;
int count = parser->toknext;
- for(; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) {
+ for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++)
+ {
char c;
jsmntype_t type;
c = js[parser->pos];
- switch(c) {
+ switch (c)
+ {
case '{':
case '[':
count++;
- if(tokens == NULL) {
+ if (tokens == NULL)
+ {
break;
}
token = jsmn_alloc_token(parser, tokens, num_tokens);
- if(token == NULL) {
+ if (token == NULL)
+ {
return JSMN_ERROR_NOMEM;
}
- if(parser->toksuper != -1) {
- jsmntok_t* t = &tokens[parser->toksuper];
+ 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) {
+ if (t->type == JSMN_OBJECT)
+ {
return JSMN_ERROR_INVAL;
}
#endif
@@ -236,26 +252,33 @@ int jsmn_parse(
break;
case '}':
case ']':
- if(tokens == NULL) {
+ if (tokens == NULL)
+ {
break;
}
type = (c == '}' ? JSMN_OBJECT : JSMN_ARRAY);
#ifdef JSMN_PARENT_LINKS
- if(parser->toknext < 1) {
+ 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) {
+ 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) {
+ if (token->parent == -1)
+ {
+ if (token->type != type || parser->toksuper == -1)
+ {
return JSMN_ERROR_INVAL;
}
break;
@@ -263,10 +286,13 @@ int jsmn_parse(
token = &tokens[token->parent];
}
#else
- for(i = parser->toknext - 1; i >= 0; i--) {
+ for (i = parser->toknext - 1; i >= 0; i--)
+ {
token = &tokens[i];
- if(token->start != -1 && token->end == -1) {
- if(token->type != type) {
+ if (token->start != -1 && token->end == -1)
+ {
+ if (token->type != type)
+ {
return JSMN_ERROR_INVAL;
}
parser->toksuper = -1;
@@ -275,12 +301,15 @@ int jsmn_parse(
}
}
/* Error if unmatched closing bracket */
- if(i == -1) {
+ if (i == -1)
+ {
return JSMN_ERROR_INVAL;
}
- for(; i >= 0; i--) {
+ for (; i >= 0; i--)
+ {
token = &tokens[i];
- if(token->start != -1 && token->end == -1) {
+ if (token->start != -1 && token->end == -1)
+ {
parser->toksuper = i;
break;
}
@@ -289,11 +318,13 @@ int jsmn_parse(
break;
case '\"':
r = jsmn_parse_string(parser, js, len, tokens, num_tokens);
- if(r < 0) {
+ if (r < 0)
+ {
return r;
}
count++;
- if(parser->toksuper != -1 && tokens != NULL) {
+ if (parser->toksuper != -1 && tokens != NULL)
+ {
tokens[parser->toksuper].size++;
}
break;
@@ -306,15 +337,19 @@ int jsmn_parse(
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) {
+ 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) {
+ 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;
}
@@ -340,9 +375,12 @@ int jsmn_parse(
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)) {
+ 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;
}
}
@@ -351,11 +389,13 @@ int jsmn_parse(
default:
#endif
r = jsmn_parse_primitive(parser, js, len, tokens, num_tokens);
- if(r < 0) {
+ if (r < 0)
+ {
return r;
}
count++;
- if(parser->toksuper != -1 && tokens != NULL) {
+ if (parser->toksuper != -1 && tokens != NULL)
+ {
tokens[parser->toksuper].size++;
}
break;
@@ -368,10 +408,13 @@ int jsmn_parse(
}
}
- if(tokens != NULL) {
- for(i = parser->toknext - 1; i >= 0; i--) {
+ 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) {
+ if (tokens[i].start != -1 && tokens[i].end == -1)
+ {
return JSMN_ERROR_PART;
}
}
@@ -381,10 +424,12 @@ int jsmn_parse(
}
// 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) {
+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);
@@ -392,30 +437,36 @@ 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) {
- if(tok->type == JSMN_STRING && (int)strlen(s) == tok->end - tok->start &&
- strncmp(json + tok->start, s, tok->end - tok->start) == 0) {
+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) {
+char *get_json_value(char *key, char *json_data, uint32_t max_tokens)
+{
// Parse the JSON feed
- if(json_data != NULL) {
+ 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) {
+ 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) {
+ if (ret < 0)
+ {
// Handle parsing errors
FURI_LOG_E("JSMM.H", "Failed to parse JSON: %d", ret);
free(tokens);
@@ -423,19 +474,23 @@ char* get_json_value(char* key, char* json_data, uint32_t max_tokens) {
}
// Ensure that the root element is an object
- if(ret < 1 || tokens[0].type != JSMN_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) {
+ 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) {
+ char *value = malloc(length + 1);
+ if (value == NULL)
+ {
FURI_LOG_E("JSMM.H", "Failed to allocate memory for value.");
free(tokens);
return NULL;
@@ -450,7 +505,9 @@ char* get_json_value(char* key, char* json_data, uint32_t max_tokens) {
// Free the token array if key was not found
free(tokens);
- } else {
+ }
+ else
+ {
FURI_LOG_E("JSMM.H", "JSON data is NULL");
}
FURI_LOG_E("JSMM.H", "Failed to find the key in the JSON.");
@@ -458,10 +515,12 @@ 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) {
+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) {
+ 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;
}
@@ -471,8 +530,9 @@ char* get_json_array_value(char* key, uint32_t index, char* json_data, uint32_t
jsmn_init(&parser);
// Allocate memory for JSON tokens
- jsmntok_t* tokens = malloc(sizeof(jsmntok_t) * max_tokens);
- if(tokens == NULL) {
+ 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;
@@ -480,7 +540,8 @@ char* get_json_array_value(char* key, uint32_t index, char* json_data, uint32_t
// Parse the JSON array
int ret = jsmn_parse(&parser, array_str, strlen(array_str), tokens, max_tokens);
- if(ret < 0) {
+ if (ret < 0)
+ {
FURI_LOG_E("JSMM.H", "Failed to parse JSON array: %d", ret);
free(tokens);
free(array_str);
@@ -488,7 +549,8 @@ char* get_json_array_value(char* key, uint32_t index, char* json_data, uint32_t
}
// Ensure the root element is an array
- if(ret < 1 || tokens[0].type != JSMN_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);
@@ -496,12 +558,9 @@ char* get_json_array_value(char* key, uint32_t index, char* json_data, uint32_t
}
// 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);
+ 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;
@@ -509,20 +568,27 @@ char* get_json_array_value(char* key, uint32_t index, char* json_data, uint32_t
// 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 (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) {
+ }
+ else if (tokens[current_token].type == JSMN_ARRAY)
+ {
// For nested arrays, skip all elements
current_token += 1 + tokens[current_token].size;
- } else {
+ }
+ else
+ {
// For primitive types, simply move to the next token
current_token += 1;
}
// Safety check to prevent out-of-bounds
- if(current_token >= ret) {
+ if (current_token >= ret)
+ {
FURI_LOG_E("JSMM.H", "Unexpected end of tokens while traversing array.");
free(tokens);
free(array_str);
@@ -533,8 +599,9 @@ char* get_json_array_value(char* key, uint32_t index, char* json_data, uint32_t
// Extract the array element
jsmntok_t element = tokens[current_token];
int length = element.end - element.start;
- char* value = malloc(length + 1);
- if(value == NULL) {
+ 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);
@@ -553,10 +620,12 @@ char* get_json_array_value(char* key, uint32_t index, char* json_data, uint32_t
}
// 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) {
+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) {
+ 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;
}
@@ -566,8 +635,9 @@ char** get_json_array_values(char* key, char* json_data, uint32_t max_tokens, in
jsmn_init(&parser);
// Allocate memory for JSON tokens
- jsmntok_t* tokens = malloc(sizeof(jsmntok_t) * max_tokens); // Allocate on the heap
- if(tokens == NULL) {
+ 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;
@@ -575,7 +645,8 @@ char** get_json_array_values(char* key, char* json_data, uint32_t max_tokens, in
// Parse the JSON array
int ret = jsmn_parse(&parser, array_str, strlen(array_str), tokens, max_tokens);
- if(ret < 0) {
+ if (ret < 0)
+ {
FURI_LOG_E("JSMM.H", "Failed to parse JSON array: %d", ret);
free(tokens);
free(array_str);
@@ -583,7 +654,8 @@ char** get_json_array_values(char* key, char* json_data, uint32_t max_tokens, in
}
// Ensure the root element is an array
- if(tokens[0].type != JSMN_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);
@@ -592,8 +664,9 @@ char** get_json_array_values(char* key, char* json_data, uint32_t max_tokens, in
// 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) {
+ 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);
@@ -604,15 +677,18 @@ char** get_json_array_values(char* key, char* json_data, uint32_t max_tokens, in
// 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) {
+ 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) {
+ 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;
@@ -622,10 +698,12 @@ char** get_json_array_values(char* key, char* json_data, uint32_t max_tokens, in
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) {
+ 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++) {
+ for (int j = 0; j < actual_num_values; j++)
+ {
free(values[j]);
}
free(values);
@@ -647,14 +725,17 @@ char** get_json_array_values(char* key, char* json_data, uint32_t max_tokens, in
*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) {
+ 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++) {
+ for (int i = actual_num_values; i < array_size; i++)
+ {
free(values[i]);
}
}
diff --git a/flip_trader/jsmn/jsmn.h b/flip_trader/jsmn/jsmn.h
index cd95a0e58..74cdccf95 100644
--- a/flip_trader/jsmn/jsmn.h
+++ b/flip_trader/jsmn/jsmn.h
@@ -19,7 +19,8 @@
#include
#ifdef __cplusplus
-extern "C" {
+extern "C"
+{
#endif
#ifdef JSMN_STATIC
@@ -28,71 +29,71 @@ extern "C" {
#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
-};
-
-/**
+ 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;
+ typedef struct
+ {
+ jsmntype_t type;
+ int start;
+ int end;
+ int size;
#ifdef JSMN_PARENT_LINKS
- int parent;
+ int parent;
#endif
-} jsmntok_t;
+ } 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;
-
-/**
+ 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);
+ 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);
+ 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 */
@@ -116,16 +117,16 @@ JSMN_API int jsmn_parse(
#include
// Helper function to create a JSON object
-char* jsmn(const char* key, const char* value);
+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);
+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);
+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);
+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);
+char **get_json_array_values(char *key, char *json_data, uint32_t max_tokens, int *num_values);
#endif /* JB_JSMN_EDIT */
diff --git a/flip_weather/alloc/flip_weather_alloc.c b/flip_weather/alloc/flip_weather_alloc.c
index 8cebbc983..f18711531 100644
--- a/flip_weather/alloc/flip_weather_alloc.c
+++ b/flip_weather/alloc/flip_weather_alloc.c
@@ -1,13 +1,15 @@
#include
// Function to allocate resources for the FlipWeatherApp
-FlipWeatherApp* flip_weather_app_alloc() {
- FlipWeatherApp* app = (FlipWeatherApp*)malloc(sizeof(FlipWeatherApp));
+FlipWeatherApp *flip_weather_app_alloc()
+{
+ FlipWeatherApp *app = (FlipWeatherApp *)malloc(sizeof(FlipWeatherApp));
- Gui* gui = furi_record_open(RECORD_GUI);
+ Gui *gui = furi_record_open(RECORD_GUI);
// initialize uart
- if(!flipper_http_init(flipper_http_rx_callback, app)) {
+ if (!flipper_http_init(flipper_http_rx_callback, app))
+ {
FURI_LOG_E(TAG, "Failed to initialize flipper http");
return NULL;
}
@@ -15,150 +17,94 @@ FlipWeatherApp* flip_weather_app_alloc() {
// Allocate the text input buffer
app->uart_text_input_buffer_size_ssid = 64;
app->uart_text_input_buffer_size_password = 64;
- if(!easy_flipper_set_buffer(
- &app->uart_text_input_buffer_ssid, app->uart_text_input_buffer_size_ssid)) {
+ if (!easy_flipper_set_buffer(&app->uart_text_input_buffer_ssid, app->uart_text_input_buffer_size_ssid))
+ {
return NULL;
}
- if(!easy_flipper_set_buffer(
- &app->uart_text_input_temp_buffer_ssid, app->uart_text_input_buffer_size_ssid)) {
+ if (!easy_flipper_set_buffer(&app->uart_text_input_temp_buffer_ssid, app->uart_text_input_buffer_size_ssid))
+ {
return NULL;
}
- if(!easy_flipper_set_buffer(
- &app->uart_text_input_buffer_password, app->uart_text_input_buffer_size_password)) {
+ if (!easy_flipper_set_buffer(&app->uart_text_input_buffer_password, app->uart_text_input_buffer_size_password))
+ {
return NULL;
}
- if(!easy_flipper_set_buffer(
- &app->uart_text_input_temp_buffer_password,
- app->uart_text_input_buffer_size_password)) {
+ if (!easy_flipper_set_buffer(&app->uart_text_input_temp_buffer_password, app->uart_text_input_buffer_size_password))
+ {
return NULL;
}
// Allocate ViewDispatcher
- if(!easy_flipper_set_view_dispatcher(&app->view_dispatcher, gui, app)) {
+ if (!easy_flipper_set_view_dispatcher(&app->view_dispatcher, gui, app))
+ {
return NULL;
}
- view_dispatcher_set_custom_event_callback(
- app->view_dispatcher, flip_weather_custom_event_callback);
+ view_dispatcher_set_custom_event_callback(app->view_dispatcher, flip_weather_custom_event_callback);
// Main view
- if(!easy_flipper_set_view(
- &app->view_loader,
- FlipWeatherViewLoader,
- flip_weather_loader_draw_callback,
- NULL,
- callback_to_submenu,
- &app->view_dispatcher,
- app)) {
+ if (!easy_flipper_set_view(&app->view_loader, FlipWeatherViewLoader, flip_weather_loader_draw_callback, NULL, callback_to_submenu, &app->view_dispatcher, app))
+ {
return NULL;
}
flip_weather_loader_init(app->view_loader);
// Widget
- if(!easy_flipper_set_widget(
- &app->widget,
- FlipWeatherViewAbout,
- "FlipWeather v1.2\n-----\nUse WiFi to get GPS and \nWeather information.\n-----\nwww.github.com/jblanked",
- callback_to_submenu,
- &app->view_dispatcher)) {
+ if (!easy_flipper_set_widget(&app->widget, FlipWeatherViewAbout, "FlipWeather v1.2\n-----\nUse WiFi to get GPS and \nWeather information.\n-----\nwww.github.com/jblanked", callback_to_submenu, &app->view_dispatcher))
+ {
return NULL;
}
- if(!easy_flipper_set_widget(
- &app->widget_result,
- FlipWeatherViewWidgetResult,
- "Error, try again.",
- callback_to_submenu,
- &app->view_dispatcher)) {
+ if (!easy_flipper_set_widget(&app->widget_result, FlipWeatherViewWidgetResult, "Error, try again.", callback_to_submenu, &app->view_dispatcher))
+ {
return NULL;
}
// Text Input
- if(!easy_flipper_set_uart_text_input(
- &app->uart_text_input_ssid,
- FlipWeatherViewTextInputSSID,
- "Enter SSID",
- app->uart_text_input_temp_buffer_ssid,
- app->uart_text_input_buffer_size_ssid,
- text_updated_ssid,
- callback_to_wifi_settings,
- &app->view_dispatcher,
- app)) {
+ if (!easy_flipper_set_uart_text_input(&app->uart_text_input_ssid, FlipWeatherViewTextInputSSID, "Enter SSID", app->uart_text_input_temp_buffer_ssid, app->uart_text_input_buffer_size_ssid, text_updated_ssid, callback_to_wifi_settings, &app->view_dispatcher, app))
+ {
return NULL;
}
- if(!easy_flipper_set_uart_text_input(
- &app->uart_text_input_password,
- FlipWeatherViewTextInputPassword,
- "Enter Password",
- app->uart_text_input_temp_buffer_password,
- app->uart_text_input_buffer_size_password,
- text_updated_password,
- callback_to_wifi_settings,
- &app->view_dispatcher,
- app)) {
+ if (!easy_flipper_set_uart_text_input(&app->uart_text_input_password, FlipWeatherViewTextInputPassword, "Enter Password", app->uart_text_input_temp_buffer_password, app->uart_text_input_buffer_size_password, text_updated_password, callback_to_wifi_settings, &app->view_dispatcher, app))
+ {
return NULL;
}
// Variable Item List
- if(!easy_flipper_set_variable_item_list(
- &app->variable_item_list,
- FlipWeatherViewSettings,
- settings_item_selected,
- callback_to_submenu,
- &app->view_dispatcher,
- app)) {
+ if (!easy_flipper_set_variable_item_list(&app->variable_item_list, FlipWeatherViewSettings, settings_item_selected, callback_to_submenu, &app->view_dispatcher, app))
+ {
return NULL;
}
- app->variable_item_ssid =
- variable_item_list_add(app->variable_item_list, "SSID", 0, NULL, NULL);
- app->variable_item_password =
- variable_item_list_add(app->variable_item_list, "Password", 0, NULL, NULL);
+ app->variable_item_ssid = variable_item_list_add(app->variable_item_list, "SSID", 0, NULL, NULL);
+ app->variable_item_password = variable_item_list_add(app->variable_item_list, "Password", 0, NULL, NULL);
variable_item_set_current_value_text(app->variable_item_ssid, "");
variable_item_set_current_value_text(app->variable_item_password, "");
// Submenu
- if(!easy_flipper_set_submenu(
- &app->submenu,
- FlipWeatherViewSubmenu,
- "FlipWeather v1.2",
- callback_exit_app,
- &app->view_dispatcher)) {
+ if (!easy_flipper_set_submenu(&app->submenu, FlipWeatherViewSubmenu, "FlipWeather v1.2", callback_exit_app, &app->view_dispatcher))
+ {
return NULL;
}
- submenu_add_item(
- app->submenu, "Weather", FlipWeatherSubmenuIndexWeather, callback_submenu_choices, app);
- submenu_add_item(
- app->submenu, "GPS", FlipWeatherSubmenuIndexGPS, callback_submenu_choices, app);
- submenu_add_item(
- app->submenu, "About", FlipWeatherSubmenuIndexAbout, callback_submenu_choices, app);
- submenu_add_item(
- app->submenu, "Settings", FlipWeatherSubmenuIndexSettings, callback_submenu_choices, app);
+ submenu_add_item(app->submenu, "Weather", FlipWeatherSubmenuIndexWeather, callback_submenu_choices, app);
+ submenu_add_item(app->submenu, "GPS", FlipWeatherSubmenuIndexGPS, callback_submenu_choices, app);
+ submenu_add_item(app->submenu, "About", FlipWeatherSubmenuIndexAbout, callback_submenu_choices, app);
+ submenu_add_item(app->submenu, "Settings", FlipWeatherSubmenuIndexSettings, callback_submenu_choices, app);
// load settings
- if(load_settings(
- app->uart_text_input_buffer_ssid,
- app->uart_text_input_buffer_size_ssid,
- app->uart_text_input_buffer_password,
- app->uart_text_input_buffer_size_password)) {
+ if (load_settings(app->uart_text_input_buffer_ssid, app->uart_text_input_buffer_size_ssid, app->uart_text_input_buffer_password, app->uart_text_input_buffer_size_password))
+ {
// Update variable items
- if(app->variable_item_ssid)
- variable_item_set_current_value_text(
- app->variable_item_ssid, app->uart_text_input_buffer_ssid);
+ if (app->variable_item_ssid)
+ variable_item_set_current_value_text(app->variable_item_ssid, app->uart_text_input_buffer_ssid);
// dont show password
// Copy items into their temp buffers with safety checks
- if(app->uart_text_input_buffer_ssid && app->uart_text_input_temp_buffer_ssid) {
- strncpy(
- app->uart_text_input_temp_buffer_ssid,
- app->uart_text_input_buffer_ssid,
- app->uart_text_input_buffer_size_ssid - 1);
- app->uart_text_input_temp_buffer_ssid[app->uart_text_input_buffer_size_ssid - 1] =
- '\0';
+ if (app->uart_text_input_buffer_ssid && app->uart_text_input_temp_buffer_ssid)
+ {
+ strncpy(app->uart_text_input_temp_buffer_ssid, app->uart_text_input_buffer_ssid, app->uart_text_input_buffer_size_ssid - 1);
+ app->uart_text_input_temp_buffer_ssid[app->uart_text_input_buffer_size_ssid - 1] = '\0';
}
- if(app->uart_text_input_buffer_password && app->uart_text_input_temp_buffer_password) {
- strncpy(
- app->uart_text_input_temp_buffer_password,
- app->uart_text_input_buffer_password,
- app->uart_text_input_buffer_size_password - 1);
- app->uart_text_input_temp_buffer_password[app->uart_text_input_buffer_size_password - 1] =
- '\0';
+ if (app->uart_text_input_buffer_password && app->uart_text_input_temp_buffer_password)
+ {
+ strncpy(app->uart_text_input_temp_buffer_password, app->uart_text_input_buffer_password, app->uart_text_input_buffer_size_password - 1);
+ app->uart_text_input_temp_buffer_password[app->uart_text_input_buffer_size_password - 1] = '\0';
}
}
@@ -166,4 +112,4 @@ FlipWeatherApp* flip_weather_app_alloc() {
view_dispatcher_switch_to_view(app->view_dispatcher, FlipWeatherViewSubmenu);
return app;
-}
+}
\ No newline at end of file
diff --git a/flip_weather/alloc/flip_weather_alloc.h b/flip_weather/alloc/flip_weather_alloc.h
index 3fe0b5f66..25ec449aa 100644
--- a/flip_weather/alloc/flip_weather_alloc.h
+++ b/flip_weather/alloc/flip_weather_alloc.h
@@ -3,6 +3,6 @@
#include
#include
#include
-FlipWeatherApp* flip_weather_app_alloc();
+FlipWeatherApp *flip_weather_app_alloc();
-#endif
+#endif
\ No newline at end of file
diff --git a/flip_weather/app.c b/flip_weather/app.c
index f827e3173..195130e20 100644
--- a/flip_weather/app.c
+++ b/flip_weather/app.c
@@ -2,35 +2,41 @@
#include
// Entry point for the FlipWeather application
-int32_t flip_weather_app(void* p) {
+int32_t flip_weather_app(void *p)
+{
// Suppress unused parameter warning
UNUSED(p);
// Initialize the FlipWeather application
app_instance = flip_weather_app_alloc();
- if(!app_instance) {
+ if (!app_instance)
+ {
FURI_LOG_E(TAG, "Failed to allocate FlipWeatherApp");
return -1;
}
- if(!flipper_http_ping()) {
+ if (!flipper_http_ping())
+ {
FURI_LOG_E(TAG, "Failed to ping the device");
return -1;
}
// Thanks to Derek Jamison for the following edits
- if(app_instance->uart_text_input_buffer_ssid != NULL &&
- app_instance->uart_text_input_buffer_password != NULL) {
+ if (app_instance->uart_text_input_buffer_ssid != NULL &&
+ app_instance->uart_text_input_buffer_password != NULL)
+ {
// Try to wait for pong response.
uint8_t counter = 10;
- while(fhttp.state == INACTIVE && --counter > 0) {
+ while (fhttp.state == INACTIVE && --counter > 0)
+ {
FURI_LOG_D(TAG, "Waiting for PONG");
furi_delay_ms(100);
}
- if(counter == 0) {
- DialogsApp* dialogs = furi_record_open(RECORD_DIALOGS);
- DialogMessage* message = dialog_message_alloc();
+ if (counter == 0)
+ {
+ DialogsApp *dialogs = furi_record_open(RECORD_DIALOGS);
+ DialogMessage *message = dialog_message_alloc();
dialog_message_set_header(
message, "[FlipperHTTP Error]", 64, 0, AlignCenter, AlignTop);
dialog_message_set_text(
diff --git a/flip_weather/callback/flip_weather_callback.c b/flip_weather/callback/flip_weather_callback.c
index 77252ad5e..019e86b54 100644
--- a/flip_weather/callback/flip_weather_callback.c
+++ b/flip_weather/callback/flip_weather_callback.c
@@ -3,8 +3,7 @@
// Below added by Derek Jamison
// FURI_LOG_DEV will log only during app development. Be sure that Settings/System/Log Device is "LPUART"; so we dont use serial port.
#ifdef DEVELOPMENT
-#define FURI_LOG_DEV(tag, format, ...) \
- furi_log_print_format(FuriLogLevelInfo, tag, format, ##__VA_ARGS__)
+#define FURI_LOG_DEV(tag, format, ...) furi_log_print_format(FuriLogLevelInfo, tag, format, ##__VA_ARGS__)
#define DEV_CRASH() furi_crash()
#else
#define FURI_LOG_DEV(tag, format, ...)
@@ -15,83 +14,80 @@ bool weather_request_success = false;
bool sent_weather_request = false;
bool got_weather_data = false;
-static void flip_weather_request_error_draw(Canvas* canvas) {
- if(canvas == NULL) {
+static void flip_weather_request_error_draw(Canvas *canvas)
+{
+ if (canvas == NULL)
+ {
FURI_LOG_E(TAG, "flip_weather_request_error_draw - canvas is NULL");
DEV_CRASH();
return;
}
- if(fhttp.last_response != NULL) {
- if(strstr(fhttp.last_response, "[ERROR] Not connected to Wifi. Failed to reconnect.") !=
- NULL) {
+ 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) {
+ }
+ 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 if(
- strstr(fhttp.last_response, "[ERROR] GET request failed or returned empty data.") !=
- NULL) {
+ }
+ else if (strstr(fhttp.last_response, "[ERROR] GET request failed or returned empty data.") != NULL)
+ {
canvas_clear(canvas);
canvas_draw_str(canvas, 0, 10, "[ERROR] WiFi error.");
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, "[PONG]") != NULL) {
+ }
+ else if (strstr(fhttp.last_response, "[PONG]") != NULL)
+ {
canvas_clear(canvas);
canvas_draw_str(canvas, 0, 10, "[STATUS]Connecting to AP...");
- } else {
+ }
+ else
+ {
canvas_clear(canvas);
FURI_LOG_E(TAG, "Received an error: %s", fhttp.last_response);
canvas_draw_str(canvas, 0, 10, "[ERROR] Unusual error...");
canvas_draw_str(canvas, 0, 60, "Press BACK and retry.");
}
- } else {
+ }
+ else
+ {
canvas_clear(canvas);
canvas_draw_str(canvas, 0, 10, "[ERROR] Unknown error.");
canvas_draw_str(canvas, 0, 50, "Update your WiFi settings.");
canvas_draw_str(canvas, 0, 60, "Press BACK to return.");
}
}
-static void flip_weather_gps_switch_to_view(FlipWeatherApp* app) {
- flip_weather_generic_switch_to_view(
- app,
- "Fetching GPS data..",
- send_geo_location_request,
- process_geo_location,
- 1,
- callback_to_submenu,
- FlipWeatherViewLoader);
+static void flip_weather_gps_switch_to_view(FlipWeatherApp *app)
+{
+ flip_weather_generic_switch_to_view(app, "Fetching GPS data..", send_geo_location_request, process_geo_location, 1, callback_to_submenu, FlipWeatherViewLoader);
}
-static void flip_weather_weather_switch_to_view(FlipWeatherApp* app) {
- flip_weather_generic_switch_to_view(
- app,
- "Fetching Weather data..",
- send_geo_weather_request,
- process_weather,
- 1,
- callback_to_submenu,
- FlipWeatherViewLoader);
+static void flip_weather_weather_switch_to_view(FlipWeatherApp *app)
+{
+ flip_weather_generic_switch_to_view(app, "Fetching Weather data..", send_geo_weather_request, process_weather, 1, callback_to_submenu, FlipWeatherViewLoader);
}
-void callback_submenu_choices(void* context, uint32_t index) {
- FlipWeatherApp* app = (FlipWeatherApp*)context;
- if(!app) {
+void callback_submenu_choices(void *context, uint32_t index)
+{
+ FlipWeatherApp *app = (FlipWeatherApp *)context;
+ if (!app)
+ {
FURI_LOG_E(TAG, "FlipWeatherApp is NULL");
return;
}
- switch(index) {
+ switch (index)
+ {
case FlipWeatherSubmenuIndexWeather:
- flipper_http_loading_task(
- send_geo_location_request,
- process_geo_location_2,
- FlipWeatherViewSubmenu,
- FlipWeatherViewSubmenu,
- &app->view_dispatcher);
+ flipper_http_loading_task(send_geo_location_request, process_geo_location_2, FlipWeatherViewSubmenu, FlipWeatherViewSubmenu, &app->view_dispatcher);
flip_weather_weather_switch_to_view(app);
break;
case FlipWeatherSubmenuIndexGPS:
@@ -108,36 +104,35 @@ void callback_submenu_choices(void* context, uint32_t index) {
}
}
-void text_updated_ssid(void* context) {
- FlipWeatherApp* app = (FlipWeatherApp*)context;
- if(!app) {
+void text_updated_ssid(void *context)
+{
+ FlipWeatherApp *app = (FlipWeatherApp *)context;
+ if (!app)
+ {
FURI_LOG_E(TAG, "FlipWeatherApp is NULL");
return;
}
// store the entered text
- strncpy(
- app->uart_text_input_buffer_ssid,
- app->uart_text_input_temp_buffer_ssid,
- app->uart_text_input_buffer_size_ssid);
+ strncpy(app->uart_text_input_buffer_ssid, app->uart_text_input_temp_buffer_ssid, app->uart_text_input_buffer_size_ssid);
// Ensure null-termination
app->uart_text_input_buffer_ssid[app->uart_text_input_buffer_size_ssid - 1] = '\0';
// update the variable item text
- if(app->variable_item_ssid) {
- variable_item_set_current_value_text(
- app->variable_item_ssid, app->uart_text_input_buffer_ssid);
+ if (app->variable_item_ssid)
+ {
+ variable_item_set_current_value_text(app->variable_item_ssid, app->uart_text_input_buffer_ssid);
}
// save settings
save_settings(app->uart_text_input_buffer_ssid, app->uart_text_input_buffer_password);
// save wifi settings to devboard
- if(strlen(app->uart_text_input_buffer_ssid) > 0 &&
- strlen(app->uart_text_input_buffer_password) > 0) {
- if(!flipper_http_save_wifi(
- app->uart_text_input_buffer_ssid, app->uart_text_input_buffer_password)) {
+ if (strlen(app->uart_text_input_buffer_ssid) > 0 && strlen(app->uart_text_input_buffer_password) > 0)
+ {
+ if (!flipper_http_save_wifi(app->uart_text_input_buffer_ssid, app->uart_text_input_buffer_password))
+ {
FURI_LOG_E(TAG, "Failed to save wifi settings");
}
}
@@ -146,36 +141,35 @@ void text_updated_ssid(void* context) {
view_dispatcher_switch_to_view(app->view_dispatcher, FlipWeatherViewSettings);
}
-void text_updated_password(void* context) {
- FlipWeatherApp* app = (FlipWeatherApp*)context;
- if(!app) {
+void text_updated_password(void *context)
+{
+ FlipWeatherApp *app = (FlipWeatherApp *)context;
+ if (!app)
+ {
FURI_LOG_E(TAG, "FlipWeatherApp is NULL");
return;
}
// store the entered text
- strncpy(
- app->uart_text_input_buffer_password,
- app->uart_text_input_temp_buffer_password,
- app->uart_text_input_buffer_size_password);
+ strncpy(app->uart_text_input_buffer_password, app->uart_text_input_temp_buffer_password, app->uart_text_input_buffer_size_password);
// Ensure null-termination
app->uart_text_input_buffer_password[app->uart_text_input_buffer_size_password - 1] = '\0';
// update the variable item text
- if(app->variable_item_password) {
- variable_item_set_current_value_text(
- app->variable_item_password, app->uart_text_input_buffer_password);
+ if (app->variable_item_password)
+ {
+ variable_item_set_current_value_text(app->variable_item_password, app->uart_text_input_buffer_password);
}
// save settings
save_settings(app->uart_text_input_buffer_ssid, app->uart_text_input_buffer_password);
// save wifi settings to devboard
- if(strlen(app->uart_text_input_buffer_ssid) > 0 &&
- strlen(app->uart_text_input_buffer_password) > 0) {
- if(!flipper_http_save_wifi(
- app->uart_text_input_buffer_ssid, app->uart_text_input_buffer_password)) {
+ if (strlen(app->uart_text_input_buffer_ssid) > 0 && strlen(app->uart_text_input_buffer_password) > 0)
+ {
+ if (!flipper_http_save_wifi(app->uart_text_input_buffer_ssid, app->uart_text_input_buffer_password))
+ {
FURI_LOG_E(TAG, "Failed to save wifi settings");
}
}
@@ -184,8 +178,10 @@ void text_updated_password(void* context) {
view_dispatcher_switch_to_view(app->view_dispatcher, FlipWeatherViewSettings);
}
-uint32_t callback_to_submenu(void* context) {
- if(!context) {
+uint32_t callback_to_submenu(void *context)
+{
+ if (!context)
+ {
FURI_LOG_E(TAG, "Context is NULL");
return VIEW_NONE;
}
@@ -198,24 +194,29 @@ uint32_t callback_to_submenu(void* context) {
weather_information_processed = false;
sent_weather_request = false;
weather_request_success = false;
- if(weather_data != NULL) {
+ if (weather_data != NULL)
+ {
free(weather_data);
weather_data = NULL;
}
- if(total_data != NULL) {
+ if (total_data != NULL)
+ {
free(total_data);
total_data = NULL;
}
return FlipWeatherViewSubmenu;
}
-void settings_item_selected(void* context, uint32_t index) {
- FlipWeatherApp* app = (FlipWeatherApp*)context;
- if(!app) {
+void settings_item_selected(void *context, uint32_t index)
+{
+ FlipWeatherApp *app = (FlipWeatherApp *)context;
+ if (!app)
+ {
FURI_LOG_E(TAG, "FlipWeatherApp is NULL");
return;
}
- switch(index) {
+ switch (index)
+ {
case 0: // Input SSID
view_dispatcher_switch_to_view(app->view_dispatcher, FlipWeatherViewTextInputSSID);
break;
@@ -233,9 +234,11 @@ void settings_item_selected(void* context, uint32_t index) {
* @param context The context - unused
* @return next view id (VIEW_NONE to exit the app)
*/
-uint32_t callback_exit_app(void* context) {
+uint32_t callback_exit_app(void *context)
+{
// Exit the application
- if(!context) {
+ if (!context)
+ {
FURI_LOG_E(TAG, "Context is NULL");
return VIEW_NONE;
}
@@ -243,8 +246,10 @@ uint32_t callback_exit_app(void* context) {
return VIEW_NONE; // Return VIEW_NONE to exit the app
}
-uint32_t callback_to_wifi_settings(void* context) {
- if(!context) {
+uint32_t callback_to_wifi_settings(void *context)
+{
+ if (!context)
+ {
FURI_LOG_E(TAG, "Context is NULL");
return VIEW_NONE;
}
@@ -252,13 +257,16 @@ uint32_t callback_to_wifi_settings(void* context) {
return FlipWeatherViewSettings;
}
-static void flip_weather_widget_set_text(char* message, Widget** widget) {
- if(widget == NULL) {
+static void flip_weather_widget_set_text(char *message, Widget **widget)
+{
+ if (widget == NULL)
+ {
FURI_LOG_E(TAG, "flip_weather_set_widget_text - widget is NULL");
DEV_CRASH();
return;
}
- if(message == NULL) {
+ if (message == NULL)
+ {
FURI_LOG_E(TAG, "flip_weather_set_widget_text - message is NULL");
DEV_CRASH();
return;
@@ -266,32 +274,36 @@ static void flip_weather_widget_set_text(char* message, Widget** widget) {
widget_reset(*widget);
uint32_t message_length = strlen(message); // Length of the message
- uint32_t i = 0; // Index tracker
- uint32_t formatted_index = 0; // Tracker for where we are in the formatted message
- char* formatted_message; // Buffer to hold the final formatted message
+ uint32_t i = 0; // Index tracker
+ uint32_t formatted_index = 0; // Tracker for where we are in the formatted message
+ char *formatted_message; // Buffer to hold the final formatted message
// Allocate buffer with double the message length plus one for safety
- if(!easy_flipper_set_buffer(&formatted_message, message_length * 2 + 1)) {
+ if (!easy_flipper_set_buffer(&formatted_message, message_length * 2 + 1))
+ {
return;
}
- while(i < message_length) {
- uint32_t max_line_length = 31; // Maximum characters per line
+ while (i < message_length)
+ {
+ uint32_t max_line_length = 31; // Maximum characters per line
uint32_t remaining_length = message_length - i; // Remaining characters
- uint32_t line_length = (remaining_length < max_line_length) ? remaining_length :
- max_line_length;
+ uint32_t line_length = (remaining_length < max_line_length) ? remaining_length : max_line_length;
// Check for newline character within the current segment
uint32_t newline_pos = i;
bool found_newline = false;
- for(; newline_pos < i + line_length && newline_pos < message_length; newline_pos++) {
- if(message[newline_pos] == '\n') {
+ for (; newline_pos < i + line_length && newline_pos < message_length; newline_pos++)
+ {
+ if (message[newline_pos] == '\n')
+ {
found_newline = true;
break;
}
}
- if(found_newline) {
+ if (found_newline)
+ {
// If newline found, set line_length up to the newline
line_length = newline_pos - i;
}
@@ -302,15 +314,19 @@ static void flip_weather_widget_set_text(char* message, Widget** widget) {
line[line_length] = '\0';
// If newline was found, skip it for the next iteration
- if(found_newline) {
+ if (found_newline)
+ {
i += line_length + 1; // +1 to skip the '\n' character
- } else {
+ }
+ else
+ {
// Check if the line ends in the middle of a word and adjust accordingly
- if(line_length == max_line_length && message[i + line_length] != '\0' &&
- message[i + line_length] != ' ') {
+ if (line_length == max_line_length && message[i + line_length] != '\0' && message[i + line_length] != ' ')
+ {
// Find the last space within the current line to avoid breaking a word
- char* last_space = strrchr(line, ' ');
- if(last_space != NULL) {
+ char *last_space = strrchr(line, ' ');
+ if (last_space != NULL)
+ {
// Adjust the line_length to avoid cutting the word
line_length = last_space - line;
line[line_length] = '\0'; // Null-terminate at the space
@@ -321,13 +337,15 @@ static void flip_weather_widget_set_text(char* message, Widget** widget) {
i += line_length;
// Skip any spaces at the beginning of the next line
- while(i < message_length && message[i] == ' ') {
+ while (i < message_length && message[i] == ' ')
+ {
i++;
}
}
// Manually copy the fixed line into the formatted_message buffer
- for(uint32_t j = 0; j < line_length; j++) {
+ for (uint32_t j = 0; j < line_length; j++)
+ {
formatted_message[formatted_index++] = line[j];
}
@@ -342,20 +360,23 @@ static void flip_weather_widget_set_text(char* message, Widget** widget) {
widget_add_text_scroll_element(*widget, 0, 0, 128, 64, formatted_message);
}
-void flip_weather_loader_draw_callback(Canvas* canvas, void* model) {
- if(!canvas || !model) {
+void flip_weather_loader_draw_callback(Canvas *canvas, void *model)
+{
+ if (!canvas || !model)
+ {
FURI_LOG_E(TAG, "flip_weather_loader_draw_callback - canvas or model is NULL");
return;
}
SerialState http_state = fhttp.state;
- DataLoaderModel* data_loader_model = (DataLoaderModel*)model;
+ DataLoaderModel *data_loader_model = (DataLoaderModel *)model;
DataState data_state = data_loader_model->data_state;
- char* title = data_loader_model->title;
+ char *title = data_loader_model->title;
canvas_set_font(canvas, FontSecondary);
- if(http_state == INACTIVE) {
+ if (http_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,");
@@ -365,7 +386,8 @@ void flip_weather_loader_draw_callback(Canvas* canvas, void* model) {
return;
}
- if(data_state == DataStateError || data_state == DataStateParseError) {
+ if (data_state == DataStateError || data_state == DataStateParseError)
+ {
flip_weather_request_error_draw(canvas);
return;
}
@@ -373,53 +395,61 @@ void flip_weather_loader_draw_callback(Canvas* canvas, void* model) {
canvas_draw_str(canvas, 0, 7, title);
canvas_draw_str(canvas, 0, 17, "Loading...");
- if(data_state == DataStateInitial) {
+ if (data_state == DataStateInitial)
+ {
return;
}
- if(http_state == SENDING) {
+ if (http_state == SENDING)
+ {
canvas_draw_str(canvas, 0, 27, "Sending...");
return;
}
- if(http_state == RECEIVING || data_state == DataStateRequested) {
+ if (http_state == RECEIVING || data_state == DataStateRequested)
+ {
canvas_draw_str(canvas, 0, 27, "Receiving...");
return;
}
- if(http_state == IDLE && data_state == DataStateReceived) {
+ if (http_state == IDLE && data_state == DataStateReceived)
+ {
canvas_draw_str(canvas, 0, 27, "Processing...");
return;
}
- if(http_state == IDLE && data_state == DataStateParsed) {
+ if (http_state == IDLE && data_state == DataStateParsed)
+ {
canvas_draw_str(canvas, 0, 27, "Processed...");
return;
}
}
-static void flip_weather_loader_process_callback(void* context) {
- if(context == NULL) {
+static void flip_weather_loader_process_callback(void *context)
+{
+ if (context == NULL)
+ {
FURI_LOG_E(TAG, "flip_weather_loader_process_callback - context is NULL");
DEV_CRASH();
return;
}
- FlipWeatherApp* app = (FlipWeatherApp*)context;
- View* view = app->view_loader;
+ FlipWeatherApp *app = (FlipWeatherApp *)context;
+ View *view = app->view_loader;
DataState current_data_state;
- with_view_model(
- view, DataLoaderModel * model, { current_data_state = model->data_state; }, false);
+ with_view_model(view, DataLoaderModel * model, { current_data_state = model->data_state; }, false);
- if(current_data_state == DataStateInitial) {
+ if (current_data_state == DataStateInitial)
+ {
with_view_model(
view,
DataLoaderModel * model,
{
model->data_state = DataStateRequested;
DataLoaderFetch fetch = model->fetcher;
- if(fetch == NULL) {
+ if (fetch == NULL)
+ {
FURI_LOG_E(TAG, "Model doesn't have Fetch function assigned.");
model->data_state = DataStateError;
return;
@@ -428,151 +458,174 @@ static void flip_weather_loader_process_callback(void* context) {
// Clear any previous responses
strncpy(fhttp.last_response, "", 1);
bool request_status = fetch(model);
- if(!request_status) {
+ if (!request_status)
+ {
model->data_state = DataStateError;
}
},
true);
- } else if(current_data_state == DataStateRequested || current_data_state == DataStateError) {
- if(fhttp.state == IDLE && fhttp.last_response != NULL) {
- if(strstr(fhttp.last_response, "[PONG]") != NULL) {
+ }
+ else if (current_data_state == DataStateRequested || current_data_state == DataStateError)
+ {
+ if (fhttp.state == IDLE && fhttp.last_response != NULL)
+ {
+ if (strstr(fhttp.last_response, "[PONG]") != NULL)
+ {
FURI_LOG_DEV(TAG, "PONG received.");
- } else if(strncmp(fhttp.last_response, "[SUCCESS]", 9) == 0) {
- FURI_LOG_DEV(
- TAG,
- "SUCCESS received. %s",
- fhttp.last_response ? fhttp.last_response : "NULL");
- } else if(strncmp(fhttp.last_response, "[ERROR]", 9) == 0) {
- FURI_LOG_DEV(
- TAG, "ERROR received. %s", fhttp.last_response ? fhttp.last_response : "NULL");
- } else if(strlen(fhttp.last_response) == 0) {
+ }
+ else if (strncmp(fhttp.last_response, "[SUCCESS]", 9) == 0)
+ {
+ FURI_LOG_DEV(TAG, "SUCCESS received. %s", fhttp.last_response ? fhttp.last_response : "NULL");
+ }
+ else if (strncmp(fhttp.last_response, "[ERROR]", 9) == 0)
+ {
+ FURI_LOG_DEV(TAG, "ERROR received. %s", fhttp.last_response ? fhttp.last_response : "NULL");
+ }
+ else if (strlen(fhttp.last_response) == 0)
+ {
// Still waiting on response
- } else {
- with_view_model(
- view,
- DataLoaderModel * model,
- { model->data_state = DataStateReceived; },
- true);
}
- } else if(fhttp.state == SENDING || fhttp.state == RECEIVING) {
+ else
+ {
+ with_view_model(view, DataLoaderModel * model, { model->data_state = DataStateReceived; }, true);
+ }
+ }
+ else if (fhttp.state == SENDING || fhttp.state == RECEIVING)
+ {
// continue waiting
- } else if(fhttp.state == INACTIVE) {
+ }
+ else if (fhttp.state == INACTIVE)
+ {
// inactive. try again
- } else if(fhttp.state == ISSUE) {
- with_view_model(
- view, DataLoaderModel * model, { model->data_state = DataStateError; }, true);
- } else {
- FURI_LOG_DEV(
- TAG,
- "Unexpected state: %d lastresp: %s",
- fhttp.state,
- fhttp.last_response ? fhttp.last_response : "NULL");
+ }
+ else if (fhttp.state == ISSUE)
+ {
+ with_view_model(view, DataLoaderModel * model, { model->data_state = DataStateError; }, true);
+ }
+ else
+ {
+ FURI_LOG_DEV(TAG, "Unexpected state: %d lastresp: %s", fhttp.state, fhttp.last_response ? fhttp.last_response : "NULL");
DEV_CRASH();
}
- } else if(current_data_state == DataStateReceived) {
+ }
+ else if (current_data_state == DataStateReceived)
+ {
with_view_model(
view,
DataLoaderModel * model,
{
- char* data_text;
- if(model->parser == NULL) {
+ char *data_text;
+ if (model->parser == NULL)
+ {
data_text = NULL;
FURI_LOG_DEV(TAG, "Parser is NULL");
DEV_CRASH();
- } else {
+ }
+ else
+ {
data_text = model->parser(model);
}
- FURI_LOG_DEV(
- TAG,
- "Parsed data: %s\r\ntext: %s",
- fhttp.last_response ? fhttp.last_response : "NULL",
- data_text ? data_text : "NULL");
+ FURI_LOG_DEV(TAG, "Parsed data: %s\r\ntext: %s", fhttp.last_response ? fhttp.last_response : "NULL", data_text ? data_text : "NULL");
model->data_text = data_text;
- if(data_text == NULL) {
+ if (data_text == NULL)
+ {
model->data_state = DataStateParseError;
- } else {
+ }
+ else
+ {
model->data_state = DataStateParsed;
}
},
true);
- } else if(current_data_state == DataStateParsed) {
+ }
+ else if (current_data_state == DataStateParsed)
+ {
with_view_model(
view,
DataLoaderModel * model,
{
- if(++model->request_index < model->request_count) {
+ if (++model->request_index < model->request_count)
+ {
model->data_state = DataStateInitial;
- } else {
- flip_weather_widget_set_text(
- model->data_text != NULL ? model->data_text : "Empty result",
- &app_instance->widget_result);
- if(model->data_text != NULL) {
+ }
+ else
+ {
+ flip_weather_widget_set_text(model->data_text != NULL ? model->data_text : "Empty result", &app_instance->widget_result);
+ if (model->data_text != NULL)
+ {
free(model->data_text);
model->data_text = NULL;
}
- view_set_previous_callback(
- widget_get_view(app_instance->widget_result), model->back_callback);
- view_dispatcher_switch_to_view(
- app_instance->view_dispatcher, FlipWeatherViewWidgetResult);
+ view_set_previous_callback(widget_get_view(app_instance->widget_result), model->back_callback);
+ view_dispatcher_switch_to_view(app_instance->view_dispatcher, FlipWeatherViewWidgetResult);
}
},
true);
}
}
-static void flip_weather_loader_timer_callback(void* context) {
- if(context == NULL) {
+static void flip_weather_loader_timer_callback(void *context)
+{
+ if (context == NULL)
+ {
FURI_LOG_E(TAG, "flip_weather_loader_timer_callback - context is NULL");
DEV_CRASH();
return;
}
- FlipWeatherApp* app = (FlipWeatherApp*)context;
+ FlipWeatherApp *app = (FlipWeatherApp *)context;
view_dispatcher_send_custom_event(app->view_dispatcher, FlipWeatherCustomEventProcess);
}
-static void flip_weather_loader_on_enter(void* context) {
- if(context == NULL) {
+static void flip_weather_loader_on_enter(void *context)
+{
+ if (context == NULL)
+ {
FURI_LOG_E(TAG, "flip_weather_loader_on_enter - context is NULL");
DEV_CRASH();
return;
}
- FlipWeatherApp* app = (FlipWeatherApp*)context;
- View* view = app->view_loader;
+ FlipWeatherApp *app = (FlipWeatherApp *)context;
+ View *view = app->view_loader;
with_view_model(
view,
DataLoaderModel * model,
{
view_set_previous_callback(view, model->back_callback);
- if(model->timer == NULL) {
- model->timer = furi_timer_alloc(
- flip_weather_loader_timer_callback, FuriTimerTypePeriodic, app);
+ if (model->timer == NULL)
+ {
+ model->timer = furi_timer_alloc(flip_weather_loader_timer_callback, FuriTimerTypePeriodic, app);
}
furi_timer_start(model->timer, 250);
},
true);
}
-static void flip_weather_loader_on_exit(void* context) {
- if(context == NULL) {
+static void flip_weather_loader_on_exit(void *context)
+{
+ if (context == NULL)
+ {
FURI_LOG_E(TAG, "flip_weather_loader_on_exit - context is NULL");
DEV_CRASH();
return;
}
- FlipWeatherApp* app = (FlipWeatherApp*)context;
- View* view = app->view_loader;
+ FlipWeatherApp *app = (FlipWeatherApp *)context;
+ View *view = app->view_loader;
with_view_model(
view,
DataLoaderModel * model,
{
- if(model->timer) {
+ if (model->timer)
+ {
furi_timer_stop(model->timer);
}
},
false);
}
-void flip_weather_loader_init(View* view) {
- if(view == NULL) {
+void flip_weather_loader_init(View *view)
+{
+ if (view == NULL)
+ {
FURI_LOG_E(TAG, "flip_weather_loader_init - view is NULL");
DEV_CRASH();
return;
@@ -582,8 +635,10 @@ void flip_weather_loader_init(View* view) {
view_set_exit_callback(view, flip_weather_loader_on_exit);
}
-void flip_weather_loader_free_model(View* view) {
- if(view == NULL) {
+void flip_weather_loader_free_model(View *view)
+{
+ if (view == NULL)
+ {
FURI_LOG_E(TAG, "flip_weather_loader_free_model - view is NULL");
DEV_CRASH();
return;
@@ -592,11 +647,13 @@ void flip_weather_loader_free_model(View* view) {
view,
DataLoaderModel * model,
{
- if(model->timer) {
+ if (model->timer)
+ {
furi_timer_free(model->timer);
model->timer = NULL;
}
- if(model->parser_context) {
+ if (model->parser_context)
+ {
free(model->parser_context);
model->parser_context = NULL;
}
@@ -604,14 +661,17 @@ void flip_weather_loader_free_model(View* view) {
false);
}
-bool flip_weather_custom_event_callback(void* context, uint32_t index) {
- if(context == NULL) {
+bool flip_weather_custom_event_callback(void *context, uint32_t index)
+{
+ if (context == NULL)
+ {
FURI_LOG_E(TAG, "flip_weather_custom_event_callback - context is NULL");
DEV_CRASH();
return false;
}
- switch(index) {
+ switch (index)
+ {
case FlipWeatherCustomEventProcess:
flip_weather_loader_process_callback(context);
return true;
@@ -621,22 +681,18 @@ bool flip_weather_custom_event_callback(void* context, uint32_t index) {
}
}
-void flip_weather_generic_switch_to_view(
- FlipWeatherApp* app,
- char* title,
- DataLoaderFetch fetcher,
- DataLoaderParser parser,
- size_t request_count,
- ViewNavigationCallback back,
- uint32_t view_id) {
- if(app == NULL) {
+void flip_weather_generic_switch_to_view(FlipWeatherApp *app, char *title, DataLoaderFetch fetcher, DataLoaderParser parser, size_t request_count, ViewNavigationCallback back, uint32_t view_id)
+{
+ if (app == NULL)
+ {
FURI_LOG_E(TAG, "flip_weather_generic_switch_to_view - app is NULL");
DEV_CRASH();
return;
}
- View* view = app->view_loader;
- if(view == NULL) {
+ View *view = app->view_loader;
+ if (view == NULL)
+ {
FURI_LOG_E(TAG, "flip_weather_generic_switch_to_view - view is NULL");
DEV_CRASH();
return;
diff --git a/flip_weather/callback/flip_weather_callback.h b/flip_weather/callback/flip_weather_callback.h
index 174c43b3f..9b12bdd3c 100644
--- a/flip_weather/callback/flip_weather_callback.h
+++ b/flip_weather/callback/flip_weather_callback.h
@@ -9,37 +9,30 @@ extern bool weather_request_success;
extern bool sent_weather_request;
extern bool got_weather_data;
-void flip_weather_view_draw_callback_weather(Canvas* canvas, void* model);
-void flip_weather_view_draw_callback_gps(Canvas* canvas, void* model);
-void callback_submenu_choices(void* context, uint32_t index);
-void text_updated_ssid(void* context);
-void text_updated_password(void* context);
-uint32_t callback_to_submenu(void* context);
-void settings_item_selected(void* context, uint32_t index);
+void flip_weather_view_draw_callback_weather(Canvas *canvas, void *model);
+void flip_weather_view_draw_callback_gps(Canvas *canvas, void *model);
+void callback_submenu_choices(void *context, uint32_t index);
+void text_updated_ssid(void *context);
+void text_updated_password(void *context);
+uint32_t callback_to_submenu(void *context);
+void settings_item_selected(void *context, uint32_t index);
/**
* @brief Navigation callback for exiting the application
* @param context The context - unused
* @return next view id (VIEW_NONE to exit the app)
*/
-uint32_t callback_exit_app(void* context);
-uint32_t callback_to_wifi_settings(void* context);
+uint32_t callback_exit_app(void *context);
+uint32_t callback_to_wifi_settings(void *context);
// Add edits by Derek Jamison
-void flip_weather_generic_switch_to_view(
- FlipWeatherApp* app,
- char* title,
- DataLoaderFetch fetcher,
- DataLoaderParser parser,
- size_t request_count,
- ViewNavigationCallback back,
- uint32_t view_id);
+void flip_weather_generic_switch_to_view(FlipWeatherApp *app, char *title, DataLoaderFetch fetcher, DataLoaderParser parser, size_t request_count, ViewNavigationCallback back, uint32_t view_id);
-void flip_weather_loader_draw_callback(Canvas* canvas, void* model);
+void flip_weather_loader_draw_callback(Canvas *canvas, void *model);
-void flip_weather_loader_init(View* view);
+void flip_weather_loader_init(View *view);
-void flip_weather_loader_free_model(View* view);
+void flip_weather_loader_free_model(View *view);
-bool flip_weather_custom_event_callback(void* context, uint32_t index);
-#endif
+bool flip_weather_custom_event_callback(void *context, uint32_t index);
+#endif
\ No newline at end of file
diff --git a/flip_weather/easy_flipper/easy_flipper.c b/flip_weather/easy_flipper/easy_flipper.c
index 8b98e1a1b..f61b7d231 100644
--- a/flip_weather/easy_flipper/easy_flipper.c
+++ b/flip_weather/easy_flipper/easy_flipper.c
@@ -5,9 +5,11 @@
* @param context The context - unused
* @return next view id (VIEW_NONE to exit the app)
*/
-uint32_t easy_flipper_callback_exit_app(void* context) {
+uint32_t easy_flipper_callback_exit_app(void *context)
+{
// Exit the application
- if(!context) {
+ if (!context)
+ {
FURI_LOG_E(EASY_TAG, "Context is NULL");
return VIEW_NONE;
}
@@ -21,13 +23,16 @@ uint32_t easy_flipper_callback_exit_app(void* context) {
* @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) {
+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) {
+ *buffer = (char *)malloc(buffer_size);
+ if (!*buffer)
+ {
FURI_LOG_E(EASY_TAG, "Failed to allocate buffer");
return false;
}
@@ -46,32 +51,39 @@ bool easy_flipper_set_buffer(char** buffer, uint32_t buffer_size) {
* @return true if successful, false otherwise
*/
bool easy_flipper_set_view(
- View** 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) {
+ 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) {
+ if (!*view)
+ {
FURI_LOG_E(EASY_TAG, "Failed to allocate View");
return false;
}
- if(draw_callback) {
+ if (draw_callback)
+ {
view_set_draw_callback(*view, draw_callback);
}
- if(input_callback) {
+ if (input_callback)
+ {
view_set_input_callback(*view, input_callback);
}
- if(context) {
+ if (context)
+ {
view_set_context(*view, context);
}
- if(previous_callback) {
+ if (previous_callback)
+ {
view_set_previous_callback(*view, previous_callback);
}
view_dispatcher_add_view(*view_dispatcher, view_id, *view);
@@ -85,18 +97,22 @@ bool easy_flipper_set_view(
* @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) {
+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) {
+ 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) {
+ if (context)
+ {
view_dispatcher_set_event_callback_context(*view_dispatcher, context);
}
return true;
@@ -113,24 +129,29 @@ bool easy_flipper_set_view_dispatcher(ViewDispatcher** view_dispatcher, Gui* gui
* @return true if successful, false otherwise
*/
bool easy_flipper_set_submenu(
- Submenu** submenu,
+ Submenu **submenu,
int32_t view_id,
- char* title,
- uint32_t(previous_callback)(void*),
- ViewDispatcher** view_dispatcher) {
- if(!submenu) {
+ 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) {
+ if (!*submenu)
+ {
FURI_LOG_E(EASY_TAG, "Failed to allocate Submenu");
return false;
}
- if(title) {
+ if (title)
+ {
submenu_set_header(*submenu, title);
}
- if(previous_callback) {
+ 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));
@@ -147,20 +168,24 @@ bool easy_flipper_set_submenu(
* @return true if successful, false otherwise
*/
bool easy_flipper_set_menu(
- Menu** menu,
+ Menu **menu,
int32_t view_id,
- uint32_t(previous_callback)(void*),
- ViewDispatcher** view_dispatcher) {
- if(!menu) {
+ 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) {
+ if (!*menu)
+ {
FURI_LOG_E(EASY_TAG, "Failed to allocate Menu");
return false;
}
- if(previous_callback) {
+ 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));
@@ -177,24 +202,29 @@ bool easy_flipper_set_menu(
* @return true if successful, false otherwise
*/
bool easy_flipper_set_widget(
- Widget** widget,
+ Widget **widget,
int32_t view_id,
- char* text,
- uint32_t(previous_callback)(void*),
- ViewDispatcher** view_dispatcher) {
- if(!widget) {
+ 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) {
+ if (!*widget)
+ {
FURI_LOG_E(EASY_TAG, "Failed to allocate Widget");
return false;
}
- if(text) {
+ if (text)
+ {
widget_add_text_scroll_element(*widget, 0, 0, 128, 64, text);
}
- if(previous_callback) {
+ 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));
@@ -213,30 +243,33 @@ bool easy_flipper_set_widget(
* @return true if successful, false otherwise
*/
bool easy_flipper_set_variable_item_list(
- VariableItemList** 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) {
+ 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) {
+ if (!*variable_item_list)
+ {
FURI_LOG_E(EASY_TAG, "Failed to allocate VariableItemList");
return false;
}
- if(enter_callback) {
+ 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);
+ 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));
+ view_dispatcher_add_view(*view_dispatcher, view_id, variable_item_list_get_view(*variable_item_list));
return true;
}
@@ -249,38 +282,38 @@ bool easy_flipper_set_variable_item_list(
* @return true if successful, false otherwise
*/
bool easy_flipper_set_text_input(
- TextInput** text_input,
+ TextInput **text_input,
int32_t view_id,
- char* header_text,
- char* text_input_temp_buffer,
+ 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) {
+ 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) {
+ if (!*text_input)
+ {
FURI_LOG_E(EASY_TAG, "Failed to allocate TextInput");
return false;
}
- if(previous_callback) {
+ if (previous_callback)
+ {
view_set_previous_callback(text_input_get_view(*text_input), previous_callback);
}
- if(header_text) {
+ 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);
+ 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;
@@ -295,38 +328,38 @@ bool easy_flipper_set_text_input(
* @return true if successful, false otherwise
*/
bool easy_flipper_set_uart_text_input(
- TextInput** uart_text_input,
+ TextInput **uart_text_input,
int32_t view_id,
- char* header_text,
- char* uart_text_input_temp_buffer,
+ 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) {
+ 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 = text_input_alloc();
- if(!*uart_text_input) {
+ if (!*uart_text_input)
+ {
FURI_LOG_E(EASY_TAG, "Failed to allocate UART_TextInput");
return false;
}
- if(previous_callback) {
+ if (previous_callback)
+ {
view_set_previous_callback(text_input_get_view(*uart_text_input), previous_callback);
}
- if(header_text) {
+ if (header_text)
+ {
text_input_set_header_text(*uart_text_input, header_text);
}
- if(uart_text_input_temp_buffer && uart_text_input_buffer_size && result_callback) {
- text_input_set_result_callback(
- *uart_text_input,
- result_callback,
- context,
- uart_text_input_temp_buffer,
- uart_text_input_buffer_size,
- false);
+ if (uart_text_input_temp_buffer && uart_text_input_buffer_size && result_callback)
+ {
+ text_input_set_result_callback(*uart_text_input, result_callback, context, uart_text_input_temp_buffer, uart_text_input_buffer_size, false);
}
text_input_show_illegal_symbols(*uart_text_input, true);
view_dispatcher_add_view(*view_dispatcher, view_id, text_input_get_view(*uart_text_input));
@@ -353,52 +386,63 @@ bool easy_flipper_set_uart_text_input(
* @return true if successful, false otherwise
*/
bool easy_flipper_set_dialog_ex(
- DialogEx** dialog_ex,
+ DialogEx **dialog_ex,
int32_t view_id,
- char* header,
+ char *header,
uint16_t header_x,
uint16_t header_y,
- char* text,
+ 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) {
+ 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) {
+ if (!*dialog_ex)
+ {
FURI_LOG_E(EASY_TAG, "Failed to allocate DialogEx");
return false;
}
- if(header) {
+ if (header)
+ {
dialog_ex_set_header(*dialog_ex, header, header_x, header_y, AlignLeft, AlignTop);
}
- if(text) {
+ if (text)
+ {
dialog_ex_set_text(*dialog_ex, text, text_x, text_y, AlignLeft, AlignTop);
}
- if(left_button_text) {
+ if (left_button_text)
+ {
dialog_ex_set_left_button_text(*dialog_ex, left_button_text);
}
- if(right_button_text) {
+ if (right_button_text)
+ {
dialog_ex_set_right_button_text(*dialog_ex, right_button_text);
}
- if(center_button_text) {
+ if (center_button_text)
+ {
dialog_ex_set_center_button_text(*dialog_ex, center_button_text);
}
- if(result_callback) {
+ if (result_callback)
+ {
dialog_ex_set_result_callback(*dialog_ex, result_callback);
}
- if(previous_callback) {
+ if (previous_callback)
+ {
view_set_previous_callback(dialog_ex_get_view(*dialog_ex), previous_callback);
}
- if(context) {
+ if (context)
+ {
dialog_ex_set_context(*dialog_ex, context);
}
view_dispatcher_add_view(*view_dispatcher, view_id, dialog_ex_get_view(*dialog_ex));
@@ -422,40 +466,48 @@ bool easy_flipper_set_dialog_ex(
* @return true if successful, false otherwise
*/
bool easy_flipper_set_popup(
- Popup** popup,
+ Popup **popup,
int32_t view_id,
- char* header,
+ char *header,
uint16_t header_x,
uint16_t header_y,
- char* text,
+ 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) {
+ 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) {
+ if (!*popup)
+ {
FURI_LOG_E(EASY_TAG, "Failed to allocate Popup");
return false;
}
- if(header) {
+ if (header)
+ {
popup_set_header(*popup, header, header_x, header_y, AlignLeft, AlignTop);
}
- if(text) {
+ if (text)
+ {
popup_set_text(*popup, text, text_x, text_y, AlignLeft, AlignTop);
}
- if(result_callback) {
+ if (result_callback)
+ {
popup_set_callback(*popup, result_callback);
}
- if(previous_callback) {
+ if (previous_callback)
+ {
view_set_previous_callback(popup_get_view(*popup), previous_callback);
}
- if(context) {
+ if (context)
+ {
popup_set_context(*popup, context);
}
view_dispatcher_add_view(*view_dispatcher, view_id, popup_get_view(*popup));
@@ -471,20 +523,24 @@ bool easy_flipper_set_popup(
* @return true if successful, false otherwise
*/
bool easy_flipper_set_loading(
- Loading** loading,
+ Loading **loading,
int32_t view_id,
- uint32_t(previous_callback)(void*),
- ViewDispatcher** view_dispatcher) {
- if(!loading) {
+ 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) {
+ if (!*loading)
+ {
FURI_LOG_E(EASY_TAG, "Failed to allocate Loading");
return false;
}
- if(previous_callback) {
+ 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));
@@ -497,16 +553,19 @@ bool easy_flipper_set_loading(
* @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) {
+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) {
+ 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/flip_weather/easy_flipper/easy_flipper.h b/flip_weather/easy_flipper/easy_flipper.h
index 219e42c74..0e99e17fb 100644
--- a/flip_weather/easy_flipper/easy_flipper.h
+++ b/flip_weather/easy_flipper/easy_flipper.h
@@ -30,14 +30,14 @@
* @param context The context - unused
* @return next view id (VIEW_NONE to exit the app)
*/
-uint32_t easy_flipper_callback_exit_app(void* context);
+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);
+bool easy_flipper_set_buffer(char **buffer, uint32_t buffer_size);
/**
* @brief Initialize a View object
* @param view The View object to initialize
@@ -49,13 +49,13 @@ bool easy_flipper_set_buffer(char** buffer, uint32_t buffer_size);
* @return true if successful, false otherwise
*/
bool easy_flipper_set_view(
- View** 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);
+ 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
@@ -64,7 +64,7 @@ bool easy_flipper_set_view(
* @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);
+bool easy_flipper_set_view_dispatcher(ViewDispatcher **view_dispatcher, Gui *gui, void *context);
/**
* @brief Initialize a Submenu object
@@ -77,11 +77,11 @@ bool easy_flipper_set_view_dispatcher(ViewDispatcher** view_dispatcher, Gui* gui
* @return true if successful, false otherwise
*/
bool easy_flipper_set_submenu(
- Submenu** submenu,
+ Submenu **submenu,
int32_t view_id,
- char* title,
- uint32_t(previous_callback)(void*),
- ViewDispatcher** view_dispatcher);
+ char *title,
+ uint32_t(previous_callback)(void *),
+ ViewDispatcher **view_dispatcher);
/**
* @brief Initialize a Menu object
@@ -94,10 +94,10 @@ bool easy_flipper_set_submenu(
* @return true if successful, false otherwise
*/
bool easy_flipper_set_menu(
- Menu** menu,
+ Menu **menu,
int32_t view_id,
- uint32_t(previous_callback)(void*),
- ViewDispatcher** view_dispatcher);
+ uint32_t(previous_callback)(void *),
+ ViewDispatcher **view_dispatcher);
/**
* @brief Initialize a Widget object
@@ -109,11 +109,11 @@ bool easy_flipper_set_menu(
* @return true if successful, false otherwise
*/
bool easy_flipper_set_widget(
- Widget** widget,
+ Widget **widget,
int32_t view_id,
- char* text,
- uint32_t(previous_callback)(void*),
- ViewDispatcher** view_dispatcher);
+ char *text,
+ uint32_t(previous_callback)(void *),
+ ViewDispatcher **view_dispatcher);
/**
* @brief Initialize a VariableItemList object
@@ -127,12 +127,12 @@ bool easy_flipper_set_widget(
* @return true if successful, false otherwise
*/
bool easy_flipper_set_variable_item_list(
- VariableItemList** 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);
+ void (*enter_callback)(void *, uint32_t),
+ uint32_t(previous_callback)(void *),
+ ViewDispatcher **view_dispatcher,
+ void *context);
/**
* @brief Initialize a TextInput object
@@ -143,15 +143,15 @@ bool easy_flipper_set_variable_item_list(
* @return true if successful, false otherwise
*/
bool easy_flipper_set_text_input(
- TextInput** text_input,
+ TextInput **text_input,
int32_t view_id,
- char* header_text,
- char* text_input_temp_buffer,
+ 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);
+ void (*result_callback)(void *),
+ uint32_t(previous_callback)(void *),
+ ViewDispatcher **view_dispatcher,
+ void *context);
/**
* @brief Initialize a TextInput object with extra symbols
@@ -162,15 +162,15 @@ bool easy_flipper_set_text_input(
* @return true if successful, false otherwise
*/
bool easy_flipper_set_uart_text_input(
- TextInput** uart_text_input,
+ TextInput **uart_text_input,
int32_t view_id,
- char* header_text,
- char* uart_text_input_temp_buffer,
+ 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);
+ void (*result_callback)(void *),
+ uint32_t(previous_callback)(void *),
+ ViewDispatcher **view_dispatcher,
+ void *context);
/**
* @brief Initialize a DialogEx object
@@ -192,21 +192,21 @@ bool easy_flipper_set_uart_text_input(
* @return true if successful, false otherwise
*/
bool easy_flipper_set_dialog_ex(
- DialogEx** dialog_ex,
+ DialogEx **dialog_ex,
int32_t view_id,
- char* header,
+ char *header,
uint16_t header_x,
uint16_t header_y,
- char* text,
+ 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);
+ 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
@@ -225,18 +225,18 @@ bool easy_flipper_set_dialog_ex(
* @return true if successful, false otherwise
*/
bool easy_flipper_set_popup(
- Popup** popup,
+ Popup **popup,
int32_t view_id,
- char* header,
+ char *header,
uint16_t header_x,
uint16_t header_y,
- char* text,
+ char *text,
uint16_t text_x,
uint16_t text_y,
- void (*result_callback)(void*),
- uint32_t(previous_callback)(void*),
- ViewDispatcher** view_dispatcher,
- void* context);
+ void (*result_callback)(void *),
+ uint32_t(previous_callback)(void *),
+ ViewDispatcher **view_dispatcher,
+ void *context);
/**
* @brief Initialize a Loading object
@@ -247,10 +247,10 @@ bool easy_flipper_set_popup(
* @return true if successful, false otherwise
*/
bool easy_flipper_set_loading(
- Loading** loading,
+ Loading **loading,
int32_t view_id,
- uint32_t(previous_callback)(void*),
- ViewDispatcher** view_dispatcher);
+ uint32_t(previous_callback)(void *),
+ ViewDispatcher **view_dispatcher);
/**
* @brief Set a char butter to a FuriString
@@ -258,6 +258,6 @@ bool easy_flipper_set_loading(
* @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);
+bool easy_flipper_set_char_to_furi_string(FuriString **furi_string, char *buffer);
-#endif
+#endif
\ No newline at end of file
diff --git a/flip_weather/flip_storage/flip_weather_storage.c b/flip_weather/flip_storage/flip_weather_storage.c
index db50eb39a..e9b225cea 100644
--- a/flip_weather/flip_storage/flip_weather_storage.c
+++ b/flip_weather/flip_storage/flip_weather_storage.c
@@ -1,19 +1,22 @@
#include "flip_storage/flip_weather_storage.h"
-void save_settings(const char* ssid, const char* password) {
+void save_settings(
+ const char *ssid,
+ const char *password)
+{
// Create the directory for saving settings
char directory_path[256];
- snprintf(
- directory_path, sizeof(directory_path), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_weather");
+ snprintf(directory_path, sizeof(directory_path), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_weather");
// Create the directory
- Storage* storage = furi_record_open(RECORD_STORAGE);
+ 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)) {
+ 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);
@@ -22,15 +25,17 @@ void save_settings(const char* ssid, const char* password) {
// 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) {
+ 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) {
+ 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");
}
@@ -39,11 +44,17 @@ void save_settings(const char* ssid, const char* password) {
furi_record_close(RECORD_STORAGE);
}
-bool load_settings(char* ssid, size_t ssid_size, char* password, size_t password_size) {
- Storage* storage = furi_record_open(RECORD_STORAGE);
- File* file = storage_file_alloc(storage);
+bool load_settings(
+ char *ssid,
+ size_t ssid_size,
+ char *password,
+ size_t password_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)) {
+ 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);
@@ -52,8 +63,9 @@ bool load_settings(char* ssid, size_t ssid_size, char* password, size_t password
// 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) {
+ 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);
@@ -64,9 +76,9 @@ bool load_settings(char* ssid, size_t ssid_size, char* password, size_t password
// 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) {
+ 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);
@@ -80,4 +92,4 @@ bool load_settings(char* ssid, size_t ssid_size, char* password, size_t password
furi_record_close(RECORD_STORAGE);
return true;
-}
+}
\ No newline at end of file
diff --git a/flip_weather/flip_storage/flip_weather_storage.h b/flip_weather/flip_storage/flip_weather_storage.h
index 1cd6ddcb5..f4d2b915f 100644
--- a/flip_weather/flip_storage/flip_weather_storage.h
+++ b/flip_weather/flip_storage/flip_weather_storage.h
@@ -7,7 +7,13 @@
#define SETTINGS_PATH STORAGE_EXT_PATH_PREFIX "/apps_data/flip_weather/settings.bin"
-void save_settings(const char* ssid, const char* password);
+void save_settings(
+ const char *ssid,
+ const char *password);
-bool load_settings(char* ssid, size_t ssid_size, char* password, size_t password_size);
-#endif // FLIP_WEATHER_STORAGE_H
+bool load_settings(
+ char *ssid,
+ size_t ssid_size,
+ char *password,
+ size_t password_size);
+#endif // FLIP_WEATHER_STORAGE_H
\ No newline at end of file
diff --git a/flip_weather/flip_weather.c b/flip_weather/flip_weather.c
index b29d09bfd..afd2987ca 100644
--- a/flip_weather/flip_weather.c
+++ b/flip_weather/flip_weather.c
@@ -3,75 +3,91 @@
char lat_data[32];
char lon_data[32];
-char* total_data = NULL;
-char* weather_data = NULL;
+char *total_data = NULL;
+char *weather_data = NULL;
-FlipWeatherApp* app_instance = NULL;
-void flip_weather_loader_free_model(View* view);
+FlipWeatherApp *app_instance = NULL;
+void flip_weather_loader_free_model(View *view);
// Function to free the resources used by FlipWeatherApp
-void flip_weather_app_free(FlipWeatherApp* app) {
- if(!app) {
+void flip_weather_app_free(FlipWeatherApp *app)
+{
+ if (!app)
+ {
FURI_LOG_E(TAG, "FlipWeatherApp is NULL");
return;
}
// Free View(s)
- if(app->view_loader) {
+ if (app->view_loader)
+ {
view_dispatcher_remove_view(app->view_dispatcher, FlipWeatherViewLoader);
flip_weather_loader_free_model(app->view_loader);
view_free(app->view_loader);
}
// Free Submenu(s)
- if(app->submenu) {
+ if (app->submenu)
+ {
view_dispatcher_remove_view(app->view_dispatcher, FlipWeatherViewSubmenu);
submenu_free(app->submenu);
}
// Free Widget(s)
- if(app->widget) {
+ if (app->widget)
+ {
view_dispatcher_remove_view(app->view_dispatcher, FlipWeatherViewAbout);
widget_free(app->widget);
}
- if(app->widget_result) {
+ if (app->widget_result)
+ {
view_dispatcher_remove_view(app->view_dispatcher, FlipWeatherViewWidgetResult);
widget_free(app->widget_result);
}
// Free Variable Item List(s)
- if(app->variable_item_list) {
+ if (app->variable_item_list)
+ {
view_dispatcher_remove_view(app->view_dispatcher, FlipWeatherViewSettings);
variable_item_list_free(app->variable_item_list);
}
// Free Text Input(s)
- if(app->uart_text_input_ssid) {
+ if (app->uart_text_input_ssid)
+ {
view_dispatcher_remove_view(app->view_dispatcher, FlipWeatherViewTextInputSSID);
text_input_free(app->uart_text_input_ssid);
}
- if(app->uart_text_input_password) {
+ if (app->uart_text_input_password)
+ {
view_dispatcher_remove_view(app->view_dispatcher, FlipWeatherViewTextInputPassword);
text_input_free(app->uart_text_input_password);
}
// Free the text input buffer
- if(app->uart_text_input_buffer_ssid) free(app->uart_text_input_buffer_ssid);
- if(app->uart_text_input_temp_buffer_ssid) free(app->uart_text_input_temp_buffer_ssid);
- if(app->uart_text_input_buffer_password) free(app->uart_text_input_buffer_password);
- if(app->uart_text_input_temp_buffer_password) free(app->uart_text_input_temp_buffer_password);
+ if (app->uart_text_input_buffer_ssid)
+ free(app->uart_text_input_buffer_ssid);
+ if (app->uart_text_input_temp_buffer_ssid)
+ free(app->uart_text_input_temp_buffer_ssid);
+ if (app->uart_text_input_buffer_password)
+ free(app->uart_text_input_buffer_password);
+ if (app->uart_text_input_temp_buffer_password)
+ free(app->uart_text_input_temp_buffer_password);
// deinitalize flipper http
flipper_http_deinit();
// free the view dispatcher
- if(app->view_dispatcher) view_dispatcher_free(app->view_dispatcher);
+ if (app->view_dispatcher)
+ view_dispatcher_free(app->view_dispatcher);
// close the gui
furi_record_close(RECORD_GUI);
- if(total_data) free(total_data);
+ if (total_data)
+ free(total_data);
// free the app
- if(app) free(app);
-}
+ if (app)
+ free(app);
+}
\ No newline at end of file
diff --git a/flip_weather/flip_weather.h b/flip_weather/flip_weather.h
index 656393681..1852caec5 100644
--- a/flip_weather/flip_weather.h
+++ b/flip_weather/flip_weather.h
@@ -5,61 +5,64 @@
#include
#include
-#define TAG "FlipWeather"
+#define TAG "FlipWeather"
#define MAX_TOKENS 64 // Adjust based on expected JSON size (50)
// Define the submenu items for our FlipWeather application
-typedef enum {
- FlipWeatherSubmenuIndexWeather, // Click to view the weather
- FlipWeatherSubmenuIndexGPS, // Click to view the GPS
- FlipWeatherSubmenuIndexAbout, // Click to view the about screen
+typedef enum
+{
+ FlipWeatherSubmenuIndexWeather, // Click to view the weather
+ FlipWeatherSubmenuIndexGPS, // Click to view the GPS
+ FlipWeatherSubmenuIndexAbout, // Click to view the about screen
FlipWeatherSubmenuIndexSettings, // Click to view the settings screen
} FlipWeatherSubmenuIndex;
// Define a single view for our FlipWeather application
-typedef enum {
- FlipWeatherViewSubmenu, // The main submenu
- FlipWeatherViewAbout, // The about screen
- FlipWeatherViewSettings, // The wifi settings screen
- FlipWeatherViewTextInputSSID, // The text input screen for SSID
+typedef enum
+{
+ FlipWeatherViewSubmenu, // The main submenu
+ FlipWeatherViewAbout, // The about screen
+ FlipWeatherViewSettings, // The wifi settings screen
+ FlipWeatherViewTextInputSSID, // The text input screen for SSID
FlipWeatherViewTextInputPassword, // The text input screen for password
//
- FlipWeatherViewPopupError, // The error popup screen
+ FlipWeatherViewPopupError, // The error popup screen
FlipWeatherViewWidgetResult, // The text box that displays the random fact
- FlipWeatherViewLoader, // The loader screen retrieves data from the internet
+ FlipWeatherViewLoader, // The loader screen retrieves data from the internet
} FlipWeatherView;
// Each screen will have its own view
-typedef struct {
- ViewDispatcher* view_dispatcher; // Switches between our views
- View* view_loader; // The screen that loads data from internet
- Submenu* submenu; // The main submenu
- Widget* widget; // The widget (about)
- Widget* widget_result; // The widget that displays the result
- Popup* popup_error; // The error popup
- VariableItemList* variable_item_list; // The variable item list (settngs)
- VariableItem* variable_item_ssid; // The variable item
- VariableItem* variable_item_password; // The variable item
- TextInput* uart_text_input_ssid; // The text input
- TextInput* uart_text_input_password; // The text input
+typedef struct
+{
+ ViewDispatcher *view_dispatcher; // Switches between our views
+ View *view_loader; // The screen that loads data from internet
+ Submenu *submenu; // The main submenu
+ Widget *widget; // The widget (about)
+ Widget *widget_result; // The widget that displays the result
+ Popup *popup_error; // The error popup
+ VariableItemList *variable_item_list; // The variable item list (settngs)
+ VariableItem *variable_item_ssid; // The variable item
+ VariableItem *variable_item_password; // The variable item
+ TextInput *uart_text_input_ssid; // The text input
+ TextInput *uart_text_input_password; // The text input
- char* uart_text_input_buffer_ssid; // Buffer for the text input
- char* uart_text_input_temp_buffer_ssid; // Temporary buffer for the text input
+ char *uart_text_input_buffer_ssid; // Buffer for the text input
+ char *uart_text_input_temp_buffer_ssid; // Temporary buffer for the text input
uint32_t uart_text_input_buffer_size_ssid; // Size of the text input buffer
- char* uart_text_input_buffer_password; // Buffer for the text input
- char* uart_text_input_temp_buffer_password; // Temporary buffer for the text input
+ char *uart_text_input_buffer_password; // Buffer for the text input
+ char *uart_text_input_temp_buffer_password; // Temporary buffer for the text input
uint32_t uart_text_input_buffer_size_password; // Size of the text input buffer
} FlipWeatherApp;
extern char lat_data[32];
extern char lon_data[32];
-extern char* total_data;
-extern char* weather_data;
+extern char *total_data;
+extern char *weather_data;
// Function to free the resources used by FlipWeatherApp
-void flip_weather_app_free(FlipWeatherApp* app);
-extern FlipWeatherApp* app_instance;
+void flip_weather_app_free(FlipWeatherApp *app);
+extern FlipWeatherApp *app_instance;
-#endif
+#endif
\ No newline at end of file
diff --git a/flip_weather/flipper_http/flipper_http.c b/flip_weather/flipper_http/flipper_http.c
index e7aafd857..177ee3bf6 100644
--- a/flip_weather/flipper_http/flipper_http.c
+++ b/flip_weather/flipper_http/flipper_http.c
@@ -6,17 +6,21 @@ size_t file_buffer_len = 0;
// 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,
+ 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);
+ char *file_path)
+{
+ Storage *storage = furi_record_open(RECORD_STORAGE);
+ File *file = storage_file_alloc(storage);
- if(start_new_file) {
+ if (start_new_file)
+ {
// Delete the file if it already exists
- if(storage_file_exists(storage, file_path)) {
- if(!storage_simply_remove_recursive(storage, file_path)) {
+ if (storage_file_exists(storage, file_path))
+ {
+ if (!storage_simply_remove_recursive(storage, file_path))
+ {
FURI_LOG_E(HTTP_TAG, "Failed to delete file: %s", file_path);
storage_file_free(file);
furi_record_close(RECORD_STORAGE);
@@ -24,15 +28,19 @@ bool flipper_http_append_to_file(
}
}
// Open the file in write mode
- if(!storage_file_open(file, file_path, FSAM_WRITE, FSOM_CREATE_ALWAYS)) {
+ 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 {
+ }
+ else
+ {
// Open the file in append mode
- if(!storage_file_open(file, file_path, FSAM_WRITE, FSOM_OPEN_APPEND)) {
+ 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);
@@ -41,7 +49,8 @@ bool flipper_http_append_to_file(
}
// Write the data to the file
- if(storage_file_write(file, data, data_size) != data_size) {
+ 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);
@@ -55,32 +64,37 @@ bool flipper_http_append_to_file(
return true;
}
-FuriString* flipper_http_load_from_file(char* file_path) {
+FuriString *flipper_http_load_from_file(char *file_path)
+{
// Open the storage record
- Storage* storage = furi_record_open(RECORD_STORAGE);
- if(!storage) {
+ 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) {
+ 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)) {
+ 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) {
+ 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);
@@ -92,8 +106,9 @@ FuriString* flipper_http_load_from_file(char* file_path) {
furi_string_reset(str_result);
// Define a buffer to hold the read data
- uint8_t* buffer = (uint8_t*)malloc(MAX_FILE_SHOW);
- if(!buffer) {
+ 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);
@@ -104,7 +119,8 @@ FuriString* flipper_http_load_from_file(char* file_path) {
// 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) {
+ 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);
@@ -114,7 +130,8 @@ FuriString* flipper_http_load_from_file(char* file_path) {
}
// Append each byte to the FuriString
- for(size_t i = 0; i < read_count; i++) {
+ for (size_t i = 0; i < read_count; i++)
+ {
furi_string_push_back(str_result, buffer[i]);
}
@@ -138,39 +155,48 @@ FuriString* flipper_http_load_from_file(char* file_path) {
* @note This function will handle received data asynchronously via the callback.
*/
// UART worker thread
-int32_t flipper_http_worker(void* context) {
+int32_t flipper_http_worker(void *context)
+{
UNUSED(context);
size_t rx_line_pos = 0;
- while(1) {
+ while (1)
+ {
uint32_t events = furi_thread_flags_wait(
WorkerEvtStop | WorkerEvtRxDone, FuriFlagWaitAny, FuriWaitForever);
- if(events & WorkerEvtStop) {
+ if (events & WorkerEvtStop)
+ {
break;
}
- if(events & WorkerEvtRxDone) {
+ if (events & WorkerEvtRxDone)
+ {
// Continuously read from the stream buffer until it's empty
- while(!furi_stream_buffer_is_empty(fhttp.flipper_http_stream)) {
+ 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) {
+ if (received == 0)
+ {
// No more data to read
break;
}
// Append the received byte to the file if saving is enabled
- if(fhttp.save_bytes) {
+ 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,
- fhttp.just_started_bytes,
- fhttp.file_path)) {
+ if (file_buffer_len >= FILE_BUFFER_SIZE)
+ {
+ if (!flipper_http_append_to_file(
+ file_buffer,
+ file_buffer_len,
+ fhttp.just_started_bytes,
+ fhttp.file_path))
+ {
FURI_LOG_E(HTTP_TAG, "Failed to append data to file");
}
file_buffer_len = 0;
@@ -179,9 +205,11 @@ int32_t flipper_http_worker(void* context) {
}
// Handle line buffering only if callback is set (text data)
- if(fhttp.handle_rx_line_cb) {
+ if (fhttp.handle_rx_line_cb)
+ {
// Handle line buffering
- if(c == '\n' || rx_line_pos >= RX_LINE_BUFFER_SIZE - 1) {
+ 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
@@ -189,7 +217,9 @@ int32_t flipper_http_worker(void* context) {
// Reset the line buffer position
rx_line_pos = 0;
- } else {
+ }
+ else
+ {
rx_line_buffer[rx_line_pos++] = c; // Add character to the line buffer
}
}
@@ -206,7 +236,8 @@ int32_t flipper_http_worker(void* context) {
* @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) {
+void get_timeout_timer_callback(void *context)
+{
UNUSED(context);
FURI_LOG_E(HTTP_TAG, "Timeout reached: 2 seconds without receiving the end.");
@@ -230,11 +261,13 @@ void get_timeout_timer_callback(void* context) {
* @note This function will handle received data asynchronously via the callback.
*/
void _flipper_http_rx_callback(
- FuriHalSerialHandle* handle,
+ FuriHalSerialHandle *handle,
FuriHalSerialRxEvent event,
- void* context) {
+ void *context)
+{
UNUSED(context);
- if(event == FuriHalSerialRxEventData) {
+ 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);
@@ -249,23 +282,28 @@ void _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) {
+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) {
+ 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) {
+ 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) {
+ if (!fhttp.rx_thread)
+ {
FURI_LOG_E(HTTP_TAG, "Failed to allocate UART thread.");
furi_stream_buffer_free(fhttp.flipper_http_stream);
return false;
@@ -283,13 +321,15 @@ bool flipper_http_init(FlipperHTTP_Callback callback, void* context) {
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)) {
+ 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) {
+ 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);
@@ -312,11 +352,12 @@ bool flipper_http_init(FlipperHTTP_Callback callback, void* context) {
// 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
+ FuriTimerTypeOnce, // One-shot timer
+ &fhttp // Context passed to callback
);
- if(!fhttp.get_timeout_timer) {
+ 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);
@@ -333,8 +374,9 @@ bool flipper_http_init(FlipperHTTP_Callback callback, void* context) {
// 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) {
+ 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;
}
@@ -349,8 +391,10 @@ bool flipper_http_init(FlipperHTTP_Callback callback, void* context) {
* @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) {
+void flipper_http_deinit()
+{
+ if (fhttp.serial_handle == NULL)
+ {
FURI_LOG_E(HTTP_TAG, "UART handle is NULL. Already deinitialized?");
return;
}
@@ -373,13 +417,15 @@ void flipper_http_deinit() {
furi_stream_buffer_free(fhttp.flipper_http_stream);
// Free the timer
- if(fhttp.get_timeout_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) {
+ if (fhttp.last_response)
+ {
free(fhttp.last_response);
fhttp.last_response = NULL;
}
@@ -394,9 +440,11 @@ void flipper_http_deinit() {
* @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) {
+bool flipper_http_send_data(const char *data)
+{
size_t data_length = strlen(data);
- if(data_length == 0) {
+ if (data_length == 0)
+ {
FURI_LOG_E("FlipperHTTP", "Attempted to send empty data.");
fhttp.state = ISSUE;
return false;
@@ -404,25 +452,27 @@ bool flipper_http_send_data(const char* data) {
// Create a buffer with data + '\n'
size_t send_length = data_length + 1; // +1 for '\n'
- if(send_length > 512) { // Ensure buffer size is sufficient
+ if (send_length > 512)
+ { // Ensure buffer size is sufficient
FURI_LOG_E("FlipperHTTP", "Data too long to send over FHTTP.");
return false;
}
char send_buffer[513]; // 512 + 1 for safety
strncpy(send_buffer, data, 512);
- send_buffer[data_length] = '\n'; // Append newline
+ 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))) {
+ 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);
+ 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);
@@ -438,9 +488,11 @@ bool flipper_http_send_data(const char* data) {
* @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)) {
+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;
}
@@ -456,9 +508,11 @@ bool flipper_http_ping() {
* @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)) {
+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;
}
@@ -473,9 +527,11 @@ bool flipper_http_list_commands() {
* @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)) {
+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;
}
@@ -490,9 +546,11 @@ bool flipper_http_led_on() {
* @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)) {
+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;
}
@@ -509,8 +567,10 @@ bool flipper_http_led_off() {
* @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) {
+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;
}
@@ -518,12 +578,14 @@ bool flipper_http_parse_json(const char* key, const char* json_data) {
char buffer[256];
int ret =
snprintf(buffer, sizeof(buffer), "[PARSE]{\"key\":\"%s\",\"json\":%s}", key, json_data);
- if(ret < 0 || ret >= (int)sizeof(buffer)) {
+ 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)) {
+ if (!flipper_http_send_data(buffer))
+ {
FURI_LOG_E("FlipperHTTP", "Failed to send JSON parse command.");
return false;
}
@@ -541,8 +603,10 @@ bool flipper_http_parse_json(const char* key, const char* json_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) {
+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;
}
@@ -555,12 +619,14 @@ bool flipper_http_parse_json_array(const char* key, int index, const char* json_
key,
index,
json_data);
- if(ret < 0 || ret >= (int)sizeof(buffer)) {
+ 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)) {
+ if (!flipper_http_send_data(buffer))
+ {
FURI_LOG_E("FlipperHTTP", "Failed to send JSON parse array command.");
return false;
}
@@ -575,9 +641,11 @@ bool flipper_http_parse_json_array(const char* key, int index, const char* json_
* @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)) {
+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;
}
@@ -592,20 +660,24 @@ bool flipper_http_scan_wifi() {
* @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) {
+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)) {
+ 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)) {
+ if (!flipper_http_send_data(buffer))
+ {
FURI_LOG_E("FlipperHTTP", "Failed to send WiFi save command.");
return false;
}
@@ -620,9 +692,11 @@ bool flipper_http_save_wifi(const char* ssid, const char* password) {
* @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)) {
+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;
}
@@ -637,9 +711,11 @@ bool flipper_http_ip_address() {
* @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)) {
+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;
}
@@ -654,9 +730,11 @@ bool flipper_http_ip_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)) {
+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;
}
@@ -671,9 +749,11 @@ bool flipper_http_disconnect_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)) {
+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;
}
@@ -689,8 +769,10 @@ bool flipper_http_connect_wifi() {
* @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) {
+bool flipper_http_get_request(const char *url)
+{
+ if (!url)
+ {
FURI_LOG_E("FlipperHTTP", "Invalid arguments provided to flipper_http_get_request.");
return false;
}
@@ -698,13 +780,15 @@ bool flipper_http_get_request(const char* url) {
// Prepare GET request command
char command[512];
int ret = snprintf(command, sizeof(command), "[GET]%s", url);
- if(ret < 0 || ret >= (int)sizeof(command)) {
+ 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)) {
+ if (!flipper_http_send_data(command))
+ {
FURI_LOG_E("FlipperHTTP", "Failed to send GET request command.");
return false;
}
@@ -720,8 +804,10 @@ bool flipper_http_get_request(const char* url) {
* @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) {
+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;
@@ -731,13 +817,15 @@ bool flipper_http_get_request_with_headers(const char* url, const char* headers)
char command[512];
int ret = snprintf(
command, sizeof(command), "[GET/HTTP]{\"url\":\"%s\",\"headers\":%s}", url, headers);
- if(ret < 0 || ret >= (int)sizeof(command)) {
+ 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)) {
+ if (!flipper_http_send_data(command))
+ {
FURI_LOG_E("FlipperHTTP", "Failed to send GET request command with headers.");
return false;
}
@@ -753,8 +841,10 @@ bool flipper_http_get_request_with_headers(const char* url, const char* headers)
* @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) {
+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;
}
@@ -763,13 +853,15 @@ bool flipper_http_get_request_bytes(const char* url, const char* 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)) {
+ 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)) {
+ if (!flipper_http_send_data(command))
+ {
FURI_LOG_E("FlipperHTTP", "Failed to send GET request command with headers.");
return false;
}
@@ -787,10 +879,12 @@ bool flipper_http_get_request_bytes(const char* url, const char* headers) {
* @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) {
+ 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.");
@@ -806,13 +900,15 @@ bool flipper_http_post_request_with_headers(
url,
headers,
payload);
- if(ret < 0 || ret >= (int)sizeof(command)) {
+ 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)) {
+ if (!flipper_http_send_data(command))
+ {
FURI_LOG_E("FlipperHTTP", "Failed to send POST request command with headers and data.");
return false;
}
@@ -829,8 +925,10 @@ bool flipper_http_post_request_with_headers(
* @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) {
+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;
@@ -845,13 +943,15 @@ bool flipper_http_post_request_bytes(const char* url, const char* headers, const
url,
headers,
payload);
- if(ret < 0 || ret >= (int)sizeof(command)) {
+ 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)) {
+ if (!flipper_http_send_data(command))
+ {
FURI_LOG_E("FlipperHTTP", "Failed to send POST request command with headers and data.");
return false;
}
@@ -869,10 +969,12 @@ bool flipper_http_post_request_bytes(const char* url, const char* headers, const
* @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) {
+ 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;
@@ -887,13 +989,15 @@ bool flipper_http_put_request_with_headers(
url,
headers,
payload);
- if(ret < 0 || ret >= (int)sizeof(command)) {
+ 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)) {
+ if (!flipper_http_send_data(command))
+ {
FURI_LOG_E("FlipperHTTP", "Failed to send PUT request command with headers and data.");
return false;
}
@@ -911,10 +1015,12 @@ bool flipper_http_put_request_with_headers(
* @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) {
+ 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.");
@@ -930,14 +1036,16 @@ bool flipper_http_delete_request_with_headers(
url,
headers,
payload);
- if(ret < 0 || ret >= (int)sizeof(command)) {
+ 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)) {
+ if (!flipper_http_send_data(command))
+ {
FURI_LOG_E("FlipperHTTP", "Failed to send DELETE request command with headers and data.");
return false;
}
@@ -953,26 +1061,31 @@ bool flipper_http_delete_request_with_headers(
* @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) {
+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') {
+ 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) {
+ 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) {
+ if (fhttp.state != INACTIVE && fhttp.state != ISSUE)
+ {
fhttp.state = RECEIVING;
}
@@ -980,11 +1093,13 @@ void flipper_http_rx_callback(const char* line, void* context) {
// 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) {
+ 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) {
+ 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);
@@ -994,14 +1109,17 @@ void flipper_http_rx_callback(const char* line, void* context) {
fhttp.save_bytes = false;
fhttp.save_received_data = false;
- if(fhttp.is_bytes_request) {
+ if (fhttp.is_bytes_request)
+ {
// Search for the binary marker `[GET/END]` in the file buffer
const char marker[] = "[GET/END]";
const size_t marker_len = sizeof(marker) - 1; // Exclude null terminator
- for(size_t i = 0; i <= file_buffer_len - marker_len; i++) {
+ for (size_t i = 0; i <= file_buffer_len - marker_len; i++)
+ {
// Check if the marker is found
- if(memcmp(&file_buffer[i], marker, marker_len) == 0) {
+ if (memcmp(&file_buffer[i], marker, marker_len) == 0)
+ {
// Remove the marker by shifting the remaining data left
size_t remaining_len = file_buffer_len - (i + marker_len);
memmove(&file_buffer[i], &file_buffer[i + marker_len], remaining_len);
@@ -1011,9 +1129,10 @@ void flipper_http_rx_callback(const char* line, void* context) {
}
// If there is data left in the buffer, append it to the file
- if(file_buffer_len > 0) {
- if(!flipper_http_append_to_file(
- file_buffer, file_buffer_len, false, fhttp.file_path)) {
+ 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 data to file.");
}
file_buffer_len = 0;
@@ -1025,9 +1144,10 @@ void flipper_http_rx_callback(const char* line, void* context) {
}
// 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)) {
+ 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;
@@ -1035,18 +1155,21 @@ void flipper_http_rx_callback(const char* line, void* context) {
return;
}
- if(!fhttp.just_started_get) {
+ 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) {
+ 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) {
+ 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);
@@ -1056,14 +1179,17 @@ void flipper_http_rx_callback(const char* line, void* context) {
fhttp.save_bytes = false;
fhttp.save_received_data = false;
- if(fhttp.is_bytes_request) {
+ if (fhttp.is_bytes_request)
+ {
// Search for the binary marker `[POST/END]` in the file buffer
const char marker[] = "[POST/END]";
const size_t marker_len = sizeof(marker) - 1; // Exclude null terminator
- for(size_t i = 0; i <= file_buffer_len - marker_len; i++) {
+ for (size_t i = 0; i <= file_buffer_len - marker_len; i++)
+ {
// Check if the marker is found
- if(memcmp(&file_buffer[i], marker, marker_len) == 0) {
+ if (memcmp(&file_buffer[i], marker, marker_len) == 0)
+ {
// Remove the marker by shifting the remaining data left
size_t remaining_len = file_buffer_len - (i + marker_len);
memmove(&file_buffer[i], &file_buffer[i + marker_len], remaining_len);
@@ -1073,9 +1199,10 @@ void flipper_http_rx_callback(const char* line, void* context) {
}
// If there is data left in the buffer, append it to the file
- if(file_buffer_len > 0) {
- if(!flipper_http_append_to_file(
- file_buffer, file_buffer_len, false, fhttp.file_path)) {
+ 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 data to file.");
}
file_buffer_len = 0;
@@ -1087,9 +1214,10 @@ void flipper_http_rx_callback(const char* line, void* context) {
}
// 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)) {
+ 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;
@@ -1097,18 +1225,21 @@ void flipper_http_rx_callback(const char* line, void* context) {
return;
}
- if(!fhttp.just_started_post) {
+ 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) {
+ 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) {
+ 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);
@@ -1122,9 +1253,10 @@ void flipper_http_rx_callback(const char* line, void* context) {
}
// 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)) {
+ 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;
@@ -1132,18 +1264,21 @@ void flipper_http_rx_callback(const char* line, void* context) {
return;
}
- if(!fhttp.just_started_put) {
+ 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) {
+ 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) {
+ 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);
@@ -1157,9 +1292,10 @@ void flipper_http_rx_callback(const char* line, void* context) {
}
// 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)) {
+ 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;
@@ -1167,22 +1303,29 @@ void flipper_http_rx_callback(const char* line, void* context) {
return;
}
- if(!fhttp.just_started_delete) {
+ 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) {
+ if (strstr(line, "[SUCCESS]") != NULL || strstr(line, "[CONNECTED]") != NULL)
+ {
FURI_LOG_I(HTTP_TAG, "Operation succeeded.");
- } else if(strstr(line, "[INFO]") != NULL) {
+ }
+ 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) {
+ if (fhttp.state == INACTIVE && strstr(line, "[INFO] Already connected to Wifi.") != NULL)
+ {
fhttp.state = IDLE;
}
- } else if(strstr(line, "[GET/SUCCESS]") != NULL) {
+ }
+ 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);
@@ -1192,7 +1335,9 @@ void flipper_http_rx_callback(const char* line, void* context) {
fhttp.just_started_bytes = true;
file_buffer_len = 0;
return;
- } else if(strstr(line, "[POST/SUCCESS]") != NULL) {
+ }
+ 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);
@@ -1202,67 +1347,86 @@ void flipper_http_rx_callback(const char* line, void* context) {
fhttp.just_started_bytes = true;
file_buffer_len = 0;
return;
- } else if(strstr(line, "[PUT/SUCCESS]") != NULL) {
+ }
+ 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) {
+ }
+ 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) {
+ }
+ else if (strstr(line, "[DISCONNECTED]") != NULL)
+ {
FURI_LOG_I(HTTP_TAG, "WiFi disconnected successfully.");
- } else if(strstr(line, "[ERROR]") != NULL) {
+ }
+ 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) {
+ }
+ 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) {
+ if (fhttp.state == INACTIVE)
+ {
fhttp.state = IDLE;
return;
}
}
- if(fhttp.state == INACTIVE && strstr(line, "[PONG]") != NULL) {
+ if (fhttp.state == INACTIVE && strstr(line, "[PONG]") != NULL)
+ {
fhttp.state = IDLE;
- } else if(fhttp.state == INACTIVE && strstr(line, "[PONG]") == NULL) {
+ }
+ else if (fhttp.state == INACTIVE && strstr(line, "[PONG]") == NULL)
+ {
fhttp.state = INACTIVE;
- } else {
+ }
+ 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;
+char *trim(const char *str)
+{
+ const char *end;
+ char *trimmed_str;
size_t len;
// Trim leading space
- while(isspace((unsigned char)*str))
+ while (isspace((unsigned char)*str))
str++;
// All spaces?
- if(*str == 0) return strdup(""); // Return an empty string if 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))
+ 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) {
+ trimmed_str = (char *)malloc(len + 1);
+ if (trimmed_str == NULL)
+ {
return NULL; // Handle memory allocation failure
}
@@ -1279,21 +1443,25 @@ char* trim(const char* str) {
* @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
+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 {
+ }
+ 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) {
+ 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)
+ if (!parse_json()) // parse the JSON before switching to the view (synchonous)
{
FURI_LOG_E(HTTP_TAG, "Failed to parse the JSON...");
return false;
@@ -1310,17 +1478,18 @@ bool flipper_http_process_response_async(bool (*http_request)(void), bool (*pars
* @param view_dispatcher The view dispatcher to use
* @return
*/
-void flipper_http_loading_task(
- bool (*http_request)(void),
- bool (*parse_response)(void),
- uint32_t success_view_id,
- uint32_t failure_view_id,
- ViewDispatcher** view_dispatcher) {
- Loading* loading;
+void flipper_http_loading_task(bool (*http_request)(void),
+ bool (*parse_response)(void),
+ uint32_t success_view_id,
+ uint32_t failure_view_id,
+ ViewDispatcher **view_dispatcher)
+{
+ Loading *loading;
int32_t loading_view_id = 987654321; // Random ID
loading = loading_alloc();
- if(!loading) {
+ if (!loading)
+ {
FURI_LOG_E(HTTP_TAG, "Failed to allocate loading");
view_dispatcher_switch_to_view(*view_dispatcher, failure_view_id);
@@ -1333,7 +1502,8 @@ void flipper_http_loading_task(
view_dispatcher_switch_to_view(*view_dispatcher, loading_view_id);
// Make the request
- if(!flipper_http_process_response_async(http_request, parse_response)) {
+ if (!flipper_http_process_response_async(http_request, parse_response))
+ {
FURI_LOG_E(HTTP_TAG, "Failed to make request");
view_dispatcher_switch_to_view(*view_dispatcher, failure_view_id);
view_dispatcher_remove_view(*view_dispatcher, loading_view_id);
diff --git a/flip_weather/flipper_http/flipper_http.h b/flip_weather/flipper_http/flipper_http.h
index 1eb1e1e70..764262f72 100644
--- a/flip_weather/flipper_http/flipper_http.h
+++ b/flip_weather/flipper_http/flipper_http.h
@@ -15,69 +15,72 @@
// STORAGE_EXT_PATH_PREFIX is defined in the Furi SDK as /ext
-#define HTTP_TAG "FlipWeather" // change this to your app name
-#define http_tag "flip_weather" // change this to your app id
-#define UART_CH (momentum_settings.uart_esp_channel) // UART channel
+#define HTTP_TAG "FlipWeather" // change this to your app name
+#define http_tag "flip_weather" // change this to your app id
+#define UART_CH (momentum_settings.uart_esp_channel) // UART channel
#define TIMEOUT_DURATION_TICKS (6 * 1000) // 6 seconds
-#define BAUDRATE (115200) // UART baudrate
-#define RX_BUF_SIZE 1024 // UART RX buffer size
-#define RX_LINE_BUFFER_SIZE 4096 // UART RX line buffer size (increase for large responses)
-#define MAX_FILE_SHOW 4096 // Maximum data from file to show
-#define FILE_BUFFER_SIZE 512 // File buffer size
+#define BAUDRATE (115200) // UART baudrate
+#define RX_BUF_SIZE 1024 // UART RX buffer size
+#define RX_LINE_BUFFER_SIZE 4096 // UART RX line buffer size (increase for large responses)
+#define MAX_FILE_SHOW 4096 // 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);
+typedef void (*FlipperHTTP_Callback)(const char *line, void *context);
// State variable to track the UART state
-typedef enum {
- INACTIVE, // Inactive state
- IDLE, // Default state
+typedef enum
+{
+ INACTIVE, // Inactive state
+ IDLE, // Default state
RECEIVING, // Receiving data
- SENDING, // Sending data
- ISSUE, // Issue with connection
+ SENDING, // Sending data
+ ISSUE, // Issue with connection
} SerialState;
// Event Flags for UART Worker Thread
-typedef enum {
+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
+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
+ 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 *last_response;
char file_path[256]; // Path to save the received data
// Timer-related members
- FuriTimer* get_timeout_timer; // Timer for HTTP request timeout
+ 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 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 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 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
+ 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;
+ 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
+ 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
bool just_started_bytes; // Indicates if bytes data reception has just started
} FlipperHTTP;
@@ -93,12 +96,12 @@ extern size_t file_buffer_len;
// 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,
+ const void *data,
size_t data_size,
bool start_new_file,
- char* file_path);
+ char *file_path);
-FuriString* flipper_http_load_from_file(char* file_path);
+FuriString *flipper_http_load_from_file(char *file_path);
// UART worker thread
/**
@@ -108,7 +111,7 @@ FuriString* flipper_http_load_from_file(char* file_path);
* @note This function will handle received data asynchronously via the callback.
*/
// UART worker thread
-int32_t flipper_http_worker(void* context);
+int32_t flipper_http_worker(void *context);
// Timer callback function
/**
@@ -117,7 +120,7 @@ int32_t flipper_http_worker(void* context);
* @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);
+void get_timeout_timer_callback(void *context);
// UART RX Handler Callback (Interrupt Context)
/**
@@ -129,9 +132,9 @@ void get_timeout_timer_callback(void* context);
* @note This function will handle received data asynchronously via the callback.
*/
void _flipper_http_rx_callback(
- FuriHalSerialHandle* handle,
+ FuriHalSerialHandle *handle,
FuriHalSerialRxEvent event,
- void* context);
+ void *context);
// UART initialization function
/**
@@ -141,7 +144,7 @@ void _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);
+bool flipper_http_init(FlipperHTTP_Callback callback, void *context);
// Deinitialize UART
/**
@@ -158,7 +161,7 @@ void flipper_http_deinit();
* @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);
+bool flipper_http_send_data(const char *data);
// Function to send a PING request
/**
@@ -202,7 +205,7 @@ bool flipper_http_led_off();
* @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);
+bool flipper_http_parse_json(const char *key, const char *json_data);
// Function to parse JSON array data
/**
@@ -213,7 +216,7 @@ bool flipper_http_parse_json(const char* key, const char* json_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);
+bool flipper_http_parse_json_array(const char *key, int index, const char *json_data);
// Function to scan for WiFi networks
/**
@@ -229,7 +232,7 @@ bool flipper_http_scan_wifi();
* @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);
+bool flipper_http_save_wifi(const char *ssid, const char *password);
// Function to get IP address of WiFi Devboard
/**
@@ -270,7 +273,7 @@ bool flipper_http_connect_wifi();
* @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);
+bool flipper_http_get_request(const char *url);
// Function to send a GET request with headers
/**
@@ -280,7 +283,7 @@ bool flipper_http_get_request(const char* url);
* @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);
+bool flipper_http_get_request_with_headers(const char *url, const char *headers);
// Function to send a GET request with headers and return bytes
/**
@@ -290,7 +293,7 @@ bool flipper_http_get_request_with_headers(const char* url, const char* headers)
* @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);
+bool flipper_http_get_request_bytes(const char *url, const char *headers);
// Function to send a POST request with headers
/**
@@ -302,9 +305,9 @@ bool flipper_http_get_request_bytes(const char* url, const char* headers);
* @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);
+ const char *url,
+ const char *headers,
+ const char *payload);
// Function to send a POST request with headers and return bytes
/**
@@ -315,7 +318,7 @@ bool flipper_http_post_request_with_headers(
* @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);
+bool flipper_http_post_request_bytes(const char *url, const char *headers, const char *payload);
// Function to send a PUT request with headers
/**
@@ -327,9 +330,9 @@ bool flipper_http_post_request_bytes(const char* url, const char* headers, const
* @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);
+ const char *url,
+ const char *headers,
+ const char *payload);
// Function to send a DELETE request with headers
/**
@@ -341,9 +344,9 @@ bool flipper_http_put_request_with_headers(
* @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);
+ const char *url,
+ const char *headers,
+ const char *payload);
// Function to handle received data asynchronously
/**
@@ -353,10 +356,10 @@ bool flipper_http_delete_request_with_headers(
* @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);
+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);
+char *trim(const char *str);
/**
* @brief Process requests and parse JSON data asynchronously
* @param http_request The function to send the request
@@ -374,11 +377,10 @@ bool flipper_http_process_response_async(bool (*http_request)(void), bool (*pars
* @param view_dispatcher The view dispatcher to use
* @return
*/
-void flipper_http_loading_task(
- bool (*http_request)(void),
- bool (*parse_response)(void),
- uint32_t success_view_id,
- uint32_t failure_view_id,
- ViewDispatcher** view_dispatcher);
+void flipper_http_loading_task(bool (*http_request)(void),
+ bool (*parse_response)(void),
+ uint32_t success_view_id,
+ uint32_t failure_view_id,
+ ViewDispatcher **view_dispatcher);
#endif // FLIPPER_HTTP_H
diff --git a/flip_weather/jsmn/jsmn.c b/flip_weather/jsmn/jsmn.c
index eb33b3cc7..b85ab83b5 100644
--- a/flip_weather/jsmn/jsmn.c
+++ b/flip_weather/jsmn/jsmn.c
@@ -13,11 +13,13 @@
/**
* 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;
+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) {
+ if (parser->toknext >= num_tokens)
+ {
return NULL;
}
tok = &tokens[parser->toknext++];
@@ -32,8 +34,9 @@ static jsmntok_t*
/**
* Fills token type and boundaries.
*/
-static void
- jsmn_fill_token(jsmntok_t* token, const jsmntype_t type, const int start, const int end) {
+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;
@@ -43,19 +46,19 @@ static void
/**
* 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;
+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]) {
+ 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 ':':
@@ -72,7 +75,8 @@ static int jsmn_parse_primitive(
/* to quiet a warning from gcc*/
break;
}
- if(js[parser->pos] < 32 || js[parser->pos] >= 127) {
+ if (js[parser->pos] < 32 || js[parser->pos] >= 127)
+ {
parser->pos = start;
return JSMN_ERROR_INVAL;
}
@@ -84,12 +88,14 @@ static int jsmn_parse_primitive(
#endif
found:
- if(tokens == NULL) {
+ if (tokens == NULL)
+ {
parser->pos--;
return 0;
}
token = jsmn_alloc_token(parser, tokens, num_tokens);
- if(token == NULL) {
+ if (token == NULL)
+ {
parser->pos = start;
return JSMN_ERROR_NOMEM;
}
@@ -104,29 +110,31 @@ static int jsmn_parse_primitive(
/**
* 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;
+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++) {
+ for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++)
+ {
char c = js[parser->pos];
/* Quote: end of string */
- if(c == '\"') {
- if(tokens == NULL) {
+ if (c == '\"')
+ {
+ if (tokens == NULL)
+ {
return 0;
}
token = jsmn_alloc_token(parser, tokens, num_tokens);
- if(token == NULL) {
+ if (token == NULL)
+ {
parser->pos = start;
return JSMN_ERROR_NOMEM;
}
@@ -138,10 +146,12 @@ static int jsmn_parse_string(
}
/* Backslash: Quoted symbol expected */
- if(c == '\\' && parser->pos + 1 < len) {
+ if (c == '\\' && parser->pos + 1 < len)
+ {
int i;
parser->pos++;
- switch(js[parser->pos]) {
+ switch (js[parser->pos])
+ {
/* Allowed escaped symbols */
case '\"':
case '/':
@@ -155,11 +165,13 @@ static int jsmn_parse_string(
/* Allows escaped symbol \uXXXX */
case 'u':
parser->pos++;
- for(i = 0; i < 4 && parser->pos < len && js[parser->pos] != '\0'; i++) {
+ 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 */
+ 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;
}
@@ -181,7 +193,8 @@ static int jsmn_parse_string(
/**
* Create JSON parser over an array of tokens
*/
-void jsmn_init(jsmn_parser* parser) {
+void jsmn_init(jsmn_parser *parser)
+{
parser->pos = 0;
parser->toknext = 0;
parser->toksuper = -1;
@@ -190,38 +203,41 @@ void jsmn_init(jsmn_parser* parser) {
/**
* 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 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;
+ jsmntok_t *token;
int count = parser->toknext;
- for(; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) {
+ for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++)
+ {
char c;
jsmntype_t type;
c = js[parser->pos];
- switch(c) {
+ switch (c)
+ {
case '{':
case '[':
count++;
- if(tokens == NULL) {
+ if (tokens == NULL)
+ {
break;
}
token = jsmn_alloc_token(parser, tokens, num_tokens);
- if(token == NULL) {
+ if (token == NULL)
+ {
return JSMN_ERROR_NOMEM;
}
- if(parser->toksuper != -1) {
- jsmntok_t* t = &tokens[parser->toksuper];
+ 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) {
+ if (t->type == JSMN_OBJECT)
+ {
return JSMN_ERROR_INVAL;
}
#endif
@@ -236,26 +252,33 @@ int jsmn_parse(
break;
case '}':
case ']':
- if(tokens == NULL) {
+ if (tokens == NULL)
+ {
break;
}
type = (c == '}' ? JSMN_OBJECT : JSMN_ARRAY);
#ifdef JSMN_PARENT_LINKS
- if(parser->toknext < 1) {
+ 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) {
+ 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) {
+ if (token->parent == -1)
+ {
+ if (token->type != type || parser->toksuper == -1)
+ {
return JSMN_ERROR_INVAL;
}
break;
@@ -263,10 +286,13 @@ int jsmn_parse(
token = &tokens[token->parent];
}
#else
- for(i = parser->toknext - 1; i >= 0; i--) {
+ for (i = parser->toknext - 1; i >= 0; i--)
+ {
token = &tokens[i];
- if(token->start != -1 && token->end == -1) {
- if(token->type != type) {
+ if (token->start != -1 && token->end == -1)
+ {
+ if (token->type != type)
+ {
return JSMN_ERROR_INVAL;
}
parser->toksuper = -1;
@@ -275,12 +301,15 @@ int jsmn_parse(
}
}
/* Error if unmatched closing bracket */
- if(i == -1) {
+ if (i == -1)
+ {
return JSMN_ERROR_INVAL;
}
- for(; i >= 0; i--) {
+ for (; i >= 0; i--)
+ {
token = &tokens[i];
- if(token->start != -1 && token->end == -1) {
+ if (token->start != -1 && token->end == -1)
+ {
parser->toksuper = i;
break;
}
@@ -289,11 +318,13 @@ int jsmn_parse(
break;
case '\"':
r = jsmn_parse_string(parser, js, len, tokens, num_tokens);
- if(r < 0) {
+ if (r < 0)
+ {
return r;
}
count++;
- if(parser->toksuper != -1 && tokens != NULL) {
+ if (parser->toksuper != -1 && tokens != NULL)
+ {
tokens[parser->toksuper].size++;
}
break;
@@ -306,15 +337,19 @@ int jsmn_parse(
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) {
+ 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) {
+ 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;
}
@@ -340,9 +375,12 @@ int jsmn_parse(
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)) {
+ 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;
}
}
@@ -351,11 +389,13 @@ int jsmn_parse(
default:
#endif
r = jsmn_parse_primitive(parser, js, len, tokens, num_tokens);
- if(r < 0) {
+ if (r < 0)
+ {
return r;
}
count++;
- if(parser->toksuper != -1 && tokens != NULL) {
+ if (parser->toksuper != -1 && tokens != NULL)
+ {
tokens[parser->toksuper].size++;
}
break;
@@ -368,10 +408,13 @@ int jsmn_parse(
}
}
- if(tokens != NULL) {
- for(i = parser->toknext - 1; i >= 0; i--) {
+ 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) {
+ if (tokens[i].start != -1 && tokens[i].end == -1)
+ {
return JSMN_ERROR_PART;
}
}
@@ -381,10 +424,12 @@ int jsmn_parse(
}
// 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) {
+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);
@@ -392,30 +437,36 @@ 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) {
- if(tok->type == JSMN_STRING && (int)strlen(s) == tok->end - tok->start &&
- strncmp(json + tok->start, s, tok->end - tok->start) == 0) {
+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) {
+char *get_json_value(char *key, char *json_data, uint32_t max_tokens)
+{
// Parse the JSON feed
- if(json_data != NULL) {
+ 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) {
+ 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) {
+ if (ret < 0)
+ {
// Handle parsing errors
FURI_LOG_E("JSMM.H", "Failed to parse JSON: %d", ret);
free(tokens);
@@ -423,19 +474,23 @@ char* get_json_value(char* key, char* json_data, uint32_t max_tokens) {
}
// Ensure that the root element is an object
- if(ret < 1 || tokens[0].type != JSMN_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) {
+ 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) {
+ char *value = malloc(length + 1);
+ if (value == NULL)
+ {
FURI_LOG_E("JSMM.H", "Failed to allocate memory for value.");
free(tokens);
return NULL;
@@ -450,7 +505,9 @@ char* get_json_value(char* key, char* json_data, uint32_t max_tokens) {
// Free the token array if key was not found
free(tokens);
- } else {
+ }
+ else
+ {
FURI_LOG_E("JSMM.H", "JSON data is NULL");
}
FURI_LOG_E("JSMM.H", "Failed to find the key in the JSON.");
@@ -458,10 +515,12 @@ 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) {
+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) {
+ 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;
}
@@ -471,8 +530,9 @@ char* get_json_array_value(char* key, uint32_t index, char* json_data, uint32_t
jsmn_init(&parser);
// Allocate memory for JSON tokens
- jsmntok_t* tokens = malloc(sizeof(jsmntok_t) * max_tokens);
- if(tokens == NULL) {
+ 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;
@@ -480,7 +540,8 @@ char* get_json_array_value(char* key, uint32_t index, char* json_data, uint32_t
// Parse the JSON array
int ret = jsmn_parse(&parser, array_str, strlen(array_str), tokens, max_tokens);
- if(ret < 0) {
+ if (ret < 0)
+ {
FURI_LOG_E("JSMM.H", "Failed to parse JSON array: %d", ret);
free(tokens);
free(array_str);
@@ -488,7 +549,8 @@ char* get_json_array_value(char* key, uint32_t index, char* json_data, uint32_t
}
// Ensure the root element is an array
- if(ret < 1 || tokens[0].type != JSMN_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);
@@ -496,12 +558,9 @@ char* get_json_array_value(char* key, uint32_t index, char* json_data, uint32_t
}
// 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);
+ 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;
@@ -509,20 +568,27 @@ char* get_json_array_value(char* key, uint32_t index, char* json_data, uint32_t
// 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 (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) {
+ }
+ else if (tokens[current_token].type == JSMN_ARRAY)
+ {
// For nested arrays, skip all elements
current_token += 1 + tokens[current_token].size;
- } else {
+ }
+ else
+ {
// For primitive types, simply move to the next token
current_token += 1;
}
// Safety check to prevent out-of-bounds
- if(current_token >= ret) {
+ if (current_token >= ret)
+ {
FURI_LOG_E("JSMM.H", "Unexpected end of tokens while traversing array.");
free(tokens);
free(array_str);
@@ -533,8 +599,9 @@ char* get_json_array_value(char* key, uint32_t index, char* json_data, uint32_t
// Extract the array element
jsmntok_t element = tokens[current_token];
int length = element.end - element.start;
- char* value = malloc(length + 1);
- if(value == NULL) {
+ 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);
@@ -553,10 +620,12 @@ char* get_json_array_value(char* key, uint32_t index, char* json_data, uint32_t
}
// 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) {
+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) {
+ 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;
}
@@ -566,8 +635,9 @@ char** get_json_array_values(char* key, char* json_data, uint32_t max_tokens, in
jsmn_init(&parser);
// Allocate memory for JSON tokens
- jsmntok_t* tokens = malloc(sizeof(jsmntok_t) * max_tokens); // Allocate on the heap
- if(tokens == NULL) {
+ 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;
@@ -575,7 +645,8 @@ char** get_json_array_values(char* key, char* json_data, uint32_t max_tokens, in
// Parse the JSON array
int ret = jsmn_parse(&parser, array_str, strlen(array_str), tokens, max_tokens);
- if(ret < 0) {
+ if (ret < 0)
+ {
FURI_LOG_E("JSMM.H", "Failed to parse JSON array: %d", ret);
free(tokens);
free(array_str);
@@ -583,7 +654,8 @@ char** get_json_array_values(char* key, char* json_data, uint32_t max_tokens, in
}
// Ensure the root element is an array
- if(tokens[0].type != JSMN_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);
@@ -592,8 +664,9 @@ char** get_json_array_values(char* key, char* json_data, uint32_t max_tokens, in
// 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) {
+ 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);
@@ -604,15 +677,18 @@ char** get_json_array_values(char* key, char* json_data, uint32_t max_tokens, in
// 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) {
+ 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) {
+ 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;
@@ -622,10 +698,12 @@ char** get_json_array_values(char* key, char* json_data, uint32_t max_tokens, in
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) {
+ 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++) {
+ for (int j = 0; j < actual_num_values; j++)
+ {
free(values[j]);
}
free(values);
@@ -647,14 +725,17 @@ char** get_json_array_values(char* key, char* json_data, uint32_t max_tokens, in
*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) {
+ 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++) {
+ for (int i = actual_num_values; i < array_size; i++)
+ {
free(values[i]);
}
}
diff --git a/flip_weather/jsmn/jsmn.h b/flip_weather/jsmn/jsmn.h
index cd95a0e58..74cdccf95 100644
--- a/flip_weather/jsmn/jsmn.h
+++ b/flip_weather/jsmn/jsmn.h
@@ -19,7 +19,8 @@
#include
#ifdef __cplusplus
-extern "C" {
+extern "C"
+{
#endif
#ifdef JSMN_STATIC
@@ -28,71 +29,71 @@ extern "C" {
#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
-};
-
-/**
+ 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;
+ typedef struct
+ {
+ jsmntype_t type;
+ int start;
+ int end;
+ int size;
#ifdef JSMN_PARENT_LINKS
- int parent;
+ int parent;
#endif
-} jsmntok_t;
+ } 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;
-
-/**
+ 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);
+ 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);
+ 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 */
@@ -116,16 +117,16 @@ JSMN_API int jsmn_parse(
#include
// Helper function to create a JSON object
-char* jsmn(const char* key, const char* value);
+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);
+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);
+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);
+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);
+char **get_json_array_values(char *key, char *json_data, uint32_t max_tokens, int *num_values);
#endif /* JB_JSMN_EDIT */
diff --git a/flip_weather/parse/flip_weather_parse.c b/flip_weather/parse/flip_weather_parse.c
index 26876fcca..a5032996b 100644
--- a/flip_weather/parse/flip_weather_parse.c
+++ b/flip_weather/parse/flip_weather_parse.c
@@ -6,15 +6,17 @@ bool got_ip_address = false;
bool geo_information_processed = false;
bool weather_information_processed = false;
-bool send_geo_location_request() {
- if(fhttp.state == INACTIVE) {
+bool send_geo_location_request()
+{
+ if (fhttp.state == INACTIVE)
+ {
FURI_LOG_E(TAG, "Board is INACTIVE");
flipper_http_ping(); // ping the device
fhttp.state = ISSUE;
return false;
}
- if(!flipper_http_get_request_with_headers(
- "https://ipwhois.app/json/", "{\"Content-Type\": \"application/json\"}")) {
+ if (!flipper_http_get_request_with_headers("https://ipwhois.app/json/", "{\"Content-Type\": \"application/json\"}"))
+ {
FURI_LOG_E(TAG, "Failed to send GET request");
fhttp.state = ISSUE;
return false;
@@ -23,18 +25,15 @@ bool send_geo_location_request() {
return true;
}
-bool send_geo_weather_request(DataLoaderModel* model) {
+bool send_geo_weather_request(DataLoaderModel *model)
+{
UNUSED(model);
char url[512];
- char* lattitude = lat_data + 10;
- char* longitude = lon_data + 11;
- snprintf(
- url,
- 512,
- "https://api.open-meteo.com/v1/forecast?latitude=%s&longitude=%s¤t=temperature_2m,precipitation,rain,showers,snowfall&temperature_unit=celsius&wind_speed_unit=mph&precipitation_unit=inch&forecast_days=1",
- lattitude,
- longitude);
- if(!flipper_http_get_request_with_headers(url, "{\"Content-Type\": \"application/json\"}")) {
+ char *lattitude = lat_data + 10;
+ char *longitude = lon_data + 11;
+ snprintf(url, 512, "https://api.open-meteo.com/v1/forecast?latitude=%s&longitude=%s¤t=temperature_2m,precipitation,rain,showers,snowfall&temperature_unit=celsius&wind_speed_unit=mph&precipitation_unit=inch&forecast_days=1", lattitude, longitude);
+ if (!flipper_http_get_request_with_headers(url, "{\"Content-Type\": \"application/json\"}"))
+ {
FURI_LOG_E(TAG, "Failed to send GET request");
fhttp.state = ISSUE;
return false;
@@ -42,17 +41,19 @@ bool send_geo_weather_request(DataLoaderModel* model) {
fhttp.state = RECEIVING;
return true;
}
-char* process_geo_location(DataLoaderModel* model) {
+char *process_geo_location(DataLoaderModel *model)
+{
UNUSED(model);
- if(fhttp.last_response != NULL) {
- char* city = get_json_value("city", fhttp.last_response, MAX_TOKENS);
- char* region = get_json_value("region", fhttp.last_response, MAX_TOKENS);
- char* country = get_json_value("country", fhttp.last_response, MAX_TOKENS);
- char* latitude = get_json_value("latitude", fhttp.last_response, MAX_TOKENS);
- char* longitude = get_json_value("longitude", fhttp.last_response, MAX_TOKENS);
-
- if(city == NULL || region == NULL || country == NULL || latitude == NULL ||
- longitude == NULL) {
+ if (fhttp.last_response != NULL)
+ {
+ char *city = get_json_value("city", fhttp.last_response, MAX_TOKENS);
+ char *region = get_json_value("region", fhttp.last_response, MAX_TOKENS);
+ char *country = get_json_value("country", fhttp.last_response, MAX_TOKENS);
+ char *latitude = get_json_value("latitude", fhttp.last_response, MAX_TOKENS);
+ char *longitude = get_json_value("longitude", fhttp.last_response, MAX_TOKENS);
+
+ if (city == NULL || region == NULL || country == NULL || latitude == NULL || longitude == NULL)
+ {
FURI_LOG_E(TAG, "Failed to get geo location data");
fhttp.state = ISSUE;
return NULL;
@@ -61,23 +62,17 @@ char* process_geo_location(DataLoaderModel* model) {
snprintf(lat_data, sizeof(lat_data), "Latitude: %s", latitude);
snprintf(lon_data, sizeof(lon_data), "Longitude: %s", longitude);
- if(!total_data) {
- total_data = (char*)malloc(512);
- if(!total_data) {
+ if (!total_data)
+ {
+ total_data = (char *)malloc(512);
+ if (!total_data)
+ {
FURI_LOG_E(TAG, "Failed to allocate memory for total_data");
fhttp.state = ISSUE;
return NULL;
}
}
- snprintf(
- total_data,
- 512,
- "You are in %s, %s, %s. \nLatitude: %s, Longitude: %s",
- city,
- region,
- country,
- latitude,
- longitude);
+ snprintf(total_data, 512, "You are in %s, %s, %s. \nLatitude: %s, Longitude: %s", city, region, country, latitude, longitude);
fhttp.state = IDLE;
free(city);
@@ -89,16 +84,18 @@ char* process_geo_location(DataLoaderModel* model) {
return total_data;
}
-bool process_geo_location_2() {
- if(fhttp.last_response != NULL) {
- char* city = get_json_value("city", fhttp.last_response, MAX_TOKENS);
- char* region = get_json_value("region", fhttp.last_response, MAX_TOKENS);
- char* country = get_json_value("country", fhttp.last_response, MAX_TOKENS);
- char* latitude = get_json_value("latitude", fhttp.last_response, MAX_TOKENS);
- char* longitude = get_json_value("longitude", fhttp.last_response, MAX_TOKENS);
-
- if(city == NULL || region == NULL || country == NULL || latitude == NULL ||
- longitude == NULL) {
+bool process_geo_location_2()
+{
+ if (fhttp.last_response != NULL)
+ {
+ char *city = get_json_value("city", fhttp.last_response, MAX_TOKENS);
+ char *region = get_json_value("region", fhttp.last_response, MAX_TOKENS);
+ char *country = get_json_value("country", fhttp.last_response, MAX_TOKENS);
+ char *latitude = get_json_value("latitude", fhttp.last_response, MAX_TOKENS);
+ char *longitude = get_json_value("longitude", fhttp.last_response, MAX_TOKENS);
+
+ if (city == NULL || region == NULL || country == NULL || latitude == NULL || longitude == NULL)
+ {
FURI_LOG_E(TAG, "Failed to get geo location data");
fhttp.state = ISSUE;
return false;
@@ -118,48 +115,44 @@ bool process_geo_location_2() {
return false;
}
-char* process_weather(DataLoaderModel* model) {
+char *process_weather(DataLoaderModel *model)
+{
UNUSED(model);
- if(fhttp.last_response != NULL) {
- char* current_data = get_json_value("current", fhttp.last_response, MAX_TOKENS);
- char* temperature = get_json_value("temperature_2m", current_data, MAX_TOKENS);
- char* precipitation = get_json_value("precipitation", current_data, MAX_TOKENS);
- char* rain = get_json_value("rain", current_data, MAX_TOKENS);
- char* showers = get_json_value("showers", current_data, MAX_TOKENS);
- char* snowfall = get_json_value("snowfall", current_data, MAX_TOKENS);
- char* time = get_json_value("time", current_data, MAX_TOKENS);
-
- if(current_data == NULL || temperature == NULL || precipitation == NULL || rain == NULL ||
- showers == NULL || snowfall == NULL || time == NULL) {
+ if (fhttp.last_response != NULL)
+ {
+ char *current_data = get_json_value("current", fhttp.last_response, MAX_TOKENS);
+ char *temperature = get_json_value("temperature_2m", current_data, MAX_TOKENS);
+ char *precipitation = get_json_value("precipitation", current_data, MAX_TOKENS);
+ char *rain = get_json_value("rain", current_data, MAX_TOKENS);
+ char *showers = get_json_value("showers", current_data, MAX_TOKENS);
+ char *snowfall = get_json_value("snowfall", current_data, MAX_TOKENS);
+ char *time = get_json_value("time", current_data, MAX_TOKENS);
+
+ if (current_data == NULL || temperature == NULL || precipitation == NULL || rain == NULL || showers == NULL || snowfall == NULL || time == NULL)
+ {
FURI_LOG_E(TAG, "Failed to get weather data");
fhttp.state = ISSUE;
return NULL;
}
// replace the "T" in time with a space
- char* ptr = strstr(time, "T");
- if(ptr != NULL) {
+ char *ptr = strstr(time, "T");
+ if (ptr != NULL)
+ {
*ptr = ' ';
}
- if(!weather_data) {
- weather_data = (char*)malloc(512);
- if(!weather_data) {
+ if (!weather_data)
+ {
+ weather_data = (char *)malloc(512);
+ if (!weather_data)
+ {
FURI_LOG_E(TAG, "Failed to allocate memory for weather_data");
fhttp.state = ISSUE;
return NULL;
}
}
- snprintf(
- weather_data,
- 512,
- "Temperature: %s C\nPrecipitation: %s\nRain: %s\nShowers: %s\nSnowfall: %s\nTime: %s",
- temperature,
- precipitation,
- rain,
- showers,
- snowfall,
- time);
+ snprintf(weather_data, 512, "Temperature: %s C\nPrecipitation: %s\nRain: %s\nShowers: %s\nSnowfall: %s\nTime: %s", temperature, precipitation, rain, showers, snowfall, time);
fhttp.state = IDLE;
free(current_data);
@@ -171,4 +164,4 @@ char* process_weather(DataLoaderModel* model) {
free(time);
}
return weather_data;
-}
+}
\ No newline at end of file
diff --git a/flip_weather/parse/flip_weather_parse.h b/flip_weather/parse/flip_weather_parse.h
index 68fcce62e..417556175 100644
--- a/flip_weather/parse/flip_weather_parse.h
+++ b/flip_weather/parse/flip_weather_parse.h
@@ -9,7 +9,8 @@ extern bool weather_information_processed;
// Add edits by Derek Jamison
typedef enum DataState DataState;
-enum DataState {
+enum DataState
+{
DataStateInitial,
DataStateRequested,
DataStateReceived,
@@ -19,30 +20,32 @@ enum DataState {
};
typedef enum FlipWeatherCustomEvent FlipWeatherCustomEvent;
-enum FlipWeatherCustomEvent {
+enum FlipWeatherCustomEvent
+{
FlipWeatherCustomEventProcess,
};
typedef struct DataLoaderModel DataLoaderModel;
-typedef bool (*DataLoaderFetch)(DataLoaderModel* model);
-typedef char* (*DataLoaderParser)(DataLoaderModel* model);
-struct DataLoaderModel {
- char* title;
- char* data_text;
+typedef bool (*DataLoaderFetch)(DataLoaderModel *model);
+typedef char *(*DataLoaderParser)(DataLoaderModel *model);
+struct DataLoaderModel
+{
+ char *title;
+ char *data_text;
DataState data_state;
DataLoaderFetch fetcher;
DataLoaderParser parser;
- void* parser_context;
+ void *parser_context;
size_t request_index;
size_t request_count;
ViewNavigationCallback back_callback;
- FuriTimer* timer;
+ FuriTimer *timer;
};
bool send_geo_location_request();
-char* process_geo_location(DataLoaderModel* model);
+char *process_geo_location(DataLoaderModel *model);
bool process_geo_location_2();
-char* process_weather(DataLoaderModel* model);
-bool send_geo_weather_request(DataLoaderModel* model);
+char *process_weather(DataLoaderModel *model);
+bool send_geo_weather_request(DataLoaderModel *model);
-#endif
+#endif
\ No newline at end of file
diff --git a/flip_wifi/CHANGELOG.md b/flip_wifi/CHANGELOG.md
index 6ad3c2689..9ff81e19d 100644
--- a/flip_wifi/CHANGELOG.md
+++ b/flip_wifi/CHANGELOG.md
@@ -1,7 +1,23 @@
+## v1.3.2
+- Fixed a crash that occurred when deleting a network from the Saved APs list.
+- Updated to connect to the network after clicking "Set" on the selected SSID in the Saved APs list.
+
+## v1.3.1
+- Fixed a loading error that occurred when scanning networks.
+- Fixed a freeze that occurred when saving networks manually.
+
+## v1.3
+- Updated to save credentials for the FlipWorld game.
+- Added fast commands: CUSTOM, PING, LIST, IP/ADDRESS, and WIFI/IP.
+- Improved memory allocation.
+- Increased the WiFi scan list capacity from 25 to 100.
+
+## v1.2.1
+- Fixed crash when saving networks manually.
+
## v1.2
- Updated scan loading and parsing.
- Added connectivity check on startup. (thanks to Derek Jamison)
-- Fixed crash when saving networks manually.
## v1.1
- Fixed a freeze issue when configurations did not exist (thanks to WillyJL).
diff --git a/flip_wifi/README.md b/flip_wifi/README.md
index 4cc139d3f..50fb4cddf 100644
--- a/flip_wifi/README.md
+++ b/flip_wifi/README.md
@@ -1,11 +1,9 @@
-# FlipWiFi
-
FlipWiFi is the companion app for the popular FlipperHTTP flash, originally introduced in the https://github.com/jblanked/WebCrawler-FlipperZero/tree/main/assets/FlipperHTTP. It allows you to scan and save WiFi networks for use across all FlipperHTTP apps.
## Requirements
- WiFi Developer Board, Raspberry Pi, or ESP32 Device with FlipperHTTP Flash: https://github.com/jblanked/FlipperHTTP
-- WiFi Access Point
+- 2.4 GHz WiFi Access Point
## Features
@@ -14,9 +12,9 @@ FlipWiFi is the companion app for the popular FlipperHTTP flash, originally intr
## Setup
-FlipWiFi automatically allocates the necessary resources and initializes settings upon launch. If WiFi settings have been previously configured, they are loaded automatically for easy access. You can also edit the list of WiFi settings by downloading and modifying the "wifi_list.txt" file located in the "/SD/apps_data/flip_wifi/" directory. To use the app:
+FlipWiFi automatically allocates the necessary resources and initializes settings upon launch. If WiFi settings have been previously configured, they are loaded automatically for easy access. You can also edit the list of WiFi settings by downloading and modifying the "wifi_list.txt" file located in the "/SD/apps_data/flip_wifi/data" directory. To use the app:
-1. **Flash the WiFi Dev Board**: Follow the instructions to flash the WiFi Dev Board with FlipperHTTP: https://github.com/jblanked/FlipperHTTP
-2. **Install the App**: Download FlipWiFi from the App Store.
+1. **Flash the WiFi Developer Board**: Follow the instructions to flash the WiFi Dev Board with FlipperHTTP: https://github.com/jblanked/FlipperHTTP
+2. **Install the App**: Download FlipWiFi from the Flipper Lab.
3. **Launch FlipWiFi**: Open the app on your Flipper Zero.
4. Connect, review, and save WiFi networks.
\ No newline at end of file
diff --git a/flip_wifi/alloc/flip_wifi_alloc.c b/flip_wifi/alloc/flip_wifi_alloc.c
index cb398f5c0..188275c1a 100644
--- a/flip_wifi/alloc/flip_wifi_alloc.c
+++ b/flip_wifi/alloc/flip_wifi_alloc.c
@@ -1,205 +1,30 @@
-#include
+#include
// Function to allocate resources for the FlipWiFiApp
-FlipWiFiApp* flip_wifi_app_alloc() {
- FlipWiFiApp* app = (FlipWiFiApp*)malloc(sizeof(FlipWiFiApp));
+FlipWiFiApp *flip_wifi_app_alloc()
+{
+ FlipWiFiApp *app = (FlipWiFiApp *)malloc(sizeof(FlipWiFiApp));
- 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 flipper http");
- return NULL;
- }
-
- // Allocate the text input buffer
- app->uart_text_input_buffer_size_password_scan = 64;
- app->uart_text_input_buffer_size_password_saved = 64;
- app->uart_text_input_buffer_size_add_ssid = 64;
- app->uart_text_input_buffer_size_add_password = 64;
- if(!easy_flipper_set_buffer(
- &app->uart_text_input_buffer_password_scan,
- app->uart_text_input_buffer_size_password_scan)) {
- return NULL;
- }
- if(!easy_flipper_set_buffer(
- &app->uart_text_input_temp_buffer_password_scan,
- app->uart_text_input_buffer_size_password_scan)) {
- return NULL;
- }
- if(!easy_flipper_set_buffer(
- &app->uart_text_input_buffer_password_saved,
- app->uart_text_input_buffer_size_password_saved)) {
- return NULL;
- }
- if(!easy_flipper_set_buffer(
- &app->uart_text_input_temp_buffer_password_saved,
- app->uart_text_input_buffer_size_password_saved)) {
- return NULL;
- }
- if(!easy_flipper_set_buffer(
- &app->uart_text_input_buffer_add_ssid, app->uart_text_input_buffer_size_add_ssid)) {
- return NULL;
- }
- if(!easy_flipper_set_buffer(
- &app->uart_text_input_temp_buffer_add_ssid,
- app->uart_text_input_buffer_size_add_ssid)) {
- return NULL;
- }
- if(!easy_flipper_set_buffer(
- &app->uart_text_input_buffer_add_password,
- app->uart_text_input_buffer_size_add_password)) {
- return NULL;
- }
- if(!easy_flipper_set_buffer(
- &app->uart_text_input_temp_buffer_add_password,
- app->uart_text_input_buffer_size_add_password)) {
- return NULL;
- }
+ Gui *gui = furi_record_open(RECORD_GUI);
// Allocate ViewDispatcher
- if(!easy_flipper_set_view_dispatcher(&app->view_dispatcher, gui, app)) {
- return NULL;
- }
-
- // View(s)
- if(!easy_flipper_set_view(
- &app->view_wifi_scan,
- FlipWiFiViewWiFiScan,
- flip_wifi_view_draw_callback_scan,
- flip_wifi_view_input_callback_scan,
- callback_to_submenu_scan,
- &app->view_dispatcher,
- app)) {
- return NULL;
- }
- if(!easy_flipper_set_view(
- &app->view_wifi_saved,
- FlipWiFiViewWiFiSaved,
- flip_wifi_view_draw_callback_saved,
- flip_wifi_view_input_callback_saved,
- callback_to_submenu_saved,
- &app->view_dispatcher,
- app)) {
- return NULL;
- }
-
- // Widget
- if(!easy_flipper_set_widget(
- &app->widget_info,
- FlipWiFiViewAbout,
- "FlipWiFi v1.2.1\n-----\nFlipperHTTP companion app.\nScan and save WiFi networks.\n-----\nwww.github.com/jblanked",
- callback_to_submenu_main,
- &app->view_dispatcher)) {
- return NULL;
- }
-
- // Text Input
- if(!easy_flipper_set_uart_text_input(
- &app->uart_text_input_password_scan,
- FlipWiFiViewTextInputScan,
- "Enter WiFi Password",
- app->uart_text_input_temp_buffer_password_scan,
- app->uart_text_input_buffer_size_password_scan,
- flip_wifi_text_updated_password_scan,
- callback_to_submenu_scan,
- &app->view_dispatcher,
- app)) {
- return NULL;
- }
- if(!easy_flipper_set_uart_text_input(
- &app->uart_text_input_password_saved,
- FlipWiFiViewTextInputSaved,
- "Enter WiFi Password",
- app->uart_text_input_temp_buffer_password_saved,
- app->uart_text_input_buffer_size_password_saved,
- flip_wifi_text_updated_password_saved,
- callback_to_submenu_saved,
- &app->view_dispatcher,
- app)) {
- return NULL;
- }
- if(!easy_flipper_set_uart_text_input(
- &app->uart_text_input_add_ssid,
- FlipWiFiViewTextInputSavedAddSSID,
- "Enter SSID",
- app->uart_text_input_temp_buffer_add_ssid,
- app->uart_text_input_buffer_size_add_ssid,
- flip_wifi_text_updated_add_ssid,
- callback_to_submenu_saved,
- &app->view_dispatcher,
- app)) {
- return NULL;
- }
- if(!easy_flipper_set_uart_text_input(
- &app->uart_text_input_add_password,
- FlipWiFiViewTextInputSavedAddPassword,
- "Enter Password",
- app->uart_text_input_temp_buffer_add_password,
- app->uart_text_input_buffer_size_add_password,
- flip_wifi_text_updated_add_password,
- callback_to_submenu_saved,
- &app->view_dispatcher,
- app)) {
+ if (!easy_flipper_set_view_dispatcher(&app->view_dispatcher, gui, app))
+ {
return NULL;
}
// Submenu
- if(!easy_flipper_set_submenu(
- &app->submenu_main,
- FlipWiFiViewSubmenuMain,
- "FlipWiFi v1.2.1",
- easy_flipper_callback_exit_app,
- &app->view_dispatcher)) {
- return NULL;
- }
- if(!easy_flipper_set_submenu(
- &app->submenu_wifi_scan,
- FlipWiFiViewSubmenuScan,
- "WiFi Scan",
- callback_to_submenu_main,
- &app->view_dispatcher)) {
+ if (!easy_flipper_set_submenu(&app->submenu_main, FlipWiFiViewSubmenuMain, "FlipWiFi v1.3.2", callback_exit_app, &app->view_dispatcher))
+ {
return NULL;
}
- if(!easy_flipper_set_submenu(
- &app->submenu_wifi_saved,
- FlipWiFiViewSubmenuSaved,
- "Saved APs",
- callback_to_submenu_main,
- &app->view_dispatcher)) {
- return NULL;
- }
- submenu_add_item(
- app->submenu_main, "Scan", FlipWiFiSubmenuIndexWiFiScan, callback_submenu_choices, app);
- submenu_add_item(
- app->submenu_main,
- "Saved APs",
- FlipWiFiSubmenuIndexWiFiSaved,
- callback_submenu_choices,
- app);
- submenu_add_item(
- app->submenu_main, "Info", FlipWiFiSubmenuIndexAbout, callback_submenu_choices, app);
-
- // Load the playlist from storage
- if(!load_playlist(&app->wifi_playlist)) {
- FURI_LOG_E(TAG, "Failed to load playlist");
-
- // playlist is empty?
- submenu_reset(app->submenu_wifi_saved);
- submenu_set_header(app->submenu_wifi_saved, "Saved APs");
- submenu_add_item(
- app->submenu_wifi_saved,
- "[Add Network]",
- FlipWiFiSubmenuIndexWiFiSavedAddSSID,
- callback_submenu_choices,
- app);
- } else {
- // Update the submenu
- flip_wifi_redraw_submenu_saved(app);
- }
+ submenu_add_item(app->submenu_main, "Scan", FlipWiFiSubmenuIndexWiFiScan, callback_submenu_choices, app);
+ submenu_add_item(app->submenu_main, "Saved APs", FlipWiFiSubmenuIndexWiFiSaved, callback_submenu_choices, app);
+ submenu_add_item(app->submenu_main, "Commands", FlipWiFiSubmenuIndexCommands, callback_submenu_choices, app);
+ submenu_add_item(app->submenu_main, "Info", FlipWiFiSubmenuIndexAbout, callback_submenu_choices, app);
// Switch to the main view
view_dispatcher_switch_to_view(app->view_dispatcher, FlipWiFiViewSubmenuMain);
return app;
-}
+}
\ No newline at end of file
diff --git a/flip_wifi/alloc/flip_wifi_alloc.h b/flip_wifi/alloc/flip_wifi_alloc.h
index 630514e40..07885ecb1 100644
--- a/flip_wifi/alloc/flip_wifi_alloc.h
+++ b/flip_wifi/alloc/flip_wifi_alloc.h
@@ -1,11 +1,6 @@
-#ifndef FLIP_WIFI_I_H
-#define FLIP_WIFI_I_H
+#pragma once
#include
-#include
-#include
// Function to allocate resources for the FlipWiFiApp
-FlipWiFiApp* flip_wifi_app_alloc();
-
-#endif // FLIP_WIFI_I_H
+FlipWiFiApp *flip_wifi_app_alloc();
diff --git a/flip_wifi/app.c b/flip_wifi/app.c
index 96abe35a5..5aa219e5d 100644
--- a/flip_wifi/app.c
+++ b/flip_wifi/app.c
@@ -2,55 +2,51 @@
#include
// Entry point for the FlipWiFi application
-int32_t flip_wifi_main(void* p) {
- // Suppress unused parameter warning
+int32_t flip_wifi_main(void *p)
+{
UNUSED(p);
// Initialize the FlipWiFi application
- app_instance = flip_wifi_app_alloc();
- if(!app_instance) {
+ FlipWiFiApp *app = flip_wifi_app_alloc();
+ if (!app)
+ {
FURI_LOG_E(TAG, "Failed to allocate FlipWiFiApp");
return -1;
}
- if(!flipper_http_ping()) {
- FURI_LOG_E(TAG, "Failed to ping the device");
- return -1;
- }
+ // check if board is connected (Derek Jamison)
+ // initialize the http
+ if (flipper_http_init(flipper_http_rx_callback, app))
+ {
+ if (!flipper_http_ping())
+ {
+ FURI_LOG_E(TAG, "Failed to ping the device");
+ return -1;
+ }
- // Thanks to Derek Jamison for the following code snippet:
- if(app_instance->uart_text_input_buffer_add_ssid != NULL &&
- app_instance->uart_text_input_buffer_add_password != NULL) {
// Try to wait for pong response.
uint8_t counter = 10;
- while(fhttp.state == INACTIVE && --counter > 0) {
+ while (fhttp.state == INACTIVE && --counter > 0)
+ {
FURI_LOG_D(TAG, "Waiting for PONG");
furi_delay_ms(100);
}
- if(counter == 0) {
- DialogsApp* dialogs = furi_record_open(RECORD_DIALOGS);
- DialogMessage* message = dialog_message_alloc();
- dialog_message_set_header(
- message, "[FlipperHTTP Error]", 64, 0, AlignCenter, AlignTop);
- dialog_message_set_text(
- message,
- "Ensure your WiFi Developer\nBoard or Pico W is connected\nand the latest FlipperHTTP\nfirmware is installed.",
- 0,
- 63,
- AlignLeft,
- AlignBottom);
- dialog_message_show(dialogs, message);
- dialog_message_free(message);
- furi_record_close(RECORD_DIALOGS);
- }
+ if (counter == 0)
+ easy_flipper_dialog("FlipperHTTP Error", "Ensure your WiFi Developer\nBoard or Pico W is connected\nand the latest FlipperHTTP\nfirmware is installed.");
+
+ flipper_http_deinit();
+ }
+ else
+ {
+ easy_flipper_dialog("FlipperHTTP Error", "The UART is likely busy.\nEnsure you have the correct\nflash for your board then\nrestart your Flipper Zero.");
}
// Run the view dispatcher
- view_dispatcher_run(app_instance->view_dispatcher);
+ view_dispatcher_run(app->view_dispatcher);
// Free the resources used by the FlipWiFi application
- flip_wifi_app_free(app_instance);
+ flip_wifi_app_free(app);
// Return 0 to indicate success
return 0;
diff --git a/flip_wifi/application.fam b/flip_wifi/application.fam
index 715643a32..8e001f37d 100644
--- a/flip_wifi/application.fam
+++ b/flip_wifi/application.fam
@@ -9,6 +9,6 @@ App(
fap_icon_assets="assets",
fap_author="JBlanked",
fap_weburl="https://github.com/jblanked/FlipWiFi",
- fap_version="1.2.1",
+ fap_version="1.3.2",
fap_description="FlipperHTTP companion app.",
)
diff --git a/flip_wifi/assets/01-home.png b/flip_wifi/assets/01-home.png
index 3dbe35f24..ca3550102 100644
Binary files a/flip_wifi/assets/01-home.png and b/flip_wifi/assets/01-home.png differ
diff --git a/flip_wifi/callback/flip_wifi_callback.c b/flip_wifi/callback/flip_wifi_callback.c
index a31cf95c1..8bec32373 100644
--- a/flip_wifi/callback/flip_wifi_callback.c
+++ b/flip_wifi/callback/flip_wifi_callback.c
@@ -1,74 +1,493 @@
#include
-char* ssid_list[64];
-uint32_t ssid_index = 0;
+static char *ssid_list[64];
+static uint32_t ssid_index = 0;
+static char current_ssid[64];
+static char current_password[64];
-void flip_wifi_redraw_submenu_saved(FlipWiFiApp* app) {
- // re draw the saved submenu
- submenu_reset(app->submenu_wifi_saved);
- submenu_set_header(app->submenu_wifi_saved, "Saved APs");
- submenu_add_item(
- app->submenu_wifi_saved,
- "[Add Network]",
- FlipWiFiSubmenuIndexWiFiSavedAddSSID,
- callback_submenu_choices,
- app);
- for(uint32_t i = 0; i < app->wifi_playlist.count; i++) {
- submenu_add_item(
- app->submenu_wifi_saved,
- app->wifi_playlist.ssids[i],
- FlipWiFiSubmenuIndexWiFiSavedStart + i,
- callback_submenu_choices,
- app);
+static void flip_wifi_redraw_submenu_saved(void *context);
+static void flip_wifi_view_draw_callback_scan(Canvas *canvas, void *model);
+static void flip_wifi_view_draw_callback_saved(Canvas *canvas, void *model);
+static bool flip_wifi_view_input_callback_scan(InputEvent *event, void *context);
+static bool flip_wifi_view_input_callback_saved(InputEvent *event, void *context);
+static uint32_t callback_to_submenu_saved(void *context);
+static uint32_t callback_to_submenu_scan(void *context);
+static uint32_t callback_to_submenu_main(void *context);
+
+void flip_wifi_text_updated_password_scan(void *context);
+void flip_wifi_text_updated_password_saved(void *context);
+void flip_wifi_text_updated_add_ssid(void *context);
+void flip_wifi_text_updated_add_password(void *context);
+
+static bool flip_wifi_alloc_playlist(void *context)
+{
+ FlipWiFiApp *app = (FlipWiFiApp *)context;
+ if (!app)
+ {
+ FURI_LOG_E(TAG, "FlipWiFiApp is NULL");
+ return false;
+ }
+ if (!wifi_playlist)
+ {
+ wifi_playlist = (WiFiPlaylist *)malloc(sizeof(WiFiPlaylist));
+ if (!wifi_playlist)
+ {
+ FURI_LOG_E(TAG, "Failed to allocate playlist");
+ return false;
+ }
+ wifi_playlist->count = 0;
+ }
+ // Load the playlist from storage
+ if (!load_playlist(wifi_playlist))
+ {
+ FURI_LOG_E(TAG, "Failed to load playlist");
+
+ // playlist is empty?
+ submenu_reset(app->submenu_wifi);
+ submenu_set_header(app->submenu_wifi, "Saved APs");
+ submenu_add_item(app->submenu_wifi, "[Add Network]", FlipWiFiSubmenuIndexWiFiSavedAddSSID, callback_submenu_choices, app);
+ }
+ else
+ {
+ // Update the submenu
+ flip_wifi_redraw_submenu_saved(app);
+ }
+ return true;
+}
+static void flip_wifi_free_playlist(void)
+{
+ if (wifi_playlist)
+ {
+ free(wifi_playlist);
+ wifi_playlist = NULL;
}
}
-uint32_t callback_to_submenu_main(void* context) {
- if(!context) {
- FURI_LOG_E(TAG, "Context is NULL");
- return VIEW_NONE;
+static bool flip_wifi_alloc_views(void *context, uint32_t view)
+{
+ FlipWiFiApp *app = (FlipWiFiApp *)context;
+ if (!app)
+ {
+ FURI_LOG_E(TAG, "FlipWiFiApp is NULL");
+ return false;
+ }
+ switch (view)
+ {
+ case FlipWiFiViewWiFiScan:
+ if (!app->view_wifi)
+ {
+ if (!easy_flipper_set_view(&app->view_wifi, FlipWiFiViewGeneric, flip_wifi_view_draw_callback_scan, flip_wifi_view_input_callback_scan, callback_to_submenu_scan, &app->view_dispatcher, app))
+ {
+ return false;
+ }
+ if (!app->view_wifi)
+ {
+ FURI_LOG_E(TAG, "Failed to allocate view for WiFi Scan");
+ return false;
+ }
+ }
+ return true;
+ case FlipWiFiViewWiFiSaved:
+ if (!app->view_wifi)
+ {
+ if (!easy_flipper_set_view(&app->view_wifi, FlipWiFiViewGeneric, flip_wifi_view_draw_callback_saved, flip_wifi_view_input_callback_saved, callback_to_submenu_saved, &app->view_dispatcher, app))
+ {
+ return false;
+ }
+ if (!app->view_wifi)
+ {
+ FURI_LOG_E(TAG, "Failed to allocate view for WiFi Scan");
+ return false;
+ }
+ }
+ return true;
+ default:
+ return false;
}
- UNUSED(context);
- ssid_index = 0;
- return FlipWiFiViewSubmenuMain;
}
-uint32_t callback_to_submenu_scan(void* context) {
- if(!context) {
- FURI_LOG_E(TAG, "Context is NULL");
- return VIEW_NONE;
+static void flip_wifi_free_views(void *context)
+{
+ FlipWiFiApp *app = (FlipWiFiApp *)context;
+ if (!app)
+ {
+ FURI_LOG_E(TAG, "FlipWiFiApp is NULL");
+ return;
+ }
+ if (app->view_wifi)
+ {
+ free(app->view_wifi);
+ app->view_wifi = NULL;
+ view_dispatcher_remove_view(app->view_dispatcher, FlipWiFiViewGeneric);
}
- UNUSED(context);
- ssid_index = 0;
- return FlipWiFiViewSubmenuScan;
}
-uint32_t callback_to_submenu_saved(void* context) {
- if(!context) {
- FURI_LOG_E(TAG, "Context is NULL");
- return VIEW_NONE;
+static bool flip_wifi_alloc_widgets(void *context, uint32_t widget)
+{
+ FlipWiFiApp *app = (FlipWiFiApp *)context;
+ if (!app)
+ {
+ FURI_LOG_E(TAG, "FlipWiFiApp is NULL");
+ return false;
}
- UNUSED(context);
- ssid_index = 0;
- return FlipWiFiViewSubmenuSaved;
+ switch (widget)
+ {
+ case FlipWiFiViewAbout:
+ if (!app->widget_info)
+ {
+ if (!easy_flipper_set_widget(&app->widget_info, FlipWiFiViewAbout, "FlipWiFi v1.3.2\n-----\nFlipperHTTP companion app.\nScan and save WiFi networks.\n-----\nwww.github.com/jblanked", callback_to_submenu_main, &app->view_dispatcher))
+ {
+ return false;
+ }
+ if (!app->widget_info)
+ {
+ FURI_LOG_E(TAG, "Failed to allocate widget for About");
+ return false;
+ }
+ }
+ return true;
+ default:
+ return false;
+ }
+}
+static void flip_wifi_free_widgets(void *context)
+{
+ FlipWiFiApp *app = (FlipWiFiApp *)context;
+ if (!app)
+ {
+ FURI_LOG_E(TAG, "FlipWiFiApp is NULL");
+ return;
+ }
+ if (app->widget_info)
+ {
+ free(app->widget_info);
+ app->widget_info = NULL;
+ view_dispatcher_remove_view(app->view_dispatcher, FlipWiFiViewAbout);
+ }
+}
+static bool flip_wifi_alloc_submenus(void *context, uint32_t view)
+{
+ FlipWiFiApp *app = (FlipWiFiApp *)context;
+ if (!app)
+ {
+ FURI_LOG_E(TAG, "FlipWiFiApp is NULL");
+ return false;
+ }
+ switch (view)
+ {
+ case FlipWiFiViewSubmenuScan:
+ if (!app->submenu_wifi)
+ {
+ if (!easy_flipper_set_submenu(&app->submenu_wifi, FlipWiFiViewSubmenu, "WiFi Nearby", callback_to_submenu_main, &app->view_dispatcher))
+ {
+ return false;
+ }
+ if (!app->submenu_wifi)
+ {
+ FURI_LOG_E(TAG, "Failed to allocate submenu for WiFi Scan");
+ return false;
+ }
+ }
+ return true;
+ case FlipWiFiViewSubmenuSaved:
+ if (!app->submenu_wifi)
+ {
+ if (!easy_flipper_set_submenu(&app->submenu_wifi, FlipWiFiViewSubmenu, "Saved APs", callback_to_submenu_main, &app->view_dispatcher))
+ {
+ return false;
+ }
+ if (!app->submenu_wifi)
+ {
+ FURI_LOG_E(TAG, "Failed to allocate submenu for WiFi Saved");
+ return false;
+ }
+ if (!flip_wifi_alloc_playlist(app))
+ {
+ FURI_LOG_E(TAG, "Failed to allocate playlist");
+ return false;
+ }
+ }
+ return true;
+ case FlipWiFiViewSubmenuCommands:
+ if (!app->submenu_wifi)
+ {
+ if (!easy_flipper_set_submenu(&app->submenu_wifi, FlipWiFiViewSubmenu, "Fast Commands", callback_to_submenu_main, &app->view_dispatcher))
+ {
+ return false;
+ }
+ if (!app->submenu_wifi)
+ {
+ FURI_LOG_E(TAG, "Failed to allocate submenu for Commands");
+ return false;
+ }
+ // PING, LIST, WIFI/LIST, IP/ADDRESS, and WIFI/IP.
+ submenu_add_item(app->submenu_wifi, "[CUSTOM]", FlipWiFiSubmenuIndexFastCommandStart + 0, callback_submenu_choices, app);
+ submenu_add_item(app->submenu_wifi, "PING", FlipWiFiSubmenuIndexFastCommandStart + 1, callback_submenu_choices, app);
+ submenu_add_item(app->submenu_wifi, "LIST", FlipWiFiSubmenuIndexFastCommandStart + 2, callback_submenu_choices, app);
+ submenu_add_item(app->submenu_wifi, "IP/ADDRESS", FlipWiFiSubmenuIndexFastCommandStart + 3, callback_submenu_choices, app);
+ submenu_add_item(app->submenu_wifi, "WIFI/IP", FlipWiFiSubmenuIndexFastCommandStart + 4, callback_submenu_choices, app);
+ }
+ return true;
+ }
+ return false;
}
-static void popup_callback_saved(void* context) {
- FlipWiFiApp* app = (FlipWiFiApp*)context;
- if(!app) {
- FURI_LOG_E(TAG, "HelloWorldApp is NULL");
+static void flip_wifi_free_submenus(void *context)
+{
+ FlipWiFiApp *app = (FlipWiFiApp *)context;
+ if (!app)
+ {
+ FURI_LOG_E(TAG, "FlipWiFiApp is NULL");
return;
}
- view_dispatcher_switch_to_view(app->view_dispatcher, FlipWiFiViewSubmenuSaved);
+ if (app->submenu_wifi)
+ {
+ free(app->submenu_wifi);
+ app->submenu_wifi = NULL;
+ view_dispatcher_remove_view(app->view_dispatcher, FlipWiFiViewSubmenu);
+ }
}
-static void popup_callback_main(void* context) {
- FlipWiFiApp* app = (FlipWiFiApp*)context;
- if(!app) {
- FURI_LOG_E(TAG, "HelloWorldApp is NULL");
+
+static void flip_wifi_custom_command_updated(void *context)
+{
+ FlipWiFiApp *app = (FlipWiFiApp *)context;
+ if (!app)
+ {
+ FURI_LOG_E(TAG, "FlipWiFiApp is NULL");
return;
}
- view_dispatcher_switch_to_view(app->view_dispatcher, FlipWiFiViewSubmenuMain);
+ if (!app->uart_text_input_temp_buffer)
+ {
+ FURI_LOG_E(TAG, "Text input buffer is NULL");
+ return;
+ }
+ if (!app->uart_text_input_temp_buffer[0])
+ {
+ FURI_LOG_E(TAG, "Text input buffer is empty");
+ return;
+ }
+ // Send the custom command
+ flipper_http_send_data(app->uart_text_input_temp_buffer);
+ while (fhttp.last_response == NULL || strlen(fhttp.last_response) == 0)
+ {
+ furi_delay_ms(100);
+ }
+ // Switch to the view
+ char response[100];
+ snprintf(response, sizeof(response), "%s", fhttp.last_response);
+ easy_flipper_dialog("", response);
+ flipper_http_deinit();
+ view_dispatcher_switch_to_view(app->view_dispatcher, FlipWiFiViewSubmenu);
+}
+
+static bool flip_wifi_alloc_text_inputs(void *context, uint32_t view)
+{
+ FlipWiFiApp *app = (FlipWiFiApp *)context;
+ if (!app)
+ {
+ FURI_LOG_E(TAG, "FlipWiFiApp is NULL");
+ return false;
+ }
+ app->uart_text_input_buffer_size = MAX_SSID_LENGTH;
+ if (!app->uart_text_input_buffer)
+ {
+ if (!easy_flipper_set_buffer(&app->uart_text_input_buffer, app->uart_text_input_buffer_size))
+ {
+ FURI_LOG_E(TAG, "Failed to allocate text input buffer");
+ return false;
+ }
+ if (!app->uart_text_input_buffer)
+ {
+ FURI_LOG_E(TAG, "Failed to allocate text input buffer");
+ return false;
+ }
+ }
+ if (!app->uart_text_input_temp_buffer)
+ {
+ if (!easy_flipper_set_buffer(&app->uart_text_input_temp_buffer, app->uart_text_input_buffer_size))
+ {
+ FURI_LOG_E(TAG, "Failed to allocate text input temp buffer");
+ return false;
+ }
+ if (!app->uart_text_input_temp_buffer)
+ {
+ FURI_LOG_E(TAG, "Failed to allocate text input temp buffer");
+ return false;
+ }
+ }
+ switch (view)
+ {
+ case FlipWiFiViewTextInputScan:
+ if (!app->uart_text_input)
+ {
+ if (!easy_flipper_set_uart_text_input(&app->uart_text_input, FlipWiFiViewTextInput, "Enter WiFi Password", app->uart_text_input_temp_buffer, app->uart_text_input_buffer_size, flip_wifi_text_updated_password_scan, callback_to_submenu_scan, &app->view_dispatcher, app))
+ {
+ FURI_LOG_E(TAG, "Failed to allocate text input for WiFi Scan");
+ return false;
+ }
+ if (!app->uart_text_input)
+ {
+ FURI_LOG_E(TAG, "Failed to allocate text input for WiFi Scan");
+ return false;
+ }
+ }
+ return true;
+ case FlipWiFiViewTextInputSaved:
+ if (!app->uart_text_input)
+ {
+ if (!easy_flipper_set_uart_text_input(&app->uart_text_input, FlipWiFiViewTextInput, "Enter WiFi Password", app->uart_text_input_temp_buffer, app->uart_text_input_buffer_size, flip_wifi_text_updated_password_saved, callback_to_submenu_saved, &app->view_dispatcher, app))
+ {
+ FURI_LOG_E(TAG, "Failed to allocate text input for WiFi Saved");
+ return false;
+ }
+ if (!app->uart_text_input)
+ {
+ FURI_LOG_E(TAG, "Failed to allocate text input for WiFi Saved");
+ return false;
+ }
+ }
+ return true;
+ case FlipWiFiViewTextInputSavedAddSSID:
+ if (!app->uart_text_input)
+ {
+ if (!easy_flipper_set_uart_text_input(&app->uart_text_input, FlipWiFiViewTextInput, "Enter SSID", app->uart_text_input_temp_buffer, app->uart_text_input_buffer_size, flip_wifi_text_updated_add_ssid, callback_to_submenu_saved, &app->view_dispatcher, app))
+ {
+ FURI_LOG_E(TAG, "Failed to allocate text input for WiFi Saved Add SSID");
+ return false;
+ }
+ if (!app->uart_text_input)
+ {
+ FURI_LOG_E(TAG, "Failed to allocate text input for WiFi Saved Add SSID");
+ return false;
+ }
+ }
+ return true;
+ case FlipWiFiViewTextInputSavedAddPassword:
+ if (!app->uart_text_input)
+ {
+ if (!easy_flipper_set_uart_text_input(&app->uart_text_input, FlipWiFiViewTextInput, "Enter Password", app->uart_text_input_temp_buffer, app->uart_text_input_buffer_size, flip_wifi_text_updated_add_password, callback_to_submenu_saved, &app->view_dispatcher, app))
+ {
+ FURI_LOG_E(TAG, "Failed to allocate text input for WiFi Saved Add Password");
+ return false;
+ }
+ if (!app->uart_text_input)
+ {
+ FURI_LOG_E(TAG, "Failed to allocate text input for WiFi Saved Add Password");
+ return false;
+ }
+ }
+ return true;
+ case FlipWiFiSubmenuIndexFastCommandStart:
+ if (!app->uart_text_input)
+ {
+ if (!easy_flipper_set_uart_text_input(&app->uart_text_input, FlipWiFiViewTextInput, "Enter Command", app->uart_text_input_temp_buffer, app->uart_text_input_buffer_size, flip_wifi_custom_command_updated, callback_to_submenu_saved, &app->view_dispatcher, app))
+ {
+ FURI_LOG_E(TAG, "Failed to allocate text input for Fast Command");
+ return false;
+ }
+ if (!app->uart_text_input)
+ {
+ FURI_LOG_E(TAG, "Failed to allocate text input for Fast Command");
+ return false;
+ }
+ }
+ return true;
+ }
+ return false;
+}
+static void flip_wifi_free_text_inputs(void *context)
+{
+ FlipWiFiApp *app = (FlipWiFiApp *)context;
+ if (!app)
+ {
+ FURI_LOG_E(TAG, "FlipWiFiApp is NULL");
+ return;
+ }
+ if (app->uart_text_input)
+ {
+ free(app->uart_text_input);
+ app->uart_text_input = NULL;
+ view_dispatcher_remove_view(app->view_dispatcher, FlipWiFiViewTextInput);
+ }
+ if (app->uart_text_input_buffer)
+ {
+ free(app->uart_text_input_buffer);
+ app->uart_text_input_buffer = NULL;
+ }
+ if (app->uart_text_input_temp_buffer)
+ {
+ free(app->uart_text_input_temp_buffer);
+ app->uart_text_input_temp_buffer = NULL;
+ }
+}
+
+void flip_wifi_free_all(void *context)
+{
+ FlipWiFiApp *app = (FlipWiFiApp *)context;
+ if (!app)
+ {
+ FURI_LOG_E(TAG, "FlipWiFiApp is NULL");
+ return;
+ }
+ flip_wifi_free_views(app);
+ flip_wifi_free_widgets(app);
+ flip_wifi_free_submenus(app);
+ flip_wifi_free_text_inputs(app);
+ flip_wifi_free_playlist();
+}
+
+static void flip_wifi_redraw_submenu_saved(void *context)
+{
+ // re draw the saved submenu
+ FlipWiFiApp *app = (FlipWiFiApp *)context;
+ if (!app)
+ {
+ FURI_LOG_E(TAG, "FlipWiFiApp is NULL");
+ return;
+ }
+ if (!app->submenu_wifi)
+ {
+ FURI_LOG_E(TAG, "Submenu is NULL");
+ return;
+ }
+ if (!wifi_playlist)
+ {
+ FURI_LOG_E(TAG, "WiFi Playlist is NULL");
+ return;
+ }
+ submenu_reset(app->submenu_wifi);
+ submenu_set_header(app->submenu_wifi, "Saved APs");
+ submenu_add_item(app->submenu_wifi, "[Add Network]", FlipWiFiSubmenuIndexWiFiSavedAddSSID, callback_submenu_choices, app);
+ for (size_t i = 0; i < wifi_playlist->count; i++)
+ {
+ submenu_add_item(app->submenu_wifi, wifi_playlist->ssids[i], FlipWiFiSubmenuIndexWiFiSavedStart + i, callback_submenu_choices, app);
+ }
+}
+
+static uint32_t callback_to_submenu_main(void *context)
+{
+ UNUSED(context);
+ ssid_index = 0;
+ return FlipWiFiViewSubmenuMain;
+}
+static uint32_t callback_to_submenu_scan(void *context)
+{
+ UNUSED(context);
+ ssid_index = 0;
+ return FlipWiFiViewSubmenu;
+}
+static uint32_t callback_to_submenu_saved(void *context)
+{
+ UNUSED(context);
+ ssid_index = 0;
+ return FlipWiFiViewSubmenu;
+}
+uint32_t callback_exit_app(void *context)
+{
+ UNUSED(context);
+ return VIEW_NONE;
}
// Callback for drawing the main screen
-void flip_wifi_view_draw_callback_scan(Canvas* canvas, void* model) {
+static void flip_wifi_view_draw_callback_scan(Canvas *canvas, void *model)
+{
UNUSED(model);
canvas_clear(canvas);
canvas_set_font(canvas, FontPrimary);
@@ -78,15 +497,15 @@ void flip_wifi_view_draw_callback_scan(Canvas* canvas, void* model) {
canvas_draw_icon(canvas, 96, 53, &I_ButtonRight_4x7);
canvas_draw_str_aligned(canvas, 103, 54, AlignLeft, AlignTop, "Add");
}
-void flip_wifi_view_draw_callback_saved(Canvas* canvas, void* model) {
+static void flip_wifi_view_draw_callback_saved(Canvas *canvas, void *model)
+{
UNUSED(model);
canvas_clear(canvas);
canvas_set_font(canvas, FontPrimary);
- canvas_draw_str(canvas, 0, 10, app_instance->wifi_playlist.ssids[ssid_index]);
+ canvas_draw_str(canvas, 0, 10, current_ssid);
canvas_set_font(canvas, FontSecondary);
- char password[64];
- snprintf(
- password, sizeof(password), "Pass: %s", app_instance->wifi_playlist.passwords[ssid_index]);
+ char password[72];
+ snprintf(password, sizeof(password), "Pass: %s", current_password);
canvas_draw_str(canvas, 0, 20, password);
canvas_draw_icon(canvas, 0, 54, &I_ButtonLeft_4x7);
canvas_draw_str_aligned(canvas, 7, 54, AlignLeft, AlignTop, "Delete");
@@ -99,95 +518,106 @@ void flip_wifi_view_draw_callback_saved(Canvas* canvas, void* model) {
}
// Input callback for the view (async input handling)
-bool flip_wifi_view_input_callback_scan(InputEvent* event, void* context) {
- FlipWiFiApp* app = (FlipWiFiApp*)context;
- if(event->type == InputTypePress && event->key == InputKeyRight) {
+static bool flip_wifi_view_input_callback_scan(InputEvent *event, void *context)
+{
+ FlipWiFiApp *app = (FlipWiFiApp *)context;
+ if (event->type == InputTypePress && event->key == InputKeyRight)
+ {
// switch to text input to set password
- view_dispatcher_switch_to_view(app->view_dispatcher, FlipWiFiViewTextInputScan);
+ flip_wifi_free_text_inputs(app);
+ if (!flip_wifi_alloc_text_inputs(app, FlipWiFiViewTextInputScan))
+ {
+ FURI_LOG_E(TAG, "Failed to allocate text input for WiFi Saved Add Password");
+ return false;
+ }
+ view_dispatcher_switch_to_view(app->view_dispatcher, FlipWiFiViewTextInput);
return true;
}
return false;
}
// Input callback for the view (async input handling)
-bool flip_wifi_view_input_callback_saved(InputEvent* event, void* context) {
- FlipWiFiApp* app = (FlipWiFiApp*)context;
- if(!app) {
+static bool flip_wifi_view_input_callback_saved(InputEvent *event, void *context)
+{
+ FlipWiFiApp *app = (FlipWiFiApp *)context;
+ if (!app)
+ {
FURI_LOG_E(TAG, "FlipWiFiApp is NULL");
return false;
}
- if(event->type == InputTypePress && event->key == InputKeyRight) {
+ if (event->type == InputTypePress && event->key == InputKeyRight)
+ {
// set text input buffer as the selected password
- strncpy(
- app->uart_text_input_temp_buffer_password_saved,
- app->wifi_playlist.passwords[ssid_index],
- app->uart_text_input_buffer_size_password_saved);
+ strncpy(app->uart_text_input_temp_buffer, wifi_playlist->passwords[ssid_index], app->uart_text_input_buffer_size);
// switch to text input to set password
- view_dispatcher_switch_to_view(app->view_dispatcher, FlipWiFiViewTextInputSaved);
+ flip_wifi_free_text_inputs(app);
+ if (!flip_wifi_alloc_text_inputs(app, FlipWiFiViewTextInputSaved))
+ {
+ FURI_LOG_E(TAG, "Failed to allocate text input for WiFi Saved");
+ return false;
+ }
+ view_dispatcher_switch_to_view(app->view_dispatcher, FlipWiFiViewTextInput);
return true;
- } else if(event->type == InputTypePress && event->key == InputKeyOk) {
+ }
+ else if (event->type == InputTypePress && event->key == InputKeyOk)
+ {
// save the settings
- if(app->wifi_playlist.ssids[ssid_index] == NULL ||
- app->wifi_playlist.passwords[ssid_index] == NULL) {
+ save_settings(wifi_playlist->ssids[ssid_index], wifi_playlist->passwords[ssid_index]);
+
+ // initialize uart
+ if (!flipper_http_init(flipper_http_rx_callback, app))
+ {
+ easy_flipper_dialog("[ERROR]", "Failed to initialize flipper http");
return false;
}
- if(!app->popup) {
- if(!easy_flipper_set_popup(
- &app->popup,
- FlipWiFiViewPopup,
- "[SUCCESS]",
- 0,
- 0,
- "All FlipperHTTP apps will now\nuse the selected network.",
- 0,
- 40,
- popup_callback_saved,
- callback_to_submenu_saved,
- &app->view_dispatcher,
- app)) {
- return false;
- }
- }
- save_settings(
- app->wifi_playlist.ssids[ssid_index], app->wifi_playlist.passwords[ssid_index]);
- flipper_http_save_wifi(
- app->wifi_playlist.ssids[ssid_index], app->wifi_playlist.passwords[ssid_index]);
+ // clear response
+ if (fhttp.last_response)
+ snprintf(fhttp.last_response, RX_BUF_SIZE, "%s", "");
+
+ if (!flipper_http_save_wifi(wifi_playlist->ssids[ssid_index], wifi_playlist->passwords[ssid_index]))
+ {
+ easy_flipper_dialog("[ERROR]", "Failed to save WiFi settings");
+ return false;
+ }
- flipper_http_connect_wifi();
+ while (!fhttp.last_response || strlen(fhttp.last_response) == 0)
+ {
+ furi_delay_ms(100);
+ }
- popup_set_header(app->popup, "[SUCCESS]", 0, 0, AlignLeft, AlignTop);
- popup_set_text(
- app->popup,
- "All FlipperHTTP apps will now\nuse the selected network.",
- 0,
- 40,
- AlignLeft,
- AlignTop);
- view_set_previous_callback(popup_get_view(app->popup), callback_to_submenu_saved);
- popup_set_callback(app->popup, popup_callback_saved);
+ flipper_http_deinit();
- view_dispatcher_switch_to_view(app->view_dispatcher, FlipWiFiViewPopup);
+ easy_flipper_dialog("[SUCCESS]", "All FlipperHTTP apps will now\nuse the selected network.");
return true;
- } else if(event->type == InputTypePress && event->key == InputKeyLeft) {
- // delete the selected ssid and password
- free(app->wifi_playlist.ssids[ssid_index]);
- free(app->wifi_playlist.passwords[ssid_index]);
- free(ssid_list[ssid_index]);
+ }
+ else if (event->type == InputTypePress && event->key == InputKeyLeft)
+ {
// shift the remaining ssids and passwords
- for(uint32_t i = ssid_index; i < app->wifi_playlist.count - 1; i++) {
- app->wifi_playlist.ssids[i] = app->wifi_playlist.ssids[i + 1];
- app->wifi_playlist.passwords[i] = app->wifi_playlist.passwords[i + 1];
+ for (uint32_t i = ssid_index; i < wifi_playlist->count - 1; i++)
+ {
+ // Use strncpy to prevent buffer overflows and ensure null termination
+ strncpy(wifi_playlist->ssids[i], wifi_playlist->ssids[i + 1], MAX_SSID_LENGTH - 1);
+ wifi_playlist->ssids[i][MAX_SSID_LENGTH - 1] = '\0'; // Ensure null-termination
+
+ strncpy(wifi_playlist->passwords[i], wifi_playlist->passwords[i + 1], MAX_SSID_LENGTH - 1);
+ wifi_playlist->passwords[i][MAX_SSID_LENGTH - 1] = '\0'; // Ensure null-termination
+
+ // Shift ssid_list
ssid_list[i] = ssid_list[i + 1];
}
- app->wifi_playlist.count--;
+ wifi_playlist->count--;
+
+ // delete the last ssid and password
+ wifi_playlist->ssids[wifi_playlist->count][0] = '\0';
+ wifi_playlist->passwords[wifi_playlist->count][0] = '\0';
// save the playlist to storage
- save_playlist(&app->wifi_playlist);
+ save_playlist(wifi_playlist);
// re draw the saved submenu
flip_wifi_redraw_submenu_saved(app);
// switch back to the saved view
- view_dispatcher_switch_to_view(app->view_dispatcher, FlipWiFiViewSubmenuSaved);
+ view_dispatcher_switch_to_view(app->view_dispatcher, FlipWiFiViewSubmenu);
return true;
}
return false;
@@ -195,67 +625,70 @@ bool flip_wifi_view_input_callback_saved(InputEvent* event, void* context) {
// Function to trim leading and trailing whitespace
// Returns the trimmed start pointer and updates the length
-static char* trim_whitespace(char* start, size_t* length) {
+static char *trim_whitespace(char *start, size_t *length)
+{
// Trim leading whitespace
- while(*length > 0 && isspace((unsigned char)*start)) {
+ while (*length > 0 && isspace((unsigned char)*start))
+ {
start++;
(*length)--;
}
// Trim trailing whitespace
- while(*length > 0 && isspace((unsigned char)start[*length - 1])) {
+ while (*length > 0 && isspace((unsigned char)start[*length - 1]))
+ {
(*length)--;
}
return start;
}
-static bool flip_wifi_handle_scan() {
- if(!app_instance) {
+static bool flip_wifi_handle_scan(void *context)
+{
+ FlipWiFiApp *app = (FlipWiFiApp *)context;
+ if (!app)
+ {
FURI_LOG_E(TAG, "FlipWiFiApp is NULL");
return false;
}
// load the received data from the saved file
- FuriString* scan_data = flipper_http_load_from_file(fhttp.file_path);
- if(scan_data == NULL) {
+ FuriString *scan_data = flipper_http_load_from_file(fhttp.file_path);
+ if (scan_data == NULL)
+ {
FURI_LOG_E(TAG, "Failed to load received data from file.");
fhttp.state = ISSUE;
- return false;
- }
- char* data_cstr = (char*)furi_string_get_cstr(scan_data);
- if(data_cstr == NULL) {
- FURI_LOG_E(TAG, "Failed to get C-string from FuriString.");
- furi_string_free(scan_data);
- fhttp.state = ISSUE;
- free(data_cstr);
+ easy_flipper_dialog("[ERROR]", "Failed to load data from /apps_data/flip_wifi/data/scan.txt");
return false;
}
uint32_t ssid_count = 0;
- char* current_position = data_cstr;
- char* next_comma = NULL;
+ char *current_position = (char *)furi_string_get_cstr(scan_data);
+ char *next_comma = NULL;
// Manually split the string on commas
- while((next_comma = strchr(current_position, ',')) != NULL) {
+ while ((next_comma = strchr(current_position, ',')) != NULL)
+ {
// Calculate length of the SSID
size_t ssid_length = next_comma - current_position;
// Trim leading and trailing whitespace
size_t trimmed_length = ssid_length;
- char* trim_start = trim_whitespace(current_position, &trimmed_length);
+ char *trim_start = trim_whitespace(current_position, &trimmed_length);
// Handle empty SSIDs resulting from consecutive commas
- if(trimmed_length == 0) {
+ if (trimmed_length == 0)
+ {
current_position = next_comma + 1; // Move past the comma
continue;
}
// Allocate memory for the SSID and copy it
ssid_list[ssid_count] = malloc(trimmed_length + 1);
- if(ssid_list[ssid_count] == NULL) {
+ if (ssid_list[ssid_count] == NULL)
+ {
FURI_LOG_E(TAG, "Memory allocation failed");
- free(data_cstr);
+ easy_flipper_dialog("[ERROR]", "Memory allocation failed");
furi_string_free(scan_data);
return false;
}
@@ -263,7 +696,8 @@ static bool flip_wifi_handle_scan() {
ssid_list[ssid_count][trimmed_length] = '\0'; // Null-terminate the string
ssid_count++;
- if(ssid_count >= MAX_WIFI_NETWORKS) {
+ if (ssid_count >= MAX_SCAN_NETWORKS)
+ {
FURI_LOG_E(TAG, "Maximum SSID limit reached");
break;
}
@@ -272,18 +706,22 @@ static bool flip_wifi_handle_scan() {
}
// Handle the last SSID after the last comma (if any)
- if(*current_position != '\0' && ssid_count < MAX_WIFI_NETWORKS) {
+ if (*current_position != '\0' && ssid_count < MAX_SCAN_NETWORKS)
+ {
size_t ssid_length = strlen(current_position);
// Trim leading and trailing whitespace
size_t trimmed_length = ssid_length;
- char* trim_start = trim_whitespace(current_position, &trimmed_length);
+ char *trim_start = trim_whitespace(current_position, &trimmed_length);
// Handle empty SSIDs
- if(trimmed_length > 0) {
+ if (trimmed_length > 0)
+ {
ssid_list[ssid_count] = malloc(trimmed_length + 1);
- if(ssid_list[ssid_count] == NULL) {
+ if (ssid_list[ssid_count] == NULL)
+ {
FURI_LOG_E(TAG, "Memory allocation failed for the last SSID");
+ easy_flipper_dialog("[ERROR]", "Memory allocation failed for the last SSID");
return false;
}
strncpy(ssid_list[ssid_count], trim_start, trimmed_length);
@@ -293,214 +731,328 @@ static bool flip_wifi_handle_scan() {
}
// Add each SSID as a submenu item
- submenu_reset(app_instance->submenu_wifi_scan);
- submenu_set_header(app_instance->submenu_wifi_scan, "WiFi Nearby");
- for(uint32_t i = 0; i < ssid_count; i++) {
- char* ssid_item = ssid_list[i];
- if(ssid_item == NULL) {
+ submenu_reset(app->submenu_wifi);
+ submenu_set_header(app->submenu_wifi, "WiFi Nearby");
+ for (uint32_t i = 0; i < ssid_count; i++)
+ {
+ char *ssid_item = ssid_list[i];
+ if (ssid_item == NULL)
+ {
// skip any NULL entries
continue;
}
char ssid[64];
snprintf(ssid, sizeof(ssid), "%s", ssid_item);
- submenu_add_item(
- app_instance->submenu_wifi_scan,
- ssid,
- FlipWiFiSubmenuIndexWiFiScanStart + i,
- callback_submenu_choices,
- app_instance);
- }
- free(data_cstr);
+ submenu_add_item(app->submenu_wifi, ssid, FlipWiFiSubmenuIndexWiFiScanStart + i, callback_submenu_choices, app);
+ }
furi_string_free(scan_data);
return true;
}
-void callback_submenu_choices(void* context, uint32_t index) {
- FlipWiFiApp* app = (FlipWiFiApp*)context;
- if(!app) {
+void callback_submenu_choices(void *context, uint32_t index)
+{
+ FlipWiFiApp *app = (FlipWiFiApp *)context;
+ if (!app)
+ {
FURI_LOG_E(TAG, "FlipWiFiApp is NULL");
return;
}
- switch(index) {
+ switch (index)
+ {
case FlipWiFiSubmenuIndexWiFiScan:
- // Popup
- if(!app->popup) {
- if(!easy_flipper_set_popup(
- &app->popup,
- FlipWiFiViewPopup,
- "Success",
- 0,
- 0,
- "The WiFi setting has been set.",
- 0,
- 10,
- popup_callback_saved,
- callback_to_submenu_saved,
- &app->view_dispatcher,
- app)) {
- return;
- }
+ flip_wifi_free_all(app);
+ if (!flip_wifi_alloc_submenus(app, FlipWiFiViewSubmenuScan))
+ {
+ easy_flipper_dialog("[ERROR]", "Failed to allocate submenus for WiFi Scan");
+ return;
}
- popup_set_header(app->popup, "[ERROR]", 0, 0, AlignLeft, AlignTop);
- view_set_previous_callback(popup_get_view(app->popup), callback_to_submenu_main);
- popup_set_callback(app->popup, popup_callback_main);
-
- if(fhttp.state == INACTIVE) {
- popup_set_text(
- app->popup,
- "WiFi Devboard Disconnected.\nPlease reconnect the board.",
- 0,
- 40,
- AlignLeft,
- AlignTop);
- view_dispatcher_switch_to_view(app->view_dispatcher, FlipWiFiViewPopup);
+ // initialize uart
+ if (!flipper_http_init(flipper_http_rx_callback, app))
+ {
+ easy_flipper_dialog("[ERROR]", "Failed to initialize flipper http");
return;
}
-
- // update the text in case the loading task fails
- popup_set_text(
- app->popup,
- "Failed to scan...\nTry reconnecting the board!",
- 0,
- 40,
- AlignLeft,
- AlignTop);
-
+ bool _flip_wifi_handle_scan()
+ {
+ return flip_wifi_handle_scan(app);
+ }
+ Storage *storage = furi_record_open(RECORD_STORAGE);
+ // ensure flip_wifi directory is there
+ char directory_path[128];
+ snprintf(directory_path, sizeof(directory_path), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_wifi");
+ storage_common_mkdir(storage, directory_path);
+ // ensure directory is there for saving data
+ snprintf(directory_path, sizeof(directory_path), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_wifi/data");
+ storage_common_mkdir(storage, directory_path);
+ furi_record_close(RECORD_STORAGE);
// scan for wifi ad parse the results
- flipper_http_loading_task(
- flipper_http_scan_wifi,
- flip_wifi_handle_scan,
- FlipWiFiViewSubmenuScan,
- FlipWiFiViewPopup,
- &app->view_dispatcher);
+ flipper_http_loading_task(flipper_http_scan_wifi, _flip_wifi_handle_scan, FlipWiFiViewSubmenu, FlipWiFiViewSubmenuMain, &app->view_dispatcher);
+ flipper_http_deinit();
break;
case FlipWiFiSubmenuIndexWiFiSaved:
- view_dispatcher_switch_to_view(app->view_dispatcher, FlipWiFiViewSubmenuSaved);
+ flip_wifi_free_all(app);
+ if (!flip_wifi_alloc_submenus(app, FlipWiFiViewSubmenuSaved))
+ {
+ FURI_LOG_E(TAG, "Failed to allocate submenus for WiFi Saved");
+ return;
+ }
+ view_dispatcher_switch_to_view(app->view_dispatcher, FlipWiFiViewSubmenu);
break;
case FlipWiFiSubmenuIndexAbout:
+ flip_wifi_free_all(app);
+ if (!flip_wifi_alloc_widgets(app, FlipWiFiViewAbout))
+ {
+ FURI_LOG_E(TAG, "Failed to allocate widget for About");
+ return;
+ }
view_dispatcher_switch_to_view(app->view_dispatcher, FlipWiFiViewAbout);
break;
case FlipWiFiSubmenuIndexWiFiSavedAddSSID:
- view_dispatcher_switch_to_view(app->view_dispatcher, FlipWiFiViewTextInputSavedAddSSID);
+ flip_wifi_free_text_inputs(app);
+ if (!flip_wifi_alloc_text_inputs(app, FlipWiFiViewTextInputSavedAddSSID))
+ {
+ FURI_LOG_E(TAG, "Failed to allocate text input for WiFi Saved Add Password");
+ return;
+ }
+ view_dispatcher_switch_to_view(app->view_dispatcher, FlipWiFiViewTextInput);
break;
- case 100 ... 163:
+ case FlipWiFiSubmenuIndexCommands:
+ flip_wifi_free_all(app);
+ if (!flip_wifi_alloc_submenus(app, FlipWiFiViewSubmenuCommands))
+ {
+ FURI_LOG_E(TAG, "Failed to allocate submenus for Commands");
+ return;
+ }
+ view_dispatcher_switch_to_view(app->view_dispatcher, FlipWiFiViewSubmenu);
+ break;
+ case FlipWiFiSubmenuIndexFastCommandStart ... FlipWiFiSubmenuIndexFastCommandStart + 4:
+ // initialize uart
+ if (!flipper_http_init(flipper_http_rx_callback, app))
+ {
+ easy_flipper_dialog("[ERROR]", "Failed to initialize flipper http");
+ return;
+ }
+ // Handle fast commands
+ switch (index)
+ {
+ case FlipWiFiSubmenuIndexFastCommandStart + 0:
+ // CUSTOM - send to text input and return
+ flip_wifi_free_text_inputs(app);
+ if (!flip_wifi_alloc_text_inputs(app, FlipWiFiSubmenuIndexFastCommandStart))
+ {
+ FURI_LOG_E(TAG, "Failed to allocate text input for Fast Command");
+ return;
+ }
+ view_dispatcher_switch_to_view(app->view_dispatcher, FlipWiFiViewTextInput);
+ return;
+ case FlipWiFiSubmenuIndexFastCommandStart + 1:
+ // PING
+ flipper_http_ping();
+ break;
+ case FlipWiFiSubmenuIndexFastCommandStart + 2:
+ // LIST
+ flipper_http_list_commands();
+ break;
+ case FlipWiFiSubmenuIndexFastCommandStart + 3:
+ // IP/ADDRESS
+ flipper_http_ip_address();
+ break;
+ case FlipWiFiSubmenuIndexFastCommandStart + 4:
+ // WIFI/IP
+ flipper_http_ip_wifi();
+ break;
+ default:
+ break;
+ }
+ while (fhttp.last_response == NULL || strlen(fhttp.last_response) == 0)
+ {
+ // Wait for the response
+ furi_delay_ms(100);
+ }
+ if (fhttp.last_response != NULL)
+ {
+ char response[100];
+ snprintf(response, sizeof(response), "%s", fhttp.last_response);
+ easy_flipper_dialog("", response);
+ }
+ flipper_http_deinit();
+ break;
+ case 100 ... 199:
ssid_index = index - FlipWiFiSubmenuIndexWiFiScanStart;
- view_dispatcher_switch_to_view(app->view_dispatcher, FlipWiFiViewWiFiScan);
+ flip_wifi_free_views(app);
+ if (!flip_wifi_alloc_views(app, FlipWiFiViewWiFiScan))
+ {
+ FURI_LOG_E(TAG, "Failed to allocate views for WiFi Scan");
+ return;
+ }
+ view_dispatcher_switch_to_view(app->view_dispatcher, FlipWiFiViewGeneric);
break;
- case 200 ... 263:
+ case 200 ... 299:
ssid_index = index - FlipWiFiSubmenuIndexWiFiSavedStart;
- view_dispatcher_switch_to_view(app->view_dispatcher, FlipWiFiViewWiFiSaved);
+ flip_wifi_free_views(app);
+ snprintf(current_ssid, sizeof(current_ssid), "%s", wifi_playlist->ssids[ssid_index]);
+ snprintf(current_password, sizeof(current_password), "%s", wifi_playlist->passwords[ssid_index]);
+ if (!flip_wifi_alloc_views(app, FlipWiFiViewWiFiSaved))
+ {
+ FURI_LOG_E(TAG, "Failed to allocate views for WiFi Saved");
+ return;
+ }
+ view_dispatcher_switch_to_view(app->view_dispatcher, FlipWiFiViewGeneric);
break;
default:
break;
}
}
-void flip_wifi_text_updated_password_scan(void* context) {
- FlipWiFiApp* app = (FlipWiFiApp*)context;
- if(!app) {
+void flip_wifi_text_updated_password_scan(void *context)
+{
+ FlipWiFiApp *app = (FlipWiFiApp *)context;
+ if (!app)
+ {
FURI_LOG_E(TAG, "FlipWiFiApp is NULL");
return;
}
- // store the entered text
- strncpy(
- app->uart_text_input_buffer_password_scan,
- app->uart_text_input_temp_buffer_password_scan,
- app->uart_text_input_buffer_size_password_scan);
-
+ // Store the entered text with buffer size limit
+ strncpy(app->uart_text_input_buffer, app->uart_text_input_temp_buffer, app->uart_text_input_buffer_size - 1);
// Ensure null-termination
- app->uart_text_input_buffer_password_scan[app->uart_text_input_buffer_size_password_scan - 1] =
- '\0';
+ app->uart_text_input_buffer[app->uart_text_input_buffer_size - 1] = '\0';
- // add the SSID and password_scan to the playlist
- app->wifi_playlist.ssids[app->wifi_playlist.count] = strdup(ssid_list[ssid_index]);
- app->wifi_playlist.passwords[app->wifi_playlist.count] =
- strdup(app->uart_text_input_buffer_password_scan);
- app->wifi_playlist.count++;
+ if (!flip_wifi_alloc_playlist(app))
+ {
+ FURI_LOG_E(TAG, "Failed to allocate playlist");
+ return;
+ }
- // save the playlist to storage
- save_playlist(&app->wifi_playlist);
+ // Ensure ssid_index is valid
+ if (ssid_index >= MAX_SCAN_NETWORKS)
+ {
+ FURI_LOG_E(TAG, "Invalid ssid_index: %ld", ssid_index);
+ return;
+ }
+
+ // Check if there's space in the playlist
+ if (wifi_playlist->count >= MAX_SAVED_NETWORKS)
+ {
+ FURI_LOG_E(TAG, "Playlist is full. Cannot add more entries.");
+ return;
+ }
+ FURI_LOG_I(TAG, "Adding SSID: %s", ssid_list[ssid_index]);
+ FURI_LOG_I(TAG, "Count: %d", wifi_playlist->count);
+ // Add the SSID and password to the playlist
+ snprintf(wifi_playlist->ssids[wifi_playlist->count], MAX_SSID_LENGTH, "%s", ssid_list[ssid_index]);
+ snprintf(wifi_playlist->passwords[wifi_playlist->count], MAX_SSID_LENGTH, "%s", app->uart_text_input_buffer);
+ wifi_playlist->count++;
+ // Save the updated playlist to storage
+ save_playlist(wifi_playlist);
+
+ // Redraw the submenu to reflect changes
flip_wifi_redraw_submenu_saved(app);
- // switch to back to the scan view
- view_dispatcher_switch_to_view(app->view_dispatcher, FlipWiFiViewSubmenuScan);
+ // Switch back to the scan view
+ view_dispatcher_switch_to_view(app->view_dispatcher, FlipWiFiViewSubmenu);
}
-void flip_wifi_text_updated_password_saved(void* context) {
- FlipWiFiApp* app = (FlipWiFiApp*)context;
- if(!app) {
+
+void flip_wifi_text_updated_password_saved(void *context)
+{
+ FlipWiFiApp *app = (FlipWiFiApp *)context;
+ if (!app)
+ {
FURI_LOG_E(TAG, "FlipWiFiApp is NULL");
return;
}
// store the entered text
- strncpy(
- app->uart_text_input_buffer_password_saved,
- app->uart_text_input_temp_buffer_password_saved,
- app->uart_text_input_buffer_size_password_saved);
+ strncpy(app->uart_text_input_buffer, app->uart_text_input_temp_buffer, app->uart_text_input_buffer_size);
// Ensure null-termination
- app->uart_text_input_buffer_password_saved[app->uart_text_input_buffer_size_password_saved - 1] =
- '\0';
+ app->uart_text_input_buffer[app->uart_text_input_buffer_size - 1] = '\0';
// update the password_saved in the playlist
- app->wifi_playlist.passwords[ssid_index] = strdup(app->uart_text_input_buffer_password_saved);
+ snprintf(wifi_playlist->passwords[ssid_index], MAX_SSID_LENGTH, app->uart_text_input_buffer);
// save the playlist to storage
- save_playlist(&app->wifi_playlist);
+ save_playlist(wifi_playlist);
// switch to back to the saved view
- view_dispatcher_switch_to_view(app->view_dispatcher, FlipWiFiViewSubmenuSaved);
+ view_dispatcher_switch_to_view(app->view_dispatcher, FlipWiFiViewSubmenu);
}
-void flip_wifi_text_updated_add_ssid(void* context) {
- FlipWiFiApp* app = (FlipWiFiApp*)context;
- if(!app) {
+void flip_wifi_text_updated_add_ssid(void *context)
+{
+ FlipWiFiApp *app = (FlipWiFiApp *)context;
+ if (!app)
+ {
FURI_LOG_E(TAG, "FlipWiFiApp is NULL");
return;
}
+ // check if empty
+ if (strlen(app->uart_text_input_temp_buffer) == 0)
+ {
+ easy_flipper_dialog("[ERROR]", "SSID cannot be empty");
+ return;
+ }
+
// store the entered text
- strncpy(
- app->uart_text_input_buffer_add_ssid,
- app->uart_text_input_temp_buffer_add_ssid,
- app->uart_text_input_buffer_size_add_ssid);
+ strncpy(app->uart_text_input_buffer, app->uart_text_input_temp_buffer, app->uart_text_input_buffer_size);
// Ensure null-termination
- app->uart_text_input_buffer_add_ssid[app->uart_text_input_buffer_size_add_ssid - 1] = '\0';
-
- // do nothing for now, go to the next text input to set the password
- view_dispatcher_switch_to_view(app->view_dispatcher, FlipWiFiViewTextInputSavedAddPassword);
+ app->uart_text_input_buffer[app->uart_text_input_buffer_size - 1] = '\0';
+ save_char("wifi-ssid", app->uart_text_input_buffer);
+ view_dispatcher_switch_to_view(app->view_dispatcher, FlipWiFiViewSubmenuMain);
+ text_input_reset(app->uart_text_input);
+ text_input_set_header_text(app->uart_text_input, "Enter Password");
+ app->uart_text_input_buffer_size = MAX_SSID_LENGTH;
+ free(app->uart_text_input_buffer);
+ free(app->uart_text_input_temp_buffer);
+ easy_flipper_set_buffer(&app->uart_text_input_buffer, app->uart_text_input_buffer_size);
+ easy_flipper_set_buffer(&app->uart_text_input_temp_buffer, app->uart_text_input_buffer_size);
+ text_input_set_result_callback(app->uart_text_input, flip_wifi_text_updated_add_password, app, app->uart_text_input_temp_buffer, app->uart_text_input_buffer_size, false);
+ view_dispatcher_switch_to_view(app->view_dispatcher, FlipWiFiViewTextInput);
}
-void flip_wifi_text_updated_add_password(void* context) {
- FlipWiFiApp* app = (FlipWiFiApp*)context;
- if(!app) {
+void flip_wifi_text_updated_add_password(void *context)
+{
+ FlipWiFiApp *app = (FlipWiFiApp *)context;
+ if (!app)
+ {
FURI_LOG_E(TAG, "FlipWiFiApp is NULL");
return;
}
- // store the entered text
- strncpy(
- app->uart_text_input_buffer_add_password,
- app->uart_text_input_temp_buffer_add_password,
- app->uart_text_input_buffer_size_add_password);
+ // check if empty
+ if (strlen(app->uart_text_input_temp_buffer) == 0)
+ {
+ easy_flipper_dialog("[ERROR]", "Password cannot be empty");
+ return;
+ }
+ // store the entered text
+ strncpy(app->uart_text_input_buffer, app->uart_text_input_temp_buffer, app->uart_text_input_buffer_size);
// Ensure null-termination
- app->uart_text_input_buffer_add_password[app->uart_text_input_buffer_size_add_password - 1] =
- '\0';
+ app->uart_text_input_buffer[app->uart_text_input_buffer_size - 1] = '\0';
+
+ view_dispatcher_switch_to_view(app->view_dispatcher, FlipWiFiViewSubmenuMain);
+
+ save_char("wifi-password", app->uart_text_input_buffer);
+
+ char wifi_ssid[64];
+ if (!load_char("wifi-ssid", wifi_ssid, sizeof(wifi_ssid)))
+ {
+ FURI_LOG_E(TAG, "Failed to load wifi ssid");
+ return;
+ }
// add the SSID and password_scan to the playlist
- app->wifi_playlist.ssids[app->wifi_playlist.count] =
- strdup(app->uart_text_input_buffer_add_ssid);
- app->wifi_playlist.passwords[app->wifi_playlist.count] =
- strdup(app->uart_text_input_buffer_add_password);
- app->wifi_playlist.count++;
+ snprintf(wifi_playlist->ssids[wifi_playlist->count], MAX_SSID_LENGTH, wifi_ssid);
+ snprintf(wifi_playlist->passwords[wifi_playlist->count], MAX_SSID_LENGTH, app->uart_text_input_buffer);
+ wifi_playlist->count++;
// save the playlist to storage
- save_playlist(&app->wifi_playlist);
+ save_playlist(wifi_playlist);
flip_wifi_redraw_submenu_saved(app);
// switch to back to the saved view
- view_dispatcher_switch_to_view(app->view_dispatcher, FlipWiFiViewSubmenuSaved);
-}
+ view_dispatcher_switch_to_view(app->view_dispatcher, FlipWiFiViewSubmenu);
+}
\ No newline at end of file
diff --git a/flip_wifi/callback/flip_wifi_callback.h b/flip_wifi/callback/flip_wifi_callback.h
index 39c266223..2e14a2095 100644
--- a/flip_wifi/callback/flip_wifi_callback.h
+++ b/flip_wifi/callback/flip_wifi_callback.h
@@ -1,41 +1,8 @@
-#ifndef FLIP_WIFI_CALLBACK_H
-#define FLIP_WIFI_CALLBACK_H
-
+#pragma once
#include
#include
#include
-// array to store each SSID
-extern char* ssid_list[64];
-extern uint32_t ssid_index;
-
-void flip_wifi_redraw_submenu_saved(FlipWiFiApp* app);
-
-uint32_t callback_to_submenu_main(void* context);
-
-uint32_t callback_to_submenu_scan(void* context);
-
-uint32_t callback_to_submenu_saved(void* context);
-
-// Callback for drawing the main screen
-void flip_wifi_view_draw_callback_scan(Canvas* canvas, void* model);
-
-void flip_wifi_view_draw_callback_saved(Canvas* canvas, void* model);
-
-// Input callback for the view (async input handling)
-bool flip_wifi_view_input_callback_scan(InputEvent* event, void* context);
-
-// Input callback for the view (async input handling)
-bool flip_wifi_view_input_callback_saved(InputEvent* event, void* context);
-
-void callback_submenu_choices(void* context, uint32_t index);
-
-void flip_wifi_text_updated_password_scan(void* context);
-
-void flip_wifi_text_updated_password_saved(void* context);
-
-void flip_wifi_text_updated_add_ssid(void* context);
-
-void flip_wifi_text_updated_add_password(void* context);
-
-#endif // FLIP_WIFI_CALLBACK_H
+void flip_wifi_free_all(void *context);
+uint32_t callback_exit_app(void *context);
+void callback_submenu_choices(void *context, uint32_t index);
\ No newline at end of file
diff --git a/flip_wifi/easy_flipper/easy_flipper.c b/flip_wifi/easy_flipper/easy_flipper.c
index 8b98e1a1b..c2ce949e4 100644
--- a/flip_wifi/easy_flipper/easy_flipper.c
+++ b/flip_wifi/easy_flipper/easy_flipper.c
@@ -1,13 +1,35 @@
#include
+void easy_flipper_dialog(
+ char *header,
+ char *text)
+{
+ DialogsApp *dialogs = furi_record_open(RECORD_DIALOGS);
+ DialogMessage *message = dialog_message_alloc();
+ dialog_message_set_header(
+ message, header, 64, 0, AlignCenter, AlignTop);
+ dialog_message_set_text(
+ message,
+ text,
+ 0,
+ 63,
+ AlignLeft,
+ AlignBottom);
+ dialog_message_show(dialogs, message);
+ dialog_message_free(message);
+ furi_record_close(RECORD_DIALOGS);
+}
+
/**
* @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) {
+uint32_t easy_flipper_callback_exit_app(void *context)
+{
// Exit the application
- if(!context) {
+ if (!context)
+ {
FURI_LOG_E(EASY_TAG, "Context is NULL");
return VIEW_NONE;
}
@@ -21,13 +43,16 @@ uint32_t easy_flipper_callback_exit_app(void* context) {
* @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) {
+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) {
+ *buffer = (char *)malloc(buffer_size);
+ if (!*buffer)
+ {
FURI_LOG_E(EASY_TAG, "Failed to allocate buffer");
return false;
}
@@ -46,32 +71,39 @@ bool easy_flipper_set_buffer(char** buffer, uint32_t buffer_size) {
* @return true if successful, false otherwise
*/
bool easy_flipper_set_view(
- View** 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) {
+ 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) {
+ if (!*view)
+ {
FURI_LOG_E(EASY_TAG, "Failed to allocate View");
return false;
}
- if(draw_callback) {
+ if (draw_callback)
+ {
view_set_draw_callback(*view, draw_callback);
}
- if(input_callback) {
+ if (input_callback)
+ {
view_set_input_callback(*view, input_callback);
}
- if(context) {
+ if (context)
+ {
view_set_context(*view, context);
}
- if(previous_callback) {
+ if (previous_callback)
+ {
view_set_previous_callback(*view, previous_callback);
}
view_dispatcher_add_view(*view_dispatcher, view_id, *view);
@@ -85,18 +117,22 @@ bool easy_flipper_set_view(
* @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) {
+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) {
+ 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) {
+ if (context)
+ {
view_dispatcher_set_event_callback_context(*view_dispatcher, context);
}
return true;
@@ -113,24 +149,29 @@ bool easy_flipper_set_view_dispatcher(ViewDispatcher** view_dispatcher, Gui* gui
* @return true if successful, false otherwise
*/
bool easy_flipper_set_submenu(
- Submenu** submenu,
+ Submenu **submenu,
int32_t view_id,
- char* title,
- uint32_t(previous_callback)(void*),
- ViewDispatcher** view_dispatcher) {
- if(!submenu) {
+ 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) {
+ if (!*submenu)
+ {
FURI_LOG_E(EASY_TAG, "Failed to allocate Submenu");
return false;
}
- if(title) {
+ if (title)
+ {
submenu_set_header(*submenu, title);
}
- if(previous_callback) {
+ 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));
@@ -147,20 +188,24 @@ bool easy_flipper_set_submenu(
* @return true if successful, false otherwise
*/
bool easy_flipper_set_menu(
- Menu** menu,
+ Menu **menu,
int32_t view_id,
- uint32_t(previous_callback)(void*),
- ViewDispatcher** view_dispatcher) {
- if(!menu) {
+ 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) {
+ if (!*menu)
+ {
FURI_LOG_E(EASY_TAG, "Failed to allocate Menu");
return false;
}
- if(previous_callback) {
+ 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));
@@ -177,24 +222,29 @@ bool easy_flipper_set_menu(
* @return true if successful, false otherwise
*/
bool easy_flipper_set_widget(
- Widget** widget,
+ Widget **widget,
int32_t view_id,
- char* text,
- uint32_t(previous_callback)(void*),
- ViewDispatcher** view_dispatcher) {
- if(!widget) {
+ 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) {
+ if (!*widget)
+ {
FURI_LOG_E(EASY_TAG, "Failed to allocate Widget");
return false;
}
- if(text) {
+ if (text)
+ {
widget_add_text_scroll_element(*widget, 0, 0, 128, 64, text);
}
- if(previous_callback) {
+ 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));
@@ -213,30 +263,33 @@ bool easy_flipper_set_widget(
* @return true if successful, false otherwise
*/
bool easy_flipper_set_variable_item_list(
- VariableItemList** 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) {
+ 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) {
+ if (!*variable_item_list)
+ {
FURI_LOG_E(EASY_TAG, "Failed to allocate VariableItemList");
return false;
}
- if(enter_callback) {
+ 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);
+ 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));
+ view_dispatcher_add_view(*view_dispatcher, view_id, variable_item_list_get_view(*variable_item_list));
return true;
}
@@ -249,38 +302,38 @@ bool easy_flipper_set_variable_item_list(
* @return true if successful, false otherwise
*/
bool easy_flipper_set_text_input(
- TextInput** text_input,
+ TextInput **text_input,
int32_t view_id,
- char* header_text,
- char* text_input_temp_buffer,
+ 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) {
+ 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) {
+ if (!*text_input)
+ {
FURI_LOG_E(EASY_TAG, "Failed to allocate TextInput");
return false;
}
- if(previous_callback) {
+ if (previous_callback)
+ {
view_set_previous_callback(text_input_get_view(*text_input), previous_callback);
}
- if(header_text) {
+ 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);
+ 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;
@@ -295,38 +348,38 @@ bool easy_flipper_set_text_input(
* @return true if successful, false otherwise
*/
bool easy_flipper_set_uart_text_input(
- TextInput** uart_text_input,
+ TextInput **uart_text_input,
int32_t view_id,
- char* header_text,
- char* uart_text_input_temp_buffer,
+ 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) {
+ 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 = text_input_alloc();
- if(!*uart_text_input) {
+ if (!*uart_text_input)
+ {
FURI_LOG_E(EASY_TAG, "Failed to allocate UART_TextInput");
return false;
}
- if(previous_callback) {
+ if (previous_callback)
+ {
view_set_previous_callback(text_input_get_view(*uart_text_input), previous_callback);
}
- if(header_text) {
+ if (header_text)
+ {
text_input_set_header_text(*uart_text_input, header_text);
}
- if(uart_text_input_temp_buffer && uart_text_input_buffer_size && result_callback) {
- text_input_set_result_callback(
- *uart_text_input,
- result_callback,
- context,
- uart_text_input_temp_buffer,
- uart_text_input_buffer_size,
- false);
+ if (uart_text_input_temp_buffer && uart_text_input_buffer_size && result_callback)
+ {
+ text_input_set_result_callback(*uart_text_input, result_callback, context, uart_text_input_temp_buffer, uart_text_input_buffer_size, false);
}
text_input_show_illegal_symbols(*uart_text_input, true);
view_dispatcher_add_view(*view_dispatcher, view_id, text_input_get_view(*uart_text_input));
@@ -353,52 +406,63 @@ bool easy_flipper_set_uart_text_input(
* @return true if successful, false otherwise
*/
bool easy_flipper_set_dialog_ex(
- DialogEx** dialog_ex,
+ DialogEx **dialog_ex,
int32_t view_id,
- char* header,
+ char *header,
uint16_t header_x,
uint16_t header_y,
- char* text,
+ 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) {
+ 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) {
+ if (!*dialog_ex)
+ {
FURI_LOG_E(EASY_TAG, "Failed to allocate DialogEx");
return false;
}
- if(header) {
+ if (header)
+ {
dialog_ex_set_header(*dialog_ex, header, header_x, header_y, AlignLeft, AlignTop);
}
- if(text) {
+ if (text)
+ {
dialog_ex_set_text(*dialog_ex, text, text_x, text_y, AlignLeft, AlignTop);
}
- if(left_button_text) {
+ if (left_button_text)
+ {
dialog_ex_set_left_button_text(*dialog_ex, left_button_text);
}
- if(right_button_text) {
+ if (right_button_text)
+ {
dialog_ex_set_right_button_text(*dialog_ex, right_button_text);
}
- if(center_button_text) {
+ if (center_button_text)
+ {
dialog_ex_set_center_button_text(*dialog_ex, center_button_text);
}
- if(result_callback) {
+ if (result_callback)
+ {
dialog_ex_set_result_callback(*dialog_ex, result_callback);
}
- if(previous_callback) {
+ if (previous_callback)
+ {
view_set_previous_callback(dialog_ex_get_view(*dialog_ex), previous_callback);
}
- if(context) {
+ if (context)
+ {
dialog_ex_set_context(*dialog_ex, context);
}
view_dispatcher_add_view(*view_dispatcher, view_id, dialog_ex_get_view(*dialog_ex));
@@ -422,40 +486,48 @@ bool easy_flipper_set_dialog_ex(
* @return true if successful, false otherwise
*/
bool easy_flipper_set_popup(
- Popup** popup,
+ Popup **popup,
int32_t view_id,
- char* header,
+ char *header,
uint16_t header_x,
uint16_t header_y,
- char* text,
+ 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) {
+ 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) {
+ if (!*popup)
+ {
FURI_LOG_E(EASY_TAG, "Failed to allocate Popup");
return false;
}
- if(header) {
+ if (header)
+ {
popup_set_header(*popup, header, header_x, header_y, AlignLeft, AlignTop);
}
- if(text) {
+ if (text)
+ {
popup_set_text(*popup, text, text_x, text_y, AlignLeft, AlignTop);
}
- if(result_callback) {
+ if (result_callback)
+ {
popup_set_callback(*popup, result_callback);
}
- if(previous_callback) {
+ if (previous_callback)
+ {
view_set_previous_callback(popup_get_view(*popup), previous_callback);
}
- if(context) {
+ if (context)
+ {
popup_set_context(*popup, context);
}
view_dispatcher_add_view(*view_dispatcher, view_id, popup_get_view(*popup));
@@ -471,20 +543,24 @@ bool easy_flipper_set_popup(
* @return true if successful, false otherwise
*/
bool easy_flipper_set_loading(
- Loading** loading,
+ Loading **loading,
int32_t view_id,
- uint32_t(previous_callback)(void*),
- ViewDispatcher** view_dispatcher) {
- if(!loading) {
+ 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) {
+ if (!*loading)
+ {
FURI_LOG_E(EASY_TAG, "Failed to allocate Loading");
return false;
}
- if(previous_callback) {
+ 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));
@@ -497,16 +573,19 @@ bool easy_flipper_set_loading(
* @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) {
+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) {
+ 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/flip_wifi/easy_flipper/easy_flipper.h b/flip_wifi/easy_flipper/easy_flipper.h
index 219e42c74..8c71106c7 100644
--- a/flip_wifi/easy_flipper/easy_flipper.h
+++ b/flip_wifi/easy_flipper/easy_flipper.h
@@ -8,6 +8,7 @@
#include
#include
#include
+#include
#include
#include
#include
@@ -22,22 +23,27 @@
#include
#include
#include
+#include
#define EASY_TAG "EasyFlipper"
+void easy_flipper_dialog(
+ char *header,
+ char *text);
+
/**
* @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);
+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);
+bool easy_flipper_set_buffer(char **buffer, uint32_t buffer_size);
/**
* @brief Initialize a View object
* @param view The View object to initialize
@@ -49,13 +55,13 @@ bool easy_flipper_set_buffer(char** buffer, uint32_t buffer_size);
* @return true if successful, false otherwise
*/
bool easy_flipper_set_view(
- View** 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);
+ 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
@@ -64,7 +70,7 @@ bool easy_flipper_set_view(
* @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);
+bool easy_flipper_set_view_dispatcher(ViewDispatcher **view_dispatcher, Gui *gui, void *context);
/**
* @brief Initialize a Submenu object
@@ -77,11 +83,11 @@ bool easy_flipper_set_view_dispatcher(ViewDispatcher** view_dispatcher, Gui* gui
* @return true if successful, false otherwise
*/
bool easy_flipper_set_submenu(
- Submenu** submenu,
+ Submenu **submenu,
int32_t view_id,
- char* title,
- uint32_t(previous_callback)(void*),
- ViewDispatcher** view_dispatcher);
+ char *title,
+ uint32_t(previous_callback)(void *),
+ ViewDispatcher **view_dispatcher);
/**
* @brief Initialize a Menu object
@@ -94,10 +100,10 @@ bool easy_flipper_set_submenu(
* @return true if successful, false otherwise
*/
bool easy_flipper_set_menu(
- Menu** menu,
+ Menu **menu,
int32_t view_id,
- uint32_t(previous_callback)(void*),
- ViewDispatcher** view_dispatcher);
+ uint32_t(previous_callback)(void *),
+ ViewDispatcher **view_dispatcher);
/**
* @brief Initialize a Widget object
@@ -109,11 +115,11 @@ bool easy_flipper_set_menu(
* @return true if successful, false otherwise
*/
bool easy_flipper_set_widget(
- Widget** widget,
+ Widget **widget,
int32_t view_id,
- char* text,
- uint32_t(previous_callback)(void*),
- ViewDispatcher** view_dispatcher);
+ char *text,
+ uint32_t(previous_callback)(void *),
+ ViewDispatcher **view_dispatcher);
/**
* @brief Initialize a VariableItemList object
@@ -127,12 +133,12 @@ bool easy_flipper_set_widget(
* @return true if successful, false otherwise
*/
bool easy_flipper_set_variable_item_list(
- VariableItemList** 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);
+ void (*enter_callback)(void *, uint32_t),
+ uint32_t(previous_callback)(void *),
+ ViewDispatcher **view_dispatcher,
+ void *context);
/**
* @brief Initialize a TextInput object
@@ -143,15 +149,15 @@ bool easy_flipper_set_variable_item_list(
* @return true if successful, false otherwise
*/
bool easy_flipper_set_text_input(
- TextInput** text_input,
+ TextInput **text_input,
int32_t view_id,
- char* header_text,
- char* text_input_temp_buffer,
+ 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);
+ void (*result_callback)(void *),
+ uint32_t(previous_callback)(void *),
+ ViewDispatcher **view_dispatcher,
+ void *context);
/**
* @brief Initialize a TextInput object with extra symbols
@@ -162,15 +168,15 @@ bool easy_flipper_set_text_input(
* @return true if successful, false otherwise
*/
bool easy_flipper_set_uart_text_input(
- TextInput** uart_text_input,
+ TextInput **uart_text_input,
int32_t view_id,
- char* header_text,
- char* uart_text_input_temp_buffer,
+ 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);
+ void (*result_callback)(void *),
+ uint32_t(previous_callback)(void *),
+ ViewDispatcher **view_dispatcher,
+ void *context);
/**
* @brief Initialize a DialogEx object
@@ -192,21 +198,21 @@ bool easy_flipper_set_uart_text_input(
* @return true if successful, false otherwise
*/
bool easy_flipper_set_dialog_ex(
- DialogEx** dialog_ex,
+ DialogEx **dialog_ex,
int32_t view_id,
- char* header,
+ char *header,
uint16_t header_x,
uint16_t header_y,
- char* text,
+ 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);
+ 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
@@ -225,18 +231,18 @@ bool easy_flipper_set_dialog_ex(
* @return true if successful, false otherwise
*/
bool easy_flipper_set_popup(
- Popup** popup,
+ Popup **popup,
int32_t view_id,
- char* header,
+ char *header,
uint16_t header_x,
uint16_t header_y,
- char* text,
+ char *text,
uint16_t text_x,
uint16_t text_y,
- void (*result_callback)(void*),
- uint32_t(previous_callback)(void*),
- ViewDispatcher** view_dispatcher,
- void* context);
+ void (*result_callback)(void *),
+ uint32_t(previous_callback)(void *),
+ ViewDispatcher **view_dispatcher,
+ void *context);
/**
* @brief Initialize a Loading object
@@ -247,10 +253,10 @@ bool easy_flipper_set_popup(
* @return true if successful, false otherwise
*/
bool easy_flipper_set_loading(
- Loading** loading,
+ Loading **loading,
int32_t view_id,
- uint32_t(previous_callback)(void*),
- ViewDispatcher** view_dispatcher);
+ uint32_t(previous_callback)(void *),
+ ViewDispatcher **view_dispatcher);
/**
* @brief Set a char butter to a FuriString
@@ -258,6 +264,6 @@ bool easy_flipper_set_loading(
* @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);
+bool easy_flipper_set_char_to_furi_string(FuriString **furi_string, char *buffer);
-#endif
+#endif
\ No newline at end of file
diff --git a/flip_wifi/flip_storage/flip_wifi_storage.c b/flip_wifi/flip_storage/flip_wifi_storage.c
index 0622b1210..f287bdc63 100644
--- a/flip_wifi/flip_storage/flip_wifi_storage.c
+++ b/flip_wifi/flip_storage/flip_wifi_storage.c
@@ -1,6 +1,6 @@
#include
-char* app_ids[8] = {
+static char *app_ids[8] = {
"flip_wifi",
"flip_store",
"flip_social",
@@ -8,216 +8,140 @@ char* app_ids[8] = {
"flip_weather",
"flip_library",
"web_crawler",
- "flip_rss"};
+ "flip_world"};
// Function to save the playlist
-void save_playlist(WiFiPlaylist* playlist) {
- if(!playlist) {
+void save_playlist(WiFiPlaylist *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_wifi");
+ snprintf(directory_path, sizeof(directory_path), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_wifi");
// Open storage
- Storage* storage = furi_record_open(RECORD_STORAGE);
- if(!storage) {
+ Storage *storage = furi_record_open(RECORD_STORAGE);
+ if (!storage)
+ {
FURI_LOG_E(TAG, "Failed to open storage record");
return;
}
// Create the directory
storage_common_mkdir(storage, directory_path);
+ snprintf(directory_path, sizeof(directory_path), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_wifi/data");
+ storage_common_mkdir(storage, directory_path);
// Open the settings file
- File* file = storage_file_alloc(storage);
- if(!file) {
+ File *file = storage_file_alloc(storage);
+ if (!file)
+ {
FURI_LOG_E(TAG, "Failed to allocate file handle");
furi_record_close(RECORD_STORAGE);
return;
}
- if(!storage_file_open(file, WIFI_SSID_LIST_PATH, FSAM_WRITE, FSOM_CREATE_ALWAYS)) {
+ if (!storage_file_open(file, WIFI_SSID_LIST_PATH, FSAM_WRITE, FSOM_CREATE_ALWAYS))
+ {
FURI_LOG_E(TAG, "Failed to open settings file for writing: %s", WIFI_SSID_LIST_PATH);
storage_file_free(file);
furi_record_close(RECORD_STORAGE);
return;
}
- for(size_t i = 0; i < playlist->count; i++) {
- if(!playlist->ssids[i] || !playlist->passwords[i]) {
- FURI_LOG_E(TAG, "Invalid SSID or password at index %zu", i);
- continue;
- }
- size_t ssid_length = strlen(playlist->ssids[i]);
- size_t password_length = strlen(playlist->passwords[i]);
- if(storage_file_write(file, playlist->ssids[i], ssid_length) != ssid_length ||
- storage_file_write(file, ",", 1) != 1 ||
- storage_file_write(file, playlist->passwords[i], password_length) != password_length ||
- storage_file_write(file, "\n", 1) != 1) {
- FURI_LOG_E(TAG, "Failed to write playlist");
+ FuriString *json_result = furi_string_alloc();
+ if (!json_result)
+ {
+ FURI_LOG_E(TAG, "Failed to allocate FuriString");
+ storage_file_close(file);
+ storage_file_free(file);
+ furi_record_close(RECORD_STORAGE);
+ return;
+ }
+ furi_string_cat(json_result, "{\"ssids\":[\n");
+ for (size_t i = 0; i < playlist->count; i++)
+ {
+ furi_string_cat_printf(json_result, "{\"ssid\":\"%s\",\"password\":\"%s\"}", playlist->ssids[i], playlist->passwords[i]);
+ if (i < playlist->count - 1)
+ {
+ furi_string_cat(json_result, ",\n");
}
}
-
+ furi_string_cat(json_result, "\n]}");
+ size_t json_length = furi_string_size(json_result);
+ if (storage_file_write(file, furi_string_get_cstr(json_result), json_length) != json_length)
+ {
+ FURI_LOG_E(TAG, "Failed to write playlist to file");
+ }
+ furi_string_free(json_result);
storage_file_close(file);
storage_file_free(file);
furi_record_close(RECORD_STORAGE);
}
-// Function to load the playlist
-bool load_playlist(WiFiPlaylist* playlist) {
- if(!playlist) {
+bool load_playlist(WiFiPlaylist *playlist)
+{
+ if (!playlist)
+ {
FURI_LOG_E(TAG, "Playlist is NULL");
return false;
}
- // Initialize playlist count
- playlist->count = 0;
-
- // Allocate memory for SSIDs and passwords if not already allocated
- for(size_t i = 0; i < MAX_WIFI_NETWORKS; i++) {
- if(!playlist->ssids[i]) {
- playlist->ssids[i] = malloc(64); // Adjust size as needed
- if(!playlist->ssids[i]) {
- FURI_LOG_E(TAG, "Memory allocation failed for ssids[%zu]", i);
- // Handle memory allocation failure (e.g., clean up and return)
- return false;
- }
- }
-
- if(!playlist->passwords[i]) {
- playlist->passwords[i] = malloc(64); // Adjust size as needed
- if(!playlist->passwords[i]) {
- FURI_LOG_E(TAG, "Memory allocation failed for passwords[%zu]", i);
- // Handle memory allocation failure (e.g., clean up and return)
- return false;
- }
- }
- }
-
- // Open the settings file
- Storage* storage = furi_record_open(RECORD_STORAGE);
- if(!storage) {
- FURI_LOG_E(TAG, "Failed to open storage record");
- return false;
- }
-
- File* file = storage_file_alloc(storage);
- if(!file) {
- FURI_LOG_E(TAG, "Failed to allocate file handle");
- furi_record_close(RECORD_STORAGE);
+ FuriString *json_result = flipper_http_load_from_file(WIFI_SSID_LIST_PATH);
+ if (!json_result)
+ {
+ FURI_LOG_E(TAG, "Failed to load playlist from file");
return false;
}
- if(!storage_file_open(file, WIFI_SSID_LIST_PATH, FSAM_READ, FSOM_OPEN_EXISTING)) {
- FURI_LOG_E(TAG, "Failed to open settings file for reading: %s", WIFI_SSID_LIST_PATH);
- storage_file_free(file);
- furi_record_close(RECORD_STORAGE);
- return false; // Return false if the file does not exist
- }
-
- // Buffer to hold each line
- char line_buffer[128];
- size_t line_pos = 0;
- char ch;
-
- while(storage_file_read(file, &ch, 1) == 1) {
- if(ch == '\n') {
- // Null-terminate the line
- line_buffer[line_pos] = '\0';
-
- // Split the line into SSID and Password
- char* comma_pos = strchr(line_buffer, ',');
- if(comma_pos) {
- *comma_pos = '\0'; // Replace comma with null character
-
- // Copy SSID
- strncpy(playlist->ssids[playlist->count], line_buffer, 63);
- playlist->ssids[playlist->count][63] = '\0'; // Ensure null-termination
-
- // Copy Password
- strncpy(playlist->passwords[playlist->count], comma_pos + 1, 63);
- playlist->passwords[playlist->count][63] = '\0'; // Ensure null-termination
-
- playlist->count++;
-
- if(playlist->count >= MAX_WIFI_NETWORKS) {
- FURI_LOG_W(
- TAG, "Reached maximum number of WiFi networks: %d", MAX_WIFI_NETWORKS);
- break;
- }
- } else {
- FURI_LOG_E(TAG, "Invalid line format (no comma found): %s", line_buffer);
- }
+ // Initialize playlist count
+ playlist->count = 0;
- // Reset line buffer position for the next line
- line_pos = 0;
- } else {
- if(line_pos < sizeof(line_buffer) - 1) {
- line_buffer[line_pos++] = ch;
- } else {
- FURI_LOG_E(TAG, "Line buffer overflow");
- // Optionally handle line overflow (e.g., skip the rest of the line)
- line_pos = 0;
- }
+ // Parse the JSON result
+ for (size_t i = 0; i < MAX_SAVED_NETWORKS; i++)
+ {
+ FuriString *json_data = get_json_array_value_furi("ssids", i, json_result);
+ if (!json_data)
+ {
+ break;
}
- }
-
- // Handle the last line if it does not end with a newline
- if(line_pos > 0) {
- line_buffer[line_pos] = '\0';
- char* comma_pos = strchr(line_buffer, ',');
- if(comma_pos) {
- *comma_pos = '\0'; // Replace comma with null character
-
- // Copy SSID
- strncpy(playlist->ssids[playlist->count], line_buffer, 63);
- playlist->ssids[playlist->count][63] = '\0'; // Ensure null-termination
-
- // Copy Password
- strncpy(playlist->passwords[playlist->count], comma_pos + 1, 63);
- playlist->passwords[playlist->count][63] = '\0'; // Ensure null-termination
-
- playlist->count++;
-
- if(playlist->count >= MAX_WIFI_NETWORKS) {
- FURI_LOG_W(TAG, "Reached maximum number of WiFi networks: %d", MAX_WIFI_NETWORKS);
- }
- } else {
- FURI_LOG_E(TAG, "Invalid line format (no comma found): %s", line_buffer);
+ FuriString *ssid = get_json_value_furi("ssid", json_data);
+ FuriString *password = get_json_value_furi("password", json_data);
+ if (!ssid || !password)
+ {
+ FURI_LOG_E(TAG, "Failed to get SSID or Password from JSON");
+ furi_string_free(json_data);
+ break;
}
+ snprintf(playlist->ssids[i], MAX_SSID_LENGTH, "%s", furi_string_get_cstr(ssid));
+ snprintf(playlist->passwords[i], MAX_SSID_LENGTH, "%s", furi_string_get_cstr(password));
+ playlist->count++;
+ furi_string_free(json_data);
+ furi_string_free(ssid);
+ furi_string_free(password);
}
-
- // Close and free file resources
- storage_file_close(file);
- storage_file_free(file);
- furi_record_close(RECORD_STORAGE);
-
+ furi_string_free(json_result);
return true;
}
-void save_settings(const char* ssid, const char* password) {
+void save_settings(const char *ssid, const char *password)
+{
char edited_directory_path[128];
char edited_file_path[128];
- for(size_t i = 0; i < 8; i++) {
+ for (size_t i = 0; i < 8; i++)
+ {
// Construct the directory and file paths for the current app
- snprintf(
- edited_directory_path,
- sizeof(edited_directory_path),
- STORAGE_EXT_PATH_PREFIX "/apps_data/%s",
- app_ids[i]);
- snprintf(
- edited_file_path,
- sizeof(edited_file_path),
- STORAGE_EXT_PATH_PREFIX "/apps_data/%s/settings.bin",
- app_ids[i]);
+ snprintf(edited_directory_path, sizeof(edited_directory_path), STORAGE_EXT_PATH_PREFIX "/apps_data/%s", app_ids[i]);
+ snprintf(edited_file_path, sizeof(edited_file_path), STORAGE_EXT_PATH_PREFIX "/apps_data/%s/settings.bin", app_ids[i]);
// Open the storage record
- Storage* storage = furi_record_open(RECORD_STORAGE);
- if(!storage) {
+ Storage *storage = furi_record_open(RECORD_STORAGE);
+ if (!storage)
+ {
FURI_LOG_E(TAG, "Failed to open storage record for app: %s", app_ids[i]);
continue; // Skip to the next app
}
@@ -226,24 +150,26 @@ void save_settings(const char* ssid, const char* password) {
storage_common_mkdir(storage, edited_directory_path);
// Allocate a file handle
- File* file = storage_file_alloc(storage);
- if(!file) {
+ File *file = storage_file_alloc(storage);
+ if (!file)
+ {
FURI_LOG_E(TAG, "Failed to allocate storage file for app: %s", app_ids[i]);
furi_record_close(RECORD_STORAGE);
continue; // Skip to the next app
}
// Open the file in read mode to read existing data
- bool file_opened =
- storage_file_open(file, edited_file_path, FSAM_READ, FSOM_OPEN_EXISTING);
+ bool file_opened = storage_file_open(file, edited_file_path, FSAM_READ, FSOM_OPEN_EXISTING);
size_t file_size = 0;
- uint8_t* buffer = NULL;
+ uint8_t *buffer = NULL;
- if(file_opened) {
+ if (file_opened)
+ {
// Get the file size
file_size = storage_file_size(file);
buffer = malloc(file_size);
- if(!buffer) {
+ if (!buffer)
+ {
FURI_LOG_E(TAG, "Failed to allocate buffer for app: %s", app_ids[i]);
storage_file_close(file);
storage_file_free(file);
@@ -252,7 +178,8 @@ void save_settings(const char* ssid, const char* password) {
}
// Read the existing data
- if(storage_file_read(file, buffer, file_size) != file_size) {
+ if (storage_file_read(file, buffer, file_size) != file_size)
+ {
FURI_LOG_E(TAG, "Failed to read settings file for app: %s", app_ids[i]);
free(buffer);
storage_file_close(file);
@@ -262,7 +189,9 @@ void save_settings(const char* ssid, const char* password) {
}
storage_file_close(file);
- } else {
+ }
+ else
+ {
// If the file doesn't exist, initialize an empty buffer
file_size = 0;
buffer = NULL;
@@ -271,65 +200,70 @@ void save_settings(const char* ssid, const char* password) {
storage_file_free(file);
// Prepare new SSID and Password
- size_t new_ssid_length = strlen(ssid) + 1; // Including null terminator
+ size_t new_ssid_length = strlen(ssid) + 1; // Including null terminator
size_t new_password_length = strlen(password) + 1; // Including null terminator
// Calculate the new file size
- size_t new_file_size =
- sizeof(size_t) + new_ssid_length + sizeof(size_t) + new_password_length;
+ size_t new_file_size = sizeof(size_t) + new_ssid_length + sizeof(size_t) + new_password_length;
// If there is additional data beyond SSID and Password, preserve it
size_t additional_data_size = 0;
- uint8_t* additional_data = NULL;
+ uint8_t *additional_data = NULL;
- if(buffer) {
+ if (buffer)
+ {
// Parse existing SSID length
- if(file_size >= sizeof(size_t)) {
+ if (file_size >= sizeof(size_t))
+ {
size_t existing_ssid_length;
memcpy(&existing_ssid_length, buffer, sizeof(size_t));
// Parse existing Password length
- if(file_size >= sizeof(size_t) + existing_ssid_length + sizeof(size_t)) {
+ if (file_size >= sizeof(size_t) + existing_ssid_length + sizeof(size_t))
+ {
size_t existing_password_length;
- memcpy(
- &existing_password_length,
- buffer + sizeof(size_t) + existing_ssid_length,
- sizeof(size_t));
+ memcpy(&existing_password_length, buffer + sizeof(size_t) + existing_ssid_length, sizeof(size_t));
// Calculate the offset where additional data starts
- size_t additional_offset = sizeof(size_t) + existing_ssid_length +
- sizeof(size_t) + existing_password_length;
- if(additional_offset < file_size) {
+ size_t additional_offset = sizeof(size_t) + existing_ssid_length + sizeof(size_t) + existing_password_length;
+ if (additional_offset < file_size)
+ {
additional_data_size = file_size - additional_offset;
additional_data = malloc(additional_data_size);
- if(additional_data) {
- memcpy(
- additional_data, buffer + additional_offset, additional_data_size);
- } else {
- FURI_LOG_E(
- TAG,
- "Failed to allocate memory for additional data for app: %s",
- app_ids[i]);
+ if (additional_data)
+ {
+ memcpy(additional_data, buffer + additional_offset, additional_data_size);
+ }
+ else
+ {
+ FURI_LOG_E(TAG, "Failed to allocate memory for additional data for app: %s", app_ids[i]);
free(buffer);
furi_record_close(RECORD_STORAGE);
continue;
}
}
- } else {
+ }
+ else
+ {
FURI_LOG_E(TAG, "Settings file format invalid for app: %s", app_ids[i]);
}
- } else {
+ }
+ else
+ {
FURI_LOG_E(TAG, "Settings file too small for app: %s", app_ids[i]);
}
}
// Allocate a new buffer for updated data
size_t total_new_size = new_file_size + additional_data_size;
- uint8_t* new_buffer = malloc(total_new_size);
- if(!new_buffer) {
+ uint8_t *new_buffer = malloc(total_new_size);
+ if (!new_buffer)
+ {
FURI_LOG_E(TAG, "Failed to allocate new buffer for app: %s", app_ids[i]);
- if(buffer) free(buffer);
- if(additional_data) free(additional_data);
+ if (buffer)
+ free(buffer);
+ if (additional_data)
+ free(additional_data);
furi_record_close(RECORD_STORAGE);
continue;
}
@@ -349,25 +283,30 @@ void save_settings(const char* ssid, const char* password) {
offset += new_password_length;
// Append any additional data if present
- if(additional_data_size > 0 && additional_data) {
+ if (additional_data_size > 0 && additional_data)
+ {
memcpy(new_buffer + offset, additional_data, additional_data_size);
offset += additional_data_size;
}
// Free temporary buffers
- if(buffer) free(buffer);
- if(additional_data) free(additional_data);
+ if (buffer)
+ free(buffer);
+ if (additional_data)
+ free(additional_data);
// Open the file in write mode with FSOM_CREATE_ALWAYS to overwrite it
file = storage_file_alloc(storage);
- if(!file) {
+ if (!file)
+ {
FURI_LOG_E(TAG, "Failed to allocate storage file for writing: %s", app_ids[i]);
free(new_buffer);
furi_record_close(RECORD_STORAGE);
continue;
}
- if(!storage_file_open(file, edited_file_path, FSAM_WRITE, FSOM_CREATE_ALWAYS)) {
+ if (!storage_file_open(file, edited_file_path, FSAM_WRITE, FSOM_CREATE_ALWAYS))
+ {
FURI_LOG_E(TAG, "Failed to open settings file for writing: %s", edited_file_path);
storage_file_free(file);
free(new_buffer);
@@ -376,7 +315,8 @@ void save_settings(const char* ssid, const char* password) {
}
// Write the updated buffer back to the file
- if(storage_file_write(file, new_buffer, total_new_size) != total_new_size) {
+ if (storage_file_write(file, new_buffer, total_new_size) != total_new_size)
+ {
FURI_LOG_E(TAG, "Failed to write updated settings for app: %s", app_ids[i]);
}
@@ -387,3 +327,100 @@ void save_settings(const char* ssid, const char* password) {
furi_record_close(RECORD_STORAGE);
}
}
+bool save_char(
+ const char *path_name, const char *value)
+{
+ if (!value)
+ {
+ return false;
+ }
+ // Create the directory for saving settings
+ char directory_path[256];
+ snprintf(directory_path, sizeof(directory_path), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_wifi");
+
+ // Create the directory
+ Storage *storage = furi_record_open(RECORD_STORAGE);
+ if (!storage)
+ {
+ FURI_LOG_E(HTTP_TAG, "Failed to open storage record");
+ return false;
+ }
+ storage_common_mkdir(storage, directory_path);
+ snprintf(directory_path, sizeof(directory_path), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_wifi/data");
+ storage_common_mkdir(storage, directory_path);
+
+ // Open the settings file
+ File *file = storage_file_alloc(storage);
+ char file_path[256];
+ snprintf(file_path, sizeof(file_path), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_wifi/data/%s.txt", path_name);
+
+ // 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;
+ }
+
+ // Write the data to the file
+ size_t data_size = strlen(value) + 1; // Include null terminator
+ if (storage_file_write(file, value, 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;
+}
+
+bool load_char(
+ const char *path_name,
+ char *value,
+ size_t value_size)
+{
+ if (!value)
+ {
+ return false;
+ }
+ Storage *storage = furi_record_open(RECORD_STORAGE);
+ File *file = storage_file_alloc(storage);
+
+ char file_path[256];
+ snprintf(file_path, sizeof(file_path), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_wifi/data/%s.txt", path_name);
+
+ // 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 false; // Return false if the file does not exist
+ }
+
+ // Read data into the buffer
+ size_t read_count = storage_file_read(file, value, value_size);
+ if (storage_file_get_error(file) != FSE_OK)
+ {
+ FURI_LOG_E(HTTP_TAG, "Error reading from file.");
+ storage_file_close(file);
+ storage_file_free(file);
+ furi_record_close(RECORD_STORAGE);
+ return false;
+ }
+
+ // Ensure null-termination
+ value[read_count - 1] = '\0';
+
+ storage_file_close(file);
+ storage_file_free(file);
+ furi_record_close(RECORD_STORAGE);
+
+ return true;
+}
\ No newline at end of file
diff --git a/flip_wifi/flip_storage/flip_wifi_storage.h b/flip_wifi/flip_storage/flip_wifi_storage.h
index 1948e8608..cd59e975e 100644
--- a/flip_wifi/flip_storage/flip_wifi_storage.h
+++ b/flip_wifi/flip_storage/flip_wifi_storage.h
@@ -1,18 +1,22 @@
-#ifndef FLIP_WIFI_STORAGE_H
-#define FLIP_WIFI_STORAGE_H
+#pragma once
#include
// define the paths for all of the FlipperHTTP apps
-#define WIFI_SSID_LIST_PATH STORAGE_EXT_PATH_PREFIX "/apps_data/flip_wifi/wifi_list.txt"
-
-extern char* app_ids[8];
+#define WIFI_SSID_LIST_PATH STORAGE_EXT_PATH_PREFIX "/apps_data/flip_wifi/data/wifi_list.txt"
// Function to save the playlist
-void save_playlist(WiFiPlaylist* playlist);
+void save_playlist(WiFiPlaylist *playlist);
// Function to load the playlist
-bool load_playlist(WiFiPlaylist* playlist);
+bool load_playlist(WiFiPlaylist *playlist);
+
+void save_settings(const char *ssid, const char *password);
+
+bool save_char(
+ const char *path_name, const char *value);
-void save_settings(const char* ssid, const char* password);
-#endif
+bool load_char(
+ const char *path_name,
+ char *value,
+ size_t value_size);
diff --git a/flip_wifi/flip_wifi.c b/flip_wifi/flip_wifi.c
index af54dcc69..52dccf196 100644
--- a/flip_wifi/flip_wifi.c
+++ b/flip_wifi/flip_wifi.c
@@ -1,83 +1,32 @@
#include "flip_wifi.h"
-
-FlipWiFiApp* app_instance = NULL;
-
+#include
+WiFiPlaylist *wifi_playlist = NULL;
// Function to free the resources used by FlipWiFiApp
-void flip_wifi_app_free(FlipWiFiApp* app) {
- if(!app) {
+void flip_wifi_app_free(FlipWiFiApp *app)
+{
+ if (!app)
+ {
FURI_LOG_E(TAG, "FlipWiFiApp is NULL");
return;
}
- // Free View(s)
- if(app->view_wifi_scan) {
- view_dispatcher_remove_view(app->view_dispatcher, FlipWiFiViewWiFiScan);
- view_free(app->view_wifi_scan);
- }
- if(app->view_wifi_saved) {
- view_dispatcher_remove_view(app->view_dispatcher, FlipWiFiViewWiFiSaved);
- view_free(app->view_wifi_saved);
- }
-
// Free Submenu(s)
- if(app->submenu_main) {
+ if (app->submenu_main)
+ {
view_dispatcher_remove_view(app->view_dispatcher, FlipWiFiViewSubmenuMain);
submenu_free(app->submenu_main);
}
- if(app->submenu_wifi_scan) {
- view_dispatcher_remove_view(app->view_dispatcher, FlipWiFiViewSubmenuScan);
- submenu_free(app->submenu_wifi_scan);
- }
- if(app->submenu_wifi_saved) {
- view_dispatcher_remove_view(app->view_dispatcher, FlipWiFiViewSubmenuSaved);
- submenu_free(app->submenu_wifi_saved);
- }
-
- // Free Widget(s)
- if(app->widget_info) {
- view_dispatcher_remove_view(app->view_dispatcher, FlipWiFiViewAbout);
- widget_free(app->widget_info);
- }
-
- // Free Text Input(s)
- if(app->uart_text_input_password_scan) {
- view_dispatcher_remove_view(app->view_dispatcher, FlipWiFiViewTextInputScan);
- text_input_free(app->uart_text_input_password_scan);
- }
- if(app->uart_text_input_password_saved) {
- view_dispatcher_remove_view(app->view_dispatcher, FlipWiFiViewTextInputSaved);
- text_input_free(app->uart_text_input_password_saved);
- }
- if(app->uart_text_input_add_ssid) {
- view_dispatcher_remove_view(app->view_dispatcher, FlipWiFiViewTextInputSavedAddSSID);
- text_input_free(app->uart_text_input_add_ssid);
- }
- if(app->uart_text_input_add_password) {
- view_dispatcher_remove_view(app->view_dispatcher, FlipWiFiViewTextInputSavedAddPassword);
- text_input_free(app->uart_text_input_add_password);
- }
-
- // free playlist
- for(size_t i = 0; i < app->wifi_playlist.count; i++) {
- if(app->wifi_playlist.ssids[i]) free(app->wifi_playlist.ssids[i]);
- if(app->wifi_playlist.passwords[i]) free(app->wifi_playlist.passwords[i]);
- }
-
- // free popup
- if(app->popup) {
- view_dispatcher_remove_view(app->view_dispatcher, FlipWiFiViewPopup);
- popup_free(app->popup);
- }
- // deinitalize flipper http
- flipper_http_deinit();
+ flip_wifi_free_all(app);
// free the view dispatcher
- if(app->view_dispatcher) view_dispatcher_free(app->view_dispatcher);
+ if (app->view_dispatcher)
+ view_dispatcher_free(app->view_dispatcher);
// close the gui
furi_record_close(RECORD_GUI);
// free the app
- if(app) free(app);
+ if (app)
+ free(app);
}
diff --git a/flip_wifi/flip_wifi.h b/flip_wifi/flip_wifi.h
index 93b87256e..bf943bb5a 100644
--- a/flip_wifi/flip_wifi.h
+++ b/flip_wifi/flip_wifi.h
@@ -5,86 +5,75 @@
#include
#include
-#define TAG "FlipWiFi"
-#define MAX_WIFI_NETWORKS 25
+#define TAG "FlipWiFi"
+#define MAX_SCAN_NETWORKS 100
+#define MAX_SAVED_NETWORKS 25
+#define MAX_SSID_LENGTH 64
// Define the submenu items for our FlipWiFi application
-typedef enum {
+typedef enum
+{
FlipWiFiSubmenuIndexAbout,
//
FlipWiFiSubmenuIndexWiFiScan,
FlipWiFiSubmenuIndexWiFiSaved,
+ FlipWiFiSubmenuIndexCommands,
//
FlipWiFiSubmenuIndexWiFiSavedAddSSID,
//
+ FlipWiFiSubmenuIndexFastCommandStart = 50,
FlipWiFiSubmenuIndexWiFiScanStart = 100,
FlipWiFiSubmenuIndexWiFiSavedStart = 200,
} FlipWiFiSubmenuIndex;
// Define a single view for our FlipWiFi application
-typedef enum {
- FlipWiFiViewWiFiScan, // The view for the wifi scan screen
+typedef enum
+{
+ FlipWiFiViewWiFiScan, // The view for the wifi scan screen
FlipWiFiViewWiFiSaved, // The view for the wifi scan screen
- FlipWiFiViewSubmenuMain, // The submenu for the main screen
- FlipWiFiViewSubmenuScan, // The submenu for the wifi scan screen
- FlipWiFiViewSubmenuSaved, // The submenu for the wifi scan screen
- FlipWiFiViewAbout, // The about screen
- FlipWiFiViewTextInputScan, // The text input screen for the wifi scan screen
- FlipWiFiViewTextInputSaved, // The text input screen for the wifi saved screen
//
- FlipWiFiViewTextInputSavedAddSSID, // The text input screen for the wifi saved screen
+ FlipWiFiViewSubmenuMain, // The submenu for the main screen
+ FlipWiFiViewSubmenuScan, // The submenu for the wifi scan screen
+ FlipWiFiViewSubmenuSaved, // The submenu for the wifi saved screen
+ FlipWiFiViewSubmenuCommands, // The submenu for the fast commands screen
+ FlipWiFiViewAbout, // The about screen
+ FlipWiFiViewTextInputScan, // The text input screen for the wifi scan screen
+ FlipWiFiViewTextInputSaved, // The text input screen for the wifi saved screen
+ //
+ FlipWiFiViewTextInputSavedAddSSID, // The text input screen for the wifi saved screen
FlipWiFiViewTextInputSavedAddPassword, // The text input screen for the wifi saved screen
//
- FlipWiFiViewPopup, // The popup screen
+ FlipWiFiViewGeneric, // generic view
+ FlipWiFiViewSubmenu, // generic submenu
+ FlipWiFiViewTextInput, // generic text input
} FlipWiFiView;
// Define the WiFiPlaylist structure
-typedef struct {
- char* ssids[MAX_WIFI_NETWORKS];
- char* passwords[MAX_WIFI_NETWORKS];
+typedef struct
+{
+ char ssids[MAX_SAVED_NETWORKS][MAX_SSID_LENGTH];
+ char passwords[MAX_SAVED_NETWORKS][MAX_SSID_LENGTH];
size_t count;
} WiFiPlaylist;
// Each screen will have its own view
-typedef struct {
- ViewDispatcher* view_dispatcher; // Switches between our views
- Popup* popup; // The popup for the app
- View* view_wifi_scan; // The view for the wifi scan screen
- View* view_wifi_saved; // The view for the wifi saved screen
- Submenu* submenu_main; // The submenu for the main screen
- Submenu* submenu_wifi_scan; // The submenu for the wifi scan screen
- Submenu* submenu_wifi_saved; // The submenu for the saved wifi screen
- Widget* widget_info; // The widget
- VariableItemList* variable_item_list_wifi; // The variable item list (settngs)
- VariableItem* variable_item_ssid; // The variable item
- TextInput* uart_text_input_password_scan; // The text input for the wifi scan screen
- TextInput* uart_text_input_password_saved; // The text input for the wifi saved screen
- //
- TextInput* uart_text_input_add_ssid; // The text input for the wifi saved screen
- TextInput* uart_text_input_add_password; // The text input for the wifi saved screen
-
- char* uart_text_input_buffer_password_scan; // Buffer for the text input
- char* uart_text_input_temp_buffer_password_scan; // Temporary buffer for the text input
- uint32_t uart_text_input_buffer_size_password_scan; // Size of the text input buffer
-
- char* uart_text_input_buffer_password_saved; // Buffer for the text input
- char* uart_text_input_temp_buffer_password_saved; // Temporary buffer for the text input
- uint32_t uart_text_input_buffer_size_password_saved; // Size of the text input buffer
-
- char* uart_text_input_buffer_add_ssid; // Buffer for the text input
- char* uart_text_input_temp_buffer_add_ssid; // Temporary buffer for the text input
- uint32_t uart_text_input_buffer_size_add_ssid; // Size of the text input buffer
-
- char* uart_text_input_buffer_add_password; // Buffer for the text input
- char* uart_text_input_temp_buffer_add_password; // Temporary buffer for the text input
- uint32_t uart_text_input_buffer_size_add_password; // Size of the text input buffer
-
- WiFiPlaylist wifi_playlist; // The playlist of wifi networks
+typedef struct
+{
+ ViewDispatcher *view_dispatcher; // Switches between our views
+ Widget *widget_info; // The widget for the about screen
+ View *view_wifi; // generic view for the wifi scan and saved screens
+ Submenu *submenu_main; // The submenu for the main screen
+ Submenu *submenu_wifi; // generic submenu for the wifi scan and saved screens
+ VariableItemList *variable_item_list_wifi; // The variable item list (settngs)
+ VariableItem *variable_item_ssid; // The variable item
+ TextInput *uart_text_input; // The text input screen
+ char *uart_text_input_buffer; // Buffer for the text input
+ char *uart_text_input_temp_buffer; // Temporary buffer for the text input
+ uint32_t uart_text_input_buffer_size; // Size of the text input buffer
} FlipWiFiApp;
-extern FlipWiFiApp* app_instance;
-
// Function to free the resources used by FlipWiFiApp
-void flip_wifi_app_free(FlipWiFiApp* app);
+void flip_wifi_app_free(FlipWiFiApp *app);
+extern WiFiPlaylist *wifi_playlist; // The playlist of wifi networks
-#endif // FLIP_WIFI_E_H
+#endif // FLIP_WIFI_E_H
\ No newline at end of file
diff --git a/flip_wifi/flipper_http/flipper_http.c b/flip_wifi/flipper_http/flipper_http.c
index 060d7f58c..50206bb86 100644
--- a/flip_wifi/flipper_http/flipper_http.c
+++ b/flip_wifi/flipper_http/flipper_http.c
@@ -6,17 +6,21 @@ size_t file_buffer_len = 0;
// 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,
+ 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);
+ char *file_path)
+{
+ Storage *storage = furi_record_open(RECORD_STORAGE);
+ File *file = storage_file_alloc(storage);
- if(start_new_file) {
+ if (start_new_file)
+ {
// Delete the file if it already exists
- if(storage_file_exists(storage, file_path)) {
- if(!storage_simply_remove_recursive(storage, file_path)) {
+ if (storage_file_exists(storage, file_path))
+ {
+ if (!storage_simply_remove_recursive(storage, file_path))
+ {
FURI_LOG_E(HTTP_TAG, "Failed to delete file: %s", file_path);
storage_file_free(file);
furi_record_close(RECORD_STORAGE);
@@ -24,15 +28,19 @@ bool flipper_http_append_to_file(
}
}
// Open the file in write mode
- if(!storage_file_open(file, file_path, FSAM_WRITE, FSOM_CREATE_ALWAYS)) {
+ 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 {
+ }
+ else
+ {
// Open the file in append mode
- if(!storage_file_open(file, file_path, FSAM_WRITE, FSOM_OPEN_APPEND)) {
+ 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);
@@ -41,7 +49,8 @@ bool flipper_http_append_to_file(
}
// Write the data to the file
- if(storage_file_write(file, data, data_size) != data_size) {
+ 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);
@@ -55,32 +64,37 @@ bool flipper_http_append_to_file(
return true;
}
-FuriString* flipper_http_load_from_file(char* file_path) {
+FuriString *flipper_http_load_from_file(char *file_path)
+{
// Open the storage record
- Storage* storage = furi_record_open(RECORD_STORAGE);
- if(!storage) {
+ 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) {
+ 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)) {
+ 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) {
+ 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);
@@ -92,8 +106,9 @@ FuriString* flipper_http_load_from_file(char* file_path) {
furi_string_reset(str_result);
// Define a buffer to hold the read data
- uint8_t* buffer = (uint8_t*)malloc(MAX_FILE_SHOW);
- if(!buffer) {
+ 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);
@@ -104,7 +119,8 @@ FuriString* flipper_http_load_from_file(char* file_path) {
// 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) {
+ 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);
@@ -114,7 +130,8 @@ FuriString* flipper_http_load_from_file(char* file_path) {
}
// Append each byte to the FuriString
- for(size_t i = 0; i < read_count; i++) {
+ for (size_t i = 0; i < read_count; i++)
+ {
furi_string_push_back(str_result, buffer[i]);
}
@@ -138,39 +155,48 @@ FuriString* flipper_http_load_from_file(char* file_path) {
* @note This function will handle received data asynchronously via the callback.
*/
// UART worker thread
-int32_t flipper_http_worker(void* context) {
+int32_t flipper_http_worker(void *context)
+{
UNUSED(context);
size_t rx_line_pos = 0;
- while(1) {
+ while (1)
+ {
uint32_t events = furi_thread_flags_wait(
WorkerEvtStop | WorkerEvtRxDone, FuriFlagWaitAny, FuriWaitForever);
- if(events & WorkerEvtStop) {
+ if (events & WorkerEvtStop)
+ {
break;
}
- if(events & WorkerEvtRxDone) {
+ if (events & WorkerEvtRxDone)
+ {
// Continuously read from the stream buffer until it's empty
- while(!furi_stream_buffer_is_empty(fhttp.flipper_http_stream)) {
+ 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) {
+ if (received == 0)
+ {
// No more data to read
break;
}
// Append the received byte to the file if saving is enabled
- if(fhttp.save_bytes) {
+ 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,
- fhttp.just_started_bytes,
- fhttp.file_path)) {
+ if (file_buffer_len >= FILE_BUFFER_SIZE)
+ {
+ if (!flipper_http_append_to_file(
+ file_buffer,
+ file_buffer_len,
+ fhttp.just_started_bytes,
+ fhttp.file_path))
+ {
FURI_LOG_E(HTTP_TAG, "Failed to append data to file");
}
file_buffer_len = 0;
@@ -179,9 +205,11 @@ int32_t flipper_http_worker(void* context) {
}
// Handle line buffering only if callback is set (text data)
- if(fhttp.handle_rx_line_cb) {
+ if (fhttp.handle_rx_line_cb)
+ {
// Handle line buffering
- if(c == '\n' || rx_line_pos >= RX_LINE_BUFFER_SIZE - 1) {
+ 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
@@ -189,7 +217,9 @@ int32_t flipper_http_worker(void* context) {
// Reset the line buffer position
rx_line_pos = 0;
- } else {
+ }
+ else
+ {
rx_line_buffer[rx_line_pos++] = c; // Add character to the line buffer
}
}
@@ -206,7 +236,8 @@ int32_t flipper_http_worker(void* context) {
* @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) {
+void get_timeout_timer_callback(void *context)
+{
UNUSED(context);
FURI_LOG_E(HTTP_TAG, "Timeout reached: 2 seconds without receiving the end.");
@@ -230,11 +261,13 @@ void get_timeout_timer_callback(void* context) {
* @note This function will handle received data asynchronously via the callback.
*/
void _flipper_http_rx_callback(
- FuriHalSerialHandle* handle,
+ FuriHalSerialHandle *handle,
FuriHalSerialRxEvent event,
- void* context) {
+ void *context)
+{
UNUSED(context);
- if(event == FuriHalSerialRxEventData) {
+ 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);
@@ -249,23 +282,28 @@ void _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) {
+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) {
+ 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) {
+ 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) {
+ if (!fhttp.rx_thread)
+ {
FURI_LOG_E(HTTP_TAG, "Failed to allocate UART thread.");
furi_stream_buffer_free(fhttp.flipper_http_stream);
return false;
@@ -283,13 +321,15 @@ bool flipper_http_init(FlipperHTTP_Callback callback, void* context) {
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)) {
+ 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) {
+ 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);
@@ -312,11 +352,12 @@ bool flipper_http_init(FlipperHTTP_Callback callback, void* context) {
// 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
+ FuriTimerTypeOnce, // One-shot timer
+ &fhttp // Context passed to callback
);
- if(!fhttp.get_timeout_timer) {
+ 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);
@@ -333,8 +374,9 @@ bool flipper_http_init(FlipperHTTP_Callback callback, void* context) {
// 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) {
+ 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;
}
@@ -349,8 +391,10 @@ bool flipper_http_init(FlipperHTTP_Callback callback, void* context) {
* @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) {
+void flipper_http_deinit()
+{
+ if (fhttp.serial_handle == NULL)
+ {
FURI_LOG_E(HTTP_TAG, "UART handle is NULL. Already deinitialized?");
return;
}
@@ -373,13 +417,15 @@ void flipper_http_deinit() {
furi_stream_buffer_free(fhttp.flipper_http_stream);
// Free the timer
- if(fhttp.get_timeout_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) {
+ if (fhttp.last_response)
+ {
free(fhttp.last_response);
fhttp.last_response = NULL;
}
@@ -394,34 +440,38 @@ void flipper_http_deinit() {
* @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) {
+bool flipper_http_send_data(const char *data)
+{
size_t data_length = strlen(data);
- if(data_length == 0) {
+ 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
+ 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] = '\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))) {
+ 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);
+ 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);
@@ -437,9 +487,11 @@ bool flipper_http_send_data(const char* data) {
* @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)) {
+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;
}
@@ -455,9 +507,11 @@ bool flipper_http_ping() {
* @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)) {
+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;
}
@@ -472,9 +526,11 @@ bool flipper_http_list_commands() {
* @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)) {
+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;
}
@@ -489,9 +545,11 @@ bool flipper_http_led_on() {
* @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)) {
+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;
}
@@ -508,8 +566,10 @@ bool flipper_http_led_off() {
* @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) {
+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;
}
@@ -517,12 +577,14 @@ bool flipper_http_parse_json(const char* key, const char* json_data) {
char buffer[256];
int ret =
snprintf(buffer, sizeof(buffer), "[PARSE]{\"key\":\"%s\",\"json\":%s}", key, json_data);
- if(ret < 0 || ret >= (int)sizeof(buffer)) {
+ 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)) {
+ if (!flipper_http_send_data(buffer))
+ {
FURI_LOG_E("FlipperHTTP", "Failed to send JSON parse command.");
return false;
}
@@ -540,8 +602,10 @@ bool flipper_http_parse_json(const char* key, const char* json_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) {
+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;
}
@@ -554,12 +618,14 @@ bool flipper_http_parse_json_array(const char* key, int index, const char* json_
key,
index,
json_data);
- if(ret < 0 || ret >= (int)sizeof(buffer)) {
+ 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)) {
+ if (!flipper_http_send_data(buffer))
+ {
FURI_LOG_E("FlipperHTTP", "Failed to send JSON parse array command.");
return false;
}
@@ -574,21 +640,30 @@ bool flipper_http_parse_json_array(const char* key, int index, const char* json_
* @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() {
- if(!flipper_http_send_data("[WIFI/SCAN]")) {
- FURI_LOG_E("FlipperHTTP", "Failed to send WiFi scan command.");
- return false;
- }
+bool flipper_http_scan_wifi()
+{
// custom for FlipWiFi app
fhttp.just_started_get = true;
+ // ensure the data folder exists
+
snprintf(
fhttp.file_path,
sizeof(fhttp.file_path),
- STORAGE_EXT_PATH_PREFIX "/apps_data/flip_wifi/scan.txt");
+ STORAGE_EXT_PATH_PREFIX "/apps_data/flip_wifi/data/scan.txt");
+
+ // ensure the file is empty
+ Storage *storage = furi_record_open(RECORD_STORAGE);
+ storage_simply_remove_recursive(storage, fhttp.file_path);
+ furi_record_close(RECORD_STORAGE);
fhttp.save_received_data = true;
+ if (!flipper_http_send_data("[WIFI/SCAN]"))
+ {
+ FURI_LOG_E("FlipperHTTP", "Failed to send WiFi scan command.");
+ return false;
+ }
// The response will be handled asynchronously via the callback
return true;
}
@@ -599,24 +674,34 @@ bool flipper_http_scan_wifi() {
* @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) {
+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;
}
+
+ // custom for FlipWiFi app
+ fhttp.just_started_get = true;
+
char buffer[256];
int ret = snprintf(
buffer, sizeof(buffer), "[WIFI/SAVE]{\"ssid\":\"%s\",\"password\":\"%s\"}", ssid, password);
- if(ret < 0 || ret >= (int)sizeof(buffer)) {
+ 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)) {
+ if (!flipper_http_send_data(buffer))
+ {
FURI_LOG_E("FlipperHTTP", "Failed to send WiFi save command.");
return false;
}
+ fhttp.state = RECEIVING;
+
// The response will be handled asynchronously via the callback
return true;
}
@@ -627,8 +712,10 @@ bool flipper_http_save_wifi(const char* ssid, const char* password) {
* @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() {
- if(!flipper_http_send_data("[IP/ADDRESS]")) {
+bool flipper_http_ip_address()
+{
+ if (!flipper_http_send_data("[IP/ADDRESS]"))
+ {
FURI_LOG_E("FlipperHTTP", "Failed to send IP address command.");
return false;
}
@@ -643,9 +730,11 @@ bool flipper_http_ip_address() {
* @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)) {
+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;
}
@@ -660,8 +749,10 @@ bool flipper_http_ip_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() {
- if(!flipper_http_send_data("[WIFI/DISCONNECT]")) {
+bool flipper_http_disconnect_wifi()
+{
+ if (!flipper_http_send_data("[WIFI/DISCONNECT]"))
+ {
FURI_LOG_E("FlipperHTTP", "Failed to send WiFi disconnect command.");
return false;
}
@@ -676,8 +767,10 @@ bool flipper_http_disconnect_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() {
- if(!flipper_http_send_data("[WIFI/CONNECT]")) {
+bool flipper_http_connect_wifi()
+{
+ if (!flipper_http_send_data("[WIFI/CONNECT]"))
+ {
FURI_LOG_E("FlipperHTTP", "Failed to send WiFi connect command.");
return false;
}
@@ -693,8 +786,10 @@ bool flipper_http_connect_wifi() {
* @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) {
+bool flipper_http_get_request(const char *url)
+{
+ if (!url)
+ {
FURI_LOG_E("FlipperHTTP", "Invalid arguments provided to flipper_http_get_request.");
return false;
}
@@ -702,13 +797,15 @@ bool flipper_http_get_request(const char* url) {
// Prepare GET request command
char command[256];
int ret = snprintf(command, sizeof(command), "[GET]%s", url);
- if(ret < 0 || ret >= (int)sizeof(command)) {
+ 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)) {
+ if (!flipper_http_send_data(command))
+ {
FURI_LOG_E("FlipperHTTP", "Failed to send GET request command.");
return false;
}
@@ -724,8 +821,10 @@ bool flipper_http_get_request(const char* url) {
* @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) {
+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;
@@ -735,13 +834,15 @@ bool flipper_http_get_request_with_headers(const char* url, const char* 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)) {
+ 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)) {
+ if (!flipper_http_send_data(command))
+ {
FURI_LOG_E("FlipperHTTP", "Failed to send GET request command with headers.");
return false;
}
@@ -757,8 +858,10 @@ bool flipper_http_get_request_with_headers(const char* url, const char* headers)
* @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) {
+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;
}
@@ -767,13 +870,15 @@ bool flipper_http_get_request_bytes(const char* url, const char* 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)) {
+ 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)) {
+ if (!flipper_http_send_data(command))
+ {
FURI_LOG_E("FlipperHTTP", "Failed to send GET request command with headers.");
return false;
}
@@ -791,10 +896,12 @@ bool flipper_http_get_request_bytes(const char* url, const char* headers) {
* @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) {
+ 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.");
@@ -810,13 +917,15 @@ bool flipper_http_post_request_with_headers(
url,
headers,
payload);
- if(ret < 0 || ret >= (int)sizeof(command)) {
+ 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)) {
+ if (!flipper_http_send_data(command))
+ {
FURI_LOG_E("FlipperHTTP", "Failed to send POST request command with headers and data.");
return false;
}
@@ -833,8 +942,10 @@ bool flipper_http_post_request_with_headers(
* @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) {
+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;
@@ -849,13 +960,15 @@ bool flipper_http_post_request_bytes(const char* url, const char* headers, const
url,
headers,
payload);
- if(ret < 0 || ret >= (int)sizeof(command)) {
+ 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)) {
+ if (!flipper_http_send_data(command))
+ {
FURI_LOG_E("FlipperHTTP", "Failed to send POST request command with headers and data.");
return false;
}
@@ -873,10 +986,12 @@ bool flipper_http_post_request_bytes(const char* url, const char* headers, const
* @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) {
+ 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;
@@ -891,13 +1006,15 @@ bool flipper_http_put_request_with_headers(
url,
headers,
payload);
- if(ret < 0 || ret >= (int)sizeof(command)) {
+ 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)) {
+ if (!flipper_http_send_data(command))
+ {
FURI_LOG_E("FlipperHTTP", "Failed to send PUT request command with headers and data.");
return false;
}
@@ -915,10 +1032,12 @@ bool flipper_http_put_request_with_headers(
* @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) {
+ 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.");
@@ -934,14 +1053,16 @@ bool flipper_http_delete_request_with_headers(
url,
headers,
payload);
- if(ret < 0 || ret >= (int)sizeof(command)) {
+ 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)) {
+ if (!flipper_http_send_data(command))
+ {
FURI_LOG_E("FlipperHTTP", "Failed to send DELETE request command with headers and data.");
return false;
}
@@ -957,26 +1078,31 @@ bool flipper_http_delete_request_with_headers(
* @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) {
+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') {
+ 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) {
+ 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) {
+ if (fhttp.state != INACTIVE && fhttp.state != ISSUE)
+ {
fhttp.state = RECEIVING;
}
@@ -984,24 +1110,28 @@ void flipper_http_rx_callback(const char* line, void* context) {
// FURI_LOG_I(HTTP_TAG, "Received UART line: %s", line);
// custom function to FlipWiFi
- if(fhttp.save_received_data) {
- if(!flipper_http_append_to_file(
- line, strlen(line), fhttp.just_started_get, fhttp.file_path)) {
+ if (fhttp.save_received_data)
+ {
+ if (!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.state = ISSUE;
return;
}
- if(fhttp.just_started_get) {
+ if (fhttp.just_started_get)
+ {
fhttp.just_started_get = false;
}
}
// Check if we've started receiving data from a GET request
- if(fhttp.started_receiving_get) {
+ 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) {
+ 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);
@@ -1011,14 +1141,17 @@ void flipper_http_rx_callback(const char* line, void* context) {
fhttp.save_bytes = false;
fhttp.save_received_data = false;
- if(fhttp.is_bytes_request) {
+ if (fhttp.is_bytes_request)
+ {
// Search for the binary marker `[GET/END]` in the file buffer
const char marker[] = "[GET/END]";
const size_t marker_len = sizeof(marker) - 1; // Exclude null terminator
- for(size_t i = 0; i <= file_buffer_len - marker_len; i++) {
+ for (size_t i = 0; i <= file_buffer_len - marker_len; i++)
+ {
// Check if the marker is found
- if(memcmp(&file_buffer[i], marker, marker_len) == 0) {
+ if (memcmp(&file_buffer[i], marker, marker_len) == 0)
+ {
// Remove the marker by shifting the remaining data left
size_t remaining_len = file_buffer_len - (i + marker_len);
memmove(&file_buffer[i], &file_buffer[i + marker_len], remaining_len);
@@ -1028,9 +1161,10 @@ void flipper_http_rx_callback(const char* line, void* context) {
}
// If there is data left in the buffer, append it to the file
- if(file_buffer_len > 0) {
- if(!flipper_http_append_to_file(
- file_buffer, file_buffer_len, false, fhttp.file_path)) {
+ 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 data to file.");
}
file_buffer_len = 0;
@@ -1042,9 +1176,10 @@ void flipper_http_rx_callback(const char* line, void* context) {
}
// 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)) {
+ 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;
@@ -1052,18 +1187,21 @@ void flipper_http_rx_callback(const char* line, void* context) {
return;
}
- if(!fhttp.just_started_get) {
+ 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) {
+ 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) {
+ 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);
@@ -1073,14 +1211,17 @@ void flipper_http_rx_callback(const char* line, void* context) {
fhttp.save_bytes = false;
fhttp.save_received_data = false;
- if(fhttp.is_bytes_request) {
+ if (fhttp.is_bytes_request)
+ {
// Search for the binary marker `[POST/END]` in the file buffer
const char marker[] = "[POST/END]";
const size_t marker_len = sizeof(marker) - 1; // Exclude null terminator
- for(size_t i = 0; i <= file_buffer_len - marker_len; i++) {
+ for (size_t i = 0; i <= file_buffer_len - marker_len; i++)
+ {
// Check if the marker is found
- if(memcmp(&file_buffer[i], marker, marker_len) == 0) {
+ if (memcmp(&file_buffer[i], marker, marker_len) == 0)
+ {
// Remove the marker by shifting the remaining data left
size_t remaining_len = file_buffer_len - (i + marker_len);
memmove(&file_buffer[i], &file_buffer[i + marker_len], remaining_len);
@@ -1090,9 +1231,10 @@ void flipper_http_rx_callback(const char* line, void* context) {
}
// If there is data left in the buffer, append it to the file
- if(file_buffer_len > 0) {
- if(!flipper_http_append_to_file(
- file_buffer, file_buffer_len, false, fhttp.file_path)) {
+ 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 data to file.");
}
file_buffer_len = 0;
@@ -1104,9 +1246,10 @@ void flipper_http_rx_callback(const char* line, void* context) {
}
// 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)) {
+ 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;
@@ -1114,18 +1257,21 @@ void flipper_http_rx_callback(const char* line, void* context) {
return;
}
- if(!fhttp.just_started_post) {
+ 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) {
+ 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) {
+ 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);
@@ -1139,9 +1285,10 @@ void flipper_http_rx_callback(const char* line, void* context) {
}
// 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)) {
+ 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;
@@ -1149,18 +1296,21 @@ void flipper_http_rx_callback(const char* line, void* context) {
return;
}
- if(!fhttp.just_started_put) {
+ 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) {
+ 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) {
+ 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);
@@ -1174,9 +1324,10 @@ void flipper_http_rx_callback(const char* line, void* context) {
}
// 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)) {
+ 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;
@@ -1184,22 +1335,29 @@ void flipper_http_rx_callback(const char* line, void* context) {
return;
}
- if(!fhttp.just_started_delete) {
+ 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) {
+ if (strstr(line, "[SUCCESS]") != NULL || strstr(line, "[CONNECTED]") != NULL)
+ {
FURI_LOG_I(HTTP_TAG, "Operation succeeded.");
- } else if(strstr(line, "[INFO]") != NULL) {
+ }
+ 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) {
+ if (fhttp.state == INACTIVE && strstr(line, "[INFO] Already connected to Wifi.") != NULL)
+ {
fhttp.state = IDLE;
}
- } else if(strstr(line, "[GET/SUCCESS]") != NULL) {
+ }
+ 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);
@@ -1209,7 +1367,9 @@ void flipper_http_rx_callback(const char* line, void* context) {
fhttp.just_started_bytes = true;
file_buffer_len = 0;
return;
- } else if(strstr(line, "[POST/SUCCESS]") != NULL) {
+ }
+ 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);
@@ -1219,67 +1379,86 @@ void flipper_http_rx_callback(const char* line, void* context) {
fhttp.just_started_bytes = true;
file_buffer_len = 0;
return;
- } else if(strstr(line, "[PUT/SUCCESS]") != NULL) {
+ }
+ 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) {
+ }
+ 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) {
+ }
+ else if (strstr(line, "[DISCONNECTED]") != NULL)
+ {
FURI_LOG_I(HTTP_TAG, "WiFi disconnected successfully.");
- } else if(strstr(line, "[ERROR]") != NULL) {
+ }
+ 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) {
+ }
+ 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) {
+ if (fhttp.state == INACTIVE)
+ {
fhttp.state = IDLE;
return;
}
}
- if(fhttp.state == INACTIVE && strstr(line, "[PONG]") != NULL) {
+ if (fhttp.state == INACTIVE && strstr(line, "[PONG]") != NULL)
+ {
fhttp.state = IDLE;
- } else if(fhttp.state == INACTIVE && strstr(line, "[PONG]") == NULL) {
+ }
+ else if (fhttp.state == INACTIVE && strstr(line, "[PONG]") == NULL)
+ {
fhttp.state = INACTIVE;
- } else {
+ }
+ 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;
+char *trim(const char *str)
+{
+ const char *end;
+ char *trimmed_str;
size_t len;
// Trim leading space
- while(isspace((unsigned char)*str))
+ while (isspace((unsigned char)*str))
str++;
// All spaces?
- if(*str == 0) return strdup(""); // Return an empty string if 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))
+ 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) {
+ trimmed_str = (char *)malloc(len + 1);
+ if (trimmed_str == NULL)
+ {
return NULL; // Handle memory allocation failure
}
@@ -1296,21 +1475,25 @@ char* trim(const char* str) {
* @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
+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 {
+ }
+ 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) {
+ 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)
+ if (!parse_json()) // parse the JSON before switching to the view (synchonous)
{
FURI_LOG_E(HTTP_TAG, "Failed to parse the JSON...");
return false;
@@ -1327,17 +1510,18 @@ bool flipper_http_process_response_async(bool (*http_request)(void), bool (*pars
* @param view_dispatcher The view dispatcher to use
* @return
*/
-void flipper_http_loading_task(
- bool (*http_request)(void),
- bool (*parse_response)(void),
- uint32_t success_view_id,
- uint32_t failure_view_id,
- ViewDispatcher** view_dispatcher) {
- Loading* loading;
+void flipper_http_loading_task(bool (*http_request)(void),
+ bool (*parse_response)(void),
+ uint32_t success_view_id,
+ uint32_t failure_view_id,
+ ViewDispatcher **view_dispatcher)
+{
+ Loading *loading;
int32_t loading_view_id = 987654321; // Random ID
loading = loading_alloc();
- if(!loading) {
+ if (!loading)
+ {
FURI_LOG_E(HTTP_TAG, "Failed to allocate loading");
view_dispatcher_switch_to_view(*view_dispatcher, failure_view_id);
@@ -1350,7 +1534,8 @@ void flipper_http_loading_task(
view_dispatcher_switch_to_view(*view_dispatcher, loading_view_id);
// Make the request
- if(!flipper_http_process_response_async(http_request, parse_response)) {
+ if (!flipper_http_process_response_async(http_request, parse_response))
+ {
FURI_LOG_E(HTTP_TAG, "Failed to make request");
view_dispatcher_switch_to_view(*view_dispatcher, failure_view_id);
view_dispatcher_remove_view(*view_dispatcher, loading_view_id);
diff --git a/flip_wifi/flipper_http/flipper_http.h b/flip_wifi/flipper_http/flipper_http.h
index c072967e0..4458594ff 100644
--- a/flip_wifi/flipper_http/flipper_http.h
+++ b/flip_wifi/flipper_http/flipper_http.h
@@ -15,69 +15,72 @@
// STORAGE_EXT_PATH_PREFIX is defined in the Furi SDK as /ext
-#define HTTP_TAG "FlipWiFi" // change this to your app name
-#define http_tag "flip_wifi" // change this to your app id
-#define UART_CH (momentum_settings.uart_esp_channel) // UART channel
+#define HTTP_TAG "FlipWiFi" // change this to your app name
+#define http_tag "flip_wifi" // change this to your app id
+#define UART_CH (momentum_settings.uart_esp_channel) // 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 4096 // UART RX line buffer size (increase for large responses)
-#define MAX_FILE_SHOW 4096 // Maximum data from file to show
-#define FILE_BUFFER_SIZE 512 // File buffer size
+#define BAUDRATE (115200) // UART baudrate
+#define RX_BUF_SIZE 2048 // UART RX buffer size
+#define RX_LINE_BUFFER_SIZE 4096 // UART RX line buffer size (increase for large responses)
+#define MAX_FILE_SHOW 4096 // 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);
+typedef void (*FlipperHTTP_Callback)(const char *line, void *context);
// State variable to track the UART state
-typedef enum {
- INACTIVE, // Inactive state
- IDLE, // Default state
+typedef enum
+{
+ INACTIVE, // Inactive state
+ IDLE, // Default state
RECEIVING, // Receiving data
- SENDING, // Sending data
- ISSUE, // Issue with connection
+ SENDING, // Sending data
+ ISSUE, // Issue with connection
} SerialState;
// Event Flags for UART Worker Thread
-typedef enum {
+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
+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
+ 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 *last_response;
char file_path[256]; // Path to save the received data
// Timer-related members
- FuriTimer* get_timeout_timer; // Timer for HTTP request timeout
+ 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 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 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 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
+ 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;
+ 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
+ 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
bool just_started_bytes; // Indicates if bytes data reception has just started
} FlipperHTTP;
@@ -93,12 +96,12 @@ extern size_t file_buffer_len;
// 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,
+ const void *data,
size_t data_size,
bool start_new_file,
- char* file_path);
+ char *file_path);
-FuriString* flipper_http_load_from_file(char* file_path);
+FuriString *flipper_http_load_from_file(char *file_path);
// UART worker thread
/**
@@ -108,7 +111,7 @@ FuriString* flipper_http_load_from_file(char* file_path);
* @note This function will handle received data asynchronously via the callback.
*/
// UART worker thread
-int32_t flipper_http_worker(void* context);
+int32_t flipper_http_worker(void *context);
// Timer callback function
/**
@@ -117,7 +120,7 @@ int32_t flipper_http_worker(void* context);
* @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);
+void get_timeout_timer_callback(void *context);
// UART RX Handler Callback (Interrupt Context)
/**
@@ -129,9 +132,9 @@ void get_timeout_timer_callback(void* context);
* @note This function will handle received data asynchronously via the callback.
*/
void _flipper_http_rx_callback(
- FuriHalSerialHandle* handle,
+ FuriHalSerialHandle *handle,
FuriHalSerialRxEvent event,
- void* context);
+ void *context);
// UART initialization function
/**
@@ -141,7 +144,7 @@ void _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);
+bool flipper_http_init(FlipperHTTP_Callback callback, void *context);
// Deinitialize UART
/**
@@ -158,7 +161,7 @@ void flipper_http_deinit();
* @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);
+bool flipper_http_send_data(const char *data);
// Function to send a PING request
/**
@@ -202,7 +205,7 @@ bool flipper_http_led_off();
* @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);
+bool flipper_http_parse_json(const char *key, const char *json_data);
// Function to parse JSON array data
/**
@@ -213,7 +216,7 @@ bool flipper_http_parse_json(const char* key, const char* json_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);
+bool flipper_http_parse_json_array(const char *key, int index, const char *json_data);
// Function to scan for WiFi networks
/**
@@ -229,7 +232,7 @@ bool flipper_http_scan_wifi();
* @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);
+bool flipper_http_save_wifi(const char *ssid, const char *password);
// Function to get IP address of WiFi Devboard
/**
@@ -270,7 +273,7 @@ bool flipper_http_connect_wifi();
* @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);
+bool flipper_http_get_request(const char *url);
// Function to send a GET request with headers
/**
@@ -280,7 +283,7 @@ bool flipper_http_get_request(const char* url);
* @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);
+bool flipper_http_get_request_with_headers(const char *url, const char *headers);
// Function to send a GET request with headers and return bytes
/**
@@ -290,7 +293,7 @@ bool flipper_http_get_request_with_headers(const char* url, const char* headers)
* @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);
+bool flipper_http_get_request_bytes(const char *url, const char *headers);
// Function to send a POST request with headers
/**
@@ -302,9 +305,9 @@ bool flipper_http_get_request_bytes(const char* url, const char* headers);
* @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);
+ const char *url,
+ const char *headers,
+ const char *payload);
// Function to send a POST request with headers and return bytes
/**
@@ -315,7 +318,7 @@ bool flipper_http_post_request_with_headers(
* @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);
+bool flipper_http_post_request_bytes(const char *url, const char *headers, const char *payload);
// Function to send a PUT request with headers
/**
@@ -327,9 +330,9 @@ bool flipper_http_post_request_bytes(const char* url, const char* headers, const
* @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);
+ const char *url,
+ const char *headers,
+ const char *payload);
// Function to send a DELETE request with headers
/**
@@ -341,9 +344,9 @@ bool flipper_http_put_request_with_headers(
* @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);
+ const char *url,
+ const char *headers,
+ const char *payload);
// Function to handle received data asynchronously
/**
@@ -353,10 +356,10 @@ bool flipper_http_delete_request_with_headers(
* @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);
+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);
+char *trim(const char *str);
/**
* @brief Process requests and parse JSON data asynchronously
* @param http_request The function to send the request
@@ -374,11 +377,10 @@ bool flipper_http_process_response_async(bool (*http_request)(void), bool (*pars
* @param view_dispatcher The view dispatcher to use
* @return
*/
-void flipper_http_loading_task(
- bool (*http_request)(void),
- bool (*parse_response)(void),
- uint32_t success_view_id,
- uint32_t failure_view_id,
- ViewDispatcher** view_dispatcher);
+void flipper_http_loading_task(bool (*http_request)(void),
+ bool (*parse_response)(void),
+ uint32_t success_view_id,
+ uint32_t failure_view_id,
+ ViewDispatcher **view_dispatcher);
#endif // FLIPPER_HTTP_H
diff --git a/flip_wifi/jsmn/jsmn.c b/flip_wifi/jsmn/jsmn.c
index eb33b3cc7..d101530c6 100644
--- a/flip_wifi/jsmn/jsmn.c
+++ b/flip_wifi/jsmn/jsmn.c
@@ -7,17 +7,17 @@
*/
#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;
+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) {
+ if (parser->toknext >= num_tokens)
+ {
return NULL;
}
tok = &tokens[parser->toknext++];
@@ -32,8 +32,9 @@ static jsmntok_t*
/**
* Fills token type and boundaries.
*/
-static void
- jsmn_fill_token(jsmntok_t* token, const jsmntype_t type, const int start, const int end) {
+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;
@@ -43,19 +44,19 @@ static void
/**
* 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;
+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]) {
+ 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 ':':
@@ -72,7 +73,8 @@ static int jsmn_parse_primitive(
/* to quiet a warning from gcc*/
break;
}
- if(js[parser->pos] < 32 || js[parser->pos] >= 127) {
+ if (js[parser->pos] < 32 || js[parser->pos] >= 127)
+ {
parser->pos = start;
return JSMN_ERROR_INVAL;
}
@@ -84,12 +86,14 @@ static int jsmn_parse_primitive(
#endif
found:
- if(tokens == NULL) {
+ if (tokens == NULL)
+ {
parser->pos--;
return 0;
}
token = jsmn_alloc_token(parser, tokens, num_tokens);
- if(token == NULL) {
+ if (token == NULL)
+ {
parser->pos = start;
return JSMN_ERROR_NOMEM;
}
@@ -104,29 +108,31 @@ static int jsmn_parse_primitive(
/**
* 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;
+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++) {
+ for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++)
+ {
char c = js[parser->pos];
/* Quote: end of string */
- if(c == '\"') {
- if(tokens == NULL) {
+ if (c == '\"')
+ {
+ if (tokens == NULL)
+ {
return 0;
}
token = jsmn_alloc_token(parser, tokens, num_tokens);
- if(token == NULL) {
+ if (token == NULL)
+ {
parser->pos = start;
return JSMN_ERROR_NOMEM;
}
@@ -138,10 +144,12 @@ static int jsmn_parse_string(
}
/* Backslash: Quoted symbol expected */
- if(c == '\\' && parser->pos + 1 < len) {
+ if (c == '\\' && parser->pos + 1 < len)
+ {
int i;
parser->pos++;
- switch(js[parser->pos]) {
+ switch (js[parser->pos])
+ {
/* Allowed escaped symbols */
case '\"':
case '/':
@@ -155,11 +163,13 @@ static int jsmn_parse_string(
/* Allows escaped symbol \uXXXX */
case 'u':
parser->pos++;
- for(i = 0; i < 4 && parser->pos < len && js[parser->pos] != '\0'; i++) {
+ 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 */
+ 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;
}
@@ -181,7 +191,8 @@ static int jsmn_parse_string(
/**
* Create JSON parser over an array of tokens
*/
-void jsmn_init(jsmn_parser* parser) {
+void jsmn_init(jsmn_parser *parser)
+{
parser->pos = 0;
parser->toknext = 0;
parser->toksuper = -1;
@@ -190,38 +201,41 @@ void jsmn_init(jsmn_parser* parser) {
/**
* 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 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;
+ jsmntok_t *token;
int count = parser->toknext;
- for(; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) {
+ for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++)
+ {
char c;
jsmntype_t type;
c = js[parser->pos];
- switch(c) {
+ switch (c)
+ {
case '{':
case '[':
count++;
- if(tokens == NULL) {
+ if (tokens == NULL)
+ {
break;
}
token = jsmn_alloc_token(parser, tokens, num_tokens);
- if(token == NULL) {
+ if (token == NULL)
+ {
return JSMN_ERROR_NOMEM;
}
- if(parser->toksuper != -1) {
- jsmntok_t* t = &tokens[parser->toksuper];
+ 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) {
+ if (t->type == JSMN_OBJECT)
+ {
return JSMN_ERROR_INVAL;
}
#endif
@@ -236,26 +250,33 @@ int jsmn_parse(
break;
case '}':
case ']':
- if(tokens == NULL) {
+ if (tokens == NULL)
+ {
break;
}
type = (c == '}' ? JSMN_OBJECT : JSMN_ARRAY);
#ifdef JSMN_PARENT_LINKS
- if(parser->toknext < 1) {
+ 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) {
+ 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) {
+ if (token->parent == -1)
+ {
+ if (token->type != type || parser->toksuper == -1)
+ {
return JSMN_ERROR_INVAL;
}
break;
@@ -263,10 +284,13 @@ int jsmn_parse(
token = &tokens[token->parent];
}
#else
- for(i = parser->toknext - 1; i >= 0; i--) {
+ for (i = parser->toknext - 1; i >= 0; i--)
+ {
token = &tokens[i];
- if(token->start != -1 && token->end == -1) {
- if(token->type != type) {
+ if (token->start != -1 && token->end == -1)
+ {
+ if (token->type != type)
+ {
return JSMN_ERROR_INVAL;
}
parser->toksuper = -1;
@@ -275,12 +299,15 @@ int jsmn_parse(
}
}
/* Error if unmatched closing bracket */
- if(i == -1) {
+ if (i == -1)
+ {
return JSMN_ERROR_INVAL;
}
- for(; i >= 0; i--) {
+ for (; i >= 0; i--)
+ {
token = &tokens[i];
- if(token->start != -1 && token->end == -1) {
+ if (token->start != -1 && token->end == -1)
+ {
parser->toksuper = i;
break;
}
@@ -289,11 +316,13 @@ int jsmn_parse(
break;
case '\"':
r = jsmn_parse_string(parser, js, len, tokens, num_tokens);
- if(r < 0) {
+ if (r < 0)
+ {
return r;
}
count++;
- if(parser->toksuper != -1 && tokens != NULL) {
+ if (parser->toksuper != -1 && tokens != NULL)
+ {
tokens[parser->toksuper].size++;
}
break;
@@ -306,15 +335,19 @@ int jsmn_parse(
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) {
+ 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) {
+ 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;
}
@@ -340,9 +373,12 @@ int jsmn_parse(
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)) {
+ 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;
}
}
@@ -351,11 +387,13 @@ int jsmn_parse(
default:
#endif
r = jsmn_parse_primitive(parser, js, len, tokens, num_tokens);
- if(r < 0) {
+ if (r < 0)
+ {
return r;
}
count++;
- if(parser->toksuper != -1 && tokens != NULL) {
+ if (parser->toksuper != -1 && tokens != NULL)
+ {
tokens[parser->toksuper].size++;
}
break;
@@ -368,10 +406,13 @@ int jsmn_parse(
}
}
- if(tokens != NULL) {
- for(i = parser->toknext - 1; i >= 0; i--) {
+ 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) {
+ if (tokens[i].start != -1 && tokens[i].end == -1)
+ {
return JSMN_ERROR_PART;
}
}
@@ -381,10 +422,12 @@ int jsmn_parse(
}
// 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) {
+char *get_json(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);
@@ -392,30 +435,41 @@ 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) {
- if(tok->type == JSMN_STRING && (int)strlen(s) == tok->end - tok->start &&
- strncmp(json + tok->start, s, tok->end - tok->start) == 0) {
+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) {
+char *get_json_value(char *key, const char *json_data)
+{
// Parse the JSON feed
- if(json_data != NULL) {
+ if (json_data != NULL)
+ {
jsmn_parser parser;
jsmn_init(&parser);
-
+ uint32_t max_tokens = json_token_count(json_data);
+ if (!jsmn_memory_check(max_tokens))
+ {
+ FURI_LOG_E("JSMM.H", "Insufficient memory for JSON tokens.");
+ return NULL;
+ }
// Allocate tokens array on the heap
- jsmntok_t* tokens = malloc(sizeof(jsmntok_t) * max_tokens);
- if(tokens == NULL) {
+ 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) {
+ if (ret < 0)
+ {
// Handle parsing errors
FURI_LOG_E("JSMM.H", "Failed to parse JSON: %d", ret);
free(tokens);
@@ -423,19 +477,23 @@ char* get_json_value(char* key, char* json_data, uint32_t max_tokens) {
}
// Ensure that the root element is an object
- if(ret < 1 || tokens[0].type != JSMN_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) {
+ 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) {
+ char *value = malloc(length + 1);
+ if (value == NULL)
+ {
FURI_LOG_E("JSMM.H", "Failed to allocate memory for value.");
free(tokens);
return NULL;
@@ -450,102 +508,146 @@ char* get_json_value(char* key, char* json_data, uint32_t max_tokens) {
// Free the token array if key was not found
free(tokens);
- } else {
+ }
+ else
+ {
FURI_LOG_E("JSMM.H", "JSON data is NULL");
}
- FURI_LOG_E("JSMM.H", "Failed to find the key in the JSON.");
+ char warning[128];
+ snprintf(warning, sizeof(warning), "Failed to find the key \"%s\" in the JSON.", key);
+ FURI_LOG_E("JSMM.H", warning);
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) {
+// Helper function to skip a token and all its descendants.
+// Returns the index of the next token after skipping this one.
+// On error or out of bounds, returns -1.
+static int skip_token(const jsmntok_t *tokens, int start, int total)
+{
+ if (start < 0 || start >= total)
+ return -1;
+
+ int i = start;
+ if (tokens[i].type == JSMN_OBJECT)
+ {
+ // For an object: size is number of key-value pairs
+ int pairs = tokens[i].size;
+ i++; // move to first key-value pair
+ for (int p = 0; p < pairs; p++)
+ {
+ // skip key (primitive/string)
+ i++;
+ if (i >= total)
+ return -1;
+ // skip value (which could be object/array and must be skipped recursively)
+ i = skip_token(tokens, i, total);
+ if (i == -1)
+ return -1;
+ }
+ return i; // i is now just past the object
+ }
+ else if (tokens[i].type == JSMN_ARRAY)
+ {
+ // For an array: size is number of elements
+ int elems = tokens[i].size;
+ i++; // move to first element
+ for (int e = 0; e < elems; e++)
+ {
+ i = skip_token(tokens, i, total);
+ if (i == -1)
+ return -1;
+ }
+ return i; // i is now just past the array
+ }
+ else
+ {
+ // Primitive or string token, just skip it
+ return i + 1;
+ }
+}
+
+// Revised get_json_array_value
+char *get_json_array_value(char *key, uint32_t index, const char *json_data)
+{
+ // Always extract the full array each time from the original json_data
+ char *array_str = get_json_value(key, json_data);
+ if (array_str == NULL)
+ {
FURI_LOG_E("JSMM.H", "Failed to get array for key: %s", key);
return NULL;
}
+ uint32_t max_tokens = json_token_count(array_str);
+ if (!jsmn_memory_check(max_tokens))
+ {
+ FURI_LOG_E("JSMM.H", "Insufficient memory for JSON tokens.");
+ free(array_str);
+ 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) {
+ 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) {
+ 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) {
+ 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);
+ if (index >= (uint32_t)tokens[0].size)
+ {
+ // FURI_LOG_E("JSMM.H", "Index %lu out of bounds for array with size %u.", 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.");
+ // Find the index-th element: start from token[1], which is the first element
+ int elem_token = 1;
+ for (uint32_t i = 0; i < index; i++)
+ {
+ elem_token = skip_token(tokens, elem_token, ret);
+ if (elem_token == -1 || elem_token >= ret)
+ {
+ FURI_LOG_E("JSMM.H", "Error skipping tokens to reach element %lu.", i);
free(tokens);
free(array_str);
return NULL;
}
}
- // Extract the array element
- jsmntok_t element = tokens[current_token];
+ // Now elem_token should point to the token of the requested element
+ jsmntok_t element = tokens[elem_token];
int length = element.end - element.start;
- char* value = malloc(length + 1);
- if(value == NULL) {
+ char *value = malloc(length + 1);
+ if (!value)
+ {
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
+ value[length] = '\0';
- // Clean up
free(tokens);
free(array_str);
@@ -553,21 +655,30 @@ char* get_json_array_value(char* key, uint32_t index, char* json_data, uint32_t
}
// 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) {
+char **get_json_array_values(char *key, char *json_data, 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) {
+ char *array_str = get_json_value(key, json_data);
+ if (array_str == NULL)
+ {
FURI_LOG_E("JSMM.H", "Failed to get array for key: %s", key);
return NULL;
}
-
+ uint32_t max_tokens = json_token_count(array_str);
+ if (!jsmn_memory_check(max_tokens))
+ {
+ FURI_LOG_E("JSMM.H", "Insufficient memory for JSON tokens.");
+ free(array_str);
+ 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) {
+ 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;
@@ -575,7 +686,8 @@ char** get_json_array_values(char* key, char* json_data, uint32_t max_tokens, in
// Parse the JSON array
int ret = jsmn_parse(&parser, array_str, strlen(array_str), tokens, max_tokens);
- if(ret < 0) {
+ if (ret < 0)
+ {
FURI_LOG_E("JSMM.H", "Failed to parse JSON array: %d", ret);
free(tokens);
free(array_str);
@@ -583,7 +695,8 @@ char** get_json_array_values(char* key, char* json_data, uint32_t max_tokens, in
}
// Ensure the root element is an array
- if(tokens[0].type != JSMN_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);
@@ -592,8 +705,9 @@ char** get_json_array_values(char* key, char* json_data, uint32_t max_tokens, in
// 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) {
+ 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);
@@ -604,15 +718,18 @@ char** get_json_array_values(char* key, char* json_data, uint32_t max_tokens, in
// 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) {
+ 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) {
+ 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;
@@ -622,10 +739,12 @@ char** get_json_array_values(char* key, char* json_data, uint32_t max_tokens, in
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) {
+ 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++) {
+ for (int j = 0; j < actual_num_values; j++)
+ {
free(values[j]);
}
free(values);
@@ -647,14 +766,17 @@ char** get_json_array_values(char* key, char* json_data, uint32_t max_tokens, in
*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) {
+ 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++) {
+ for (int i = actual_num_values; i < array_size; i++)
+ {
free(values[i]);
}
}
@@ -664,3 +786,18 @@ char** get_json_array_values(char* key, char* json_data, uint32_t max_tokens, in
free(array_str);
return values;
}
+
+int json_token_count(const char *json)
+{
+ if (json == NULL)
+ {
+ return JSMN_ERROR_INVAL;
+ }
+
+ jsmn_parser parser;
+ jsmn_init(&parser);
+
+ // Pass NULL for tokens and 0 for num_tokens to get the token count only
+ int ret = jsmn_parse(&parser, json, strlen(json), NULL, 0);
+ return ret; // If ret >= 0, it represents the number of tokens needed.
+}
diff --git a/flip_wifi/jsmn/jsmn.h b/flip_wifi/jsmn/jsmn.h
index cd95a0e58..5f96e5596 100644
--- a/flip_wifi/jsmn/jsmn.h
+++ b/flip_wifi/jsmn/jsmn.h
@@ -17,9 +17,11 @@
#define JSMN_H
#include
+#include
#ifdef __cplusplus
-extern "C" {
+extern "C"
+{
#endif
#ifdef JSMN_STATIC
@@ -27,72 +29,17 @@ extern "C" {
#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);
+ 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);
+ 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 */
@@ -109,23 +56,19 @@ JSMN_API int jsmn_parse(
#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);
+char *get_json(const char *key, const char *value);
// Helper function to compare JSON keys
-int jsoneq(const char* json, jsmntok_t* tok, const char* s);
+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);
+char *get_json_value(char *key, const char *json_data);
// Revised get_json_array_value function
-char* get_json_array_value(char* key, uint32_t index, char* json_data, uint32_t max_tokens);
+char *get_json_array_value(char *key, uint32_t index, const char *json_data);
// 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);
+char **get_json_array_values(char *key, char *json_data, int *num_values);
+
+int json_token_count(const char *json);
#endif /* JB_JSMN_EDIT */
diff --git a/flip_wifi/jsmn/jsmn_furi.c b/flip_wifi/jsmn/jsmn_furi.c
new file mode 100644
index 000000000..d3bde9365
--- /dev/null
+++ b/flip_wifi/jsmn/jsmn_furi.c
@@ -0,0 +1,736 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2010 Serge Zaitsev
+ *
+ * [License text continues...]
+ */
+
+#include
+
+// Forward declarations of helper functions
+static int jsoneq_furi(const FuriString *json, jsmntok_t *tok, const FuriString *s);
+static int skip_token(const jsmntok_t *tokens, int start, int total);
+
+/**
+ * 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)
+{
+ if (parser->toknext >= num_tokens)
+ {
+ return NULL;
+ }
+ jsmntok_t *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.
+ * Now uses FuriString to access characters.
+ */
+static int jsmn_parse_primitive(jsmn_parser *parser, const FuriString *js,
+ jsmntok_t *tokens, const size_t num_tokens)
+{
+ size_t len = furi_string_size(js);
+ int start = parser->pos;
+
+ for (; parser->pos < len; parser->pos++)
+ {
+ char c = furi_string_get_char(js, parser->pos);
+ switch (c)
+ {
+#ifndef JSMN_STRICT
+ case ':':
+#endif
+ case '\t':
+ case '\r':
+ case '\n':
+ case ' ':
+ case ',':
+ case ']':
+ case '}':
+ goto found;
+ default:
+ break;
+ }
+ if (c < 32 || c >= 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;
+ }
+ jsmntok_t *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.
+ * Now uses FuriString to access characters.
+ */
+static int jsmn_parse_string(jsmn_parser *parser, const FuriString *js,
+ jsmntok_t *tokens, const size_t num_tokens)
+{
+ size_t len = furi_string_size(js);
+ int start = parser->pos;
+ parser->pos++;
+
+ for (; parser->pos < len; parser->pos++)
+ {
+ char c = furi_string_get_char(js, parser->pos);
+ if (c == '\"')
+ {
+ if (tokens == NULL)
+ {
+ return 0;
+ }
+ jsmntok_t *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;
+ }
+
+ if (c == '\\' && (parser->pos + 1) < len)
+ {
+ parser->pos++;
+ char esc = furi_string_get_char(js, parser->pos);
+ switch (esc)
+ {
+ case '\"':
+ case '/':
+ case '\\':
+ case 'b':
+ case 'f':
+ case 'r':
+ case 'n':
+ case 't':
+ break;
+ case 'u':
+ {
+ parser->pos++;
+ for (int i = 0; i < 4 && parser->pos < len; i++)
+ {
+ char hex = furi_string_get_char(js, parser->pos);
+ if (!((hex >= '0' && hex <= '9') ||
+ (hex >= 'A' && hex <= 'F') ||
+ (hex >= 'a' && hex <= 'f')))
+ {
+ parser->pos = start;
+ return JSMN_ERROR_INVAL;
+ }
+ parser->pos++;
+ }
+ parser->pos--;
+ break;
+ }
+ default:
+ parser->pos = start;
+ return JSMN_ERROR_INVAL;
+ }
+ }
+ }
+ parser->pos = start;
+ return JSMN_ERROR_PART;
+}
+
+/**
+ * Create JSON parser
+ */
+void jsmn_init_furi(jsmn_parser *parser)
+{
+ parser->pos = 0;
+ parser->toknext = 0;
+ parser->toksuper = -1;
+}
+
+/**
+ * Parse JSON string and fill tokens.
+ * Now uses FuriString for the input JSON.
+ */
+int jsmn_parse_furi(jsmn_parser *parser, const FuriString *js,
+ jsmntok_t *tokens, const unsigned int num_tokens)
+{
+ size_t len = furi_string_size(js);
+ int r;
+ int i;
+ int count = parser->toknext;
+
+ for (; parser->pos < len; parser->pos++)
+ {
+ char c = furi_string_get_char(js, parser->pos);
+ jsmntype_t type;
+
+ switch (c)
+ {
+ case '{':
+ case '[':
+ {
+ count++;
+ if (tokens == NULL)
+ {
+ break;
+ }
+ jsmntok_t *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
+ 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;
+ }
+ {
+ jsmntok_t *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
+ {
+ jsmntok_t *token;
+ 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;
+ }
+ }
+ 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, 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 ' ':
+ // Whitespace - ignore
+ 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
+ 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':
+ 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
+ default:
+#endif
+ r = jsmn_parse_primitive(parser, js, tokens, num_tokens);
+ if (r < 0)
+ return r;
+ count++;
+ if (parser->toksuper != -1 && tokens != NULL)
+ {
+ tokens[parser->toksuper].size++;
+ }
+ break;
+#ifdef JSMN_STRICT
+ default:
+ return JSMN_ERROR_INVAL;
+#endif
+ }
+ }
+
+ if (tokens != NULL)
+ {
+ for (i = parser->toknext - 1; i >= 0; i--)
+ {
+ if (tokens[i].start != -1 && tokens[i].end == -1)
+ {
+ return JSMN_ERROR_PART;
+ }
+ }
+ }
+
+ return count;
+}
+
+// Helper function to create a JSON object: {"key":"value"}
+FuriString *get_json_furi(const FuriString *key, const FuriString *value)
+{
+ FuriString *result = furi_string_alloc();
+ furi_string_printf(result, "{\"%s\":\"%s\"}",
+ furi_string_get_cstr(key),
+ furi_string_get_cstr(value));
+ return result; // Caller responsible for furi_string_free
+}
+
+// Helper function to compare JSON keys
+static int jsoneq_furi(const FuriString *json, jsmntok_t *tok, const FuriString *s)
+{
+ size_t s_len = furi_string_size(s);
+ size_t tok_len = tok->end - tok->start;
+
+ if (tok->type != JSMN_STRING)
+ return -1;
+ if (s_len != tok_len)
+ return -1;
+
+ FuriString *sub = furi_string_alloc_set(json);
+ furi_string_mid(sub, tok->start, tok_len);
+
+ int res = furi_string_cmp(sub, s);
+ furi_string_free(sub);
+
+ return (res == 0) ? 0 : -1;
+}
+
+// Skip a token and its descendants
+static int skip_token(const jsmntok_t *tokens, int start, int total)
+{
+ if (start < 0 || start >= total)
+ return -1;
+
+ int i = start;
+ if (tokens[i].type == JSMN_OBJECT)
+ {
+ int pairs = tokens[i].size;
+ i++;
+ for (int p = 0; p < pairs; p++)
+ {
+ i++; // skip key
+ if (i >= total)
+ return -1;
+ i = skip_token(tokens, i, total); // skip value
+ if (i == -1)
+ return -1;
+ }
+ return i;
+ }
+ else if (tokens[i].type == JSMN_ARRAY)
+ {
+ int elems = tokens[i].size;
+ i++;
+ for (int e = 0; e < elems; e++)
+ {
+ i = skip_token(tokens, i, total);
+ if (i == -1)
+ return -1;
+ }
+ return i;
+ }
+ else
+ {
+ return i + 1;
+ }
+}
+
+/**
+ * Parse JSON and return the value associated with a given char* key.
+ */
+FuriString *get_json_value_furi(const char *key, const FuriString *json_data)
+{
+ if (json_data == NULL)
+ {
+ FURI_LOG_E("JSMM.H", "JSON data is NULL");
+ return NULL;
+ }
+ uint32_t max_tokens = json_token_count_furi(json_data);
+ if (!jsmn_memory_check(max_tokens))
+ {
+ FURI_LOG_E("JSMM.H", "Insufficient memory for JSON tokens.");
+ return NULL;
+ }
+ // Create a temporary FuriString from key
+ FuriString *key_str = furi_string_alloc();
+ furi_string_cat_str(key_str, key);
+
+ jsmn_parser parser;
+ jsmn_init_furi(&parser);
+
+ jsmntok_t *tokens = (jsmntok_t *)malloc(sizeof(jsmntok_t) * max_tokens);
+ if (tokens == NULL)
+ {
+ FURI_LOG_E("JSMM.H", "Failed to allocate memory for JSON tokens.");
+ furi_string_free(key_str);
+ return NULL;
+ }
+
+ int ret = jsmn_parse_furi(&parser, json_data, tokens, max_tokens);
+ if (ret < 0)
+ {
+ FURI_LOG_E("JSMM.H", "Failed to parse JSON: %d", ret);
+ free(tokens);
+ furi_string_free(key_str);
+ return NULL;
+ }
+
+ if (ret < 1 || tokens[0].type != JSMN_OBJECT)
+ {
+ FURI_LOG_E("JSMM.H", "Root element is not an object.");
+ free(tokens);
+ furi_string_free(key_str);
+ return NULL;
+ }
+
+ for (int i = 1; i < ret; i++)
+ {
+ if (jsoneq_furi(json_data, &tokens[i], key_str) == 0)
+ {
+ int length = tokens[i + 1].end - tokens[i + 1].start;
+ FuriString *value = furi_string_alloc_set(json_data);
+ furi_string_mid(value, tokens[i + 1].start, length);
+ free(tokens);
+ furi_string_free(key_str);
+ return value;
+ }
+ }
+
+ free(tokens);
+ furi_string_free(key_str);
+ char warning[128];
+ snprintf(warning, sizeof(warning), "Failed to find the key \"%s\" in the JSON.", key);
+ FURI_LOG_E("JSMM.H", warning);
+ return NULL;
+}
+
+/**
+ * Return the value at a given index in a JSON array for a given char* key.
+ */
+FuriString *get_json_array_value_furi(const char *key, uint32_t index, const FuriString *json_data)
+{
+ FuriString *array_str = get_json_value_furi(key, json_data);
+ if (array_str == NULL)
+ {
+ FURI_LOG_E("JSMM.H", "Failed to get array for key");
+ return NULL;
+ }
+ uint32_t max_tokens = json_token_count_furi(array_str);
+ if (!jsmn_memory_check(max_tokens))
+ {
+ FURI_LOG_E("JSMM.H", "Insufficient memory for JSON tokens.");
+ furi_string_free(array_str);
+ return NULL;
+ }
+ jsmn_parser parser;
+ jsmn_init_furi(&parser);
+
+ jsmntok_t *tokens = (jsmntok_t *)malloc(sizeof(jsmntok_t) * max_tokens);
+ if (tokens == NULL)
+ {
+ FURI_LOG_E("JSMM.H", "Failed to allocate memory for JSON tokens.");
+ furi_string_free(array_str);
+ return NULL;
+ }
+
+ int ret = jsmn_parse_furi(&parser, array_str, tokens, max_tokens);
+ if (ret < 0)
+ {
+ FURI_LOG_E("JSMM.H", "Failed to parse JSON array: %d", ret);
+ free(tokens);
+ furi_string_free(array_str);
+ return NULL;
+ }
+
+ if (ret < 1 || tokens[0].type != JSMN_ARRAY)
+ {
+ FURI_LOG_E("JSMM.H", "Value for key is not an array.");
+ free(tokens);
+ furi_string_free(array_str);
+ return NULL;
+ }
+
+ if (index >= (uint32_t)tokens[0].size)
+ {
+ // FURI_LOG_E("JSMM.H", "Index %lu out of bounds for array with size %u.", index, tokens[0].size);
+ free(tokens);
+ furi_string_free(array_str);
+ return NULL;
+ }
+
+ int elem_token = 1;
+ for (uint32_t i = 0; i < index; i++)
+ {
+ elem_token = skip_token(tokens, elem_token, ret);
+ if (elem_token == -1 || elem_token >= ret)
+ {
+ FURI_LOG_E("JSMM.H", "Error skipping tokens to reach element %lu.", i);
+ free(tokens);
+ furi_string_free(array_str);
+ return NULL;
+ }
+ }
+
+ jsmntok_t element = tokens[elem_token];
+ int length = element.end - element.start;
+
+ FuriString *value = furi_string_alloc_set(array_str);
+ furi_string_mid(value, element.start, length);
+
+ free(tokens);
+ furi_string_free(array_str);
+
+ return value;
+}
+
+/**
+ * Extract all object values from a JSON array associated with a given char* key.
+ */
+FuriString **get_json_array_values_furi(const char *key, const FuriString *json_data, int *num_values)
+{
+ *num_values = 0;
+ // Convert key to FuriString and call get_json_value_furi
+ FuriString *array_str = get_json_value_furi(key, json_data);
+ if (array_str == NULL)
+ {
+ FURI_LOG_E("JSMM.H", "Failed to get array for key");
+ return NULL;
+ }
+
+ uint32_t max_tokens = json_token_count_furi(array_str);
+ if (!jsmn_memory_check(max_tokens))
+ {
+ FURI_LOG_E("JSMM.H", "Insufficient memory for JSON tokens.");
+ furi_string_free(array_str);
+ return NULL;
+ }
+ jsmn_parser parser;
+ jsmn_init_furi(&parser);
+
+ jsmntok_t *tokens = (jsmntok_t *)malloc(sizeof(jsmntok_t) * max_tokens);
+ if (tokens == NULL)
+ {
+ FURI_LOG_E("JSMM.H", "Failed to allocate memory for JSON tokens.");
+ furi_string_free(array_str);
+ return NULL;
+ }
+
+ int ret = jsmn_parse_furi(&parser, array_str, tokens, max_tokens);
+ if (ret < 0)
+ {
+ FURI_LOG_E("JSMM.H", "Failed to parse JSON array: %d", ret);
+ free(tokens);
+ furi_string_free(array_str);
+ return NULL;
+ }
+
+ if (tokens[0].type != JSMN_ARRAY)
+ {
+ FURI_LOG_E("JSMM.H", "Value for key is not an array.");
+ free(tokens);
+ furi_string_free(array_str);
+ return NULL;
+ }
+
+ int array_size = tokens[0].size;
+ FuriString **values = (FuriString **)malloc(array_size * sizeof(FuriString *));
+ if (values == NULL)
+ {
+ FURI_LOG_E("JSMM.H", "Failed to allocate memory for array of values.");
+ free(tokens);
+ furi_string_free(array_str);
+ return NULL;
+ }
+
+ int actual_num_values = 0;
+ int current_token = 1;
+ 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];
+
+ int length = element.end - element.start;
+ FuriString *value = furi_string_alloc_set(array_str);
+ furi_string_mid(value, element.start, length);
+
+ values[actual_num_values] = value;
+ actual_num_values++;
+
+ // Skip this element and its descendants
+ current_token = skip_token(tokens, current_token, ret);
+ if (current_token == -1)
+ {
+ FURI_LOG_E("JSMM.H", "Error skipping tokens after element %d.", i);
+ break;
+ }
+ }
+
+ *num_values = actual_num_values;
+ if (actual_num_values < array_size)
+ {
+ FuriString **reduced_values = (FuriString **)realloc(values, actual_num_values * sizeof(FuriString *));
+ if (reduced_values != NULL)
+ {
+ values = reduced_values;
+ }
+ }
+
+ free(tokens);
+ furi_string_free(array_str);
+ return values;
+}
+
+uint32_t json_token_count_furi(const FuriString *json)
+{
+ if (json == NULL)
+ {
+ return JSMN_ERROR_INVAL;
+ }
+
+ jsmn_parser parser;
+ jsmn_init_furi(&parser);
+
+ // Pass NULL for tokens and 0 for num_tokens to get the token count only
+ int ret = jsmn_parse_furi(&parser, json, NULL, 0);
+ return ret; // If ret >= 0, it represents the number of tokens needed.
+}
diff --git a/flip_wifi/jsmn/jsmn_furi.h b/flip_wifi/jsmn/jsmn_furi.h
new file mode 100644
index 000000000..cb01f38ca
--- /dev/null
+++ b/flip_wifi/jsmn/jsmn_furi.h
@@ -0,0 +1,74 @@
+/*
+ * 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_FURI_H
+#define JSMN_FURI_H
+
+#include
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+#ifdef JSMN_STATIC
+#define JSMN_API static
+#else
+#define JSMN_API extern
+#endif
+
+ JSMN_API void jsmn_init_furi(jsmn_parser *parser);
+ JSMN_API int jsmn_parse_furi(jsmn_parser *parser, const FuriString *js,
+ jsmntok_t *tokens, const unsigned int num_tokens);
+
+#ifndef JSMN_HEADER
+/* Implementation in jsmn_furi.c */
+#endif /* JSMN_HEADER */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* JSMN_FURI_H */
+
+#ifndef JB_JSMN_FURI_EDIT
+#define JB_JSMN_FURI_EDIT
+
+// Helper function to create a JSON object
+FuriString *get_json_furi(const FuriString *key, const FuriString *value);
+
+// Updated signatures to accept const char* key
+FuriString *get_json_value_furi(const char *key, const FuriString *json_data);
+FuriString *get_json_array_value_furi(const char *key, uint32_t index, const FuriString *json_data);
+FuriString **get_json_array_values_furi(const char *key, const FuriString *json_data, int *num_values);
+
+uint32_t json_token_count_furi(const FuriString *json);
+/* Example usage:
+char *json = "{\"key1\":\"value1\",\"key2\":\"value2\"}";
+FuriString *json_data = char_to_furi_string(json);
+if (!json_data)
+{
+ FURI_LOG_E(TAG, "Failed to allocate FuriString");
+ return -1;
+}
+FuriString *value = get_json_value_furi("key1", json_data, json_token_count_furi(json_data));
+if (value)
+{
+ FURI_LOG_I(TAG, "Value: %s", furi_string_get_cstr(value));
+ furi_string_free(value);
+}
+furi_string_free(json_data);
+*/
+#endif /* JB_JSMN_EDIT */
diff --git a/flip_wifi/jsmn/jsmn_h.c b/flip_wifi/jsmn/jsmn_h.c
new file mode 100644
index 000000000..59480e6e6
--- /dev/null
+++ b/flip_wifi/jsmn/jsmn_h.c
@@ -0,0 +1,15 @@
+#include
+FuriString *char_to_furi_string(const char *str)
+{
+ FuriString *furi_str = furi_string_alloc();
+ if (!furi_str)
+ {
+ return NULL;
+ }
+ for (size_t i = 0; i < strlen(str); i++)
+ {
+ furi_string_push_back(furi_str, str[i]);
+ }
+ return furi_str;
+}
+bool jsmn_memory_check(size_t heap_size) { return memmgr_get_free_heap() > (heap_size + 1024); }
\ No newline at end of file
diff --git a/flip_wifi/jsmn/jsmn_h.h b/flip_wifi/jsmn/jsmn_h.h
new file mode 100644
index 000000000..97d53e7ff
--- /dev/null
+++ b/flip_wifi/jsmn/jsmn_h.h
@@ -0,0 +1,56 @@
+#pragma once
+#include
+#include
+#include
+#include
+#include
+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
+{
+ JSMN_ERROR_NOMEM = -1,
+ JSMN_ERROR_INVAL = -2,
+ JSMN_ERROR_PART = -3
+};
+
+typedef struct
+{
+ jsmntype_t type;
+ int start;
+ int end;
+ int size;
+#ifdef JSMN_PARENT_LINKS
+ int parent;
+#endif
+} jsmntok_t;
+
+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;
+
+typedef struct
+{
+ char *key;
+ char *value;
+} JSON;
+
+typedef struct
+{
+ FuriString *key;
+ FuriString *value;
+} FuriJSON;
+
+FuriString *char_to_furi_string(const char *str);
+
+// check memory
+bool jsmn_memory_check(size_t heap_size);
diff --git a/flip_world/.gitattributes b/flip_world/.gitattributes
new file mode 100644
index 000000000..dfe077042
--- /dev/null
+++ b/flip_world/.gitattributes
@@ -0,0 +1,2 @@
+# Auto detect text files and perform LF normalization
+* text=auto
diff --git a/flip_world/.gitsubtree b/flip_world/.gitsubtree
new file mode 100644
index 000000000..dc338fa12
--- /dev/null
+++ b/flip_world/.gitsubtree
@@ -0,0 +1 @@
+https://github.com/jblanked/FlipWorld main /
diff --git a/flip_world/LICENSE b/flip_world/LICENSE
new file mode 100644
index 000000000..c5e43e291
--- /dev/null
+++ b/flip_world/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2024 jblanked
+
+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.
diff --git a/flip_world/README.md b/flip_world/README.md
new file mode 100644
index 000000000..3af806a3e
--- /dev/null
+++ b/flip_world/README.md
@@ -0,0 +1,84 @@
+# FlipWorld
+
+The first open-world multiplayer game for the Flipper Zero, best played with the VGM. Here's a video tutorial: https://www.youtube.com/watch?v=Qp7qmYMfdUA
+
+## Requirements
+
+- WiFi Developer Board, Raspberry Pi, or ESP32 device with the FlipperHTTP flash: [FlipperHTTP GitHub](https://github.com/jblanked/FlipperHTTP)
+- 2.4 GHz WiFi access point
+
+## How It Works
+
+FlipWorld and FlipSocial are connected. Your login information is the same in both apps; if you register an account in either app, you can log in to both using that information. This also means that your friends, messages, and achievements are synced between apps. You only need a username and password to start, which are set in the User Settings. Keep in mind your username will be displayed to others, so choose wisely.
+
+**Settings**
+
+- **WiFi**: Enter your SSID and password to connect to your 2.4 GHz network.
+- **User**: Add or update your username and password (this is the same login information as your FlipSocial account).
+- **Game**: Install the Official World Pack, set your FPS (30, 60, 120, or 240), and select whether you want the screen backlight to always be on, the sound to be on, and the vibration to be on.
+
+**Controls**
+
+- **Press/Hold LEFT**: Turn left if not already facing left, then walk left if the button is still pressed.
+- **Press/Hold RIGHT**: Turn right if not already facing right, then walk right if the button is still pressed.
+- **Press/Hold UP**: Walk up.
+- **Press/Hold DOWN**: Walk down.
+- **Press/Hold OK**: Attack/Teleport (set to attack until all enemies are defeated).
+
+**Player Attributes**
+
+- **Health**: The amount of life points the player has.
+- **XP**: The amount of experience points the player has.
+- **Level**: The rank/level of the player.
+- **Strength**: The attack power of the player's attacks.
+- **Health Regeneration**: The amount of health a player gains per second.
+- **Attack Timer**: The duration the player must wait between attacks.
+
+As a new player, you have 100 health, 0 XP, 10 strength, 1 health regeneration, an attack timer of 1, and are level 1. Each level, the player gains an extra 1 strength and 10 health. Additionally, the amount of XP needed to level up increases exponentially by 1.5. For example, to reach level 2, you need 100 XP; for level 3, 150 XP; for level 4, 225 XP; and so on.
+
+**Enemies**
+
+Enemies have similar attributes to players but do not have XP or health regeneration. For example, level 1 enemies have 100 health and 10 strength, just like a level 1 player.
+
+**Attacks**
+
+If an enemy attacks you, your health decreases by the enemy's strength (attack power). Additionally, if an enemy defeats you, your XP decreases by the enemy's strength. Conversely, when you successfully attack an enemy, you gain health equal to 10% of the enemy's strength and increase your XP by the enemy's full strength.
+
+An enemy attack registers if the enemy is facing you and collides with you. However, to attack an enemy successfully, the enemy must be facing away from you, and you must collide with them while pressing `OK`.
+
+
+## Short Tutorial
+
+1. Ensure your WiFi Developer Board and Video Game Module are flashed with FlipperHTTP.
+2. Install the app.
+3. Restart your Flipper Zero, then open FlipWorld.
+4. Click `Settings -> WiFi`, then input your WiFi SSID and password.
+5. Hit the `BACK` button, click `User`. If your username is not present, click `Username` and add one. Do the same for the password field.
+6. Go back to the main menu and hit `Play`. It will register an account if necessary and fetch data from our API that's used to render our graphics.
+
+## Roadmap
+
+**v0.2**
+- Game Mechanics
+- Video Game Module support
+
+**v0.3**
+- Stability patch
+
+**v0.4**
+- New game features
+
+**v0.5**
+- ???
+
+**v0.6**
+- ???
+
+**v0.7**
+- ???
+
+**v0.8**
+- Multiplayer support
+
+**v1.0**
+- Official release
diff --git a/flip_world/alloc/alloc.c b/flip_world/alloc/alloc.c
new file mode 100644
index 000000000..d034ac17a
--- /dev/null
+++ b/flip_world/alloc/alloc.c
@@ -0,0 +1,96 @@
+#include
+#include
+
+/**
+ * @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 callback_exit_app(void *context)
+{
+ UNUSED(context);
+ return VIEW_NONE; // Return VIEW_NONE to exit the app
+}
+
+// Function to allocate resources for the FlipWorldApp
+FlipWorldApp *flip_world_app_alloc()
+{
+ FlipWorldApp *app = (FlipWorldApp *)malloc(sizeof(FlipWorldApp));
+
+ Gui *gui = furi_record_open(RECORD_GUI);
+
+ // Allocate ViewDispatcher
+ if (!easy_flipper_set_view_dispatcher(&app->view_dispatcher, gui, app))
+ {
+ return NULL;
+ }
+ view_dispatcher_set_custom_event_callback(app->view_dispatcher, flip_world_custom_event_callback);
+ // Main view
+ if (!easy_flipper_set_view(&app->view_loader, FlipWorldViewLoader, flip_world_loader_draw_callback, NULL, callback_to_submenu, &app->view_dispatcher, app))
+ {
+ return NULL;
+ }
+ flip_world_loader_init(app->view_loader);
+ if (!easy_flipper_set_widget(&app->widget_result, FlipWorldViewWidgetResult, "", callback_to_submenu, &app->view_dispatcher))
+ {
+ return NULL;
+ }
+
+ // Submenu
+ if (!easy_flipper_set_submenu(&app->submenu, FlipWorldViewSubmenu, VERSION_TAG, callback_exit_app, &app->view_dispatcher))
+ {
+ return NULL;
+ }
+ submenu_add_item(app->submenu, "Play", FlipWorldSubmenuIndexRun, callback_submenu_choices, app);
+ submenu_add_item(app->submenu, "About", FlipWorldSubmenuIndexAbout, callback_submenu_choices, app);
+ submenu_add_item(app->submenu, "Settings", FlipWorldSubmenuIndexSettings, callback_submenu_choices, app);
+ //
+
+ // Switch to the main view
+ view_dispatcher_switch_to_view(app->view_dispatcher, FlipWorldViewSubmenu);
+
+ return app;
+}
+
+// Function to free the resources used by FlipWorldApp
+void flip_world_app_free(FlipWorldApp *app)
+{
+ if (!app)
+ {
+ FURI_LOG_E(TAG, "FlipWorldApp is NULL");
+ return;
+ }
+
+ // Free Submenu(s)
+ if (app->submenu)
+ {
+ view_dispatcher_remove_view(app->view_dispatcher, FlipWorldViewSubmenu);
+ submenu_free(app->submenu);
+ }
+ // Free Widget(s)
+ if (app->widget_result)
+ {
+ view_dispatcher_remove_view(app->view_dispatcher, FlipWorldViewWidgetResult);
+ widget_free(app->widget_result);
+ }
+
+ // Free View(s)
+ if (app->view_loader)
+ {
+ view_dispatcher_remove_view(app->view_dispatcher, FlipWorldViewLoader);
+ flip_world_loader_free_model(app->view_loader);
+ view_free(app->view_loader);
+ }
+
+ free_all_views(app, true, true);
+
+ // free the view dispatcher
+ view_dispatcher_free(app->view_dispatcher);
+
+ // close the gui
+ furi_record_close(RECORD_GUI);
+
+ // free the app
+ if (app)
+ free(app);
+}
diff --git a/flip_world/alloc/alloc.h b/flip_world/alloc/alloc.h
new file mode 100644
index 000000000..6cd5b87f2
--- /dev/null
+++ b/flip_world/alloc/alloc.h
@@ -0,0 +1,5 @@
+#pragma once
+#include
+
+FlipWorldApp *flip_world_app_alloc();
+void flip_world_app_free(FlipWorldApp *app);
\ No newline at end of file
diff --git a/flip_world/app.c b/flip_world/app.c
new file mode 100644
index 000000000..96db706db
--- /dev/null
+++ b/flip_world/app.c
@@ -0,0 +1,64 @@
+#include
+#include
+
+// Entry point for the FlipWorld application
+int32_t flip_world_main(void *p)
+{
+ // Suppress unused parameter warning
+ UNUSED(p);
+
+ // Initialize the FlipWorld application
+ FlipWorldApp *app = flip_world_app_alloc();
+ if (!app)
+ {
+ FURI_LOG_E(TAG, "Failed to allocate FlipWorldApp");
+ return -1;
+ }
+
+ // initialize the VGM
+ furi_hal_gpio_init_simple(&gpio_ext_pc1, GpioModeOutputPushPull);
+ furi_hal_gpio_write(&gpio_ext_pc1, false); // pull pin 15 low
+
+ // check if board is connected (Derek Jamison)
+ FlipperHTTP *fhttp = flipper_http_alloc();
+ if (!fhttp)
+ {
+ easy_flipper_dialog("FlipperHTTP Error", "The UART is likely busy.\nEnsure you have the correct\nflash for your board then\nrestart your Flipper Zero.");
+ return -1;
+ }
+
+ if (!flipper_http_ping(fhttp))
+ {
+ FURI_LOG_E(TAG, "Failed to ping the device");
+ flipper_http_free(fhttp);
+ return -1;
+ }
+
+ // Try to wait for pong response.
+ uint32_t counter = 10;
+ while (fhttp->state == INACTIVE && --counter > 0)
+ {
+ FURI_LOG_D(TAG, "Waiting for PONG");
+ furi_delay_ms(100); // this causes a BusFault
+ }
+
+ flipper_http_free(fhttp);
+ if (counter == 0)
+ {
+ easy_flipper_dialog("FlipperHTTP Error", "Ensure your WiFi Developer\nBoard or Pico W is connected\nand the latest FlipperHTTP\nfirmware is installed.");
+ }
+
+ // save app version
+ char app_version[16];
+ snprintf(app_version, sizeof(app_version), "%f", (double)VERSION);
+ save_char("app_version", app_version);
+
+ // Run the view dispatcher
+ view_dispatcher_run(app->view_dispatcher);
+
+ // Free the resources used by the FlipWorld application
+ flip_world_app_free(app);
+
+ // Return 0 to indicate success
+ return 0;
+}
diff --git a/flip_world/app.png b/flip_world/app.png
new file mode 100644
index 000000000..1b7196829
Binary files /dev/null and b/flip_world/app.png differ
diff --git a/flip_world/application.fam b/flip_world/application.fam
new file mode 100644
index 000000000..920b2f883
--- /dev/null
+++ b/flip_world/application.fam
@@ -0,0 +1,21 @@
+App(
+ appid="flip_world",
+ name="FlipWorld",
+ apptype=FlipperAppType.EXTERNAL,
+ entry_point="flip_world_main",
+ stack_size=4 * 1024,
+ fap_icon="app.png",
+ fap_category="GPIO/FlipperHTTP",
+ fap_description="The first open-world multiplayer game, best played with the VGM.",
+ fap_icon_assets="assets",
+ fap_file_assets="file_assets",
+ fap_extbuild=(
+ ExtFile(
+ path="${FAP_SRC_DIR}/file_assets",
+ command="${PYTHON3} ${FAP_SRC_DIR}/engine/scripts/sprite_builder.py ${FAP_SRC_DIR.abspath}/sprites ${TARGET.abspath}/sprites",
+ ),
+ ),
+ fap_author="JBlanked",
+ fap_weburl="https://github.com/jblanked/FlipWorld",
+ fap_version="0.3",
+)
diff --git a/flip_world/assets/01-home.png b/flip_world/assets/01-home.png
new file mode 100644
index 000000000..a13d71ecd
Binary files /dev/null and b/flip_world/assets/01-home.png differ
diff --git a/flip_world/assets/02-town.png b/flip_world/assets/02-town.png
new file mode 100644
index 000000000..3a50d107b
Binary files /dev/null and b/flip_world/assets/02-town.png differ
diff --git a/flip_world/assets/03-town.png b/flip_world/assets/03-town.png
new file mode 100644
index 000000000..3a2f37ae4
Binary files /dev/null and b/flip_world/assets/03-town.png differ
diff --git a/flip_world/assets/04-town.png b/flip_world/assets/04-town.png
new file mode 100644
index 000000000..4df399d03
Binary files /dev/null and b/flip_world/assets/04-town.png differ
diff --git a/flip_world/assets/05-tree.png b/flip_world/assets/05-tree.png
new file mode 100644
index 000000000..47fca819a
Binary files /dev/null and b/flip_world/assets/05-tree.png differ
diff --git a/flip_world/assets/06-tree.png b/flip_world/assets/06-tree.png
new file mode 100644
index 000000000..34a92f52c
Binary files /dev/null and b/flip_world/assets/06-tree.png differ
diff --git a/flip_world/assets/CHANGELOG.md b/flip_world/assets/CHANGELOG.md
new file mode 100644
index 000000000..527fca847
--- /dev/null
+++ b/flip_world/assets/CHANGELOG.md
@@ -0,0 +1,20 @@
+**0.3 (2025-01-14)**
+- Added new worlds.
+- Improved memory allocation.
+- Updated API integration to load and save player attributes.
+- Upgraded FlipperHTTP to the latest version.
+
+**0.2 (2025-01-02)**
+- Added support for the Video Game Module (requires a FlipperHTTP flash).
+- Introduced various enemy types to enhance gameplay.
+- Added features for player health, XP, level, health regeneration, attack, and strength.
+- Implemented vibration, sound, and LED notifications when a player is attacking or being attacked.
+- Displayed the player's username above their character and showed the player's health, XP, and level in the bottom left corner of the screen at all times.
+- Updated all game icons for improved visual appeal.
+- Upgraded to the latest version of the FlipperHTTP library.
+- Revised toggles in the Game Settings to ensure they work as intended.
+- Improved collision mechanics for more accurate interactions.
+- Updated the default icon representing the player's character.
+
+**0.1 (2024-12-21)**
+- Initial release.
\ No newline at end of file
diff --git a/flip_world/assets/README.md b/flip_world/assets/README.md
new file mode 100644
index 000000000..074b8228a
--- /dev/null
+++ b/flip_world/assets/README.md
@@ -0,0 +1,81 @@
+The first open-world multiplayer game for the Flipper Zero, best played with the VGM.
+
+## Requirements
+
+- WiFi Developer Board, Raspberry Pi, or ESP32 device with the FlipperHTTP flash: https://github.com/jblanked/FlipperHTTP
+- 2.4 GHz WiFi access point
+
+## How It Works
+
+FlipWorld and FlipSocial are connected. Your login information is the same in both apps; if you register an account in either app, you can log in to both using that information. This also means that your friends, messages, and achievements are synced between apps. You only need a username and password to start, which are set in the User Settings. Keep in mind your username will be displayed to others, so choose wisely.
+
+**Settings**
+
+- **WiFi**: Enter your SSID and password to connect to your 2.4 GHz network.
+- **User**: Add or update your username and password (this is the same login information as your FlipSocial account).
+- **Game**: Install the Official World Pack, set your FPS (30, 60, 120, or 240), and select whether you want the screen backlight to always be on, the sound to be on, and the vibration to be on.
+
+**Controls**
+
+- **Press/Hold LEFT**: Turn left if not already facing left, then walk left if the button is still pressed.
+- **Press/Hold RIGHT**: Turn right if not already facing right, then walk right if the button is still pressed.
+- **Press/Hold UP**: Walk up.
+- **Press/Hold DOWN**: Walk down.
+- **Press/Hold OK**: Attack/Teleport (set to attack until all enemies are defeated).
+
+**Player Attributes**
+
+- **Health**: The amount of life points the player has.
+- **XP**: The amount of experience points the player has.
+- **Level**: The rank/level of the player.
+- **Strength**: The attack power of the player's attacks.
+- **Health Regeneration**: The amount of health a player gains per second.
+- **Attack Timer**: The duration the player must wait between attacks.
+
+As a new player, you have 100 health, 0 XP, 10 strength, 1 health regeneration, an attack timer of 1, and are level 1. Each level, the player gains an extra 1 strength and 10 health. Additionally, the amount of XP needed to level up increases exponentially by 1.5. For example, to reach level 2, you need 100 XP; for level 3, 150 XP; for level 4, 225 XP; and so on.
+
+**Enemies**
+
+Enemies have similar attributes to players but do not have XP or health regeneration. For example, level 1 enemies have 100 health and 10 strength, just like a level 1 player.
+
+**Attacks**
+
+If an enemy attacks you, your health decreases by the enemy's strength (attack power). Additionally, if an enemy defeats you, your XP decreases by the enemy's strength. Conversely, when you successfully attack an enemy, you gain health equal to 10% of the enemy's strength and increase your XP by the enemy's full strength.
+
+An enemy attack registers if the enemy is facing you and collides with you. However, to attack an enemy successfully, the enemy must be facing away from you, and you must collide with them while pressing `OK`.
+
+## Short Tutorial
+
+1. Ensure your WiFi Developer Board and Video Game Module are flashed with FlipperHTTP.
+2. Install the app.
+3. Restart your Flipper Zero, then open FlipWorld.
+4. Click "Settings -> WiFi", then input your WiFi SSID and password.
+5. Hit the "BACK" button, click "User". If your username is not present, click "Username" and add one. Do the same for the password field.
+6. Go back to the main menu and hit "Play". It will register an account if necessary and fetch data from our API that's used to render our graphics.
+
+## Roadmap
+
+**v0.2**
+- Game Mechanics
+- Video Game Module support
+
+**v0.3**
+- Stability patch
+
+**v0.4**
+- New game features
+
+**v0.5**
+- ???
+
+**v0.6**
+- ???
+
+**v0.7**
+- ???
+
+**v0.8**
+- Multiplayer support
+
+**v1.0**
+- Official release
diff --git a/flip_world/assets/WarningDolphin_45x42.png b/flip_world/assets/WarningDolphin_45x42.png
new file mode 100644
index 000000000..d766ffbb4
Binary files /dev/null and b/flip_world/assets/WarningDolphin_45x42.png differ
diff --git a/flip_world/assets/icon_alien_gun_10x10px.png b/flip_world/assets/icon_alien_gun_10x10px.png
new file mode 100644
index 000000000..676a6f4aa
Binary files /dev/null and b/flip_world/assets/icon_alien_gun_10x10px.png differ
diff --git a/flip_world/assets/icon_axe_10x10px.png b/flip_world/assets/icon_axe_10x10px.png
new file mode 100644
index 000000000..338e9d8d7
Binary files /dev/null and b/flip_world/assets/icon_axe_10x10px.png differ
diff --git a/flip_world/assets/icon_axe_16x16px.png b/flip_world/assets/icon_axe_16x16px.png
new file mode 100644
index 000000000..ebd807616
Binary files /dev/null and b/flip_world/assets/icon_axe_16x16px.png differ
diff --git a/flip_world/assets/icon_axe_alt_10x10px.png b/flip_world/assets/icon_axe_alt_10x10px.png
new file mode 100644
index 000000000..c7702e6e2
Binary files /dev/null and b/flip_world/assets/icon_axe_alt_10x10px.png differ
diff --git a/flip_world/assets/icon_axe_alt_16x16px.png b/flip_world/assets/icon_axe_alt_16x16px.png
new file mode 100644
index 000000000..7e2d29fa8
Binary files /dev/null and b/flip_world/assets/icon_axe_alt_16x16px.png differ
diff --git a/flip_world/assets/icon_bow_10x10px.png b/flip_world/assets/icon_bow_10x10px.png
new file mode 100644
index 000000000..c7889df88
Binary files /dev/null and b/flip_world/assets/icon_bow_10x10px.png differ
diff --git a/flip_world/assets/icon_chest_closed_16x13px.png b/flip_world/assets/icon_chest_closed_16x13px.png
new file mode 100644
index 000000000..6f04fb41b
Binary files /dev/null and b/flip_world/assets/icon_chest_closed_16x13px.png differ
diff --git a/flip_world/assets/icon_chest_open_16x16px.png b/flip_world/assets/icon_chest_open_16x16px.png
new file mode 100644
index 000000000..2eb27e27e
Binary files /dev/null and b/flip_world/assets/icon_chest_open_16x16px.png differ
diff --git a/flip_world/assets/icon_earth_15x16.png b/flip_world/assets/icon_earth_15x16.png
new file mode 100644
index 000000000..a586c215c
Binary files /dev/null and b/flip_world/assets/icon_earth_15x16.png differ
diff --git a/flip_world/assets/icon_fence_16x8px.png b/flip_world/assets/icon_fence_16x8px.png
new file mode 100644
index 000000000..b730291db
Binary files /dev/null and b/flip_world/assets/icon_fence_16x8px.png differ
diff --git a/flip_world/assets/icon_fence_end_16x8px.png b/flip_world/assets/icon_fence_end_16x8px.png
new file mode 100644
index 000000000..4dc7945d5
Binary files /dev/null and b/flip_world/assets/icon_fence_end_16x8px.png differ
diff --git a/flip_world/assets/icon_fence_vertical_end_6x8px.png b/flip_world/assets/icon_fence_vertical_end_6x8px.png
new file mode 100644
index 000000000..4b27759e7
Binary files /dev/null and b/flip_world/assets/icon_fence_vertical_end_6x8px.png differ
diff --git a/flip_world/assets/icon_fence_vertical_start_6x15px.png b/flip_world/assets/icon_fence_vertical_start_6x15px.png
new file mode 100644
index 000000000..0fa1d2036
Binary files /dev/null and b/flip_world/assets/icon_fence_vertical_start_6x15px.png differ
diff --git a/flip_world/assets/icon_flower_16x16.png b/flip_world/assets/icon_flower_16x16.png
new file mode 100644
index 000000000..d369a9cbf
Binary files /dev/null and b/flip_world/assets/icon_flower_16x16.png differ
diff --git a/flip_world/assets/icon_home_15x16.png b/flip_world/assets/icon_home_15x16.png
new file mode 100644
index 000000000..2f485731b
Binary files /dev/null and b/flip_world/assets/icon_home_15x16.png differ
diff --git a/flip_world/assets/icon_house_3d_34x45px.png b/flip_world/assets/icon_house_3d_34x45px.png
new file mode 100644
index 000000000..bb81e9f03
Binary files /dev/null and b/flip_world/assets/icon_house_3d_34x45px.png differ
diff --git a/flip_world/assets/icon_house_48x32px.png b/flip_world/assets/icon_house_48x32px.png
new file mode 100644
index 000000000..858648337
Binary files /dev/null and b/flip_world/assets/icon_house_48x32px.png differ
diff --git a/flip_world/assets/icon_info_15x16.png b/flip_world/assets/icon_info_15x16.png
new file mode 100644
index 000000000..176683969
Binary files /dev/null and b/flip_world/assets/icon_info_15x16.png differ
diff --git a/flip_world/assets/icon_lake_bottom_31x12px.png b/flip_world/assets/icon_lake_bottom_31x12px.png
new file mode 100644
index 000000000..b30dbd1c5
Binary files /dev/null and b/flip_world/assets/icon_lake_bottom_31x12px.png differ
diff --git a/flip_world/assets/icon_lake_bottom_left_24x22px.png b/flip_world/assets/icon_lake_bottom_left_24x22px.png
new file mode 100644
index 000000000..455ef89c1
Binary files /dev/null and b/flip_world/assets/icon_lake_bottom_left_24x22px.png differ
diff --git a/flip_world/assets/icon_lake_bottom_right_24x22px.png b/flip_world/assets/icon_lake_bottom_right_24x22px.png
new file mode 100644
index 000000000..f34acb8b9
Binary files /dev/null and b/flip_world/assets/icon_lake_bottom_right_24x22px.png differ
diff --git a/flip_world/assets/icon_lake_left_11x31px.png b/flip_world/assets/icon_lake_left_11x31px.png
new file mode 100644
index 000000000..89a789f65
Binary files /dev/null and b/flip_world/assets/icon_lake_left_11x31px.png differ
diff --git a/flip_world/assets/icon_lake_right_11x31.png b/flip_world/assets/icon_lake_right_11x31.png
new file mode 100644
index 000000000..138916409
Binary files /dev/null and b/flip_world/assets/icon_lake_right_11x31.png differ
diff --git a/flip_world/assets/icon_lake_top_31x12px.png b/flip_world/assets/icon_lake_top_31x12px.png
new file mode 100644
index 000000000..ae717e1f4
Binary files /dev/null and b/flip_world/assets/icon_lake_top_31x12px.png differ
diff --git a/flip_world/assets/icon_lake_top_left_24x22px.png b/flip_world/assets/icon_lake_top_left_24x22px.png
new file mode 100644
index 000000000..f5d1eee2d
Binary files /dev/null and b/flip_world/assets/icon_lake_top_left_24x22px.png differ
diff --git a/flip_world/assets/icon_lake_top_right_24x22px.png b/flip_world/assets/icon_lake_top_right_24x22px.png
new file mode 100644
index 000000000..2a5472156
Binary files /dev/null and b/flip_world/assets/icon_lake_top_right_24x22px.png differ
diff --git a/flip_world/assets/icon_man_7x16.png b/flip_world/assets/icon_man_7x16.png
new file mode 100644
index 000000000..d475bbe5a
Binary files /dev/null and b/flip_world/assets/icon_man_7x16.png differ
diff --git a/flip_world/assets/icon_menu_128x64px.png b/flip_world/assets/icon_menu_128x64px.png
new file mode 100644
index 000000000..8482581a1
Binary files /dev/null and b/flip_world/assets/icon_menu_128x64px.png differ
diff --git a/flip_world/assets/icon_plant_16x16.png b/flip_world/assets/icon_plant_16x16.png
new file mode 100644
index 000000000..af9c4aba9
Binary files /dev/null and b/flip_world/assets/icon_plant_16x16.png differ
diff --git a/flip_world/assets/icon_plant_fern_18x16px.png b/flip_world/assets/icon_plant_fern_18x16px.png
new file mode 100644
index 000000000..e7558649a
Binary files /dev/null and b/flip_world/assets/icon_plant_fern_18x16px.png differ
diff --git a/flip_world/assets/icon_plant_pointy_13x16px.png b/flip_world/assets/icon_plant_pointy_13x16px.png
new file mode 100644
index 000000000..289401915
Binary files /dev/null and b/flip_world/assets/icon_plant_pointy_13x16px.png differ
diff --git a/flip_world/assets/icon_rock_large_18x19px.png b/flip_world/assets/icon_rock_large_18x19px.png
new file mode 100644
index 000000000..89a381c18
Binary files /dev/null and b/flip_world/assets/icon_rock_large_18x19px.png differ
diff --git a/flip_world/assets/icon_rock_medium_16x14px.png b/flip_world/assets/icon_rock_medium_16x14px.png
new file mode 100644
index 000000000..fe19d5400
Binary files /dev/null and b/flip_world/assets/icon_rock_medium_16x14px.png differ
diff --git a/flip_world/assets/icon_rock_small_10x8px.png b/flip_world/assets/icon_rock_small_10x8px.png
new file mode 100644
index 000000000..2d03db8a2
Binary files /dev/null and b/flip_world/assets/icon_rock_small_10x8px.png differ
diff --git a/flip_world/assets/icon_sword_10x10px.png b/flip_world/assets/icon_sword_10x10px.png
new file mode 100644
index 000000000..d52d7a0ab
Binary files /dev/null and b/flip_world/assets/icon_sword_10x10px.png differ
diff --git a/flip_world/assets/icon_tree_16x16.png b/flip_world/assets/icon_tree_16x16.png
new file mode 100644
index 000000000..0829ddfdc
Binary files /dev/null and b/flip_world/assets/icon_tree_16x16.png differ
diff --git a/flip_world/assets/icon_tree_29x30px.png b/flip_world/assets/icon_tree_29x30px.png
new file mode 100644
index 000000000..c15cd61a7
Binary files /dev/null and b/flip_world/assets/icon_tree_29x30px.png differ
diff --git a/flip_world/assets/icon_tree_48x48px.png b/flip_world/assets/icon_tree_48x48px.png
new file mode 100644
index 000000000..0d3ef1036
Binary files /dev/null and b/flip_world/assets/icon_tree_48x48px.png differ
diff --git a/flip_world/assets/icon_woman_9x16.png b/flip_world/assets/icon_woman_9x16.png
new file mode 100644
index 000000000..f0ddab645
Binary files /dev/null and b/flip_world/assets/icon_woman_9x16.png differ
diff --git a/flip_world/assets/icon_world_change_128x64px.png b/flip_world/assets/icon_world_change_128x64px.png
new file mode 100644
index 000000000..3c65fc8ce
Binary files /dev/null and b/flip_world/assets/icon_world_change_128x64px.png differ
diff --git a/flip_world/callback/callback.c b/flip_world/callback/callback.c
new file mode 100644
index 000000000..a440558f0
--- /dev/null
+++ b/flip_world/callback/callback.c
@@ -0,0 +1,1977 @@
+#include
+#include "engine/engine.h"
+#include "engine/game_engine.h"
+#include "engine/game_manager_i.h"
+#include "engine/level_i.h"
+#include "engine/entity_i.h"
+#include "game/storage.h"
+
+// Below added by Derek Jamison
+// FURI_LOG_DEV will log only during app development. Be sure that Settings/System/Log Device is "LPUART"; so we dont use serial port.
+#ifdef DEVELOPMENT
+#define FURI_LOG_DEV(tag, format, ...) furi_log_print_format(FuriLogLevelInfo, tag, format, ##__VA_ARGS__)
+#define DEV_CRASH() furi_crash()
+#else
+#define FURI_LOG_DEV(tag, format, ...)
+#define DEV_CRASH()
+#endif
+
+static void frame_cb(GameEngine *engine, Canvas *canvas, InputState input, void *context)
+{
+ UNUSED(engine);
+ GameManager *game_manager = context;
+ game_manager_input_set(game_manager, input);
+ game_manager_update(game_manager);
+ game_manager_render(game_manager, canvas);
+}
+
+static int32_t game_app(void *p)
+{
+ UNUSED(p);
+ GameManager *game_manager = game_manager_alloc();
+ if (!game_manager)
+ {
+ FURI_LOG_E("Game", "Failed to allocate game manager");
+ return -1;
+ }
+
+ // Setup game engine settings...
+ GameEngineSettings settings = game_engine_settings_init();
+ settings.target_fps = game_fps_choices_2[game_fps_index];
+ settings.show_fps = game.show_fps;
+ settings.always_backlight = strstr(yes_or_no_choices[game_screen_always_on_index], "Yes") != NULL;
+ settings.frame_callback = frame_cb;
+ settings.context = game_manager;
+ GameEngine *engine = game_engine_alloc(settings);
+ if (!engine)
+ {
+ FURI_LOG_E("Game", "Failed to allocate game engine");
+ game_manager_free(game_manager);
+ return -1;
+ }
+ game_manager_engine_set(game_manager, engine);
+
+ // Allocate custom game context if needed
+ void *game_context = NULL;
+ if (game.context_size > 0)
+ {
+ game_context = malloc(game.context_size);
+ game_manager_game_context_set(game_manager, game_context);
+ }
+
+ // Start the game
+ game.start(game_manager, game_context);
+
+ // 1) Run the engine
+ game_engine_run(engine);
+
+ // 2) Stop the game FIRST, so it can do any internal cleanup
+ game.stop(game_context);
+
+ // 3) Now free the engine
+ game_engine_free(engine);
+
+ // 4) Now free the manager
+ game_manager_free(game_manager);
+
+ // 5) Finally, free your custom context if it was allocated
+ if (game_context)
+ {
+ free(game_context);
+ }
+
+ // 6) Check for leftover entities
+ int32_t entities = entities_get_count();
+ if (entities != 0)
+ {
+ FURI_LOG_E("Game", "Memory leak detected: %ld entities still allocated", entities);
+ return -1;
+ }
+
+ return 0;
+}
+
+static void flip_world_request_error_draw(Canvas *canvas, DataLoaderModel *model)
+{
+ if (canvas == NULL)
+ {
+ FURI_LOG_E(TAG, "flip_world_request_error_draw - canvas is NULL");
+ DEV_CRASH();
+ return;
+ }
+ if (model->fhttp->last_response != NULL)
+ {
+ if (strstr(model->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(model->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 if (strstr(model->fhttp->last_response, "[ERROR] GET request failed or returned empty data.") != NULL)
+ {
+ canvas_clear(canvas);
+ canvas_draw_str(canvas, 0, 10, "[ERROR] WiFi error.");
+ canvas_draw_str(canvas, 0, 50, "Update your WiFi settings.");
+ canvas_draw_str(canvas, 0, 60, "Press BACK to return.");
+ }
+ else if (strstr(model->fhttp->last_response, "[PONG]") != NULL)
+ {
+ canvas_clear(canvas);
+ canvas_draw_str(canvas, 0, 10, "[STATUS]Connecting to AP...");
+ }
+ else
+ {
+ canvas_clear(canvas);
+ FURI_LOG_E(TAG, "Received an error: %s", model->fhttp->last_response);
+ canvas_draw_str(canvas, 0, 10, "[ERROR] Unusual error...");
+ canvas_draw_str(canvas, 0, 60, "Press BACK and retry.");
+ }
+ }
+ else
+ {
+ canvas_clear(canvas);
+ canvas_draw_str(canvas, 0, 10, "[ERROR] Unknown error.");
+ canvas_draw_str(canvas, 0, 50, "Update your WiFi settings.");
+ canvas_draw_str(canvas, 0, 60, "Press BACK to return.");
+ }
+}
+
+static bool alloc_about_view(void *context);
+static bool alloc_text_input_view(void *context, char *title);
+static bool alloc_variable_item_list(void *context, uint32_t view_id);
+//
+static void wifi_settings_item_selected(void *context, uint32_t index);
+static void text_updated_wifi_ssid(void *context);
+static void text_updated_wifi_pass(void *context);
+static void text_updated_username(void *context);
+static void text_updated_password(void *context);
+//
+static void flip_world_game_fps_change(VariableItem *item);
+static void game_settings_item_selected(void *context, uint32_t index);
+static void user_settings_item_selected(void *context, uint32_t index);
+static void flip_world_game_screen_always_on_change(VariableItem *item);
+static void flip_world_game_sound_on_change(VariableItem *item);
+static void flip_world_game_vibration_on_change(VariableItem *item);
+
+uint32_t callback_to_submenu(void *context)
+{
+ UNUSED(context);
+ return FlipWorldViewSubmenu;
+}
+static uint32_t callback_to_wifi_settings(void *context)
+{
+ UNUSED(context);
+ return FlipWorldViewVariableItemList;
+}
+static uint32_t callback_to_settings(void *context)
+{
+ UNUSED(context);
+ return FlipWorldViewSettings;
+}
+
+static void flip_world_view_about_draw_callback(Canvas *canvas, void *model)
+{
+ UNUSED(model);
+ canvas_clear(canvas);
+ // canvas_set_font_custom(canvas, FONT_SIZE_XLARGE);
+ canvas_draw_str(canvas, 0, 10, VERSION_TAG);
+ // canvas_set_font_custom(canvas, FONT_SIZE_MEDIUM);
+ canvas_set_font_custom(canvas, FONT_SIZE_SMALL);
+ canvas_draw_str(canvas, 0, 20, "Dev: JBlanked, codeallnight");
+ canvas_draw_str(canvas, 0, 30, "GFX: the1anonlypr3");
+ canvas_draw_str(canvas, 0, 40, "github.com/jblanked/FlipWorld");
+
+ canvas_draw_str_multi(canvas, 0, 55, "The first open world multiplayer\ngame on the Flipper Zero.");
+}
+
+// alloc
+static bool alloc_about_view(void *context)
+{
+ FlipWorldApp *app = (FlipWorldApp *)context;
+ if (!app)
+ {
+ FURI_LOG_E(TAG, "FlipWorldApp is NULL");
+ return false;
+ }
+ if (!app->view_about)
+ {
+ if (!easy_flipper_set_view(&app->view_about, FlipWorldViewAbout, flip_world_view_about_draw_callback, NULL, callback_to_submenu, &app->view_dispatcher, app))
+ {
+ return false;
+ }
+ if (!app->view_about)
+ {
+ return false;
+ }
+ }
+ return true;
+}
+
+static bool alloc_text_input_view(void *context, char *title)
+{
+ FlipWorldApp *app = (FlipWorldApp *)context;
+ if (!app)
+ {
+ FURI_LOG_E(TAG, "FlipWorldApp is NULL");
+ return false;
+ }
+ if (!title)
+ {
+ FURI_LOG_E(TAG, "Title is NULL");
+ return false;
+ }
+ app->text_input_buffer_size = 64;
+ if (!app->text_input_buffer)
+ {
+ if (!easy_flipper_set_buffer(&app->text_input_buffer, app->text_input_buffer_size))
+ {
+ return false;
+ }
+ }
+ if (!app->text_input_temp_buffer)
+ {
+ if (!easy_flipper_set_buffer(&app->text_input_temp_buffer, app->text_input_buffer_size))
+ {
+ return false;
+ }
+ }
+ if (!app->text_input)
+ {
+ if (!easy_flipper_set_uart_text_input(
+ &app->text_input,
+ FlipWorldViewTextInput,
+ title,
+ app->text_input_temp_buffer,
+ app->text_input_buffer_size,
+ strcmp(title, "SSID") == 0 ? text_updated_wifi_ssid : strcmp(title, "Password") == 0 ? text_updated_wifi_pass
+ : strcmp(title, "Username-Login") == 0 ? text_updated_username
+ : text_updated_password,
+ callback_to_wifi_settings,
+ &app->view_dispatcher,
+ app))
+ {
+ return false;
+ }
+ if (!app->text_input)
+ {
+ return false;
+ }
+ char ssid[64];
+ char pass[64];
+ char username[64];
+ char password[64];
+ if (load_settings(ssid, sizeof(ssid), pass, sizeof(pass), username, sizeof(username), password, sizeof(password)))
+ {
+ if (strcmp(title, "SSID") == 0)
+ {
+ strncpy(app->text_input_temp_buffer, ssid, app->text_input_buffer_size);
+ }
+ else if (strcmp(title, "Password") == 0)
+ {
+ strncpy(app->text_input_temp_buffer, pass, app->text_input_buffer_size);
+ }
+ else if (strcmp(title, "Username-Login") == 0)
+ {
+ strncpy(app->text_input_temp_buffer, username, app->text_input_buffer_size);
+ }
+ else if (strcmp(title, "Password-Login") == 0)
+ {
+ strncpy(app->text_input_temp_buffer, password, app->text_input_buffer_size);
+ }
+ }
+ }
+ return true;
+}
+static bool alloc_variable_item_list(void *context, uint32_t view_id)
+{
+ FlipWorldApp *app = (FlipWorldApp *)context;
+ if (!app)
+ {
+ FURI_LOG_E(TAG, "FlipWorldApp is NULL");
+ return false;
+ }
+ char ssid[64];
+ char pass[64];
+ char username[64];
+ char password[64];
+ if (!app->variable_item_list)
+ {
+ switch (view_id)
+ {
+ case FlipWorldSubmenuIndexWiFiSettings:
+ if (!easy_flipper_set_variable_item_list(&app->variable_item_list, FlipWorldViewVariableItemList, wifi_settings_item_selected, callback_to_settings, &app->view_dispatcher, app))
+ {
+ FURI_LOG_E(TAG, "Failed to allocate variable item list");
+ return false;
+ }
+
+ if (!app->variable_item_list)
+ {
+ FURI_LOG_E(TAG, "Variable item list is NULL");
+ return false;
+ }
+
+ if (!app->variable_item_wifi_ssid)
+ {
+ app->variable_item_wifi_ssid = variable_item_list_add(app->variable_item_list, "SSID", 0, NULL, NULL);
+ variable_item_set_current_value_text(app->variable_item_wifi_ssid, "");
+ }
+ if (!app->variable_item_wifi_pass)
+ {
+ app->variable_item_wifi_pass = variable_item_list_add(app->variable_item_list, "Password", 0, NULL, NULL);
+ variable_item_set_current_value_text(app->variable_item_wifi_pass, "");
+ }
+ if (load_settings(ssid, sizeof(ssid), pass, sizeof(pass), username, sizeof(username), password, sizeof(password)))
+ {
+ variable_item_set_current_value_text(app->variable_item_wifi_ssid, ssid);
+ // variable_item_set_current_value_text(app->variable_item_wifi_pass, pass);
+ save_char("WiFi-SSID", ssid);
+ save_char("WiFi-Password", pass);
+ save_char("Flip-Social-Username", username);
+ save_char("Flip-Social-Password", password);
+ }
+ break;
+ case FlipWorldSubmenuIndexGameSettings:
+ if (!easy_flipper_set_variable_item_list(&app->variable_item_list, FlipWorldViewVariableItemList, game_settings_item_selected, callback_to_settings, &app->view_dispatcher, app))
+ {
+ FURI_LOG_E(TAG, "Failed to allocate variable item list");
+ return false;
+ }
+
+ if (!app->variable_item_list)
+ {
+ FURI_LOG_E(TAG, "Variable item list is NULL");
+ return false;
+ }
+
+ if (!app->variable_item_game_download_world)
+ {
+ app->variable_item_game_download_world = variable_item_list_add(app->variable_item_list, "Install Official World Pack", 0, NULL, NULL);
+ variable_item_set_current_value_text(app->variable_item_game_download_world, "");
+ }
+ if (!app->variable_item_game_fps)
+ {
+ app->variable_item_game_fps = variable_item_list_add(app->variable_item_list, "FPS", 4, flip_world_game_fps_change, NULL);
+ variable_item_set_current_value_index(app->variable_item_game_fps, 0);
+ variable_item_set_current_value_text(app->variable_item_game_fps, game_fps_choices[0]);
+ }
+ if (!app->variable_item_game_screen_always_on)
+ {
+ app->variable_item_game_screen_always_on = variable_item_list_add(app->variable_item_list, "Keep Screen On?", 2, flip_world_game_screen_always_on_change, NULL);
+ variable_item_set_current_value_index(app->variable_item_game_screen_always_on, 1);
+ variable_item_set_current_value_text(app->variable_item_game_screen_always_on, yes_or_no_choices[1]);
+ }
+ if (!app->variable_item_game_sound_on)
+ {
+ app->variable_item_game_sound_on = variable_item_list_add(app->variable_item_list, "Sound On?", 2, flip_world_game_sound_on_change, NULL);
+ variable_item_set_current_value_index(app->variable_item_game_sound_on, 0);
+ variable_item_set_current_value_text(app->variable_item_game_sound_on, yes_or_no_choices[0]);
+ }
+ if (!app->variable_item_game_vibration_on)
+ {
+ app->variable_item_game_vibration_on = variable_item_list_add(app->variable_item_list, "Vibration On?", 2, flip_world_game_vibration_on_change, NULL);
+ variable_item_set_current_value_index(app->variable_item_game_vibration_on, 0);
+ variable_item_set_current_value_text(app->variable_item_game_vibration_on, yes_or_no_choices[0]);
+ }
+ char _game_fps[8];
+ if (load_char("Game-FPS", _game_fps, sizeof(_game_fps)))
+ {
+ int index = strcmp(_game_fps, "30") == 0 ? 0 : strcmp(_game_fps, "60") == 0 ? 1
+ : strcmp(_game_fps, "120") == 0 ? 2
+ : strcmp(_game_fps, "240") == 0 ? 3
+ : 0;
+ variable_item_set_current_value_text(app->variable_item_game_fps, game_fps_choices[index]);
+ variable_item_set_current_value_index(app->variable_item_game_fps, index);
+ }
+ char _game_screen_always_on[8];
+ if (load_char("Game-Screen-Always-On", _game_screen_always_on, sizeof(_game_screen_always_on)))
+ {
+ int index = strcmp(_game_screen_always_on, "No") == 0 ? 0 : strcmp(_game_screen_always_on, "Yes") == 0 ? 1
+ : 0;
+ variable_item_set_current_value_text(app->variable_item_game_screen_always_on, yes_or_no_choices[index]);
+ variable_item_set_current_value_index(app->variable_item_game_screen_always_on, index);
+ }
+ char _game_sound_on[8];
+ if (load_char("Game-Sound-On", _game_sound_on, sizeof(_game_sound_on)))
+ {
+ int index = strcmp(_game_sound_on, "No") == 0 ? 0 : strcmp(_game_sound_on, "Yes") == 0 ? 1
+ : 0;
+ variable_item_set_current_value_text(app->variable_item_game_sound_on, yes_or_no_choices[index]);
+ variable_item_set_current_value_index(app->variable_item_game_sound_on, index);
+ }
+ char _game_vibration_on[8];
+ if (load_char("Game-Vibration-On", _game_vibration_on, sizeof(_game_vibration_on)))
+ {
+ int index = strcmp(_game_vibration_on, "No") == 0 ? 0 : strcmp(_game_vibration_on, "Yes") == 0 ? 1
+ : 0;
+ variable_item_set_current_value_text(app->variable_item_game_vibration_on, yes_or_no_choices[index]);
+ variable_item_set_current_value_index(app->variable_item_game_vibration_on, index);
+ }
+ break;
+ case FlipWorldSubmenuIndexUserSettings:
+ if (!easy_flipper_set_variable_item_list(&app->variable_item_list, FlipWorldViewVariableItemList, user_settings_item_selected, callback_to_settings, &app->view_dispatcher, app))
+ {
+ FURI_LOG_E(TAG, "Failed to allocate variable item list");
+ return false;
+ }
+
+ if (!app->variable_item_list)
+ {
+ FURI_LOG_E(TAG, "Variable item list is NULL");
+ return false;
+ }
+
+ // if logged in, show profile info, otherwise show login/register
+ if (is_logged_in() || is_logged_in_to_flip_social())
+ {
+ if (!app->variable_item_user_username)
+ {
+ app->variable_item_user_username = variable_item_list_add(app->variable_item_list, "Username", 0, NULL, NULL);
+ variable_item_set_current_value_text(app->variable_item_user_username, "");
+ }
+ if (!app->variable_item_user_password)
+ {
+ app->variable_item_user_password = variable_item_list_add(app->variable_item_list, "Password", 0, NULL, NULL);
+ variable_item_set_current_value_text(app->variable_item_user_password, "");
+ }
+ if (load_settings(ssid, sizeof(ssid), pass, sizeof(pass), username, sizeof(username), password, sizeof(password)))
+ {
+ variable_item_set_current_value_text(app->variable_item_user_username, username);
+ variable_item_set_current_value_text(app->variable_item_user_password, "*****");
+ }
+ }
+ else
+ {
+ if (!app->variable_item_user_username)
+ {
+ app->variable_item_user_username = variable_item_list_add(app->variable_item_list, "Username", 0, NULL, NULL);
+ variable_item_set_current_value_text(app->variable_item_user_username, "");
+ }
+ if (!app->variable_item_user_password)
+ {
+ app->variable_item_user_password = variable_item_list_add(app->variable_item_list, "Password", 0, NULL, NULL);
+ variable_item_set_current_value_text(app->variable_item_user_password, "");
+ }
+ }
+ break;
+ }
+ }
+ return true;
+}
+static bool alloc_submenu_settings(void *context)
+{
+ FlipWorldApp *app = (FlipWorldApp *)context;
+ if (!app)
+ {
+ FURI_LOG_E(TAG, "FlipWorldApp is NULL");
+ return false;
+ }
+ if (!app->submenu_settings)
+ {
+ if (!easy_flipper_set_submenu(&app->submenu_settings, FlipWorldViewSettings, "Settings", callback_to_submenu, &app->view_dispatcher))
+ {
+ return NULL;
+ }
+ if (!app->submenu_settings)
+ {
+ return false;
+ }
+ submenu_add_item(app->submenu_settings, "WiFi", FlipWorldSubmenuIndexWiFiSettings, callback_submenu_choices, app);
+ submenu_add_item(app->submenu_settings, "Game", FlipWorldSubmenuIndexGameSettings, callback_submenu_choices, app);
+ submenu_add_item(app->submenu_settings, "User", FlipWorldSubmenuIndexUserSettings, callback_submenu_choices, app);
+ }
+ return true;
+}
+// free
+static void free_about_view(void *context)
+{
+ FlipWorldApp *app = (FlipWorldApp *)context;
+ if (!app)
+ {
+ FURI_LOG_E(TAG, "FlipWorldApp is NULL");
+ return;
+ }
+ if (app->view_about)
+ {
+ view_dispatcher_remove_view(app->view_dispatcher, FlipWorldViewAbout);
+ view_free(app->view_about);
+ app->view_about = NULL;
+ }
+}
+
+static void free_text_input_view(void *context)
+{
+ FlipWorldApp *app = (FlipWorldApp *)context;
+ if (!app)
+ {
+ FURI_LOG_E(TAG, "FlipWorldApp is NULL");
+ return;
+ }
+ if (app->text_input)
+ {
+ view_dispatcher_remove_view(app->view_dispatcher, FlipWorldViewTextInput);
+ text_input_free(app->text_input);
+ app->text_input = NULL;
+ }
+ if (app->text_input_buffer)
+ {
+ free(app->text_input_buffer);
+ app->text_input_buffer = NULL;
+ }
+ if (app->text_input_temp_buffer)
+ {
+ free(app->text_input_temp_buffer);
+ app->text_input_temp_buffer = NULL;
+ }
+}
+static void free_variable_item_list(void *context)
+{
+ FlipWorldApp *app = (FlipWorldApp *)context;
+ if (!app)
+ {
+ FURI_LOG_E(TAG, "FlipWorldApp is NULL");
+ return;
+ }
+ if (app->variable_item_list)
+ {
+ view_dispatcher_remove_view(app->view_dispatcher, FlipWorldViewVariableItemList);
+ variable_item_list_free(app->variable_item_list);
+ app->variable_item_list = NULL;
+ }
+ if (app->variable_item_wifi_ssid)
+ {
+ free(app->variable_item_wifi_ssid);
+ app->variable_item_wifi_ssid = NULL;
+ }
+ if (app->variable_item_wifi_pass)
+ {
+ free(app->variable_item_wifi_pass);
+ app->variable_item_wifi_pass = NULL;
+ }
+ if (app->variable_item_game_fps)
+ {
+ free(app->variable_item_game_fps);
+ app->variable_item_game_fps = NULL;
+ }
+ if (app->variable_item_game_screen_always_on)
+ {
+ free(app->variable_item_game_screen_always_on);
+ app->variable_item_game_screen_always_on = NULL;
+ }
+ if (app->variable_item_game_download_world)
+ {
+ free(app->variable_item_game_download_world);
+ app->variable_item_game_download_world = NULL;
+ }
+ if (app->variable_item_game_sound_on)
+ {
+ free(app->variable_item_game_sound_on);
+ app->variable_item_game_sound_on = NULL;
+ }
+ if (app->variable_item_game_vibration_on)
+ {
+ free(app->variable_item_game_vibration_on);
+ app->variable_item_game_vibration_on = NULL;
+ }
+ if (app->variable_item_user_username)
+ {
+ free(app->variable_item_user_username);
+ app->variable_item_user_username = NULL;
+ }
+ if (app->variable_item_user_password)
+ {
+ free(app->variable_item_user_password);
+ app->variable_item_user_password = NULL;
+ }
+}
+static void free_submenu_settings(void *context)
+{
+ FlipWorldApp *app = (FlipWorldApp *)context;
+ if (!app)
+ {
+ FURI_LOG_E(TAG, "FlipWorldApp is NULL");
+ return;
+ }
+ if (app->submenu_settings)
+ {
+ view_dispatcher_remove_view(app->view_dispatcher, FlipWorldViewSettings);
+ submenu_free(app->submenu_settings);
+ app->submenu_settings = NULL;
+ }
+}
+static FuriThreadId thread_id;
+static bool game_thread_running = false;
+void free_all_views(void *context, bool should_free_variable_item_list, bool should_free_submenu_settings)
+{
+ FlipWorldApp *app = (FlipWorldApp *)context;
+ if (!app)
+ {
+ FURI_LOG_E(TAG, "FlipWorldApp is NULL");
+ return;
+ }
+ if (should_free_variable_item_list)
+ {
+ free_variable_item_list(app);
+ }
+ free_about_view(app);
+ free_text_input_view(app);
+
+ // free game thread
+ if (game_thread_running)
+ {
+ game_thread_running = false;
+ furi_thread_flags_set(thread_id, WorkerEvtStop);
+ furi_thread_free(thread_id);
+ }
+
+ if (should_free_submenu_settings)
+ free_submenu_settings(app);
+}
+static bool fetch_world_list(FlipperHTTP *fhttp)
+{
+ if (!fhttp)
+ {
+ FURI_LOG_E(TAG, "fhttp is NULL");
+ easy_flipper_dialog("Error", "fhttp is NULL. Press BACK to return.");
+ return false;
+ }
+ // Create the directory for saving worlds
+ char directory_path[128];
+ snprintf(directory_path, sizeof(directory_path), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_world/worlds");
+
+ // Create the directory
+ Storage *storage = furi_record_open(RECORD_STORAGE);
+ storage_common_mkdir(storage, directory_path);
+
+ // free storage
+ furi_record_close(RECORD_STORAGE);
+
+ snprintf(
+ fhttp->file_path,
+ sizeof(fhttp->file_path),
+ STORAGE_EXT_PATH_PREFIX "/apps_data/flip_world/worlds/world_list.json");
+
+ fhttp->save_received_data = true;
+ return flipper_http_get_request_with_headers(fhttp, "https://www.flipsocial.net/api/world/v3/list/10/", "{\"Content-Type\":\"application/json\"}");
+}
+// we will load the palyer stats from the API and save them
+// in player_spawn game method, it will load the player stats that we saved
+static bool fetch_player_stats(FlipperHTTP *fhttp)
+{
+ if (!fhttp)
+ {
+ FURI_LOG_E(TAG, "fhttp is NULL");
+ easy_flipper_dialog("Error", "fhttp is NULL. Press BACK to return.");
+ return false;
+ }
+ char username[64];
+ if (!load_char("Flip-Social-Username", username, sizeof(username)))
+ {
+ FURI_LOG_E(TAG, "Failed to load Flip-Social-Username");
+ easy_flipper_dialog("Error", "Failed to load saved username. Go to settings to update.");
+ return false;
+ }
+ char url[128];
+ snprintf(url, sizeof(url), "https://www.flipsocial.net/api/user/game-stats/%s/", username);
+ snprintf(
+ fhttp->file_path,
+ sizeof(fhttp->file_path),
+ STORAGE_EXT_PATH_PREFIX "/apps_data/flip_world/data/player/player_stats.json");
+
+ fhttp->save_received_data = true;
+ return flipper_http_get_request_with_headers(fhttp, url, "{\"Content-Type\":\"application/json\"}");
+}
+
+static bool start_game_thread(void *context)
+{
+ FlipWorldApp *app = (FlipWorldApp *)context;
+ if (!app)
+ {
+ FURI_LOG_E(TAG, "app is NULL");
+ easy_flipper_dialog("Error", "app is NULL. Press BACK to return.");
+ return false;
+ }
+ // free game thread
+ if (game_thread_running)
+ {
+ game_thread_running = false;
+ furi_thread_flags_set(thread_id, WorkerEvtStop);
+ furi_thread_free(thread_id);
+ }
+ // start game thread
+ FuriThread *thread = furi_thread_alloc_ex("game", 2048, game_app, app);
+ if (!thread)
+ {
+ FURI_LOG_E(TAG, "Failed to allocate game thread");
+ easy_flipper_dialog("Error", "Failed to allocate game thread. Restart your Flipper.");
+ return false;
+ }
+ furi_thread_start(thread);
+ thread_id = furi_thread_get_id(thread);
+ game_thread_running = true;
+ return true;
+}
+// combine register, login, and world list fetch into one function to switch to the loader view
+static bool flip_world_fetch_game(DataLoaderModel *model)
+{
+ FlipWorldApp *app = (FlipWorldApp *)model->parser_context;
+ if (!app)
+ {
+ FURI_LOG_E(TAG, "app is NULL");
+ easy_flipper_dialog("Error", "app is NULL. Press BACK to return.");
+ return false;
+ }
+ if (model->request_index == 0)
+ {
+ // login
+ char username[64];
+ char password[64];
+ if (!load_char("Flip-Social-Username", username, sizeof(username)))
+ {
+ FURI_LOG_E(TAG, "Failed to load Flip-Social-Username");
+ view_dispatcher_switch_to_view(app->view_dispatcher, FlipWorldViewSubmenu); // just go back to the main menu for now
+ easy_flipper_dialog("Error", "Failed to load saved username\nGo to user settings to update.");
+ return false;
+ }
+ if (!load_char("Flip-Social-Password", password, sizeof(password)))
+ {
+ FURI_LOG_E(TAG, "Failed to load Flip-Social-Password");
+ view_dispatcher_switch_to_view(app->view_dispatcher, FlipWorldViewSubmenu); // just go back to the main menu for now
+ easy_flipper_dialog("Error", "Failed to load saved password\nGo to settings to update.");
+ return false;
+ }
+ char payload[256];
+ snprintf(payload, sizeof(payload), "{\"username\":\"%s\",\"password\":\"%s\"}", username, password);
+ return flipper_http_post_request_with_headers(model->fhttp, "https://www.flipsocial.net/api/user/login/", "{\"Content-Type\":\"application/json\"}", payload);
+ }
+ else if (model->request_index == 1)
+ {
+ // check if login was successful
+ char is_logged_in[8];
+ if (!load_char("is_logged_in", is_logged_in, sizeof(is_logged_in)))
+ {
+ FURI_LOG_E(TAG, "Failed to load is_logged_in");
+ easy_flipper_dialog("Error", "Failed to load is_logged_in\nGo to user settings to update.");
+ view_dispatcher_switch_to_view(app->view_dispatcher, FlipWorldViewSubmenu); // just go back to the main menu for now
+ return false;
+ }
+ if (strcmp(is_logged_in, "false") == 0 && strcmp(model->title, "Registering...") == 0)
+ {
+ // register
+ char username[64];
+ char password[64];
+ if (!load_char("Flip-Social-Username", username, sizeof(username)))
+ {
+ FURI_LOG_E(TAG, "Failed to load Flip-Social-Username");
+ easy_flipper_dialog("Error", "Failed to load saved username. Go to settings to update.");
+ view_dispatcher_switch_to_view(app->view_dispatcher, FlipWorldViewSubmenu); // just go back to the main menu for now
+ return false;
+ }
+ if (!load_char("Flip-Social-Password", password, sizeof(password)))
+ {
+ FURI_LOG_E(TAG, "Failed to load Flip-Social-Password");
+ easy_flipper_dialog("Error", "Failed to load saved password. Go to settings to update.");
+ view_dispatcher_switch_to_view(app->view_dispatcher, FlipWorldViewSubmenu); // just go back to the main menu for now
+ return false;
+ }
+ char payload[172];
+ snprintf(payload, sizeof(payload), "{\"username\":\"%s\",\"password\":\"%s\"}", username, password);
+ model->title = "Registering...";
+ return flipper_http_post_request_with_headers(model->fhttp, "https://www.flipsocial.net/api/user/register/", "{\"Content-Type\":\"application/json\"}", payload);
+ }
+ else
+ {
+ model->title = "Fetching World List..";
+ return fetch_world_list(model->fhttp);
+ }
+ }
+ else if (model->request_index == 2)
+ {
+ model->title = "Fetching World List..";
+ return fetch_world_list(model->fhttp);
+ }
+ else if (model->request_index == 3)
+ {
+ snprintf(
+ model->fhttp->file_path,
+ sizeof(model->fhttp->file_path),
+ STORAGE_EXT_PATH_PREFIX "/apps_data/flip_world/worlds/world_list.json");
+
+ FuriString *world_list = flipper_http_load_from_file(model->fhttp->file_path);
+ if (!world_list)
+ {
+ view_dispatcher_switch_to_view(app->view_dispatcher, FlipWorldViewSubmenu); // just go back to the main menu for now
+ FURI_LOG_E(TAG, "Failed to load world list");
+ easy_flipper_dialog("Error", "Failed to load world list. Go to game settings to download packs.");
+ return false;
+ }
+ FuriString *first_world = get_json_array_value_furi("worlds", 0, world_list);
+ if (!first_world)
+ {
+ view_dispatcher_switch_to_view(app->view_dispatcher, FlipWorldViewSubmenu); // just go back to the main menu for now
+ FURI_LOG_E(TAG, "Failed to get first world");
+ easy_flipper_dialog("Error", "Failed to get first world. Go to game settings to download packs.");
+ furi_string_free(world_list);
+ return false;
+ }
+ if (world_exists(furi_string_get_cstr(first_world)))
+ {
+ furi_string_free(world_list);
+ furi_string_free(first_world);
+
+ if (!start_game_thread(app))
+ {
+ FURI_LOG_E(TAG, "Failed to start game thread");
+ easy_flipper_dialog("Error", "Failed to start game thread. Press BACK to return.");
+ view_dispatcher_switch_to_view(app->view_dispatcher, FlipWorldViewSubmenu); // just go back to the main menu for now
+ return "Failed to start game thread";
+ }
+ return true;
+ }
+ snprintf(
+ model->fhttp->file_path,
+ sizeof(model->fhttp->file_path),
+ STORAGE_EXT_PATH_PREFIX "/apps_data/flip_world/worlds/%s.json", furi_string_get_cstr(first_world));
+
+ model->fhttp->save_received_data = true;
+ char url[128];
+ snprintf(url, sizeof(url), "https://www.flipsocial.net/api/world/v3/get/world/%s/", furi_string_get_cstr(first_world));
+ furi_string_free(world_list);
+ furi_string_free(first_world);
+ return flipper_http_get_request_with_headers(model->fhttp, url, "{\"Content-Type\":\"application/json\"}");
+ }
+ FURI_LOG_E(TAG, "Unknown request index");
+ return false;
+}
+static char *flip_world_parse_game(DataLoaderModel *model)
+{
+ FlipWorldApp *app = (FlipWorldApp *)model->parser_context;
+
+ if (model->request_index == 0)
+ {
+ if (!model->fhttp->last_response)
+ {
+ save_char("is_logged_in", "false");
+ // Go back to the main menu
+ easy_flipper_dialog("Error", "Response is empty. Press BACK to return.");
+ view_dispatcher_switch_to_view(app->view_dispatcher, FlipWorldViewSubmenu);
+ return "Response is empty...";
+ }
+
+ // Check for successful conditions
+ if (strstr(model->fhttp->last_response, "[SUCCESS]") != NULL || strstr(model->fhttp->last_response, "User found") != NULL)
+ {
+ save_char("is_logged_in", "true");
+ model->title = "Login successful!";
+ model->title = "Fetching World List..";
+ return "Login successful!";
+ }
+
+ // Check if user not found
+ if (strstr(model->fhttp->last_response, "User not found") != NULL)
+ {
+ save_char("is_logged_in", "false");
+ model->title = "Registering...";
+ return "Account not found...\nRegistering now.."; // if they see this an issue happened switching to register
+ }
+
+ // If not success, not found, check length conditions
+ size_t resp_len = strlen(model->fhttp->last_response);
+ if (resp_len == 0 || resp_len > 127)
+ {
+ // Empty or too long means failed login
+ save_char("is_logged_in", "false");
+ // Go back to the main menu
+ easy_flipper_dialog("Error", "Failed to login. Press BACK to return.");
+ view_dispatcher_switch_to_view(app->view_dispatcher, FlipWorldViewSubmenu);
+ return "Failed to login...";
+ }
+
+ // Handle any other unknown response as a failure
+ save_char("is_logged_in", "false");
+ // Go back to the main menu
+ easy_flipper_dialog("Error", "Failed to login. Press BACK to return.");
+ view_dispatcher_switch_to_view(app->view_dispatcher, FlipWorldViewSubmenu);
+ return "Failed to login...";
+ }
+ else if (model->request_index == 1)
+ {
+ if (strcmp(model->title, "Registering...") == 0)
+ {
+ // check registration response
+ if (model->fhttp->last_response != NULL && (strstr(model->fhttp->last_response, "[SUCCESS]") != NULL || strstr(model->fhttp->last_response, "User created") != NULL))
+ {
+ save_char("is_logged_in", "true");
+ char username[64];
+ char password[64];
+ // load the username and password, then save them
+ if (!load_char("Flip-Social-Username", username, sizeof(username)))
+ {
+ FURI_LOG_E(TAG, "Failed to load Flip-Social-Username");
+ easy_flipper_dialog("Error", "Failed to load Flip-Social-Username");
+ view_dispatcher_switch_to_view(app->view_dispatcher, FlipWorldViewSubmenu);
+ return "Failed to load Flip-Social-Username";
+ }
+ if (!load_char("Flip-Social-Password", password, sizeof(password)))
+ {
+ FURI_LOG_E(TAG, "Failed to load Flip-Social-Password");
+ easy_flipper_dialog("Error", "Failed to load Flip-Social-Password");
+ view_dispatcher_switch_to_view(app->view_dispatcher, FlipWorldViewSubmenu);
+ return "Failed to load Flip-Social-Password";
+ }
+ // load wifi ssid,pass then save
+ char ssid[64];
+ char pass[64];
+ if (!load_char("WiFi-SSID", ssid, sizeof(ssid)))
+ {
+ FURI_LOG_E(TAG, "Failed to load WiFi-SSID");
+ easy_flipper_dialog("Error", "Failed to load WiFi-SSID");
+ view_dispatcher_switch_to_view(app->view_dispatcher, FlipWorldViewSubmenu);
+ return "Failed to load WiFi-SSID";
+ }
+ if (!load_char("WiFi-Password", pass, sizeof(pass)))
+ {
+ FURI_LOG_E(TAG, "Failed to load WiFi-Password");
+ easy_flipper_dialog("Error", "Failed to load WiFi-Password");
+ view_dispatcher_switch_to_view(app->view_dispatcher, FlipWorldViewSubmenu);
+ return "Failed to load WiFi-Password";
+ }
+ save_settings(ssid, pass, username, password);
+ model->title = "Fetching World List..";
+ return "Account created!";
+ }
+ else if (strstr(model->fhttp->last_response, "Username or password not provided") != NULL)
+ {
+ easy_flipper_dialog("Error", "Please enter your credentials.\nPress BACK to return.");
+ view_dispatcher_switch_to_view(app->view_dispatcher, FlipWorldViewSubmenu); // just go back to the main menu for now
+ return "Please enter your credentials.";
+ }
+ else if (strstr(model->fhttp->last_response, "User already exists") != NULL || strstr(model->fhttp->last_response, "Multiple users found") != NULL)
+ {
+ easy_flipper_dialog("Error", "Registration failed...\nUsername already exists.\nPress BACK to return.");
+ view_dispatcher_switch_to_view(app->view_dispatcher, FlipWorldViewSubmenu); // just go back to the main menu for now
+ return "Username already exists.";
+ }
+ else
+ {
+ easy_flipper_dialog("Error", "Registration failed...\nUpdate your credentials.\nPress BACK to return.");
+ view_dispatcher_switch_to_view(app->view_dispatcher, FlipWorldViewSubmenu); // just go back to the main menu for now
+ return "Registration failed...";
+ }
+ }
+ else
+ {
+ if (!start_game_thread(app))
+ {
+ FURI_LOG_E(TAG, "Failed to start game thread");
+ easy_flipper_dialog("Error", "Failed to start game thread. Press BACK to return.");
+ view_dispatcher_switch_to_view(app->view_dispatcher, FlipWorldViewSubmenu); // just go back to the main menu for now
+ return "Failed to start game thread";
+ }
+ return "Thanks for playing FlipWorld!\n\n\n\nPress BACK to return if this\ndoesn't automatically close.";
+ }
+ }
+ else if (model->request_index == 2)
+ {
+ return "Welcome to FlipWorld!\n\n\n\nPress BACK to return if this\ndoesn't automatically close.";
+ }
+ else if (model->request_index == 3)
+ {
+ if (!start_game_thread(app))
+ {
+ FURI_LOG_E(TAG, "Failed to start game thread");
+ easy_flipper_dialog("Error", "Failed to start game thread. Press BACK to return.");
+ view_dispatcher_switch_to_view(app->view_dispatcher, FlipWorldViewSubmenu); // just go back to the main menu for now
+ return "Failed to start game thread";
+ }
+ return "Thanks for playing FlipWorld!\n\n\n\nPress BACK to return if this\ndoesn't automatically close.";
+ }
+ easy_flipper_dialog("Error", "Unknown error. Press BACK to return.");
+ view_dispatcher_switch_to_view(app->view_dispatcher, FlipWorldViewSubmenu); // just go back to the main menu for now
+ return "Unknown error";
+}
+void flip_world_switch_to_view_get_game(FlipWorldApp *app)
+{
+ flip_world_generic_switch_to_view(app, "Starting Game..", flip_world_fetch_game, flip_world_parse_game, 5, callback_to_submenu, FlipWorldViewLoader);
+}
+
+void callback_submenu_choices(void *context, uint32_t index)
+{
+ FlipWorldApp *app = (FlipWorldApp *)context;
+ if (!app)
+ {
+ FURI_LOG_E(TAG, "FlipWorldApp is NULL");
+ return;
+ }
+ switch (index)
+ {
+ case FlipWorldSubmenuIndexRun:
+ free_all_views(app, true, true);
+ if (!is_enough_heap(45000)) // lowered from 60k to 45k since we saved 15k bytes
+ {
+ easy_flipper_dialog("Error", "Not enough heap memory.\nPlease restart your Flipper.");
+ return;
+ }
+ // check if logged in
+ if (is_logged_in() || is_logged_in_to_flip_social())
+ {
+ FlipperHTTP *fhttp = flipper_http_alloc();
+ if (!fhttp)
+ {
+ FURI_LOG_E(TAG, "Failed to allocate FlipperHTTP");
+ easy_flipper_dialog("Error", "Failed to allocate FlipperHTTP. Press BACK to return.");
+ return;
+ }
+ bool fetch_world_list_i()
+ {
+ return fetch_world_list(fhttp);
+ }
+ bool parse_world_list_i()
+ {
+ return fhttp->state != ISSUE;
+ }
+
+ bool fetch_player_stats_i()
+ {
+ return fetch_player_stats(fhttp);
+ }
+
+ Loading *loading;
+ int32_t loading_view_id = 987654321; // Random ID
+
+ loading = loading_alloc();
+ if (!loading)
+ {
+ FURI_LOG_E(HTTP_TAG, "Failed to allocate loading");
+ view_dispatcher_switch_to_view(app->view_dispatcher, FlipWorldViewSubmenu);
+ flipper_http_free(fhttp);
+ return;
+ }
+
+ view_dispatcher_add_view(app->view_dispatcher, loading_view_id, loading_get_view(loading));
+
+ // Switch to the loading view
+ view_dispatcher_switch_to_view(app->view_dispatcher, loading_view_id);
+
+ // Make the request
+ if (!flipper_http_process_response_async(fhttp, fetch_world_list_i, parse_world_list_i) ||
+ !flipper_http_process_response_async(fhttp, fetch_player_stats_i, set_player_context))
+ {
+ FURI_LOG_E(HTTP_TAG, "Failed to make request");
+ view_dispatcher_switch_to_view(app->view_dispatcher, FlipWorldViewSubmenu);
+ view_dispatcher_remove_view(app->view_dispatcher, loading_view_id);
+ loading_free(loading);
+ flipper_http_free(fhttp);
+ }
+ else
+ {
+ view_dispatcher_switch_to_view(app->view_dispatcher, FlipWorldViewSubmenu);
+ view_dispatcher_remove_view(app->view_dispatcher, loading_view_id);
+ loading_free(loading);
+ flipper_http_free(fhttp);
+ }
+ if (!start_game_thread(app))
+ {
+ FURI_LOG_E(TAG, "Failed to start game thread");
+ easy_flipper_dialog("Error", "Failed to start game thread. Press BACK to return.");
+ return;
+ }
+
+ easy_flipper_dialog("Starting Game", "Please wait...");
+ }
+ else
+ {
+ flip_world_switch_to_view_get_game(app);
+ }
+ break;
+ case FlipWorldSubmenuIndexAbout:
+ free_all_views(app, true, true);
+ if (!alloc_about_view(app))
+ {
+ FURI_LOG_E(TAG, "Failed to allocate about view");
+ return;
+ }
+ view_dispatcher_switch_to_view(app->view_dispatcher, FlipWorldViewAbout);
+ break;
+ case FlipWorldSubmenuIndexSettings:
+ free_all_views(app, true, true);
+ if (!alloc_submenu_settings(app))
+ {
+ FURI_LOG_E(TAG, "Failed to allocate settings view");
+ return;
+ }
+ view_dispatcher_switch_to_view(app->view_dispatcher, FlipWorldViewSettings);
+ break;
+ case FlipWorldSubmenuIndexWiFiSettings:
+ free_all_views(app, true, false);
+ if (!alloc_variable_item_list(app, FlipWorldSubmenuIndexWiFiSettings))
+ {
+ FURI_LOG_E(TAG, "Failed to allocate variable item list");
+ return;
+ }
+ view_dispatcher_switch_to_view(app->view_dispatcher, FlipWorldViewVariableItemList);
+ break;
+ case FlipWorldSubmenuIndexGameSettings:
+ free_all_views(app, true, false);
+ if (!alloc_variable_item_list(app, FlipWorldSubmenuIndexGameSettings))
+ {
+ FURI_LOG_E(TAG, "Failed to allocate variable item list");
+ return;
+ }
+ view_dispatcher_switch_to_view(app->view_dispatcher, FlipWorldViewVariableItemList);
+ break;
+ case FlipWorldSubmenuIndexUserSettings:
+ free_all_views(app, true, false);
+ if (!alloc_variable_item_list(app, FlipWorldSubmenuIndexUserSettings))
+ {
+ FURI_LOG_E(TAG, "Failed to allocate variable item list");
+ return;
+ }
+ view_dispatcher_switch_to_view(app->view_dispatcher, FlipWorldViewVariableItemList);
+ break;
+ default:
+ break;
+ }
+}
+
+static void text_updated_wifi_ssid(void *context)
+{
+ FlipWorldApp *app = (FlipWorldApp *)context;
+ if (!app)
+ {
+ FURI_LOG_E(TAG, "FlipWorldApp is NULL");
+ return;
+ }
+
+ // store the entered text
+ strncpy(app->text_input_buffer, app->text_input_temp_buffer, app->text_input_buffer_size);
+
+ // Ensure null-termination
+ app->text_input_buffer[app->text_input_buffer_size - 1] = '\0';
+
+ // save the setting
+ save_char("WiFi-SSID", app->text_input_buffer);
+
+ // update the variable item text
+ if (app->variable_item_wifi_ssid)
+ {
+ variable_item_set_current_value_text(app->variable_item_wifi_ssid, app->text_input_buffer);
+
+ // get value of password
+ char pass[64];
+ char username[64];
+ char password[64];
+ if (load_char("WiFi-Password", pass, sizeof(pass)))
+ {
+ if (strlen(pass) > 0 && strlen(app->text_input_buffer) > 0)
+ {
+ // save the settings
+ load_char("Flip-Social-Username", username, sizeof(username));
+ load_char("Flip-Social-Password", password, sizeof(password));
+ save_settings(app->text_input_buffer, pass, username, password);
+
+ // initialize the http
+ FlipperHTTP *fhttp = flipper_http_alloc();
+ if (fhttp)
+ {
+ // save the wifi if the device is connected
+ if (!flipper_http_save_wifi(fhttp, app->text_input_buffer, pass))
+ {
+ easy_flipper_dialog("FlipperHTTP Error", "Ensure your WiFi Developer\nBoard or Pico W is connected\nand the latest FlipperHTTP\nfirmware is installed.");
+ }
+
+ // free the resources
+ flipper_http_free(fhttp);
+ }
+ else
+ {
+ easy_flipper_dialog("FlipperHTTP Error", "The UART is likely busy.\nEnsure you have the correct\nflash for your board then\nrestart your Flipper Zero.");
+ }
+ }
+ }
+ }
+
+ // switch to the settings view
+ view_dispatcher_switch_to_view(app->view_dispatcher, FlipWorldViewVariableItemList);
+}
+static void text_updated_wifi_pass(void *context)
+{
+ FlipWorldApp *app = (FlipWorldApp *)context;
+ if (!app)
+ {
+ FURI_LOG_E(TAG, "FlipWorldApp is NULL");
+ return;
+ }
+
+ // store the entered text
+ strncpy(app->text_input_buffer, app->text_input_temp_buffer, app->text_input_buffer_size);
+
+ // Ensure null-termination
+ app->text_input_buffer[app->text_input_buffer_size - 1] = '\0';
+
+ // save the setting
+ save_char("WiFi-Password", app->text_input_buffer);
+
+ // update the variable item text
+ if (app->variable_item_wifi_pass)
+ {
+ // variable_item_set_current_value_text(app->variable_item_wifi_pass, app->text_input_buffer);
+ }
+
+ // get value of ssid
+ char ssid[64];
+ char username[64];
+ char password[64];
+ if (load_char("WiFi-SSID", ssid, sizeof(ssid)))
+ {
+ if (strlen(ssid) > 0 && strlen(app->text_input_buffer) > 0)
+ {
+ // save the settings
+ load_char("Flip-Social-Username", username, sizeof(username));
+ load_char("Flip-Social-Password", password, sizeof(password));
+ save_settings(ssid, app->text_input_buffer, username, password);
+
+ // initialize the http
+ FlipperHTTP *fhttp = flipper_http_alloc();
+ if (fhttp)
+ {
+ // save the wifi if the device is connected
+ if (!flipper_http_save_wifi(fhttp, ssid, app->text_input_buffer))
+ {
+ easy_flipper_dialog("FlipperHTTP Error", "Ensure your WiFi Developer\nBoard or Pico W is connected\nand the latest FlipperHTTP\nfirmware is installed.");
+ }
+
+ // free the resources
+ flipper_http_free(fhttp);
+ }
+ else
+ {
+ easy_flipper_dialog("FlipperHTTP Error", "The UART is likely busy.\nEnsure you have the correct\nflash for your board then\nrestart your Flipper Zero.");
+ }
+ }
+ }
+
+ // switch to the settings view
+ view_dispatcher_switch_to_view(app->view_dispatcher, FlipWorldViewVariableItemList);
+}
+static void text_updated_username(void *context)
+{
+ FlipWorldApp *app = (FlipWorldApp *)context;
+ if (!app)
+ {
+ FURI_LOG_E(TAG, "FlipWorldApp is NULL");
+ return;
+ }
+
+ // store the entered text
+ strncpy(app->text_input_buffer, app->text_input_temp_buffer, app->text_input_buffer_size);
+
+ // Ensure null-termination
+ app->text_input_buffer[app->text_input_buffer_size - 1] = '\0';
+
+ // save the setting
+ save_char("Flip-Social-Username", app->text_input_buffer);
+
+ // update the variable item text
+ if (app->variable_item_user_username)
+ {
+ variable_item_set_current_value_text(app->variable_item_user_username, app->text_input_buffer);
+ }
+ view_dispatcher_switch_to_view(app->view_dispatcher, FlipWorldViewVariableItemList); // back to user settings
+}
+static void text_updated_password(void *context)
+{
+ FlipWorldApp *app = (FlipWorldApp *)context;
+ if (!app)
+ {
+ FURI_LOG_E(TAG, "FlipWorldApp is NULL");
+ return;
+ }
+
+ // store the entered text
+ strncpy(app->text_input_buffer, app->text_input_temp_buffer, app->text_input_buffer_size);
+
+ // Ensure null-termination
+ app->text_input_buffer[app->text_input_buffer_size - 1] = '\0';
+
+ // save the setting
+ save_char("Flip-Social-Password", app->text_input_buffer);
+
+ // update the variable item text
+ if (app->variable_item_user_password)
+ {
+ variable_item_set_current_value_text(app->variable_item_user_password, app->text_input_buffer);
+ }
+
+ // get value of username
+ char username[64];
+ char ssid[64];
+ char pass[64];
+ if (load_char("Flip-Social-Username", username, sizeof(username)))
+ {
+ if (strlen(username) > 0 && strlen(app->text_input_buffer) > 0)
+ {
+ // save the settings
+ load_char("WiFi-SSID", ssid, sizeof(ssid));
+ load_char("WiFi-Password", pass, sizeof(pass));
+ save_settings(ssid, pass, username, app->text_input_buffer);
+ }
+ }
+ view_dispatcher_switch_to_view(app->view_dispatcher, FlipWorldViewVariableItemList); // back to user settings
+}
+
+static void wifi_settings_item_selected(void *context, uint32_t index)
+{
+ FlipWorldApp *app = (FlipWorldApp *)context;
+ if (!app)
+ {
+ FURI_LOG_E(TAG, "FlipWorldApp is NULL");
+ return;
+ }
+ char ssid[64];
+ char pass[64];
+ char username[64];
+ char password[64];
+ switch (index)
+ {
+ case 0: // Input SSID
+ free_all_views(app, false, false);
+ if (!alloc_text_input_view(app, "SSID"))
+ {
+ FURI_LOG_E(TAG, "Failed to allocate text input view");
+ return;
+ }
+ // load SSID
+ if (load_settings(ssid, sizeof(ssid), pass, sizeof(pass), username, sizeof(username), password, sizeof(password)))
+ {
+ strncpy(app->text_input_temp_buffer, ssid, app->text_input_buffer_size - 1);
+ app->text_input_temp_buffer[app->text_input_buffer_size - 1] = '\0';
+ }
+ view_dispatcher_switch_to_view(app->view_dispatcher, FlipWorldViewTextInput);
+ break;
+ case 1: // Input Password
+ free_all_views(app, false, false);
+ if (!alloc_text_input_view(app, "Password"))
+ {
+ FURI_LOG_E(TAG, "Failed to allocate text input view");
+ return;
+ }
+ // load password
+ if (load_settings(ssid, sizeof(ssid), pass, sizeof(pass), username, sizeof(username), password, sizeof(password)))
+ {
+ strncpy(app->text_input_temp_buffer, pass, app->text_input_buffer_size - 1);
+ app->text_input_temp_buffer[app->text_input_buffer_size - 1] = '\0';
+ }
+ view_dispatcher_switch_to_view(app->view_dispatcher, FlipWorldViewTextInput);
+ break;
+ default:
+ FURI_LOG_E(TAG, "Unknown configuration item index");
+ break;
+ }
+}
+static void flip_world_game_fps_change(VariableItem *item)
+{
+ uint8_t index = variable_item_get_current_value_index(item);
+ game_fps_index = index;
+ variable_item_set_current_value_text(item, game_fps_choices[index]);
+ variable_item_set_current_value_index(item, index);
+
+ // save the fps
+ save_char("Game-FPS", game_fps_choices[index]);
+}
+static void flip_world_game_screen_always_on_change(VariableItem *item)
+{
+ uint8_t index = variable_item_get_current_value_index(item);
+ game_screen_always_on_index = index;
+ variable_item_set_current_value_text(item, yes_or_no_choices[index]);
+ variable_item_set_current_value_index(item, index);
+
+ // save the screen always on
+ save_char("Game-Screen-Always-On", yes_or_no_choices[index]);
+}
+static void flip_world_game_sound_on_change(VariableItem *item)
+{
+ uint8_t index = variable_item_get_current_value_index(item);
+ game_sound_on_index = index;
+ variable_item_set_current_value_text(item, yes_or_no_choices[index]);
+ variable_item_set_current_value_index(item, index);
+
+ // save the screen always on
+ save_char("Game-Sound-On", yes_or_no_choices[index]);
+}
+static void flip_world_game_vibration_on_change(VariableItem *item)
+{
+ uint8_t index = variable_item_get_current_value_index(item);
+ game_vibration_on_index = index;
+ variable_item_set_current_value_text(item, yes_or_no_choices[index]);
+ variable_item_set_current_value_index(item, index);
+
+ // save the screen always on
+ save_char("Game-Vibration-On", yes_or_no_choices[index]);
+}
+
+static bool flip_world_fetch_worlds(DataLoaderModel *model)
+{
+ if (!model || !model->fhttp)
+ {
+ FURI_LOG_E(TAG, "model or fhttp is NULL");
+ return false;
+ }
+ // Create the directory for saving settings
+ char directory_path[256];
+ snprintf(directory_path, sizeof(directory_path), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_world/worlds");
+
+ // Create the directory
+ Storage *storage = furi_record_open(RECORD_STORAGE);
+ storage_common_mkdir(storage, directory_path);
+ furi_record_close(RECORD_STORAGE);
+ snprintf(
+ model->fhttp->file_path,
+ sizeof(model->fhttp->file_path),
+ STORAGE_EXT_PATH_PREFIX "/apps_data/flip_world/worlds/world_list_full.json");
+ model->fhttp->save_received_data = true;
+ return flipper_http_get_request_with_headers(model->fhttp, "https://www.flipsocial.net/api/world/v3/get/10/", "{\"Content-Type\":\"application/json\"}");
+}
+static char *flip_world_parse_worlds(DataLoaderModel *model)
+{
+ UNUSED(model);
+ return "World Pack Installed";
+}
+static void flip_world_switch_to_view_get_worlds(FlipWorldApp *app)
+{
+ flip_world_generic_switch_to_view(app, "Fetching World Pack..", flip_world_fetch_worlds, flip_world_parse_worlds, 1, callback_to_submenu, FlipWorldViewLoader);
+}
+static void game_settings_item_selected(void *context, uint32_t index)
+{
+ FlipWorldApp *app = (FlipWorldApp *)context;
+ if (!app)
+ {
+ FURI_LOG_E(TAG, "FlipWorldApp is NULL");
+ return;
+ }
+ switch (index)
+ {
+ case 0: // Download all world data s one huge json
+ flip_world_switch_to_view_get_worlds(app);
+ case 1: // Change FPS
+ break;
+ case 2: // Screen Always On
+ break;
+ }
+}
+static void user_settings_item_selected(void *context, uint32_t index)
+{
+ FlipWorldApp *app = (FlipWorldApp *)context;
+ if (!app)
+ {
+ FURI_LOG_E(TAG, "FlipWorldApp is NULL");
+ return;
+ }
+ switch (index)
+ {
+ case 0: // Username
+ free_all_views(app, false, false);
+ if (!alloc_text_input_view(app, "Username-Login"))
+ {
+ FURI_LOG_E(TAG, "Failed to allocate text input view");
+ return;
+ }
+ view_dispatcher_switch_to_view(app->view_dispatcher, FlipWorldViewTextInput);
+ break;
+ case 1: // Password
+ free_all_views(app, false, false);
+ if (!alloc_text_input_view(app, "Password-Login"))
+ {
+ FURI_LOG_E(TAG, "Failed to allocate text input view");
+ return;
+ }
+ view_dispatcher_switch_to_view(app->view_dispatcher, FlipWorldViewTextInput);
+ break;
+ }
+}
+
+static void flip_world_widget_set_text(char *message, Widget **widget)
+{
+ if (widget == NULL)
+ {
+ FURI_LOG_E(TAG, "flip_world_set_widget_text - widget is NULL");
+ DEV_CRASH();
+ return;
+ }
+ if (message == NULL)
+ {
+ FURI_LOG_E(TAG, "flip_world_set_widget_text - message is NULL");
+ DEV_CRASH();
+ return;
+ }
+ widget_reset(*widget);
+
+ uint32_t message_length = strlen(message); // Length of the message
+ uint32_t i = 0; // Index tracker
+ uint32_t formatted_index = 0; // Tracker for where we are in the formatted message
+ char *formatted_message; // Buffer to hold the final formatted message
+
+ // Allocate buffer with double the message length plus one for safety
+ if (!easy_flipper_set_buffer(&formatted_message, message_length * 2 + 1))
+ {
+ return;
+ }
+
+ while (i < message_length)
+ {
+ uint32_t max_line_length = 31; // Maximum characters per line
+ uint32_t remaining_length = message_length - i; // Remaining characters
+ uint32_t line_length = (remaining_length < max_line_length) ? remaining_length : max_line_length;
+
+ // Check for newline character within the current segment
+ uint32_t newline_pos = i;
+ bool found_newline = false;
+ for (; newline_pos < i + line_length && newline_pos < message_length; newline_pos++)
+ {
+ if (message[newline_pos] == '\n')
+ {
+ found_newline = true;
+ break;
+ }
+ }
+
+ if (found_newline)
+ {
+ // If newline found, set line_length up to the newline
+ line_length = newline_pos - i;
+ }
+
+ // Temporary buffer to hold the current line
+ char line[32];
+ strncpy(line, message + i, line_length);
+ line[line_length] = '\0';
+
+ // If newline was found, skip it for the next iteration
+ if (found_newline)
+ {
+ i += line_length + 1; // +1 to skip the '\n' character
+ }
+ else
+ {
+ // Check if the line ends in the middle of a word and adjust accordingly
+ if (line_length == max_line_length && message[i + line_length] != '\0' && message[i + line_length] != ' ')
+ {
+ // Find the last space within the current line to avoid breaking a word
+ char *last_space = strrchr(line, ' ');
+ if (last_space != NULL)
+ {
+ // Adjust the line_length to avoid cutting the word
+ line_length = last_space - line;
+ line[line_length] = '\0'; // Null-terminate at the space
+ }
+ }
+
+ // Move the index forward by the determined line_length
+ i += line_length;
+
+ // Skip any spaces at the beginning of the next line
+ while (i < message_length && message[i] == ' ')
+ {
+ i++;
+ }
+ }
+
+ // Manually copy the fixed line into the formatted_message buffer
+ for (uint32_t j = 0; j < line_length; j++)
+ {
+ formatted_message[formatted_index++] = line[j];
+ }
+
+ // Add a newline character for line spacing
+ formatted_message[formatted_index++] = '\n';
+ }
+
+ // Null-terminate the formatted_message
+ formatted_message[formatted_index] = '\0';
+
+ // Add the formatted message to the widget
+ widget_add_text_scroll_element(*widget, 0, 0, 128, 64, formatted_message);
+}
+
+void flip_world_loader_draw_callback(Canvas *canvas, void *model)
+{
+ if (!canvas || !model)
+ {
+ FURI_LOG_E(TAG, "flip_world_loader_draw_callback - canvas or model is NULL");
+ return;
+ }
+
+ DataLoaderModel *data_loader_model = (DataLoaderModel *)model;
+ SerialState http_state = data_loader_model->fhttp->state;
+ DataState data_state = data_loader_model->data_state;
+ char *title = data_loader_model->title;
+
+ canvas_set_font(canvas, FontSecondary);
+
+ if (http_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;
+ }
+
+ if (data_state == DataStateError || data_state == DataStateParseError)
+ {
+ flip_world_request_error_draw(canvas, data_loader_model);
+ return;
+ }
+
+ canvas_draw_str(canvas, 0, 7, title);
+ canvas_draw_str(canvas, 0, 17, "Loading...");
+
+ if (data_state == DataStateInitial)
+ {
+ return;
+ }
+
+ if (http_state == SENDING)
+ {
+ canvas_draw_str(canvas, 0, 27, "Fetching...");
+ return;
+ }
+
+ if (http_state == RECEIVING || data_state == DataStateRequested)
+ {
+ canvas_draw_str(canvas, 0, 27, "Receiving...");
+ return;
+ }
+
+ if (http_state == IDLE && data_state == DataStateReceived)
+ {
+ canvas_draw_str(canvas, 0, 27, "Processing...");
+ return;
+ }
+
+ if (http_state == IDLE && data_state == DataStateParsed)
+ {
+ canvas_draw_str(canvas, 0, 27, "Processed...");
+ return;
+ }
+}
+
+static void flip_world_loader_process_callback(void *context)
+{
+ if (context == NULL)
+ {
+ FURI_LOG_E(TAG, "flip_world_loader_process_callback - context is NULL");
+ DEV_CRASH();
+ return;
+ }
+
+ FlipWorldApp *app = (FlipWorldApp *)context;
+ View *view = app->view_loader;
+
+ DataState current_data_state;
+ DataLoaderModel *loader_model = NULL;
+ with_view_model(view, DataLoaderModel * model, { current_data_state = model->data_state; loader_model = model; }, false);
+ if (!loader_model || !loader_model->fhttp)
+ {
+ FURI_LOG_E(TAG, "Model or fhttp is NULL");
+ DEV_CRASH();
+ return;
+ }
+
+ if (current_data_state == DataStateInitial)
+ {
+ with_view_model(
+ view,
+ DataLoaderModel * model,
+ {
+ model->data_state = DataStateRequested;
+ DataLoaderFetch fetch = model->fetcher;
+ if (fetch == NULL)
+ {
+ FURI_LOG_E(TAG, "Model doesn't have Fetch function assigned.");
+ model->data_state = DataStateError;
+ return;
+ }
+
+ // Clear any previous responses
+ strncpy(model->fhttp->last_response, "", 1);
+ bool request_status = fetch(model);
+ if (!request_status)
+ {
+ model->data_state = DataStateError;
+ }
+ },
+ true);
+ }
+ else if (current_data_state == DataStateRequested || current_data_state == DataStateError)
+ {
+ if (loader_model->fhttp->state == IDLE && loader_model->fhttp->last_response != NULL)
+ {
+ if (strstr(loader_model->fhttp->last_response, "[PONG]") != NULL)
+ {
+ FURI_LOG_DEV(TAG, "PONG received.");
+ }
+ else if (strncmp(loader_model->fhttp->last_response, "[SUCCESS]", 9) == 0)
+ {
+ FURI_LOG_DEV(TAG, "SUCCESS received. %s", loader_model->fhttp->last_response ? loader_model->fhttp->last_response : "NULL");
+ }
+ else if (strncmp(loader_model->fhttp->last_response, "[ERROR]", 9) == 0)
+ {
+ FURI_LOG_DEV(TAG, "ERROR received. %s", loader_model->fhttp->last_response ? loader_model->fhttp->last_response : "NULL");
+ }
+ else if (strlen(loader_model->fhttp->last_response) == 0)
+ {
+ // Still waiting on response
+ }
+ else
+ {
+ with_view_model(view, DataLoaderModel * model, { model->data_state = DataStateReceived; }, true);
+ }
+ }
+ else if (loader_model->fhttp->state == SENDING || loader_model->fhttp->state == RECEIVING)
+ {
+ // continue waiting
+ }
+ else if (loader_model->fhttp->state == INACTIVE)
+ {
+ // inactive. try again
+ }
+ else if (loader_model->fhttp->state == ISSUE)
+ {
+ with_view_model(view, DataLoaderModel * model, { model->data_state = DataStateError; }, true);
+ }
+ else
+ {
+ FURI_LOG_DEV(TAG, "Unexpected state: %d lastresp: %s", loader_model->fhttp->state, loader_model->fhttp->last_response ? loader_model->fhttp->last_response : "NULL");
+ DEV_CRASH();
+ }
+ }
+ else if (current_data_state == DataStateReceived)
+ {
+ with_view_model(
+ view,
+ DataLoaderModel * model,
+ {
+ char *data_text;
+ if (model->parser == NULL)
+ {
+ data_text = NULL;
+ FURI_LOG_DEV(TAG, "Parser is NULL");
+ DEV_CRASH();
+ }
+ else
+ {
+ data_text = model->parser(model);
+ }
+ FURI_LOG_DEV(TAG, "Parsed data: %s\r\ntext: %s", model->fhttp->last_response ? model->fhttp->last_response : "NULL", data_text ? data_text : "NULL");
+ model->data_text = data_text;
+ if (data_text == NULL)
+ {
+ model->data_state = DataStateParseError;
+ }
+ else
+ {
+ model->data_state = DataStateParsed;
+ }
+ },
+ true);
+ }
+ else if (current_data_state == DataStateParsed)
+ {
+ with_view_model(
+ view,
+ DataLoaderModel * model,
+ {
+ if (++model->request_index < model->request_count)
+ {
+ model->data_state = DataStateInitial;
+ }
+ else
+ {
+ flip_world_widget_set_text(model->data_text != NULL ? model->data_text : "", &app->widget_result);
+ if (model->data_text != NULL)
+ {
+ free(model->data_text);
+ model->data_text = NULL;
+ }
+ view_set_previous_callback(widget_get_view(app->widget_result), model->back_callback);
+ view_dispatcher_switch_to_view(app->view_dispatcher, FlipWorldViewWidgetResult);
+ }
+ },
+ true);
+ }
+}
+
+static void flip_world_loader_timer_callback(void *context)
+{
+ if (context == NULL)
+ {
+ FURI_LOG_E(TAG, "flip_world_loader_timer_callback - context is NULL");
+ DEV_CRASH();
+ return;
+ }
+ FlipWorldApp *app = (FlipWorldApp *)context;
+ view_dispatcher_send_custom_event(app->view_dispatcher, FlipWorldCustomEventProcess);
+}
+
+static void flip_world_loader_on_enter(void *context)
+{
+ if (context == NULL)
+ {
+ FURI_LOG_E(TAG, "flip_world_loader_on_enter - context is NULL");
+ DEV_CRASH();
+ return;
+ }
+ FlipWorldApp *app = (FlipWorldApp *)context;
+ View *view = app->view_loader;
+ with_view_model(
+ view,
+ DataLoaderModel * model,
+ {
+ view_set_previous_callback(view, model->back_callback);
+ if (model->timer == NULL)
+ {
+ model->timer = furi_timer_alloc(flip_world_loader_timer_callback, FuriTimerTypePeriodic, app);
+ }
+ furi_timer_start(model->timer, 250);
+ },
+ true);
+}
+
+static void flip_world_loader_on_exit(void *context)
+{
+ if (context == NULL)
+ {
+ FURI_LOG_E(TAG, "flip_world_loader_on_exit - context is NULL");
+ DEV_CRASH();
+ return;
+ }
+ FlipWorldApp *app = (FlipWorldApp *)context;
+ View *view = app->view_loader;
+ with_view_model(
+ view,
+ DataLoaderModel * model,
+ {
+ if (model->timer)
+ {
+ furi_timer_stop(model->timer);
+ }
+ },
+ false);
+}
+
+void flip_world_loader_init(View *view)
+{
+ if (view == NULL)
+ {
+ FURI_LOG_E(TAG, "flip_world_loader_init - view is NULL");
+ DEV_CRASH();
+ return;
+ }
+ view_allocate_model(view, ViewModelTypeLocking, sizeof(DataLoaderModel));
+ view_set_enter_callback(view, flip_world_loader_on_enter);
+ view_set_exit_callback(view, flip_world_loader_on_exit);
+}
+
+void flip_world_loader_free_model(View *view)
+{
+ if (view == NULL)
+ {
+ FURI_LOG_E(TAG, "flip_world_loader_free_model - view is NULL");
+ DEV_CRASH();
+ return;
+ }
+ with_view_model(
+ view,
+ DataLoaderModel * model,
+ {
+ if (model->timer)
+ {
+ furi_timer_free(model->timer);
+ model->timer = NULL;
+ }
+ if (model->parser_context)
+ {
+ // do not free the context here, it is the app context
+ // free(model->parser_context);
+ // model->parser_context = NULL;
+ }
+ if (model->fhttp)
+ {
+ flipper_http_free(model->fhttp);
+ model->fhttp = NULL;
+ }
+ },
+ false);
+}
+
+bool flip_world_custom_event_callback(void *context, uint32_t index)
+{
+ if (context == NULL)
+ {
+ FURI_LOG_E(TAG, "flip_world_custom_event_callback - context is NULL");
+ DEV_CRASH();
+ return false;
+ }
+
+ switch (index)
+ {
+ case FlipWorldCustomEventProcess:
+ flip_world_loader_process_callback(context);
+ return true;
+ default:
+ FURI_LOG_DEV(TAG, "flip_world_custom_event_callback. Unknown index: %ld", index);
+ return false;
+ }
+}
+
+void flip_world_generic_switch_to_view(FlipWorldApp *app, char *title, DataLoaderFetch fetcher, DataLoaderParser parser, size_t request_count, ViewNavigationCallback back, uint32_t view_id)
+{
+ if (app == NULL)
+ {
+ FURI_LOG_E(TAG, "flip_world_generic_switch_to_view - app is NULL");
+ DEV_CRASH();
+ return;
+ }
+
+ View *view = app->view_loader;
+ if (view == NULL)
+ {
+ FURI_LOG_E(TAG, "flip_world_generic_switch_to_view - view is NULL");
+ DEV_CRASH();
+ return;
+ }
+
+ with_view_model(
+ view,
+ DataLoaderModel * model,
+ {
+ model->title = title;
+ model->fetcher = fetcher;
+ model->parser = parser;
+ model->request_index = 0;
+ model->request_count = request_count;
+ model->back_callback = back;
+ model->data_state = DataStateInitial;
+ model->data_text = NULL;
+ //
+ model->parser_context = app;
+ if (!model->fhttp)
+ {
+ model->fhttp = flipper_http_alloc();
+ }
+ },
+ true);
+
+ view_dispatcher_switch_to_view(app->view_dispatcher, view_id);
+}
diff --git a/flip_world/callback/callback.h b/flip_world/callback/callback.h
new file mode 100644
index 000000000..f2d3f854a
--- /dev/null
+++ b/flip_world/callback/callback.h
@@ -0,0 +1,45 @@
+#pragma once
+#include
+#include
+
+void free_all_views(void *context, bool should_free_variable_item_list, bool should_free_submenu_settings);
+void callback_submenu_choices(void *context, uint32_t index);
+uint32_t callback_to_submenu(void *context);
+
+// Add edits by Derek Jamison
+typedef enum DataState DataState;
+enum DataState
+{
+ DataStateInitial,
+ DataStateRequested,
+ DataStateReceived,
+ DataStateParsed,
+ DataStateParseError,
+ DataStateError,
+};
+
+typedef struct DataLoaderModel DataLoaderModel;
+typedef bool (*DataLoaderFetch)(DataLoaderModel *model);
+typedef char *(*DataLoaderParser)(DataLoaderModel *model);
+struct DataLoaderModel
+{
+ char *title;
+ char *data_text;
+ DataState data_state;
+ DataLoaderFetch fetcher;
+ DataLoaderParser parser;
+ void *parser_context;
+ size_t request_index;
+ size_t request_count;
+ ViewNavigationCallback back_callback;
+ FuriTimer *timer;
+ FlipperHTTP *fhttp;
+};
+void flip_world_generic_switch_to_view(FlipWorldApp *app, char *title, DataLoaderFetch fetcher, DataLoaderParser parser, size_t request_count, ViewNavigationCallback back, uint32_t view_id);
+
+void flip_world_loader_draw_callback(Canvas *canvas, void *model);
+
+void flip_world_loader_init(View *view);
+
+void flip_world_loader_free_model(View *view);
+bool flip_world_custom_event_callback(void *context, uint32_t index);
\ No newline at end of file
diff --git a/flip_world/easy_flipper/easy_flipper.c b/flip_world/easy_flipper/easy_flipper.c
new file mode 100644
index 000000000..fe23e9717
--- /dev/null
+++ b/flip_world/easy_flipper/easy_flipper.c
@@ -0,0 +1,627 @@
+#include
+
+void easy_flipper_dialog(
+ char *header,
+ char *text)
+{
+ DialogsApp *dialogs = furi_record_open(RECORD_DIALOGS);
+ DialogMessage *message = dialog_message_alloc();
+ dialog_message_set_header(
+ message, header, 64, 0, AlignCenter, AlignTop);
+ dialog_message_set_text(
+ message,
+ text,
+ 0,
+ 63,
+ AlignLeft,
+ AlignBottom);
+ dialog_message_show(dialogs, message);
+ dialog_message_free(message);
+ furi_record_close(RECORD_DIALOGS);
+}
+
+/**
+ * @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 TextInput object with extra symbols
+ * @param uart_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_uart_text_input(
+ 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 = 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(text_input_get_view(*uart_text_input), previous_callback);
+ }
+ if (header_text)
+ {
+ text_input_set_header_text(*uart_text_input, header_text);
+ }
+ if (uart_text_input_temp_buffer && uart_text_input_buffer_size && result_callback)
+ {
+ text_input_set_result_callback(*uart_text_input, result_callback, context, uart_text_input_temp_buffer, uart_text_input_buffer_size, false);
+ }
+ text_input_show_illegal_symbols(*uart_text_input, true);
+ view_dispatcher_add_view(*view_dispatcher, view_id, 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;
+}
+
+bool easy_flipper_set_text_box(
+ TextBox **text_box,
+ int32_t view_id,
+ char *text,
+ bool start_at_end,
+ uint32_t(previous_callback)(void *),
+ ViewDispatcher **view_dispatcher)
+{
+ if (!text_box)
+ {
+ FURI_LOG_E(EASY_TAG, "Invalid arguments provided to set_text_box");
+ return false;
+ }
+ *text_box = text_box_alloc();
+ if (!*text_box)
+ {
+ FURI_LOG_E(EASY_TAG, "Failed to allocate TextBox");
+ return false;
+ }
+ if (text)
+ {
+ text_box_set_text(*text_box, text);
+ }
+ if (previous_callback)
+ {
+ view_set_previous_callback(text_box_get_view(*text_box), previous_callback);
+ }
+ text_box_set_font(*text_box, TextBoxFontText);
+ if (start_at_end)
+ {
+ text_box_set_focus(*text_box, TextBoxFocusEnd);
+ }
+ view_dispatcher_add_view(*view_dispatcher, view_id, text_box_get_view(*text_box));
+ return true;
+}
\ No newline at end of file
diff --git a/flip_world/easy_flipper/easy_flipper.h b/flip_world/easy_flipper/easy_flipper.h
new file mode 100644
index 000000000..1da7471de
--- /dev/null
+++ b/flip_world/easy_flipper/easy_flipper.h
@@ -0,0 +1,287 @@
+#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
+#include
+#include
+#include
+
+#define EASY_TAG "EasyFlipper"
+
+void easy_flipper_dialog(
+ char *header,
+ char *text);
+
+/**
+ * @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 TextInput object with extra symbols
+ * @param uart_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_uart_text_input(
+ 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);
+
+/**
+ * @brief Initialize a TextBox object
+ * @param text_box The TextBox object to initialize
+ * @param view_id The ID/Index of the view
+ * @param text The text to display in the text box
+ * @param start_at_end Start the text box at the end
+ * @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_box(
+ TextBox **text_box,
+ int32_t view_id,
+ char *text,
+ bool start_at_end,
+ uint32_t(previous_callback)(void *),
+ ViewDispatcher **view_dispatcher);
+
+#endif
\ No newline at end of file
diff --git a/flip_world/engine/LICENSE b/flip_world/engine/LICENSE
new file mode 100644
index 000000000..f288702d2
--- /dev/null
+++ b/flip_world/engine/LICENSE
@@ -0,0 +1,674 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc.
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+ The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works. By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users. We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors. You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+ To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights. Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received. You must make sure that they, too, receive
+or can get the source code. And you must show them these terms so they
+know their rights.
+
+ Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+ For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software. For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+ Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so. This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software. The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable. Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products. If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+ Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary. To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ TERMS AND CONDITIONS
+
+ 0. Definitions.
+
+ "This License" refers to version 3 of the GNU General Public License.
+
+ "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+ "The Program" refers to any copyrightable work licensed under this
+License. Each licensee is addressed as "you". "Licensees" and
+"recipients" may be individuals or organizations.
+
+ To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy. The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+ A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+ To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy. Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+ To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies. Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+ An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License. If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+ 1. Source Code.
+
+ The "source code" for a work means the preferred form of the work
+for making modifications to it. "Object code" means any non-source
+form of a work.
+
+ A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+ The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form. A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+ The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities. However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work. For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+ The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+ The Corresponding Source for a work in source code form is that
+same work.
+
+ 2. Basic Permissions.
+
+ All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met. This License explicitly affirms your unlimited
+permission to run the unmodified Program. The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work. This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+ You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force. You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright. Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+ Conveying under any other circumstances is permitted solely under
+the conditions stated below. Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+ No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+ When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+ 4. Conveying Verbatim Copies.
+
+ You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+ You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+ 5. Conveying Modified Source Versions.
+
+ You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+ a) The work must carry prominent notices stating that you modified
+ it, and giving a relevant date.
+
+ b) The work must carry prominent notices stating that it is
+ released under this License and any conditions added under section
+ 7. This requirement modifies the requirement in section 4 to
+ "keep intact all notices".
+
+ c) You must license the entire work, as a whole, under this
+ License to anyone who comes into possession of a copy. This
+ License will therefore apply, along with any applicable section 7
+ additional terms, to the whole of the work, and all its parts,
+ regardless of how they are packaged. This License gives no
+ permission to license the work in any other way, but it does not
+ invalidate such permission if you have separately received it.
+
+ d) If the work has interactive user interfaces, each must display
+ Appropriate Legal Notices; however, if the Program has interactive
+ interfaces that do not display Appropriate Legal Notices, your
+ work need not make them do so.
+
+ A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit. Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+ 6. Conveying Non-Source Forms.
+
+ You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+ a) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by the
+ Corresponding Source fixed on a durable physical medium
+ customarily used for software interchange.
+
+ b) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by a
+ written offer, valid for at least three years and valid for as
+ long as you offer spare parts or customer support for that product
+ model, to give anyone who possesses the object code either (1) a
+ copy of the Corresponding Source for all the software in the
+ product that is covered by this License, on a durable physical
+ medium customarily used for software interchange, for a price no
+ more than your reasonable cost of physically performing this
+ conveying of source, or (2) access to copy the
+ Corresponding Source from a network server at no charge.
+
+ c) Convey individual copies of the object code with a copy of the
+ written offer to provide the Corresponding Source. This
+ alternative is allowed only occasionally and noncommercially, and
+ only if you received the object code with such an offer, in accord
+ with subsection 6b.
+
+ d) Convey the object code by offering access from a designated
+ place (gratis or for a charge), and offer equivalent access to the
+ Corresponding Source in the same way through the same place at no
+ further charge. You need not require recipients to copy the
+ Corresponding Source along with the object code. If the place to
+ copy the object code is a network server, the Corresponding Source
+ may be on a different server (operated by you or a third party)
+ that supports equivalent copying facilities, provided you maintain
+ clear directions next to the object code saying where to find the
+ Corresponding Source. Regardless of what server hosts the
+ Corresponding Source, you remain obligated to ensure that it is
+ available for as long as needed to satisfy these requirements.
+
+ e) Convey the object code using peer-to-peer transmission, provided
+ you inform other peers where the object code and Corresponding
+ Source of the work are being offered to the general public at no
+ charge under subsection 6d.
+
+ A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+ A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling. In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage. For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product. A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+ "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source. The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+ If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information. But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+ The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed. Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+ Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+ 7. Additional Terms.
+
+ "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law. If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+ When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it. (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.) You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+ Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+ a) Disclaiming warranty or limiting liability differently from the
+ terms of sections 15 and 16 of this License; or
+
+ b) Requiring preservation of specified reasonable legal notices or
+ author attributions in that material or in the Appropriate Legal
+ Notices displayed by works containing it; or
+
+ c) Prohibiting misrepresentation of the origin of that material, or
+ requiring that modified versions of such material be marked in
+ reasonable ways as different from the original version; or
+
+ d) Limiting the use for publicity purposes of names of licensors or
+ authors of the material; or
+
+ e) Declining to grant rights under trademark law for use of some
+ trade names, trademarks, or service marks; or
+
+ f) Requiring indemnification of licensors and authors of that
+ material by anyone who conveys the material (or modified versions of
+ it) with contractual assumptions of liability to the recipient, for
+ any liability that these contractual assumptions directly impose on
+ those licensors and authors.
+
+ All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10. If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term. If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+ If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+ Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+ 8. Termination.
+
+ You may not propagate or modify a covered work except as expressly
+provided under this License. Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+ However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+ Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+ Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License. If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+ 9. Acceptance Not Required for Having Copies.
+
+ You are not required to accept this License in order to receive or
+run a copy of the Program. Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance. However,
+nothing other than this License grants you permission to propagate or
+modify any covered work. These actions infringe copyright if you do
+not accept this License. Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+ 10. Automatic Licensing of Downstream Recipients.
+
+ Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License. You are not responsible
+for enforcing compliance by third parties with this License.
+
+ An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations. If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+ You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License. For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+ 11. Patents.
+
+ A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based. The
+work thus licensed is called the contributor's "contributor version".
+
+ A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version. For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+ Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+ In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement). To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+ If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients. "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+ If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+ A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License. You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+ Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+ 12. No Surrender of Others' Freedom.
+
+ If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all. For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+ 13. Use with the GNU Affero General Public License.
+
+ Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work. The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+ 14. Revised Versions of this License.
+
+ The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation. If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+ If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+ Later license versions may give you additional or different
+permissions. However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+ 15. Disclaimer of Warranty.
+
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. Limitation of Liability.
+
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+ 17. Interpretation of Sections 15 and 16.
+
+ If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+
+ Copyright (C)
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+
+Also add information on how to contact you by electronic and paper mail.
+
+ If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+ Copyright (C)
+ This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+ You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+.
+
+ The GNU General Public License does not permit incorporating your program
+into proprietary programs. If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License. But first, please read
+.
diff --git a/flip_world/engine/canvas.c b/flip_world/engine/canvas.c
new file mode 100644
index 000000000..29faa0fa6
--- /dev/null
+++ b/flip_world/engine/canvas.c
@@ -0,0 +1,28 @@
+#include
+#include "canvas.h"
+
+void canvas_printf(Canvas* canvas, uint8_t x, uint8_t y, const char* format, ...) {
+ FuriString* string = furi_string_alloc();
+ va_list args;
+ va_start(args, format);
+ furi_string_vprintf(string, format, args);
+ va_end(args);
+
+ canvas_draw_str(canvas, x, y, furi_string_get_cstr(string));
+
+ furi_string_free(string);
+}
+
+size_t canvas_printf_width(Canvas* canvas, const char* format, ...) {
+ FuriString* string = furi_string_alloc();
+ va_list args;
+ va_start(args, format);
+ furi_string_vprintf(string, format, args);
+ va_end(args);
+
+ size_t size = canvas_string_width(canvas, furi_string_get_cstr(string));
+
+ furi_string_free(string);
+
+ return size;
+}
\ No newline at end of file
diff --git a/flip_world/engine/canvas.h b/flip_world/engine/canvas.h
new file mode 100644
index 000000000..0c4bd0cd7
--- /dev/null
+++ b/flip_world/engine/canvas.h
@@ -0,0 +1,32 @@
+#pragma once
+#include
+#include
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * @brief Print formatted string to canvas
+ *
+ * @param canvas canvas instance
+ * @param x x position
+ * @param y y position
+ * @param format format string
+ * @param ... arguments
+ */
+void canvas_printf(Canvas* canvas, uint8_t x, uint8_t y, const char* format, ...);
+
+/**
+ * @brief Get width of formatted string
+ *
+ * @param canvas canvas instance
+ * @param format format string
+ * @param ... arguments
+ * @return size_t width of formatted string
+ */
+size_t canvas_printf_width(Canvas* canvas, const char* format, ...);
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/flip_world/engine/clock_timer.c b/flip_world/engine/clock_timer.c
new file mode 100644
index 000000000..3f98e6440
--- /dev/null
+++ b/flip_world/engine/clock_timer.c
@@ -0,0 +1,53 @@
+#include "clock_timer.h"
+#include
+
+#include
+#include
+#include
+
+#define FURI_HAL_CLOCK_TIMER TIM2
+#define FURI_HAL_CLOCK_TIMER_BUS FuriHalBusTIM2
+#define FURI_HAL_CLOCK_TIMER_IRQ FuriHalInterruptIdTIM2
+
+typedef struct {
+ ClockTimerCallback callback;
+ void* context;
+} ClockTimer;
+
+static ClockTimer clock_timer = {
+ .callback = NULL,
+ .context = NULL,
+};
+
+static void clock_timer_isr(void* context) {
+ if(clock_timer.callback) {
+ clock_timer.callback(context);
+ }
+
+ LL_TIM_ClearFlag_UPDATE(FURI_HAL_CLOCK_TIMER);
+}
+
+void clock_timer_start(ClockTimerCallback callback, void* context, float period) {
+ clock_timer.callback = callback;
+ clock_timer.context = context;
+
+ furi_hal_bus_enable(FURI_HAL_CLOCK_TIMER_BUS);
+
+ // init timer to produce interrupts
+ LL_TIM_InitTypeDef TIM_InitStruct = {0};
+ TIM_InitStruct.Autoreload = (SystemCoreClock / period) - 1;
+ LL_TIM_Init(FURI_HAL_CLOCK_TIMER, &TIM_InitStruct);
+
+ furi_hal_interrupt_set_isr(FURI_HAL_CLOCK_TIMER_IRQ, clock_timer_isr, clock_timer.context);
+
+ LL_TIM_EnableIT_UPDATE(FURI_HAL_CLOCK_TIMER);
+ LL_TIM_EnableCounter(FURI_HAL_CLOCK_TIMER);
+}
+
+void clock_timer_stop(void) {
+ LL_TIM_DisableIT_UPDATE(FURI_HAL_CLOCK_TIMER);
+ LL_TIM_DisableCounter(FURI_HAL_CLOCK_TIMER);
+
+ furi_hal_bus_disable(FURI_HAL_CLOCK_TIMER_BUS);
+ furi_hal_interrupt_set_isr(FURI_HAL_CLOCK_TIMER_IRQ, NULL, NULL);
+}
\ No newline at end of file
diff --git a/flip_world/engine/clock_timer.h b/flip_world/engine/clock_timer.h
new file mode 100644
index 000000000..f8b12e22d
--- /dev/null
+++ b/flip_world/engine/clock_timer.h
@@ -0,0 +1,15 @@
+#pragma once
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef void (*ClockTimerCallback)(void* context);
+
+void clock_timer_start(ClockTimerCallback callback, void* context, float period);
+
+void clock_timer_stop(void);
+
+#ifdef __cplusplus
+}
+#endif
\ No newline at end of file
diff --git a/flip_world/engine/engine.h b/flip_world/engine/engine.h
new file mode 100644
index 000000000..fb04513b1
--- /dev/null
+++ b/flip_world/engine/engine.h
@@ -0,0 +1,27 @@
+#pragma once
+#include
+#include "game_engine.h"
+#include "level.h"
+#include "entity.h"
+#include "game_manager.h"
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ typedef struct
+ {
+ float target_fps;
+ bool show_fps;
+ bool always_backlight;
+ void (*start)(GameManager *game_manager, void *context);
+ void (*stop)(void *context);
+ size_t context_size;
+ } Game;
+
+ extern const Game game;
+
+#ifdef __cplusplus
+}
+#endif
\ No newline at end of file
diff --git a/flip_world/engine/entity.c b/flip_world/engine/entity.c
new file mode 100644
index 000000000..043a1ece6
--- /dev/null
+++ b/flip_world/engine/entity.c
@@ -0,0 +1,217 @@
+#include "entity.h"
+#include "entity_i.h"
+#include
+#include
+
+#ifdef ENTITY_DEBUG
+#define ENTITY_D(...) FURI_LOG_D("Entity", __VA_ARGS__)
+#else
+#define ENTITY_D(...)
+#endif
+
+static int32_t entities_count = 0;
+
+int32_t entities_get_count(void) {
+ return entities_count;
+}
+
+Entity* entity_alloc(const EntityDescription* description) {
+ entities_count++;
+ Entity* entity = malloc(sizeof(Entity));
+ entity->position = VECTOR_ZERO;
+ entity->description = description;
+ entity->context = NULL;
+ if(description && description->context_size > 0) {
+ entity->context = malloc(description->context_size);
+ }
+ entity->collider = NULL;
+ entity->collider_offset = VECTOR_ZERO;
+ ENTITY_D("Allocated at %p", entity);
+ entity->collider_dirty = false;
+ return entity;
+}
+
+void entity_collider_add_circle(Entity* entity, float radius) {
+ furi_check(entity->collider == NULL, "Collider already added");
+ entity->collider = malloc(sizeof(Collider));
+ entity->collider->type = ColliderTypeCircle;
+ entity->collider->circle.radius = radius;
+ entity->collider_dirty = true;
+}
+
+void entity_collider_add_rect(Entity* entity, float width, float height) {
+ furi_check(entity->collider == NULL, "Collider already added");
+ entity->collider = malloc(sizeof(Collider));
+ entity->collider->type = ColliderTypeRect;
+ entity->collider->rect.half_width = width / 2;
+ entity->collider->rect.half_height = height / 2;
+ entity->collider_dirty = true;
+}
+
+void entity_collider_remove(Entity* entity) {
+ furi_check(entity->collider != NULL, "Collider not added");
+ free(entity->collider);
+ entity->collider = NULL;
+ entity->collider_dirty = false;
+}
+
+void entity_collider_offset_set(Entity* entity, Vector offset) {
+ entity->collider_offset = offset;
+ entity->collider_dirty = true;
+}
+
+Vector entity_collider_offset_get(Entity* entity) {
+ return entity->collider_offset;
+}
+
+static Vector entity_collider_position_get(Entity* entity) {
+ return (Vector){
+ .x = entity->position.x + entity->collider_offset.x,
+ .y = entity->position.y + entity->collider_offset.y,
+ };
+}
+
+void entity_free(Entity* entity) {
+ entities_count--;
+ ENTITY_D("Freeing at %p", entity);
+ if(entity->context) {
+ free(entity->context);
+ }
+ if(entity->collider) {
+ free(entity->collider);
+ }
+ free(entity);
+}
+
+const EntityDescription* entity_description_get(Entity* entity) {
+ return entity->description;
+}
+
+Vector entity_pos_get(Entity* entity) {
+ return entity->position;
+}
+
+void entity_pos_set(Entity* entity, Vector position) {
+ entity->position = position;
+ entity->collider_dirty = true;
+}
+
+void* entity_context_get(Entity* entity) {
+ return entity->context;
+}
+
+void entity_call_start(Entity* entity, GameManager* manager) {
+ if(entity->description && entity->description->start) {
+ entity->description->start(entity, manager, entity->context);
+ }
+}
+
+void entity_call_stop(Entity* entity, GameManager* manager) {
+ if(entity->description && entity->description->stop) {
+ entity->description->stop(entity, manager, entity->context);
+ }
+}
+
+void entity_call_update(Entity* entity, GameManager* manager) {
+ if(entity->description && entity->description->update) {
+ entity->description->update(entity, manager, entity->context);
+ }
+}
+
+void entity_call_render(Entity* entity, GameManager* manager, Canvas* canvas) {
+ if(entity->description && entity->description->render) {
+ entity->description->render(entity, manager, canvas, entity->context);
+ }
+}
+
+void entity_call_collision(Entity* entity, Entity* other, GameManager* manager) {
+ if(entity->description && entity->description->collision) {
+ entity->description->collision(entity, other, manager, entity->context);
+ }
+}
+
+bool entity_collider_circle_circle(Entity* entity, Entity* other) {
+ Vector pos1 = entity_collider_position_get(entity);
+ Vector pos2 = entity_collider_position_get(other);
+
+ float dx = pos1.x - pos2.x;
+ float dy = pos1.y - pos2.y;
+ float distance = sqrtf(dx * dx + dy * dy);
+ return distance < entity->collider->circle.radius + other->collider->circle.radius;
+}
+
+bool entity_collider_rect_rect(Entity* entity, Entity* other) {
+ Vector pos1 = entity_collider_position_get(entity);
+ Vector pos2 = entity_collider_position_get(other);
+
+ float left1 = pos1.x - entity->collider->rect.half_width;
+ float right1 = pos1.x + entity->collider->rect.half_width;
+ float top1 = pos1.y - entity->collider->rect.half_height;
+ float bottom1 = pos1.y + entity->collider->rect.half_height;
+
+ float left2 = pos2.x - other->collider->rect.half_width;
+ float right2 = pos2.x + other->collider->rect.half_width;
+ float top2 = pos2.y - other->collider->rect.half_height;
+ float bottom2 = pos2.y + other->collider->rect.half_height;
+
+ return left1 < right2 && right1 > left2 && top1 < bottom2 && bottom1 > top2;
+}
+
+bool entity_collider_circle_rect(Entity* circle, Entity* rect) {
+ Vector pos1 = entity_collider_position_get(circle);
+ Vector pos2 = entity_collider_position_get(rect);
+
+ float left = pos2.x - rect->collider->rect.half_width;
+ float right = pos2.x + rect->collider->rect.half_width;
+ float top = pos2.y - rect->collider->rect.half_height;
+ float bottom = pos2.y + rect->collider->rect.half_height;
+
+ float closestX = fmaxf(left, fminf(pos1.x, right));
+ float closestY = fmaxf(top, fminf(pos1.y, bottom));
+
+ float dx = pos1.x - closestX;
+ float dy = pos1.y - closestY;
+ float distance = sqrtf(dx * dx + dy * dy);
+ return distance < circle->collider->circle.radius;
+}
+
+bool entity_collider_check_collision(Entity* entity, Entity* other) {
+ furi_check(entity->collider);
+ furi_check(other->collider);
+
+ if(entity->collider->type == ColliderTypeCircle) {
+ Entity* circle = entity;
+ if(other->collider->type == ColliderTypeCircle) {
+ return entity_collider_circle_circle(circle, other);
+ } else {
+ return entity_collider_circle_rect(circle, other);
+ }
+ } else {
+ Entity* rect = entity;
+ if(other->collider->type == ColliderTypeCircle) {
+ return entity_collider_circle_rect(other, rect);
+ } else {
+ return entity_collider_rect_rect(rect, other);
+ }
+ }
+}
+
+bool entity_collider_exists(Entity* entity) {
+ return entity->collider != NULL;
+}
+
+void entity_send_event(
+ Entity* sender,
+ Entity* receiver,
+ GameManager* manager,
+ uint32_t type,
+ EntityEventValue value) {
+ if(receiver->description && receiver->description->event) {
+ EntityEvent event = {
+ .type = type,
+ .sender = sender,
+ .value = value,
+ };
+ receiver->description->event(receiver, manager, event, receiver->context);
+ }
+}
\ No newline at end of file
diff --git a/flip_world/engine/entity.h b/flip_world/engine/entity.h
new file mode 100644
index 000000000..2bd95a2a1
--- /dev/null
+++ b/flip_world/engine/entity.h
@@ -0,0 +1,65 @@
+#pragma once
+#include "vector.h"
+#include "game_engine.h"
+#include
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct Entity Entity;
+
+typedef struct Level Level;
+
+typedef union {
+ uint32_t value;
+ void* pointer;
+} EntityEventValue;
+
+typedef struct {
+ uint32_t type;
+ Entity* sender;
+ EntityEventValue value;
+} EntityEvent;
+
+typedef struct Level Level;
+typedef struct GameManager GameManager;
+
+typedef struct {
+ void (*start)(Entity* self, GameManager* manager, void* context);
+ void (*stop)(Entity* self, GameManager* manager, void* context);
+ void (*update)(Entity* self, GameManager* manager, void* context);
+ void (*render)(Entity* self, GameManager* manager, Canvas* canvas, void* context);
+ void (*collision)(Entity* self, Entity* other, GameManager* manager, void* context);
+ void (*event)(Entity* self, GameManager* manager, EntityEvent event, void* context);
+ size_t context_size;
+} EntityDescription;
+
+const EntityDescription* entity_description_get(Entity* entity);
+
+Vector entity_pos_get(Entity* entity);
+
+void entity_pos_set(Entity* entity, Vector position);
+
+void* entity_context_get(Entity* entity);
+
+void entity_collider_add_circle(Entity* entity, float radius);
+
+void entity_collider_add_rect(Entity* entity, float width, float height);
+
+void entity_collider_remove(Entity* entity);
+
+void entity_collider_offset_set(Entity* entity, Vector offset);
+
+Vector entity_collider_offset_get(Entity* entity);
+
+void entity_send_event(
+ Entity* sender,
+ Entity* receiver,
+ GameManager* manager,
+ uint32_t type,
+ EntityEventValue value);
+
+#ifdef __cplusplus
+}
+#endif
\ No newline at end of file
diff --git a/flip_world/engine/entity_i.h b/flip_world/engine/entity_i.h
new file mode 100644
index 000000000..1f60d944f
--- /dev/null
+++ b/flip_world/engine/entity_i.h
@@ -0,0 +1,58 @@
+#pragma once
+#include "entity.h"
+#include "game_manager.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef enum {
+ ColliderTypeCircle,
+ ColliderTypeRect,
+} ColliderType;
+
+typedef struct {
+ ColliderType type;
+ union {
+ struct {
+ float radius;
+ } circle;
+ struct {
+ float half_width;
+ float half_height;
+ } rect;
+ };
+} Collider;
+
+struct Entity {
+ Vector position;
+ const EntityDescription* description;
+ void* context;
+ Collider* collider;
+ Vector collider_offset;
+ bool collider_dirty;
+};
+
+Entity* entity_alloc(const EntityDescription* behaviour);
+
+void entity_free(Entity* entity);
+
+void entity_call_start(Entity* entity, GameManager* manager);
+
+void entity_call_stop(Entity* entity, GameManager* manager);
+
+void entity_call_update(Entity* entity, GameManager* manager);
+
+void entity_call_render(Entity* entity, GameManager* manager, Canvas* canvas);
+
+void entity_call_collision(Entity* entity, Entity* other, GameManager* manager);
+
+bool entity_collider_check_collision(Entity* entity, Entity* other);
+
+bool entity_collider_exists(Entity* entity);
+
+int32_t entities_get_count(void);
+
+#ifdef __cplusplus
+}
+#endif
\ No newline at end of file
diff --git a/flip_world/engine/game_engine.c b/flip_world/engine/game_engine.c
new file mode 100644
index 000000000..1a50d06cb
--- /dev/null
+++ b/flip_world/engine/game_engine.c
@@ -0,0 +1,199 @@
+#include "game_engine.h"
+#include
+#include
+#include
+#include
+#include "clock_timer.h"
+
+typedef _Atomic uint32_t AtomicUint32;
+
+GameEngineSettings game_engine_settings_init() {
+ GameEngineSettings settings;
+ settings.target_fps = 30.0f;
+ settings.show_fps = false;
+ settings.always_backlight = true;
+ settings.start_callback = NULL;
+ settings.frame_callback = NULL;
+ settings.stop_callback = NULL;
+ settings.context = NULL;
+ return settings;
+}
+
+struct GameEngine {
+ Gui* gui;
+ NotificationApp* notifications;
+ FuriPubSub* input_pubsub;
+ FuriThreadId thread_id;
+ GameEngineSettings settings;
+ float fps;
+};
+
+typedef enum {
+ GameThreadFlagUpdate = 1 << 0,
+ GameThreadFlagStop = 1 << 1,
+} GameThreadFlag;
+
+#define GameThreadFlagMask (GameThreadFlagUpdate | GameThreadFlagStop)
+
+GameEngine* game_engine_alloc(GameEngineSettings settings) {
+ furi_check(settings.frame_callback != NULL);
+
+ GameEngine* engine = malloc(sizeof(GameEngine));
+ engine->gui = furi_record_open(RECORD_GUI);
+ engine->notifications = furi_record_open(RECORD_NOTIFICATION);
+ engine->input_pubsub = furi_record_open(RECORD_INPUT_EVENTS);
+ engine->thread_id = furi_thread_get_current_id();
+ engine->settings = settings;
+ engine->fps = 1.0f;
+
+ return engine;
+}
+
+void game_engine_free(GameEngine* engine) {
+ furi_record_close(RECORD_GUI);
+ furi_record_close(RECORD_NOTIFICATION);
+ furi_record_close(RECORD_INPUT_EVENTS);
+ free(engine);
+}
+
+static void clock_timer_callback(void* context) {
+ GameEngine* engine = context;
+ furi_thread_flags_set(engine->thread_id, GameThreadFlagUpdate);
+}
+
+static const GameKey keys[] = {
+ [InputKeyUp] = GameKeyUp,
+ [InputKeyDown] = GameKeyDown,
+ [InputKeyRight] = GameKeyRight,
+ [InputKeyLeft] = GameKeyLeft,
+ [InputKeyOk] = GameKeyOk,
+ [InputKeyBack] = GameKeyBack,
+};
+
+static const size_t keys_count = sizeof(keys) / sizeof(keys[0]);
+
+static void input_events_callback(const void* value, void* context) {
+ AtomicUint32* input_state = context;
+ const InputEvent* event = value;
+
+ if(event->key < keys_count) {
+ switch(event->type) {
+ case InputTypePress:
+ *input_state |= (keys[event->key]);
+ break;
+ case InputTypeRelease:
+ *input_state &= ~(keys[event->key]);
+ break;
+ default:
+ break;
+ }
+ }
+}
+
+void game_engine_run(GameEngine* engine) {
+ // input state
+ AtomicUint32 input_state = 0;
+ uint32_t input_prev_state = 0;
+
+ // set backlight if needed
+ if(engine->settings.always_backlight) {
+ notification_message(engine->notifications, &sequence_display_backlight_enforce_on);
+ }
+
+ // acquire gui canvas
+ Canvas* canvas = gui_direct_draw_acquire(engine->gui);
+
+ // subscribe to input events
+ FuriPubSubSubscription* input_subscription =
+ furi_pubsub_subscribe(engine->input_pubsub, input_events_callback, &input_state);
+
+ // call start callback, if any
+ if(engine->settings.start_callback) {
+ engine->settings.start_callback(engine, engine->settings.context);
+ }
+
+ // start "game update" timer
+ clock_timer_start(clock_timer_callback, engine, engine->settings.target_fps);
+
+ // init fps counter
+ uint32_t time_start = DWT->CYCCNT;
+
+ while(true) {
+ uint32_t flags =
+ furi_thread_flags_wait(GameThreadFlagMask, FuriFlagWaitAny, FuriWaitForever);
+ furi_check((flags & FuriFlagError) == 0);
+
+ if(flags & GameThreadFlagUpdate) {
+ // update fps counter
+ uint32_t time_end = DWT->CYCCNT;
+ uint32_t time_delta = time_end - time_start;
+ time_start = time_end;
+
+ // update input state
+ uint32_t input_current_state = input_state;
+ InputState input = {
+ .held = input_current_state,
+ .pressed = input_current_state & ~input_prev_state,
+ .released = ~input_current_state & input_prev_state,
+ };
+ input_prev_state = input_current_state;
+
+ // clear screen
+ canvas_reset(canvas);
+
+ // calculate actual fps
+ engine->fps = (float)SystemCoreClock / time_delta;
+
+ // do the work
+ engine->settings.frame_callback(engine, canvas, input, engine->settings.context);
+
+ // show fps if needed
+ if(engine->settings.show_fps) {
+ canvas_set_color(canvas, ColorXOR);
+ canvas_printf(canvas, 0, 7, "%u", (uint32_t)roundf(engine->fps));
+ }
+
+ // and output screen buffer
+ canvas_commit(canvas);
+
+ // throttle a bit
+ furi_delay_tick(2);
+ }
+
+ if(flags & GameThreadFlagStop) {
+ break;
+ }
+ }
+
+ // stop timer
+ clock_timer_stop();
+
+ // call stop callback, if any
+ if(engine->settings.stop_callback) {
+ engine->settings.stop_callback(engine, engine->settings.context);
+ }
+
+ // release gui canvas and unsubscribe from input events
+ gui_direct_draw_release(engine->gui);
+ furi_pubsub_unsubscribe(engine->input_pubsub, input_subscription);
+
+ if(engine->settings.always_backlight) {
+ notification_message(engine->notifications, &sequence_display_backlight_enforce_auto);
+ }
+}
+
+void game_engine_stop(GameEngine* engine) {
+ furi_thread_flags_set(engine->thread_id, GameThreadFlagStop);
+}
+
+float game_engine_get_delta_time(GameEngine* engine) {
+ return 1.0f / engine->fps;
+}
+
+float game_engine_get_delta_frames(GameEngine* engine) {
+ return engine->fps / engine->settings.target_fps;
+}
+
+void game_engine_show_fps_set(GameEngine* engine, bool show_fps) {
+ engine->settings.show_fps = show_fps;
+}
\ No newline at end of file
diff --git a/flip_world/engine/game_engine.h b/flip_world/engine/game_engine.h
new file mode 100644
index 000000000..4bfb71d13
--- /dev/null
+++ b/flip_world/engine/game_engine.h
@@ -0,0 +1,88 @@
+#pragma once
+#include
+#include "canvas.h"
+#include
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef enum {
+ GameKeyUp = 1 << 0,
+ GameKeyDown = 1 << 1,
+ GameKeyRight = 1 << 2,
+ GameKeyLeft = 1 << 3,
+ GameKeyOk = 1 << 4,
+ GameKeyBack = 1 << 5,
+} GameKey;
+
+typedef struct {
+ uint32_t held; // mask of GameKey held in current frame
+ uint32_t pressed; // mask of GameKey pressed in current frame
+ uint32_t released; // mask of GameKey released in current frame
+} InputState;
+
+typedef struct GameEngine GameEngine;
+
+typedef void (*GameEngineStartCallback)(GameEngine* engine, void* context);
+
+typedef void (*GameEngineStopCallback)(GameEngine* engine, void* context);
+
+typedef void (
+ *GameEngineFrameCallback)(GameEngine* engine, Canvas* canvas, InputState input, void* context);
+
+typedef struct {
+ float target_fps; // target fps
+ bool show_fps; // show fps counter
+ bool always_backlight; // keep backlight on
+ GameEngineStartCallback start_callback; // called when engine starts
+ GameEngineFrameCallback frame_callback; // frame callback, called at target fps
+ GameEngineStopCallback stop_callback; // called when engine stops
+ void* context; // user context passed to callback
+} GameEngineSettings;
+
+/** Default settings initializer */
+GameEngineSettings game_engine_settings_init(void);
+
+/** Game Engine allocator
+ * @param settings engine settings
+ * @return GameEngine* GameEngine instance
+ */
+GameEngine* game_engine_alloc(GameEngineSettings settings);
+
+/** Run the Game Engine. Blocks until game_engine_stop() is called.
+ * @param engine GameEngine instance
+ */
+void game_engine_run(GameEngine* engine);
+
+/** Free the Game Engine
+ * @param engine GameEngine instance
+ */
+void game_engine_free(GameEngine* engine);
+
+/** Stop the Game Engine, will not block execution
+ * @param engine GameEngine instance
+ */
+void game_engine_stop(GameEngine* engine);
+
+/** Get delta time between current and previous frame
+ * @param engine GameEngine instance
+ * @return float delta time in seconds
+ */
+float game_engine_get_delta_time(GameEngine* engine);
+
+/** Get delta frames between current and previous frame
+ * @param engine GameEngine instance
+ * @return float delta frames
+ */
+float game_engine_get_delta_frames(GameEngine* engine);
+
+/** Enable/disable show fps counter
+ * @param engine GameEngine instance
+ * @param show_fps show fps counter
+ */
+void game_engine_show_fps_set(GameEngine* engine, bool show_fps);
+
+#ifdef __cplusplus
+}
+#endif
\ No newline at end of file
diff --git a/flip_world/engine/game_manager.c b/flip_world/engine/game_manager.c
new file mode 100644
index 000000000..81fd2a303
--- /dev/null
+++ b/flip_world/engine/game_manager.c
@@ -0,0 +1,162 @@
+#include "game_manager.h"
+#include "level_i.h"
+#include
+#include
+#include
+
+typedef struct {
+ Sprite* sprite;
+ FuriString* path;
+} SpriteCache;
+
+LIST_DEF(LevelList, Level*, M_POD_OPLIST);
+LIST_DEF(SpriteCacheList, SpriteCache, M_POD_OPLIST);
+
+struct GameManager {
+ LevelList_t levels;
+ Level* current_level;
+ Level* next_level;
+
+ GameEngine* engine;
+ InputState input;
+ void* game_context;
+
+ SpriteCacheList_t sprites;
+};
+
+GameManager* game_manager_alloc() {
+ GameManager* manager = malloc(sizeof(GameManager));
+ LevelList_init(manager->levels);
+ manager->current_level = NULL;
+ manager->next_level = NULL;
+ manager->engine = NULL;
+ manager->game_context = NULL;
+ memset(&manager->input, 0, sizeof(InputState));
+ SpriteCacheList_init(manager->sprites);
+ return manager;
+}
+
+void game_manager_free(GameManager* manager) {
+ level_call_stop(manager->current_level);
+
+ // Free all levels
+ {
+ LevelList_it_t it;
+ LevelList_it(it, manager->levels);
+ while(!LevelList_end_p(it)) {
+ level_call_free(*LevelList_cref(it));
+ level_free(*LevelList_cref(it));
+ LevelList_next(it);
+ }
+ }
+
+ LevelList_clear(manager->levels);
+
+ // Free all sprites
+ {
+ SpriteCacheList_it_t it;
+ SpriteCacheList_it(it, manager->sprites);
+ while(!SpriteCacheList_end_p(it)) {
+ furi_string_free(SpriteCacheList_cref(it)->path);
+ sprite_free(SpriteCacheList_cref(it)->sprite);
+ SpriteCacheList_next(it);
+ }
+ }
+ SpriteCacheList_clear(manager->sprites);
+ free(manager);
+}
+
+Level* game_manager_add_level(GameManager* manager, const LevelBehaviour* behaviour) {
+ UNUSED(manager);
+ Level* level = level_alloc(behaviour, manager);
+ LevelList_push_back(manager->levels, level);
+ level_call_alloc(level);
+ if(!manager->current_level) {
+ manager->current_level = level;
+ level_call_start(level);
+ }
+ return level;
+}
+
+void game_manager_next_level_set(GameManager* manager, Level* next_level) {
+ manager->next_level = next_level;
+}
+
+void game_manager_game_stop(GameManager* manager) {
+ GameEngine* engine = game_manager_engine_get(manager);
+ game_engine_stop(engine);
+}
+
+Level* game_manager_current_level_get(GameManager* manager) {
+ return manager->current_level;
+}
+
+void game_manager_update(GameManager* manager) {
+ if(manager->next_level) {
+ level_call_stop(manager->current_level);
+ manager->current_level = manager->next_level;
+ level_call_start(manager->current_level);
+ manager->next_level = NULL;
+ }
+
+ level_update(manager->current_level, manager);
+}
+
+void game_manager_render(GameManager* manager, Canvas* canvas) {
+ level_render(manager->current_level, manager, canvas);
+}
+
+void game_manager_engine_set(GameManager* manager, GameEngine* engine) {
+ manager->engine = engine;
+}
+
+void game_manager_input_set(GameManager* manager, InputState input) {
+ manager->input = input;
+}
+
+void game_manager_game_context_set(GameManager* manager, void* context) {
+ manager->game_context = context;
+}
+
+GameEngine* game_manager_engine_get(GameManager* manager) {
+ return manager->engine;
+}
+
+InputState game_manager_input_get(GameManager* manager) {
+ return manager->input;
+}
+
+void* game_manager_game_context_get(GameManager* manager) {
+ return manager->game_context;
+}
+
+void game_manager_show_fps_set(GameManager* manager, bool show_fps) {
+ GameEngine* engine = game_manager_engine_get(manager);
+ game_engine_show_fps_set(engine, show_fps);
+}
+
+Sprite* game_manager_sprite_load(GameManager* manager, const char* path) {
+ SpriteCacheList_it_t it;
+ SpriteCacheList_it(it, manager->sprites);
+ while(!SpriteCacheList_end_p(it)) {
+ if(furi_string_cmp(SpriteCacheList_cref(it)->path, path) == 0) {
+ return SpriteCacheList_cref(it)->sprite;
+ }
+ SpriteCacheList_next(it);
+ }
+
+ FuriString* path_full = furi_string_alloc_set(APP_ASSETS_PATH("sprites/"));
+ furi_string_cat(path_full, path);
+
+ Sprite* sprite = sprite_alloc(furi_string_get_cstr(path_full));
+ if(sprite) {
+ SpriteCache cache = {
+ .sprite = sprite,
+ .path = furi_string_alloc_set(path),
+ };
+ SpriteCacheList_push_back(manager->sprites, cache);
+ }
+ furi_string_free(path_full);
+
+ return sprite;
+}
\ No newline at end of file
diff --git a/flip_world/engine/game_manager.h b/flip_world/engine/game_manager.h
new file mode 100644
index 000000000..42113531d
--- /dev/null
+++ b/flip_world/engine/game_manager.h
@@ -0,0 +1,40 @@
+#pragma once
+#include "level.h"
+#include "game_engine.h"
+#include "sprite.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct GameManager GameManager;
+
+Level* game_manager_add_level(GameManager* manager, const LevelBehaviour* behaviour);
+
+void game_manager_next_level_set(GameManager* manager, Level* level);
+
+Level* game_manager_current_level_get(GameManager* manager);
+
+GameEngine* game_manager_engine_get(GameManager* manager);
+
+InputState game_manager_input_get(GameManager* manager);
+
+void* game_manager_game_context_get(GameManager* manager);
+
+void game_manager_game_stop(GameManager* manager);
+
+void game_manager_show_fps_set(GameManager* manager, bool show_fps);
+
+/**
+ * @brief Load a sprite from a file
+ * Sprite will be cached and reused if the same file is loaded again
+ *
+ * @param manager game manager instance
+ * @param path sprite file path, relative to the game's assets folder
+ * @return Sprite* or NULL if the sprite could not be loaded
+ */
+Sprite* game_manager_sprite_load(GameManager* manager, const char* path);
+
+#ifdef __cplusplus
+}
+#endif
\ No newline at end of file
diff --git a/flip_world/engine/game_manager_i.h b/flip_world/engine/game_manager_i.h
new file mode 100644
index 000000000..a80f047ed
--- /dev/null
+++ b/flip_world/engine/game_manager_i.h
@@ -0,0 +1,24 @@
+#pragma once
+#include "game_manager.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+GameManager* game_manager_alloc(void);
+
+void game_manager_free(GameManager* manager);
+
+void game_manager_update(GameManager* manager);
+
+void game_manager_render(GameManager* manager, Canvas* canvas);
+
+void game_manager_engine_set(GameManager* manager, GameEngine* engine);
+
+void game_manager_input_set(GameManager* manager, InputState input);
+
+void game_manager_game_context_set(GameManager* manager, void* context);
+
+#ifdef __cplusplus
+}
+#endif
\ No newline at end of file
diff --git a/flip_world/engine/level.c b/flip_world/engine/level.c
new file mode 100644
index 000000000..8ac9786d7
--- /dev/null
+++ b/flip_world/engine/level.c
@@ -0,0 +1,214 @@
+#include "level.h"
+#include "level_i.h"
+#include "entity_i.h"
+#include
+#include
+
+LIST_DEF(EntityList, Entity*, M_POD_OPLIST);
+#define M_OPL_EntityList_t() LIST_OPLIST(EntityList)
+#define FOREACH(name, list) for \
+ M_EACH(name, list, EntityList_t)
+
+#define LEVEL_DEBUG(...) FURI_LOG_D("Level", __VA_ARGS__)
+#define LEVEL_ERROR(...) FURI_LOG_E("Level", __VA_ARGS__)
+
+struct Level {
+ EntityList_t entities;
+ EntityList_t to_add;
+ EntityList_t to_remove;
+ const LevelBehaviour* behaviour;
+ void* context;
+ GameManager* manager;
+};
+
+Level* level_alloc(const LevelBehaviour* behaviour, GameManager* manager) {
+ Level* level = malloc(sizeof(Level));
+ level->manager = manager;
+ EntityList_init(level->entities);
+ EntityList_init(level->to_add);
+ EntityList_init(level->to_remove);
+ level->behaviour = behaviour;
+ if(behaviour->context_size > 0) {
+ level->context = malloc(behaviour->context_size);
+ } else {
+ level->context = NULL;
+ }
+ LEVEL_DEBUG("Allocated level at %p", level);
+ return level;
+}
+
+static void level_process_add(Level* level) {
+ // move entities from to_add to entities
+ FOREACH(item, level->to_add) {
+ EntityList_push_back(level->entities, *item);
+ }
+ EntityList_clear(level->to_add);
+}
+
+static void level_process_remove(Level* level) {
+ // remove entities in to_remove from entities and free them
+ FOREACH(item, level->to_remove) {
+ entity_free(*item);
+ EntityList_it_t it;
+
+ // find and remove the entity from the entities list
+ for(EntityList_it(it, level->entities); !EntityList_end_p(it); EntityList_next(it)) {
+ if(*EntityList_ref(it) == *item) {
+ EntityList_remove(level->entities, it);
+ break;
+ }
+ }
+ }
+ EntityList_clear(level->to_remove);
+}
+
+static bool level_entity_in_list_p(EntityList_t list, Entity* entity) {
+ FOREACH(item, list) {
+ if(*item == entity) {
+ return true;
+ }
+ }
+ return false;
+}
+
+void level_free(Level* level) {
+ level_clear(level);
+
+ EntityList_clear(level->entities);
+ EntityList_clear(level->to_add);
+ EntityList_clear(level->to_remove);
+
+ if(level->behaviour->context_size > 0) {
+ free(level->context);
+ }
+
+ LEVEL_DEBUG("Freeing level at %p", level);
+ free(level);
+}
+
+void level_clear(Level* level) {
+ size_t iterations = 0;
+
+ do {
+ // process to_add and to_remove
+ level_process_add(level);
+ level_process_remove(level);
+
+ // remove entities from entities list
+ FOREACH(item, level->entities) {
+ if(!level_entity_in_list_p(level->to_remove, *item)) {
+ level_remove_entity(level, *item);
+ }
+ }
+
+ // check if we are looping too many times
+ iterations++;
+ if(iterations >= 100) {
+ LEVEL_ERROR("Level free looped too many times");
+ }
+
+ // entity_call_stop can call level_remove_entity or level_add_entity
+ // so we need to process to_add and to_remove again
+ } while(!EntityList_empty_p(level->to_add) || !EntityList_empty_p(level->to_remove));
+}
+
+Entity* level_add_entity(Level* level, const EntityDescription* description) {
+ Entity* entity = entity_alloc(description);
+ EntityList_push_back(level->to_add, entity);
+ entity_call_start(entity, level->manager);
+ return entity;
+}
+
+void level_remove_entity(Level* level, Entity* entity) {
+ EntityList_push_back(level->to_remove, entity);
+ entity_call_stop(entity, level->manager);
+}
+
+void level_send_event(
+ Level* level,
+ Entity* sender,
+ const EntityDescription* receiver_desc,
+ uint32_t type,
+ EntityEventValue value) {
+ FOREACH(item, level->entities) {
+ if(receiver_desc == entity_description_get(*item) || receiver_desc == NULL) {
+ entity_send_event(sender, *item, level->manager, type, value);
+ }
+ }
+}
+
+static void level_process_update(Level* level, GameManager* manager) {
+ FOREACH(item, level->entities) {
+ entity_call_update(*item, manager);
+ }
+}
+
+static void level_process_collision(Level* level, GameManager* manager) {
+ EntityList_it_t it_first;
+ EntityList_it_t it_second;
+
+ EntityList_it(it_first, level->entities);
+ while(!EntityList_end_p(it_first)) {
+ Entity* first = *EntityList_ref(it_first);
+ if(entity_collider_exists(first)) {
+ // start second iterator at the next entity,
+ // so we don't check the same pair twice
+ EntityList_it_set(it_second, it_first);
+ EntityList_next(it_second);
+ while(!EntityList_end_p(it_second)) {
+ Entity* second = *EntityList_ref(it_second);
+ if(first->collider_dirty || second->collider_dirty) {
+ if(entity_collider_exists(second)) {
+ if(entity_collider_check_collision(first, second)) {
+ entity_call_collision(first, second, manager);
+ entity_call_collision(second, first, manager);
+ }
+ }
+ }
+ EntityList_next(it_second);
+ }
+ }
+ EntityList_next(it_first);
+ }
+
+ FOREACH(item, level->entities) {
+ (*item)->collider_dirty = false;
+ }
+}
+
+void level_update(Level* level, GameManager* manager) {
+ level_process_add(level);
+ level_process_remove(level);
+ level_process_update(level, manager);
+ level_process_collision(level, manager);
+}
+
+void level_render(Level* level, GameManager* manager, Canvas* canvas) {
+ FOREACH(item, level->entities) {
+ entity_call_render(*item, manager, canvas);
+ }
+}
+
+void level_call_start(Level* level) {
+ if(level->behaviour->start) {
+ level->behaviour->start(level, level->manager, level->context);
+ }
+}
+
+void level_call_stop(Level* level) {
+ if(level->behaviour->stop) {
+ level->behaviour->stop(level, level->manager, level->context);
+ }
+}
+
+void level_call_alloc(Level* level) {
+ if(level->behaviour->alloc) {
+ level->behaviour->alloc(level, level->manager, level->context);
+ }
+}
+
+void level_call_free(Level* level) {
+ if(level->behaviour->free) {
+ level->behaviour->free(level, level->manager, level->context);
+ }
+}
\ No newline at end of file
diff --git a/flip_world/engine/level.h b/flip_world/engine/level.h
new file mode 100644
index 000000000..0b745e4b0
--- /dev/null
+++ b/flip_world/engine/level.h
@@ -0,0 +1,63 @@
+#pragma once
+#include
+#include "entity.h"
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ typedef struct GameManager GameManager;
+
+ typedef struct
+ {
+ void (*alloc)(Level *level, GameManager *manager, void *context);
+ void (*free)(Level *level, GameManager *manager, void *context);
+ void (*start)(Level *level, GameManager *manager, void *context);
+ void (*stop)(Level *level, GameManager *manager, void *context);
+ size_t context_size;
+ } LevelBehaviour;
+
+ /**
+ * @brief Remove all entities from the level
+ *
+ * @param level level instance
+ */
+ void level_clear(Level *level);
+
+ /**
+ * @brief Add an entity to the level
+ *
+ * @param level level instance
+ * @param behaviour entity behaviour
+ * @return Entity*
+ */
+ Entity *level_add_entity(Level *level, const EntityDescription *behaviour);
+
+ /**
+ * @brief Remove an entity from the level
+ *
+ * @param level level instance
+ * @param entity entity to remove
+ */
+ void level_remove_entity(Level *level, Entity *entity);
+
+ /**
+ * @brief Send an event to all entities of a certain type in the level
+ *
+ * @param level level instance
+ * @param sender entity that sends the event
+ * @param receiver_desc entity description that will receive the event, NULL for all entities
+ * @param type event type
+ * @param value event value
+ */
+ void level_send_event(
+ Level *level,
+ Entity *sender,
+ const EntityDescription *receiver_desc,
+ uint32_t type,
+ EntityEventValue value);
+
+#ifdef __cplusplus
+}
+#endif
\ No newline at end of file
diff --git a/flip_world/engine/level_i.h b/flip_world/engine/level_i.h
new file mode 100644
index 000000000..2bdd83592
--- /dev/null
+++ b/flip_world/engine/level_i.h
@@ -0,0 +1,26 @@
+#pragma once
+#include "level.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+Level* level_alloc(const LevelBehaviour* behaviour, GameManager* manager);
+
+void level_free(Level* level);
+
+void level_update(Level* level, GameManager* manager);
+
+void level_render(Level* level, GameManager* manager, Canvas* canvas);
+
+void level_call_alloc(Level* level);
+
+void level_call_free(Level* level);
+
+void level_call_start(Level* level);
+
+void level_call_stop(Level* level);
+
+#ifdef __cplusplus
+}
+#endif
\ No newline at end of file
diff --git a/flip_world/engine/main.c b/flip_world/engine/main.c
new file mode 100644
index 000000000..b423e812c
--- /dev/null
+++ b/flip_world/engine/main.c
@@ -0,0 +1,59 @@
+#include
+#include "engine.h"
+#include "game_engine.h"
+#include "game_manager_i.h"
+#include "level_i.h"
+#include "entity_i.h"
+
+// static void frame_cb(GameEngine *engine, Canvas *canvas, InputState input, void *context)
+// {
+// UNUSED(engine);
+// GameManager *game_manager = context;
+// game_manager_input_set(game_manager, input);
+// game_manager_update(game_manager);
+// game_manager_render(game_manager, canvas);
+// }
+
+// int32_t game_app(void *p)
+// {
+// UNUSED(p);
+// GameManager *game_manager = game_manager_alloc();
+
+// GameEngineSettings settings = game_engine_settings_init();
+// settings.target_fps = game.target_fps;
+// settings.show_fps = game.show_fps;
+// settings.always_backlight = game.always_backlight;
+// settings.frame_callback = frame_cb;
+// settings.context = game_manager;
+
+// GameEngine *engine = game_engine_alloc(settings);
+// game_manager_engine_set(game_manager, engine);
+
+// void *game_context = NULL;
+// if (game.context_size > 0)
+// {
+// game_context = malloc(game.context_size);
+// game_manager_game_context_set(game_manager, game_context);
+// }
+// game.start(game_manager, game_context);
+
+// game_engine_run(engine);
+// game_engine_free(engine);
+
+// game_manager_free(game_manager);
+
+// game.stop(game_context);
+// if (game_context)
+// {
+// free(game_context);
+// }
+
+// int32_t entities = entities_get_count();
+// if (entities != 0)
+// {
+// FURI_LOG_E("Game", "Memory leak detected: %ld entities still allocated", entities);
+// return -1;
+// }
+
+// return 0;
+// }
\ No newline at end of file
diff --git a/flip_world/engine/scripts/sprite_builder.py b/flip_world/engine/scripts/sprite_builder.py
new file mode 100644
index 000000000..10a0d8632
--- /dev/null
+++ b/flip_world/engine/scripts/sprite_builder.py
@@ -0,0 +1,84 @@
+#!/usr/bin/env python3
+import argparse
+import io
+import logging
+import os
+import struct
+
+from PIL import Image, ImageOps
+
+# XBM flipper sprite (.fxbm) is 1-bit depth, width-padded to 8 bits
+# file format:
+# uint32 size of the rest of the file in bytes
+# uint32 width in px
+# uint32 height in px
+# uint8[] pixel data, every row is padded to 8 bits (like XBM)
+
+def image2xbm(input_file_path):
+ with Image.open(input_file_path) as im:
+ with io.BytesIO() as output:
+ bw = im.convert("1")
+ bw = ImageOps.invert(bw)
+ bw.save(output, format="XBM")
+ return output.getvalue()
+
+def xbm2fxbm(data):
+ # hell as it is, but it works
+ f = io.StringIO(data.decode().strip())
+ width = int(f.readline().strip().split(" ")[2])
+ height = int(f.readline().strip().split(" ")[2])
+ data = f.read().strip().replace("\n", "").replace(" ", "").split("=")[1][:-1]
+ data_str = data[1:-1].replace(",", " ").replace("0x", "")
+ image_bin = bytearray.fromhex(data_str)
+
+ output = struct.pack("accel_scale = accel_fs_modes[full_scale].value;
+ uint8_t reg_value = accel_fs_modes[full_scale].reg_mask | rate;
+ return icm42688p_write_reg(icm42688p->spi_bus, ICM42688_ACCEL_CONFIG0, reg_value);
+}
+
+float icm42688p_accel_get_full_scale(ICM42688P* icm42688p) {
+ return icm42688p->accel_scale;
+}
+
+bool icm42688p_gyro_config(
+ ICM42688P* icm42688p,
+ ICM42688PGyroFullScale full_scale,
+ ICM42688PDataRate rate) {
+ icm42688p->gyro_scale = gyro_fs_modes[full_scale].value;
+ uint8_t reg_value = gyro_fs_modes[full_scale].reg_mask | rate;
+ return icm42688p_write_reg(icm42688p->spi_bus, ICM42688_GYRO_CONFIG0, reg_value);
+}
+
+float icm42688p_gyro_get_full_scale(ICM42688P* icm42688p) {
+ return icm42688p->gyro_scale;
+}
+
+bool icm42688p_read_accel_raw(ICM42688P* icm42688p, ICM42688PRawData* data) {
+ bool ret = icm42688p_read_mem(
+ icm42688p->spi_bus, ICM42688_ACCEL_DATA_X1, (uint8_t*)data, sizeof(ICM42688PRawData));
+ return ret;
+}
+
+bool icm42688p_read_gyro_raw(ICM42688P* icm42688p, ICM42688PRawData* data) {
+ bool ret = icm42688p_read_mem(
+ icm42688p->spi_bus, ICM42688_GYRO_DATA_X1, (uint8_t*)data, sizeof(ICM42688PRawData));
+ return ret;
+}
+
+bool icm42688p_write_gyro_offset(ICM42688P* icm42688p, ICM42688PScaledData* scaled_data) {
+ if((fabsf(scaled_data->x) > 64.f) || (fabsf(scaled_data->y) > 64.f) ||
+ (fabsf(scaled_data->z) > 64.f)) {
+ return false;
+ }
+
+ uint16_t offset_x = (uint16_t)(-(int16_t)(scaled_data->x * 32.f) * 16) >> 4;
+ uint16_t offset_y = (uint16_t)(-(int16_t)(scaled_data->y * 32.f) * 16) >> 4;
+ uint16_t offset_z = (uint16_t)(-(int16_t)(scaled_data->z * 32.f) * 16) >> 4;
+
+ uint8_t offset_regs[9];
+ icm42688p_write_reg(icm42688p->spi_bus, ICM42688_REG_BANK_SEL, 4);
+ icm42688p_read_mem(icm42688p->spi_bus, ICM42688_OFFSET_USER0, offset_regs, 5);
+
+ offset_regs[0] = offset_x & 0xFF;
+ offset_regs[1] = (offset_x & 0xF00) >> 8;
+ offset_regs[1] |= (offset_y & 0xF00) >> 4;
+ offset_regs[2] = offset_y & 0xFF;
+ offset_regs[3] = offset_z & 0xFF;
+ offset_regs[4] &= 0xF0;
+ offset_regs[4] |= (offset_z & 0x0F00) >> 8;
+
+ icm42688p_write_reg(icm42688p->spi_bus, ICM42688_OFFSET_USER0, offset_regs[0]);
+ icm42688p_write_reg(icm42688p->spi_bus, ICM42688_OFFSET_USER1, offset_regs[1]);
+ icm42688p_write_reg(icm42688p->spi_bus, ICM42688_OFFSET_USER2, offset_regs[2]);
+ icm42688p_write_reg(icm42688p->spi_bus, ICM42688_OFFSET_USER3, offset_regs[3]);
+ icm42688p_write_reg(icm42688p->spi_bus, ICM42688_OFFSET_USER4, offset_regs[4]);
+
+ icm42688p_write_reg(icm42688p->spi_bus, ICM42688_REG_BANK_SEL, 0);
+ return true;
+}
+
+void icm42688p_apply_scale(ICM42688PRawData* raw_data, float full_scale, ICM42688PScaledData* data) {
+ data->x = ((float)(raw_data->x)) / 32768.f * full_scale;
+ data->y = ((float)(raw_data->y)) / 32768.f * full_scale;
+ data->z = ((float)(raw_data->z)) / 32768.f * full_scale;
+}
+
+void icm42688p_apply_scale_fifo(
+ ICM42688P* icm42688p,
+ ICM42688PFifoPacket* fifo_data,
+ ICM42688PScaledData* accel_data,
+ ICM42688PScaledData* gyro_data) {
+ float full_scale = icm42688p->accel_scale;
+ accel_data->x = ((float)(fifo_data->a_x)) / 32768.f * full_scale;
+ accel_data->y = ((float)(fifo_data->a_y)) / 32768.f * full_scale;
+ accel_data->z = ((float)(fifo_data->a_z)) / 32768.f * full_scale;
+
+ full_scale = icm42688p->gyro_scale;
+ gyro_data->x = ((float)(fifo_data->g_x)) / 32768.f * full_scale;
+ gyro_data->y = ((float)(fifo_data->g_y)) / 32768.f * full_scale;
+ gyro_data->z = ((float)(fifo_data->g_z)) / 32768.f * full_scale;
+}
+
+float icm42688p_read_temp(ICM42688P* icm42688p) {
+ uint8_t reg_val[2];
+
+ icm42688p_read_mem(icm42688p->spi_bus, ICM42688_TEMP_DATA1, reg_val, 2);
+ int16_t temp_int = (reg_val[0] << 8) | reg_val[1];
+ return ((float)temp_int / 132.48f) + 25.f;
+}
+
+void icm42688_fifo_enable(
+ ICM42688P* icm42688p,
+ ICM42688PIrqCallback irq_callback,
+ void* irq_context) {
+ // FIFO mode: stream
+ icm42688p_write_reg(icm42688p->spi_bus, ICM42688_FIFO_CONFIG, (1 << 6));
+ // Little-endian data, FIFO count in records
+ icm42688p_write_reg(icm42688p->spi_bus, ICM42688_INTF_CONFIG0, (1 << 7) | (1 << 6));
+ // FIFO partial read, FIFO packet: gyro + accel TODO: 20bit
+ icm42688p_write_reg(
+ icm42688p->spi_bus, ICM42688_FIFO_CONFIG1, (1 << 6) | (1 << 5) | (1 << 1) | (1 << 0));
+ // FIFO irq watermark
+ uint16_t fifo_watermark = 1;
+ icm42688p_write_reg(icm42688p->spi_bus, ICM42688_FIFO_CONFIG2, fifo_watermark & 0xFF);
+ icm42688p_write_reg(icm42688p->spi_bus, ICM42688_FIFO_CONFIG3, fifo_watermark >> 8);
+
+ // IRQ1: push-pull, active high
+ icm42688p_write_reg(icm42688p->spi_bus, ICM42688_INT_CONFIG, (1 << 1) | (1 << 0));
+ // Clear IRQ on status read
+ icm42688p_write_reg(icm42688p->spi_bus, ICM42688_INT_CONFIG0, 0);
+ // IRQ pulse duration
+ icm42688p_write_reg(icm42688p->spi_bus, ICM42688_INT_CONFIG1, (1 << 6) | (1 << 5));
+
+ uint8_t reg_data = 0;
+ icm42688p_read_reg(icm42688p->spi_bus, ICM42688_INT_STATUS, ®_data);
+
+ furi_hal_gpio_init(icm42688p->irq_pin, GpioModeInterruptRise, GpioPullDown, GpioSpeedVeryHigh);
+ furi_hal_gpio_remove_int_callback(icm42688p->irq_pin);
+ furi_hal_gpio_add_int_callback(icm42688p->irq_pin, irq_callback, irq_context);
+
+ // IRQ1 source: FIFO threshold
+ icm42688p_write_reg(icm42688p->spi_bus, ICM42688_INT_SOURCE0, (1 << 2));
+}
+
+void icm42688_fifo_disable(ICM42688P* icm42688p) {
+ furi_hal_gpio_remove_int_callback(icm42688p->irq_pin);
+ furi_hal_gpio_init(icm42688p->irq_pin, GpioModeAnalog, GpioPullNo, GpioSpeedLow);
+
+ icm42688p_write_reg(icm42688p->spi_bus, ICM42688_INT_SOURCE0, 0);
+
+ // FIFO mode: bypass
+ icm42688p_write_reg(icm42688p->spi_bus, ICM42688_FIFO_CONFIG, 0);
+}
+
+uint16_t icm42688_fifo_get_count(ICM42688P* icm42688p) {
+ uint16_t reg_val = 0;
+ icm42688p_read_mem(icm42688p->spi_bus, ICM42688_FIFO_COUNTH, (uint8_t*)®_val, 2);
+ return reg_val;
+}
+
+bool icm42688_fifo_read(ICM42688P* icm42688p, ICM42688PFifoPacket* data) {
+ icm42688p_read_mem(
+ icm42688p->spi_bus, ICM42688_FIFO_DATA, (uint8_t*)data, sizeof(ICM42688PFifoPacket));
+ return (data->header) & (1 << 7);
+}
+
+ICM42688P* icm42688p_alloc(FuriHalSpiBusHandle* spi_bus, const GpioPin* irq_pin) {
+ ICM42688P* icm42688p = malloc(sizeof(ICM42688P));
+ icm42688p->spi_bus = spi_bus;
+ icm42688p->irq_pin = irq_pin;
+ return icm42688p;
+}
+
+void icm42688p_free(ICM42688P* icm42688p) {
+ free(icm42688p);
+}
+
+bool icm42688p_init(ICM42688P* icm42688p) {
+ furi_hal_spi_bus_handle_init(icm42688p->spi_bus);
+
+ // Software reset
+ icm42688p_write_reg(icm42688p->spi_bus, ICM42688_REG_BANK_SEL, 0); // Set reg bank to 0
+ icm42688p_write_reg(icm42688p->spi_bus, ICM42688_DEVICE_CONFIG, 0x01); // SPI Mode 0, SW reset
+ furi_delay_ms(1);
+
+ uint8_t reg_value = 0;
+ bool read_ok = icm42688p_read_reg(icm42688p->spi_bus, ICM42688_WHO_AM_I, ®_value);
+ if(!read_ok) {
+ FURI_LOG_E(TAG, "Chip ID read failed");
+ return false;
+ } else if(reg_value != ICM42688_WHOAMI) {
+ FURI_LOG_E(
+ TAG, "Sensor returned wrong ID 0x%02X, expected 0x%02X", reg_value, ICM42688_WHOAMI);
+ return false;
+ }
+
+ // Disable all interrupts
+ icm42688p_write_reg(icm42688p->spi_bus, ICM42688_INT_SOURCE0, 0);
+ icm42688p_write_reg(icm42688p->spi_bus, ICM42688_INT_SOURCE1, 0);
+ icm42688p_write_reg(icm42688p->spi_bus, ICM42688_INT_SOURCE3, 0);
+ icm42688p_write_reg(icm42688p->spi_bus, ICM42688_INT_SOURCE4, 0);
+ icm42688p_write_reg(icm42688p->spi_bus, ICM42688_REG_BANK_SEL, 4);
+ icm42688p_write_reg(icm42688p->spi_bus, ICM42688_INT_SOURCE6, 0);
+ icm42688p_write_reg(icm42688p->spi_bus, ICM42688_INT_SOURCE7, 0);
+ icm42688p_write_reg(icm42688p->spi_bus, ICM42688_REG_BANK_SEL, 0);
+
+ // Data format: little endian
+ icm42688p_write_reg(icm42688p->spi_bus, ICM42688_INTF_CONFIG0, 0);
+
+ // Enable all sensors
+ icm42688p_write_reg(
+ icm42688p->spi_bus,
+ ICM42688_PWR_MGMT0,
+ ICM42688_PWR_TEMP_ON | ICM42688_PWR_GYRO_MODE_LN | ICM42688_PWR_ACCEL_MODE_LN);
+ furi_delay_ms(45);
+
+ icm42688p_accel_config(icm42688p, AccelFullScale16G, DataRate1kHz);
+ icm42688p_gyro_config(icm42688p, GyroFullScale2000DPS, DataRate1kHz);
+
+ return true;
+}
+
+bool icm42688p_deinit(ICM42688P* icm42688p) {
+ // Software reset
+ icm42688p_write_reg(icm42688p->spi_bus, ICM42688_REG_BANK_SEL, 0); // Set reg bank to 0
+ icm42688p_write_reg(icm42688p->spi_bus, ICM42688_DEVICE_CONFIG, 0x01); // SPI Mode 0, SW reset
+
+ furi_hal_spi_bus_handle_deinit(icm42688p->spi_bus);
+ return true;
+}
diff --git a/flip_world/engine/sensors/ICM42688P/ICM42688P.h b/flip_world/engine/sensors/ICM42688P/ICM42688P.h
new file mode 100644
index 000000000..b04fb9809
--- /dev/null
+++ b/flip_world/engine/sensors/ICM42688P/ICM42688P.h
@@ -0,0 +1,127 @@
+#pragma once
+
+#include
+#include
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef enum {
+ DataRate32kHz = 0x01,
+ DataRate16kHz = 0x02,
+ DataRate8kHz = 0x03,
+ DataRate4kHz = 0x04,
+ DataRate2kHz = 0x05,
+ DataRate1kHz = 0x06,
+ DataRate200Hz = 0x07,
+ DataRate100Hz = 0x08,
+ DataRate50Hz = 0x09,
+ DataRate25Hz = 0x0A,
+ DataRate12_5Hz = 0x0B,
+ DataRate6_25Hz = 0x0C, // Accelerometer only
+ DataRate3_125Hz = 0x0D, // Accelerometer only
+ DataRate1_5625Hz = 0x0E, // Accelerometer only
+ DataRate500Hz = 0x0F,
+} ICM42688PDataRate;
+
+typedef enum {
+ AccelFullScale16G = 0,
+ AccelFullScale8G,
+ AccelFullScale4G,
+ AccelFullScale2G,
+ AccelFullScaleTotal,
+} ICM42688PAccelFullScale;
+
+typedef enum {
+ GyroFullScale2000DPS = 0,
+ GyroFullScale1000DPS,
+ GyroFullScale500DPS,
+ GyroFullScale250DPS,
+ GyroFullScale125DPS,
+ GyroFullScale62_5DPS,
+ GyroFullScale31_25DPS,
+ GyroFullScale15_625DPS,
+ GyroFullScaleTotal,
+} ICM42688PGyroFullScale;
+
+typedef struct {
+ int16_t x;
+ int16_t y;
+ int16_t z;
+} __attribute__((packed)) ICM42688PRawData;
+
+typedef struct {
+ uint8_t header;
+ int16_t a_x;
+ int16_t a_y;
+ int16_t a_z;
+ int16_t g_x;
+ int16_t g_y;
+ int16_t g_z;
+ uint8_t temp;
+ uint16_t ts;
+} __attribute__((packed)) ICM42688PFifoPacket;
+
+typedef struct {
+ float x;
+ float y;
+ float z;
+} ICM42688PScaledData;
+
+typedef struct ICM42688P ICM42688P;
+
+typedef void (*ICM42688PIrqCallback)(void* ctx);
+
+ICM42688P* icm42688p_alloc(FuriHalSpiBusHandle* spi_bus, const GpioPin* irq_pin);
+
+bool icm42688p_init(ICM42688P* icm42688p);
+
+bool icm42688p_deinit(ICM42688P* icm42688p);
+
+void icm42688p_free(ICM42688P* icm42688p);
+
+bool icm42688p_accel_config(
+ ICM42688P* icm42688p,
+ ICM42688PAccelFullScale full_scale,
+ ICM42688PDataRate rate);
+
+float icm42688p_accel_get_full_scale(ICM42688P* icm42688p);
+
+bool icm42688p_gyro_config(
+ ICM42688P* icm42688p,
+ ICM42688PGyroFullScale full_scale,
+ ICM42688PDataRate rate);
+
+float icm42688p_gyro_get_full_scale(ICM42688P* icm42688p);
+
+bool icm42688p_read_accel_raw(ICM42688P* icm42688p, ICM42688PRawData* data);
+
+bool icm42688p_read_gyro_raw(ICM42688P* icm42688p, ICM42688PRawData* data);
+
+bool icm42688p_write_gyro_offset(ICM42688P* icm42688p, ICM42688PScaledData* scaled_data);
+
+void icm42688p_apply_scale(ICM42688PRawData* raw_data, float full_scale, ICM42688PScaledData* data);
+
+void icm42688p_apply_scale_fifo(
+ ICM42688P* icm42688p,
+ ICM42688PFifoPacket* fifo_data,
+ ICM42688PScaledData* accel_data,
+ ICM42688PScaledData* gyro_data);
+
+float icm42688p_read_temp(ICM42688P* icm42688p);
+
+void icm42688_fifo_enable(
+ ICM42688P* icm42688p,
+ ICM42688PIrqCallback irq_callback,
+ void* irq_context);
+
+void icm42688_fifo_disable(ICM42688P* icm42688p);
+
+uint16_t icm42688_fifo_get_count(ICM42688P* icm42688p);
+
+bool icm42688_fifo_read(ICM42688P* icm42688p, ICM42688PFifoPacket* data);
+
+#ifdef __cplusplus
+}
+#endif
\ No newline at end of file
diff --git a/flip_world/engine/sensors/ICM42688P/ICM42688P_regs.h b/flip_world/engine/sensors/ICM42688P/ICM42688P_regs.h
new file mode 100644
index 000000000..1967534df
--- /dev/null
+++ b/flip_world/engine/sensors/ICM42688P/ICM42688P_regs.h
@@ -0,0 +1,176 @@
+#pragma once
+
+#define ICM42688_WHOAMI 0x47
+
+// Bank 0
+#define ICM42688_DEVICE_CONFIG 0x11
+#define ICM42688_DRIVE_CONFIG 0x13
+#define ICM42688_INT_CONFIG 0x14
+#define ICM42688_FIFO_CONFIG 0x16
+#define ICM42688_TEMP_DATA1 0x1D
+#define ICM42688_TEMP_DATA0 0x1E
+#define ICM42688_ACCEL_DATA_X1 0x1F
+#define ICM42688_ACCEL_DATA_X0 0x20
+#define ICM42688_ACCEL_DATA_Y1 0x21
+#define ICM42688_ACCEL_DATA_Y0 0x22
+#define ICM42688_ACCEL_DATA_Z1 0x23
+#define ICM42688_ACCEL_DATA_Z0 0x24
+#define ICM42688_GYRO_DATA_X1 0x25
+#define ICM42688_GYRO_DATA_X0 0x26
+#define ICM42688_GYRO_DATA_Y1 0x27
+#define ICM42688_GYRO_DATA_Y0 0x28
+#define ICM42688_GYRO_DATA_Z1 0x29
+#define ICM42688_GYRO_DATA_Z0 0x2A
+#define ICM42688_TMST_FSYNCH 0x2B
+#define ICM42688_TMST_FSYNCL 0x2C
+#define ICM42688_INT_STATUS 0x2D
+#define ICM42688_FIFO_COUNTH 0x2E
+#define ICM42688_FIFO_COUNTL 0x2F
+#define ICM42688_FIFO_DATA 0x30
+#define ICM42688_APEX_DATA0 0x31
+#define ICM42688_APEX_DATA1 0x32
+#define ICM42688_APEX_DATA2 0x33
+#define ICM42688_APEX_DATA3 0x34
+#define ICM42688_APEX_DATA4 0x35
+#define ICM42688_APEX_DATA5 0x36
+#define ICM42688_INT_STATUS2 0x37
+#define ICM42688_INT_STATUS3 0x38
+#define ICM42688_SIGNAL_PATH_RESET 0x4B
+#define ICM42688_INTF_CONFIG0 0x4C
+#define ICM42688_INTF_CONFIG1 0x4D
+#define ICM42688_PWR_MGMT0 0x4E
+#define ICM42688_GYRO_CONFIG0 0x4F
+#define ICM42688_ACCEL_CONFIG0 0x50
+#define ICM42688_GYRO_CONFIG1 0x51
+#define ICM42688_GYRO_ACCEL_CONFIG0 0x52
+#define ICM42688_ACCEL_CONFIG1 0x53
+#define ICM42688_TMST_CONFIG 0x54
+#define ICM42688_APEX_CONFIG0 0x56
+#define ICM42688_SMD_CONFIG 0x57
+#define ICM42688_FIFO_CONFIG1 0x5F
+#define ICM42688_FIFO_CONFIG2 0x60
+#define ICM42688_FIFO_CONFIG3 0x61
+#define ICM42688_FSYNC_CONFIG 0x62
+#define ICM42688_INT_CONFIG0 0x63
+#define ICM42688_INT_CONFIG1 0x64
+#define ICM42688_INT_SOURCE0 0x65
+#define ICM42688_INT_SOURCE1 0x66
+#define ICM42688_INT_SOURCE3 0x68
+#define ICM42688_INT_SOURCE4 0x69
+#define ICM42688_FIFO_LOST_PKT0 0x6C
+#define ICM42688_FIFO_LOST_PKT1 0x6D
+#define ICM42688_SELF_TEST_CONFIG 0x70
+#define ICM42688_WHO_AM_I 0x75
+#define ICM42688_REG_BANK_SEL 0x76
+
+// Bank 1
+#define ICM42688_SENSOR_CONFIG0 0x03
+#define ICM42688_GYRO_CONFIG_STATIC2 0x0B
+#define ICM42688_GYRO_CONFIG_STATIC3 0x0C
+#define ICM42688_GYRO_CONFIG_STATIC4 0x0D
+#define ICM42688_GYRO_CONFIG_STATIC5 0x0E
+#define ICM42688_GYRO_CONFIG_STATIC6 0x0F
+#define ICM42688_GYRO_CONFIG_STATIC7 0x10
+#define ICM42688_GYRO_CONFIG_STATIC8 0x11
+#define ICM42688_GYRO_CONFIG_STATIC9 0x12
+#define ICM42688_GYRO_CONFIG_STATIC10 0x13
+#define ICM42688_XG_ST_DATA 0x5F
+#define ICM42688_YG_ST_DATA 0x60
+#define ICM42688_ZG_ST_DATA 0x61
+#define ICM42688_TMSTVAL0 0x62
+#define ICM42688_TMSTVAL1 0x63
+#define ICM42688_TMSTVAL2 0x64
+#define ICM42688_INTF_CONFIG4 0x7A
+#define ICM42688_INTF_CONFIG5 0x7B
+#define ICM42688_INTF_CONFIG6 0x7C
+
+// Bank 2
+#define ICM42688_ACCEL_CONFIG_STATIC2 0x03
+#define ICM42688_ACCEL_CONFIG_STATIC3 0x04
+#define ICM42688_ACCEL_CONFIG_STATIC4 0x05
+#define ICM42688_XA_ST_DATA 0x3B
+#define ICM42688_YA_ST_DATA 0x3C
+#define ICM42688_ZA_ST_DATA 0x3D
+
+// Bank 4
+#define ICM42688_APEX_CONFIG1 0x40
+#define ICM42688_APEX_CONFIG2 0x41
+#define ICM42688_APEX_CONFIG3 0x42
+#define ICM42688_APEX_CONFIG4 0x43
+#define ICM42688_APEX_CONFIG5 0x44
+#define ICM42688_APEX_CONFIG6 0x45
+#define ICM42688_APEX_CONFIG7 0x46
+#define ICM42688_APEX_CONFIG8 0x47
+#define ICM42688_APEX_CONFIG9 0x48
+#define ICM42688_ACCEL_WOM_X_THR 0x4A
+#define ICM42688_ACCEL_WOM_Y_THR 0x4B
+#define ICM42688_ACCEL_WOM_Z_THR 0x4C
+#define ICM42688_INT_SOURCE6 0x4D
+#define ICM42688_INT_SOURCE7 0x4E
+#define ICM42688_INT_SOURCE8 0x4F
+#define ICM42688_INT_SOURCE9 0x50
+#define ICM42688_INT_SOURCE10 0x51
+#define ICM42688_OFFSET_USER0 0x77
+#define ICM42688_OFFSET_USER1 0x78
+#define ICM42688_OFFSET_USER2 0x79
+#define ICM42688_OFFSET_USER3 0x7A
+#define ICM42688_OFFSET_USER4 0x7B
+#define ICM42688_OFFSET_USER5 0x7C
+#define ICM42688_OFFSET_USER6 0x7D
+#define ICM42688_OFFSET_USER7 0x7E
+#define ICM42688_OFFSET_USER8 0x7F
+
+// PWR_MGMT0
+#define ICM42688_PWR_TEMP_ON (0 << 5)
+#define ICM42688_PWR_TEMP_OFF (1 << 5)
+#define ICM42688_PWR_IDLE (1 << 4)
+#define ICM42688_PWR_GYRO_MODE_OFF (0 << 2)
+#define ICM42688_PWR_GYRO_MODE_LN (3 << 2)
+#define ICM42688_PWR_ACCEL_MODE_OFF (0 << 0)
+#define ICM42688_PWR_ACCEL_MODE_LP (2 << 0)
+#define ICM42688_PWR_ACCEL_MODE_LN (3 << 0)
+
+// GYRO_CONFIG0
+#define ICM42688_GFS_2000DPS (0x00 << 5)
+#define ICM42688_GFS_1000DPS (0x01 << 5)
+#define ICM42688_GFS_500DPS (0x02 << 5)
+#define ICM42688_GFS_250DPS (0x03 << 5)
+#define ICM42688_GFS_125DPS (0x04 << 5)
+#define ICM42688_GFS_62_5DPS (0x05 << 5)
+#define ICM42688_GFS_31_25DPS (0x06 << 5)
+#define ICM42688_GFS_15_625DPS (0x07 << 5)
+
+#define ICM42688_GODR_32kHz 0x01
+#define ICM42688_GODR_16kHz 0x02
+#define ICM42688_GODR_8kHz 0x03
+#define ICM42688_GODR_4kHz 0x04
+#define ICM42688_GODR_2kHz 0x05
+#define ICM42688_GODR_1kHz 0x06
+#define ICM42688_GODR_200Hz 0x07
+#define ICM42688_GODR_100Hz 0x08
+#define ICM42688_GODR_50Hz 0x09
+#define ICM42688_GODR_25Hz 0x0A
+#define ICM42688_GODR_12_5Hz 0x0B
+#define ICM42688_GODR_500Hz 0x0F
+
+// ACCEL_CONFIG0
+#define ICM42688_AFS_16G (0x00 << 5)
+#define ICM42688_AFS_8G (0x01 << 5)
+#define ICM42688_AFS_4G (0x02 << 5)
+#define ICM42688_AFS_2G (0x03 << 5)
+
+#define ICM42688_AODR_32kHz 0x01
+#define ICM42688_AODR_16kHz 0x02
+#define ICM42688_AODR_8kHz 0x03
+#define ICM42688_AODR_4kHz 0x04
+#define ICM42688_AODR_2kHz 0x05
+#define ICM42688_AODR_1kHz 0x06
+#define ICM42688_AODR_200Hz 0x07
+#define ICM42688_AODR_100Hz 0x08
+#define ICM42688_AODR_50Hz 0x09
+#define ICM42688_AODR_25Hz 0x0A
+#define ICM42688_AODR_12_5Hz 0x0B
+#define ICM42688_AODR_6_25Hz 0x0C
+#define ICM42688_AODR_3_125Hz 0x0D
+#define ICM42688_AODR_1_5625Hz 0x0E
+#define ICM42688_AODR_500Hz 0x0F
diff --git a/flip_world/engine/sensors/imu.c b/flip_world/engine/sensors/imu.c
new file mode 100644
index 000000000..cae742d1c
--- /dev/null
+++ b/flip_world/engine/sensors/imu.c
@@ -0,0 +1,326 @@
+#include
+#include "imu.h"
+#include "ICM42688P/ICM42688P.h"
+
+#define TAG "IMU"
+
+#define ACCEL_GYRO_RATE DataRate100Hz
+
+#define FILTER_SAMPLE_FREQ 100.f
+#define FILTER_BETA 0.08f
+
+#define SAMPLE_RATE_DIV 5
+
+#define SENSITIVITY_K 30.f
+#define EXP_RATE 1.1f
+
+#define IMU_CALI_AVG 64
+
+typedef enum {
+ ImuStop = (1 << 0),
+ ImuNewData = (1 << 1),
+} ImuThreadFlags;
+
+#define FLAGS_ALL (ImuStop | ImuNewData)
+
+typedef struct {
+ float q0;
+ float q1;
+ float q2;
+ float q3;
+ float roll;
+ float pitch;
+ float yaw;
+} ImuProcessedData;
+
+typedef struct {
+ FuriThread* thread;
+ ICM42688P* icm42688p;
+ ImuProcessedData processed_data;
+} ImuThread;
+
+static void imu_madgwick_filter(
+ ImuProcessedData* out,
+ ICM42688PScaledData* accel,
+ ICM42688PScaledData* gyro);
+
+static void imu_irq_callback(void* context) {
+ furi_assert(context);
+ ImuThread* imu = context;
+ furi_thread_flags_set(furi_thread_get_id(imu->thread), ImuNewData);
+}
+
+static void imu_process_data(ImuThread* imu, ICM42688PFifoPacket* in_data) {
+ ICM42688PScaledData accel_data;
+ ICM42688PScaledData gyro_data;
+
+ // Get accel and gyro data in g and degrees/s
+ icm42688p_apply_scale_fifo(imu->icm42688p, in_data, &accel_data, &gyro_data);
+
+ // Gyro: degrees/s to rads/s
+ gyro_data.x = gyro_data.x / 180.f * M_PI;
+ gyro_data.y = gyro_data.y / 180.f * M_PI;
+ gyro_data.z = gyro_data.z / 180.f * M_PI;
+
+ // Sensor Fusion algorithm
+ ImuProcessedData* out = &imu->processed_data;
+ imu_madgwick_filter(out, &accel_data, &gyro_data);
+
+ // Quaternion to euler angles
+ float roll = atan2f(
+ out->q0 * out->q1 + out->q2 * out->q3, 0.5f - out->q1 * out->q1 - out->q2 * out->q2);
+ float pitch = asinf(-2.0f * (out->q1 * out->q3 - out->q0 * out->q2));
+ float yaw = atan2f(
+ out->q1 * out->q2 + out->q0 * out->q3, 0.5f - out->q2 * out->q2 - out->q3 * out->q3);
+
+ // Euler angles: rads to degrees
+ out->roll = roll / M_PI * 180.f;
+ out->pitch = pitch / M_PI * 180.f;
+ out->yaw = yaw / M_PI * 180.f;
+}
+
+static void calibrate_gyro(ImuThread* imu) {
+ ICM42688PRawData data;
+ ICM42688PScaledData offset_scaled = {.x = 0.f, .y = 0.f, .z = 0.f};
+
+ icm42688p_write_gyro_offset(imu->icm42688p, &offset_scaled);
+ furi_delay_ms(10);
+
+ int32_t avg_x = 0;
+ int32_t avg_y = 0;
+ int32_t avg_z = 0;
+
+ for(uint8_t i = 0; i < IMU_CALI_AVG; i++) {
+ icm42688p_read_gyro_raw(imu->icm42688p, &data);
+ avg_x += data.x;
+ avg_y += data.y;
+ avg_z += data.z;
+ furi_delay_ms(2);
+ }
+
+ data.x = avg_x / IMU_CALI_AVG;
+ data.y = avg_y / IMU_CALI_AVG;
+ data.z = avg_z / IMU_CALI_AVG;
+
+ icm42688p_apply_scale(&data, icm42688p_gyro_get_full_scale(imu->icm42688p), &offset_scaled);
+ FURI_LOG_I(
+ TAG,
+ "Offsets: x %f, y %f, z %f",
+ (double)offset_scaled.x,
+ (double)offset_scaled.y,
+ (double)offset_scaled.z);
+ icm42688p_write_gyro_offset(imu->icm42688p, &offset_scaled);
+}
+
+// static float imu_angle_diff(float a, float b) {
+// float diff = a - b;
+// if(diff > 180.f)
+// diff -= 360.f;
+// else if(diff < -180.f)
+// diff += 360.f;
+
+// return diff;
+// }
+
+static int32_t imu_thread(void* context) {
+ furi_assert(context);
+ ImuThread* imu = context;
+
+ // float yaw_last = 0.f;
+ // float pitch_last = 0.f;
+ // float diff_x = 0.f;
+ // float diff_y = 0.f;
+
+ calibrate_gyro(imu);
+
+ icm42688p_accel_config(imu->icm42688p, AccelFullScale16G, ACCEL_GYRO_RATE);
+ icm42688p_gyro_config(imu->icm42688p, GyroFullScale2000DPS, ACCEL_GYRO_RATE);
+
+ imu->processed_data.q0 = 1.f;
+ imu->processed_data.q1 = 0.f;
+ imu->processed_data.q2 = 0.f;
+ imu->processed_data.q3 = 0.f;
+ icm42688_fifo_enable(imu->icm42688p, imu_irq_callback, imu);
+
+ while(1) {
+ uint32_t events = furi_thread_flags_wait(FLAGS_ALL, FuriFlagWaitAny, FuriWaitForever);
+
+ if(events & ImuStop) {
+ break;
+ }
+
+ if(events & ImuNewData) {
+ uint16_t data_pending = icm42688_fifo_get_count(imu->icm42688p);
+ ICM42688PFifoPacket data;
+ while(data_pending--) {
+ icm42688_fifo_read(imu->icm42688p, &data);
+ imu_process_data(imu, &data);
+ }
+ }
+ }
+
+ icm42688_fifo_disable(imu->icm42688p);
+
+ return 0;
+}
+
+ImuThread* imu_start(ICM42688P* icm42688p) {
+ ImuThread* imu = malloc(sizeof(ImuThread));
+ imu->icm42688p = icm42688p;
+ imu->thread = furi_thread_alloc_ex("ImuThread", 4096, imu_thread, imu);
+
+ furi_thread_start(imu->thread);
+
+ return imu;
+}
+
+void imu_stop(ImuThread* imu) {
+ furi_assert(imu);
+
+ furi_thread_flags_set(furi_thread_get_id(imu->thread), ImuStop);
+
+ furi_thread_join(imu->thread);
+ furi_thread_free(imu->thread);
+
+ free(imu);
+}
+
+static float imu_inv_sqrt(float number) {
+ union {
+ float f;
+ uint32_t i;
+ } conv = {.f = number};
+ conv.i = 0x5F3759Df - (conv.i >> 1);
+ conv.f *= 1.5f - (number * 0.5f * conv.f * conv.f);
+ return conv.f;
+}
+
+/* Simple madgwik filter, based on: https://github.com/arduino-libraries/MadgwickAHRS/ */
+
+static void imu_madgwick_filter(
+ ImuProcessedData* out,
+ ICM42688PScaledData* accel,
+ ICM42688PScaledData* gyro) {
+ float recipNorm;
+ float s0, s1, s2, s3;
+ float qDot1, qDot2, qDot3, qDot4;
+ float _2q0, _2q1, _2q2, _2q3, _4q0, _4q1, _4q2, _8q1, _8q2, q0q0, q1q1, q2q2, q3q3;
+
+ // Rate of change of quaternion from gyroscope
+ qDot1 = 0.5f * (-out->q1 * gyro->x - out->q2 * gyro->y - out->q3 * gyro->z);
+ qDot2 = 0.5f * (out->q0 * gyro->x + out->q2 * gyro->z - out->q3 * gyro->y);
+ qDot3 = 0.5f * (out->q0 * gyro->y - out->q1 * gyro->z + out->q3 * gyro->x);
+ qDot4 = 0.5f * (out->q0 * gyro->z + out->q1 * gyro->y - out->q2 * gyro->x);
+
+ // Compute feedback only if accelerometer measurement valid (avoids NaN in accelerometer normalisation)
+ if(!((accel->x == 0.0f) && (accel->y == 0.0f) && (accel->z == 0.0f))) {
+ // Normalise accelerometer measurement
+ recipNorm = imu_inv_sqrt(accel->x * accel->x + accel->y * accel->y + accel->z * accel->z);
+ accel->x *= recipNorm;
+ accel->y *= recipNorm;
+ accel->z *= recipNorm;
+
+ // Auxiliary variables to avoid repeated arithmetic
+ _2q0 = 2.0f * out->q0;
+ _2q1 = 2.0f * out->q1;
+ _2q2 = 2.0f * out->q2;
+ _2q3 = 2.0f * out->q3;
+ _4q0 = 4.0f * out->q0;
+ _4q1 = 4.0f * out->q1;
+ _4q2 = 4.0f * out->q2;
+ _8q1 = 8.0f * out->q1;
+ _8q2 = 8.0f * out->q2;
+ q0q0 = out->q0 * out->q0;
+ q1q1 = out->q1 * out->q1;
+ q2q2 = out->q2 * out->q2;
+ q3q3 = out->q3 * out->q3;
+
+ // Gradient decent algorithm corrective step
+ s0 = _4q0 * q2q2 + _2q2 * accel->x + _4q0 * q1q1 - _2q1 * accel->y;
+ s1 = _4q1 * q3q3 - _2q3 * accel->x + 4.0f * q0q0 * out->q1 - _2q0 * accel->y - _4q1 +
+ _8q1 * q1q1 + _8q1 * q2q2 + _4q1 * accel->z;
+ s2 = 4.0f * q0q0 * out->q2 + _2q0 * accel->x + _4q2 * q3q3 - _2q3 * accel->y - _4q2 +
+ _8q2 * q1q1 + _8q2 * q2q2 + _4q2 * accel->z;
+ s3 = 4.0f * q1q1 * out->q3 - _2q1 * accel->x + 4.0f * q2q2 * out->q3 - _2q2 * accel->y;
+ recipNorm =
+ imu_inv_sqrt(s0 * s0 + s1 * s1 + s2 * s2 + s3 * s3); // normalise step magnitude
+ s0 *= recipNorm;
+ s1 *= recipNorm;
+ s2 *= recipNorm;
+ s3 *= recipNorm;
+
+ // Apply feedback step
+ qDot1 -= FILTER_BETA * s0;
+ qDot2 -= FILTER_BETA * s1;
+ qDot3 -= FILTER_BETA * s2;
+ qDot4 -= FILTER_BETA * s3;
+ }
+
+ // Integrate rate of change of quaternion to yield quaternion
+ out->q0 += qDot1 * (1.0f / FILTER_SAMPLE_FREQ);
+ out->q1 += qDot2 * (1.0f / FILTER_SAMPLE_FREQ);
+ out->q2 += qDot3 * (1.0f / FILTER_SAMPLE_FREQ);
+ out->q3 += qDot4 * (1.0f / FILTER_SAMPLE_FREQ);
+
+ // Normalise quaternion
+ recipNorm = imu_inv_sqrt(
+ out->q0 * out->q0 + out->q1 * out->q1 + out->q2 * out->q2 + out->q3 * out->q3);
+ out->q0 *= recipNorm;
+ out->q1 *= recipNorm;
+ out->q2 *= recipNorm;
+ out->q3 *= recipNorm;
+}
+
+/* IMU API */
+
+struct Imu {
+ FuriHalSpiBusHandle* icm42688p_device;
+ ICM42688P* icm42688p;
+ ImuThread* thread;
+ bool present;
+};
+
+Imu* imu_alloc(void) {
+ Imu* imu = malloc(sizeof(Imu));
+ imu->icm42688p_device = malloc(sizeof(FuriHalSpiBusHandle));
+ memcpy(imu->icm42688p_device, &furi_hal_spi_bus_handle_external, sizeof(FuriHalSpiBusHandle));
+ imu->icm42688p_device->cs = &gpio_ext_pc3;
+
+ imu->icm42688p = icm42688p_alloc(imu->icm42688p_device, &gpio_ext_pb2);
+ imu->present = icm42688p_init(imu->icm42688p);
+
+ if(imu->present) {
+ imu->thread = imu_start(imu->icm42688p);
+ }
+
+ return imu;
+}
+
+void imu_free(Imu* imu) {
+ if(imu->present) {
+ imu_stop(imu->thread);
+ }
+ icm42688p_deinit(imu->icm42688p);
+ icm42688p_free(imu->icm42688p);
+ free(imu->icm42688p_device);
+ free(imu);
+}
+
+bool imu_present(Imu* imu) {
+ return imu->present;
+}
+
+float imu_pitch_get(Imu* imu) {
+ // we pretend that reading a float is an atomic operation
+ return imu->thread->processed_data.pitch;
+}
+
+float imu_roll_get(Imu* imu) {
+ // we pretend that reading a float is an atomic operation
+ return imu->thread->processed_data.roll;
+}
+
+float imu_yaw_get(Imu* imu) {
+ // we pretend that reading a float is an atomic operation
+ return imu->thread->processed_data.yaw;
+}
\ No newline at end of file
diff --git a/flip_world/engine/sensors/imu.h b/flip_world/engine/sensors/imu.h
new file mode 100644
index 000000000..42a08fe0c
--- /dev/null
+++ b/flip_world/engine/sensors/imu.h
@@ -0,0 +1,15 @@
+#pragma once
+
+typedef struct Imu Imu;
+
+Imu* imu_alloc(void);
+
+void imu_free(Imu* imu);
+
+bool imu_present(Imu* imu);
+
+float imu_pitch_get(Imu* imu);
+
+float imu_roll_get(Imu* imu);
+
+float imu_yaw_get(Imu* imu);
\ No newline at end of file
diff --git a/flip_world/engine/sprite.c b/flip_world/engine/sprite.c
new file mode 100644
index 000000000..395f8a31b
--- /dev/null
+++ b/flip_world/engine/sprite.c
@@ -0,0 +1,69 @@
+#include "sprite.h"
+#include
+
+#ifdef SPRITE_DEBUG
+#define SPRITE_D(...) FURI_LOG_D("Sprite", __VA_ARGS__)
+#define SPRITE_E(...) FURI_LOG_E("Sprite", __VA_ARGS__)
+#else
+#define SPRITE_D(...)
+#define SPRITE_E(...)
+#endif
+
+struct Sprite {
+ uint32_t width;
+ uint32_t height;
+ uint8_t data[];
+};
+
+Sprite* sprite_alloc(const char* path) {
+ Storage* storage = furi_record_open(RECORD_STORAGE);
+ File* file = storage_file_alloc(storage);
+ Sprite* sprite = NULL;
+
+ do {
+ if(!storage_file_open(file, path, FSAM_READ, FSOM_OPEN_EXISTING)) {
+ SPRITE_E("Failed to open file: %s", path);
+ break;
+ }
+
+ uint32_t size = 0;
+ if(storage_file_read(file, &size, sizeof(size)) != sizeof(size)) {
+ SPRITE_E("Failed to read file size: %s", path);
+ break;
+ }
+
+ sprite = malloc(size);
+ if(storage_file_read(file, sprite, size) != size) {
+ SPRITE_E("Failed to read file: %s", path);
+ free(sprite);
+ sprite = NULL;
+ break;
+ }
+
+ SPRITE_D(
+ "Loaded sprite: %s, width: %lu, height: %lu", path, sprite->width, sprite->height);
+ } while(false);
+
+ storage_file_free(file);
+
+ return sprite;
+}
+
+void sprite_free(Sprite* sprite) {
+ free(sprite);
+}
+
+size_t sprite_get_width(Sprite* sprite) {
+ return sprite->width;
+}
+
+size_t sprite_get_height(Sprite* sprite) {
+ return sprite->height;
+}
+
+void canvas_draw_sprite(Canvas* canvas, Sprite* sprite, int32_t x, int32_t y) {
+ furi_check(sprite->width);
+ furi_check(sprite->height);
+ furi_check(sprite->data);
+ canvas_draw_xbm(canvas, x, y, sprite->width, sprite->height, sprite->data);
+}
\ No newline at end of file
diff --git a/flip_world/engine/sprite.h b/flip_world/engine/sprite.h
new file mode 100644
index 000000000..992f96e0a
--- /dev/null
+++ b/flip_world/engine/sprite.h
@@ -0,0 +1,44 @@
+#pragma once
+#include
+#include
+#include
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct Sprite Sprite;
+
+/** Sprite allocator
+ * @return Sprite* Sprite instance or NULL, if failed
+ */
+Sprite* sprite_alloc(const char* path);
+
+/** Sprite deallocator
+ * @param sprite Sprite instance
+ */
+void sprite_free(Sprite* sprite);
+
+/** Get sprite width
+ * @param sprite Sprite instance
+ * @return size_t sprite width
+ */
+size_t sprite_get_width(Sprite* sprite);
+
+/** Get sprite height
+ * @param sprite Sprite instance
+ * @return size_t sprite height
+ */
+size_t sprite_get_height(Sprite* sprite);
+
+/** Draw sprite on canvas
+ * @param canvas Canvas instance
+ * @param sprite Sprite instance
+ * @param x x coordinate
+ * @param y y coordinate
+ */
+void canvas_draw_sprite(Canvas* canvas, Sprite* sprite, int32_t x, int32_t y);
+
+#ifdef __cplusplus
+}
+#endif
\ No newline at end of file
diff --git a/flip_world/engine/vector.c b/flip_world/engine/vector.c
new file mode 100644
index 000000000..64224988d
--- /dev/null
+++ b/flip_world/engine/vector.c
@@ -0,0 +1,33 @@
+#include "vector.h"
+
+Vector vector_add(Vector a, Vector b) {
+ return (Vector){.x = a.x + b.x, .y = a.y + b.y};
+}
+
+Vector vector_sub(Vector a, Vector b) {
+ return (Vector){.x = a.x - b.x, .y = a.y - b.y};
+}
+
+Vector vector_mul(Vector a, Vector b) {
+ return (Vector){.x = a.x * b.x, .y = a.y * b.y};
+}
+
+Vector vector_div(Vector a, Vector b) {
+ return (Vector){.x = a.x / b.x, .y = a.y / b.y};
+}
+
+Vector vector_addf(Vector a, float b) {
+ return (Vector){.x = a.x + b, .y = a.y + b};
+}
+
+Vector vector_subf(Vector a, float b) {
+ return (Vector){.x = a.x - b, .y = a.y - b};
+}
+
+Vector vector_mulf(Vector a, float b) {
+ return (Vector){.x = a.x * b, .y = a.y * b};
+}
+
+Vector vector_divf(Vector a, float b) {
+ return (Vector){.x = a.x / b, .y = a.y / b};
+}
\ No newline at end of file
diff --git a/flip_world/engine/vector.h b/flip_world/engine/vector.h
new file mode 100644
index 000000000..6a842f8d5
--- /dev/null
+++ b/flip_world/engine/vector.h
@@ -0,0 +1,32 @@
+#pragma once
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct {
+ float x;
+ float y;
+} Vector;
+
+#define VECTOR_ZERO ((Vector){0, 0})
+
+Vector vector_add(Vector a, Vector b);
+
+Vector vector_sub(Vector a, Vector b);
+
+Vector vector_mul(Vector a, Vector b);
+
+Vector vector_div(Vector a, Vector b);
+
+Vector vector_addf(Vector a, float b);
+
+Vector vector_subf(Vector a, float b);
+
+Vector vector_mulf(Vector a, float b);
+
+Vector vector_divf(Vector a, float b);
+
+#ifdef __cplusplus
+}
+#endif
\ No newline at end of file
diff --git a/flip_world/file_assets/sprites/enemy_left_cyclops_10x11px.fxbm b/flip_world/file_assets/sprites/enemy_left_cyclops_10x11px.fxbm
new file mode 100644
index 000000000..5a37f7007
Binary files /dev/null and b/flip_world/file_assets/sprites/enemy_left_cyclops_10x11px.fxbm differ
diff --git a/flip_world/file_assets/sprites/enemy_left_ghost_15x15px.fxbm b/flip_world/file_assets/sprites/enemy_left_ghost_15x15px.fxbm
new file mode 100644
index 000000000..6605fad93
Binary files /dev/null and b/flip_world/file_assets/sprites/enemy_left_ghost_15x15px.fxbm differ
diff --git a/flip_world/file_assets/sprites/enemy_left_ogre_10x13px.fxbm b/flip_world/file_assets/sprites/enemy_left_ogre_10x13px.fxbm
new file mode 100644
index 000000000..b47eb80b3
Binary files /dev/null and b/flip_world/file_assets/sprites/enemy_left_ogre_10x13px.fxbm differ
diff --git a/flip_world/file_assets/sprites/enemy_right_cyclops_10x11px.fxbm b/flip_world/file_assets/sprites/enemy_right_cyclops_10x11px.fxbm
new file mode 100644
index 000000000..a26712b7c
Binary files /dev/null and b/flip_world/file_assets/sprites/enemy_right_cyclops_10x11px.fxbm differ
diff --git a/flip_world/file_assets/sprites/enemy_right_ghost_15x15px.fxbm b/flip_world/file_assets/sprites/enemy_right_ghost_15x15px.fxbm
new file mode 100644
index 000000000..6256440a9
Binary files /dev/null and b/flip_world/file_assets/sprites/enemy_right_ghost_15x15px.fxbm differ
diff --git a/flip_world/file_assets/sprites/enemy_right_ogre_10x13px.fxbm b/flip_world/file_assets/sprites/enemy_right_ogre_10x13px.fxbm
new file mode 100644
index 000000000..886bdded8
Binary files /dev/null and b/flip_world/file_assets/sprites/enemy_right_ogre_10x13px.fxbm differ
diff --git a/flip_world/file_assets/sprites/player_left_axe_15x11px.fxbm b/flip_world/file_assets/sprites/player_left_axe_15x11px.fxbm
new file mode 100644
index 000000000..270014143
Binary files /dev/null and b/flip_world/file_assets/sprites/player_left_axe_15x11px.fxbm differ
diff --git a/flip_world/file_assets/sprites/player_left_bow_13x11px.fxbm b/flip_world/file_assets/sprites/player_left_bow_13x11px.fxbm
new file mode 100644
index 000000000..41d8e186c
Binary files /dev/null and b/flip_world/file_assets/sprites/player_left_bow_13x11px.fxbm differ
diff --git a/flip_world/file_assets/sprites/player_left_naked_10x10px.fxbm b/flip_world/file_assets/sprites/player_left_naked_10x10px.fxbm
new file mode 100644
index 000000000..4204c43c5
Binary files /dev/null and b/flip_world/file_assets/sprites/player_left_naked_10x10px.fxbm differ
diff --git a/flip_world/file_assets/sprites/player_left_sword_15x11px.fxbm b/flip_world/file_assets/sprites/player_left_sword_15x11px.fxbm
new file mode 100644
index 000000000..869ec9fb2
Binary files /dev/null and b/flip_world/file_assets/sprites/player_left_sword_15x11px.fxbm differ
diff --git a/flip_world/file_assets/sprites/player_right_axe_15x11px.fxbm b/flip_world/file_assets/sprites/player_right_axe_15x11px.fxbm
new file mode 100644
index 000000000..ce5c2645b
Binary files /dev/null and b/flip_world/file_assets/sprites/player_right_axe_15x11px.fxbm differ
diff --git a/flip_world/file_assets/sprites/player_right_bow_13x11.fxbm b/flip_world/file_assets/sprites/player_right_bow_13x11.fxbm
new file mode 100644
index 000000000..3f6f82055
Binary files /dev/null and b/flip_world/file_assets/sprites/player_right_bow_13x11.fxbm differ
diff --git a/flip_world/file_assets/sprites/player_right_naked_10x10px.fxbm b/flip_world/file_assets/sprites/player_right_naked_10x10px.fxbm
new file mode 100644
index 000000000..73d6e277a
Binary files /dev/null and b/flip_world/file_assets/sprites/player_right_naked_10x10px.fxbm differ
diff --git a/flip_world/file_assets/sprites/player_right_sword_15x11px.fxbm b/flip_world/file_assets/sprites/player_right_sword_15x11px.fxbm
new file mode 100644
index 000000000..7a488e395
Binary files /dev/null and b/flip_world/file_assets/sprites/player_right_sword_15x11px.fxbm differ
diff --git a/flip_world/flip_storage/storage.c b/flip_world/flip_storage/storage.c
new file mode 100644
index 000000000..f10a88bef
--- /dev/null
+++ b/flip_world/flip_storage/storage.c
@@ -0,0 +1,664 @@
+
+#include "flip_storage/storage.h"
+
+// Forward declaration for use in other functions
+static bool load_flip_social_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 *change_bio_logged_in,
+ size_t change_bio_size,
+ char *is_logged_in,
+ size_t is_logged_in_size);
+
+void save_settings(
+ const char *wifi_ssid,
+ const char *wifi_password,
+ const char *username,
+ const char *password)
+{
+ // Create the directory for saving settings
+ char directory_path[256];
+ snprintf(directory_path, sizeof(directory_path), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_world");
+
+ // 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 wifi_ssid length and data
+ size_t wifi_ssid_length = strlen(wifi_ssid) + 1; // Include null terminator
+ if (storage_file_write(file, &wifi_ssid_length, sizeof(size_t)) != sizeof(size_t) ||
+ storage_file_write(file, wifi_ssid, wifi_ssid_length) != wifi_ssid_length)
+ {
+ FURI_LOG_E(TAG, "Failed to write wifi_SSID");
+ }
+
+ // Save the wifi_password length and data
+ size_t wifi_password_length = strlen(wifi_password) + 1; // Include null terminator
+ if (storage_file_write(file, &wifi_password_length, sizeof(size_t)) != sizeof(size_t) ||
+ storage_file_write(file, wifi_password, wifi_password_length) != wifi_password_length)
+ {
+ FURI_LOG_E(TAG, "Failed to write wifi_password");
+ }
+
+ // Save the username length and data
+ size_t username_length = strlen(username) + 1; // Include null terminator
+ if (storage_file_write(file, &username_length, sizeof(size_t)) != sizeof(size_t) ||
+ storage_file_write(file, username, username_length) != username_length)
+ {
+ FURI_LOG_E(TAG, "Failed to write username");
+ }
+
+ // 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");
+ }
+
+ storage_file_close(file);
+ storage_file_free(file);
+ furi_record_close(RECORD_STORAGE);
+}
+
+bool load_settings(
+ char *wifi_ssid,
+ size_t wifi_ssid_size,
+ char *wifi_password,
+ size_t wifi_password_size,
+ char *username,
+ size_t username_size,
+ char *password,
+ size_t password_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 wifi_ssid
+ size_t wifi_ssid_length;
+ if (storage_file_read(file, &wifi_ssid_length, sizeof(size_t)) != sizeof(size_t) || wifi_ssid_length > wifi_ssid_size ||
+ storage_file_read(file, wifi_ssid, wifi_ssid_length) != wifi_ssid_length)
+ {
+ FURI_LOG_E(TAG, "Failed to read wifi_SSID");
+ storage_file_close(file);
+ storage_file_free(file);
+ furi_record_close(RECORD_STORAGE);
+ return false;
+ }
+ wifi_ssid[wifi_ssid_length - 1] = '\0'; // Ensure null-termination
+
+ // Load the wifi_password
+ size_t wifi_password_length;
+ if (storage_file_read(file, &wifi_password_length, sizeof(size_t)) != sizeof(size_t) || wifi_password_length > wifi_password_size ||
+ storage_file_read(file, wifi_password, wifi_password_length) != wifi_password_length)
+ {
+ FURI_LOG_E(TAG, "Failed to read wifi_password");
+ storage_file_close(file);
+ storage_file_free(file);
+ furi_record_close(RECORD_STORAGE);
+ return false;
+ }
+ wifi_password[wifi_password_length - 1] = '\0'; // Ensure null-termination
+
+ // Load the username
+ size_t username_length;
+ if (storage_file_read(file, &username_length, sizeof(size_t)) != sizeof(size_t) || username_length > username_size ||
+ storage_file_read(file, username, username_length) != username_length)
+ {
+ FURI_LOG_E(TAG, "Failed to read username");
+ storage_file_close(file);
+ storage_file_free(file);
+ furi_record_close(RECORD_STORAGE);
+ return false;
+ }
+ username[username_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;
+ }
+ password[password_length - 1] = '\0'; // Ensure null-termination
+
+ storage_file_close(file);
+ storage_file_free(file);
+ furi_record_close(RECORD_STORAGE);
+
+ return true;
+}
+
+bool save_char(
+ const char *path_name, const char *value)
+{
+ if (!value)
+ {
+ return false;
+ }
+ // Create the directory for saving settings
+ char directory_path[256];
+ snprintf(directory_path, sizeof(directory_path), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_world");
+
+ // Create the directory
+ Storage *storage = furi_record_open(RECORD_STORAGE);
+ storage_common_mkdir(storage, directory_path);
+ snprintf(directory_path, sizeof(directory_path), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_world/data");
+ storage_common_mkdir(storage, directory_path);
+
+ // Open the settings file
+ File *file = storage_file_alloc(storage);
+ char file_path[256];
+ snprintf(file_path, sizeof(file_path), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_world/data/%s.txt", path_name);
+
+ // 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;
+ }
+
+ // Write the data to the file
+ size_t data_size = strlen(value) + 1; // Include null terminator
+ if (storage_file_write(file, value, 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;
+}
+
+bool load_char(
+ const char *path_name,
+ char *value,
+ size_t value_size)
+{
+ if (!value)
+ {
+ return false;
+ }
+ Storage *storage = furi_record_open(RECORD_STORAGE);
+ File *file = storage_file_alloc(storage);
+
+ char file_path[256];
+ snprintf(file_path, sizeof(file_path), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_world/data/%s.txt", path_name);
+
+ // 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
+ }
+
+ // Read data into the buffer
+ size_t read_count = storage_file_read(file, value, value_size);
+ if (storage_file_get_error(file) != FSE_OK)
+ {
+ FURI_LOG_E(HTTP_TAG, "Error reading from file.");
+ storage_file_close(file);
+ storage_file_free(file);
+ furi_record_close(RECORD_STORAGE);
+ return false;
+ }
+
+ // Ensure null-termination
+ value[read_count - 1] = '\0';
+
+ storage_file_close(file);
+ storage_file_free(file);
+ furi_record_close(RECORD_STORAGE);
+
+ return strlen(value) > 0;
+}
+
+FuriString *load_furi_world(
+ const char *name)
+{
+ Storage *storage = furi_record_open(RECORD_STORAGE);
+ File *file = storage_file_alloc(storage);
+ char file_path[128];
+ snprintf(file_path, sizeof(file_path), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_world/worlds/%s.json", name);
+ // 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;
+}
+
+bool world_exists(const char *name)
+{
+ if (!name)
+ {
+ FURI_LOG_E(TAG, "Invalid name");
+ return false;
+ }
+ Storage *storage = furi_record_open(RECORD_STORAGE);
+ if (!storage)
+ {
+ FURI_LOG_E(TAG, "Failed to open storage");
+ return false;
+ }
+ char file_path[256];
+ snprintf(file_path, sizeof(file_path), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_world/worlds/%s.json", name);
+ bool does_exist = storage_file_exists(storage, file_path);
+
+ // Clean up
+ furi_record_close(RECORD_STORAGE);
+ return does_exist;
+}
+
+bool save_world_names(const FuriString *json)
+{
+ // Create the directory for saving settings
+ char directory_path[256];
+ snprintf(directory_path, sizeof(directory_path), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_world/worlds");
+
+ // 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);
+ char file_path[128];
+ snprintf(file_path, sizeof(file_path), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_world/worlds/world_list.json");
+
+ // 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;
+ }
+
+ // Write the data to the file
+ size_t data_size = furi_string_size(json) + 1; // Include null terminator
+ if (storage_file_write(file, furi_string_get_cstr(json), 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;
+}
+
+static FuriString *flip_social_info(char *key)
+{
+ char ssid[64];
+ char password[64];
+ char login_username_logged_out[64];
+ char login_username_logged_in[64];
+ char login_password_logged_out[64];
+ char change_password_logged_in[64];
+ char change_bio_logged_in[64];
+ char is_logged_in[64];
+ FuriString *result = furi_string_alloc();
+ if (!result)
+ {
+ FURI_LOG_E(TAG, "Failed to allocate FuriString");
+ return NULL;
+ }
+ if (!load_flip_social_settings(ssid, sizeof(ssid), password, sizeof(password), login_username_logged_out, sizeof(login_username_logged_out), login_username_logged_in, sizeof(login_username_logged_in), login_password_logged_out, sizeof(login_password_logged_out), change_password_logged_in, sizeof(change_password_logged_in), change_bio_logged_in, sizeof(change_bio_logged_in), is_logged_in, sizeof(is_logged_in)))
+ {
+ FURI_LOG_E(TAG, "Failed to load flip social settings");
+ return NULL;
+ }
+ if (strcmp(key, "ssid") == 0)
+ {
+ furi_string_set_str(result, ssid);
+ }
+ else if (strcmp(key, "password") == 0)
+ {
+ furi_string_set_str(result, password);
+ }
+ else if (strcmp(key, "login_username_logged_out") == 0)
+ {
+ furi_string_set_str(result, login_username_logged_out);
+ }
+ else if (strcmp(key, "login_username_logged_in") == 0)
+ {
+ furi_string_set_str(result, login_username_logged_in);
+ }
+ else if (strcmp(key, "login_password_logged_out") == 0)
+ {
+ furi_string_set_str(result, login_password_logged_out);
+ }
+ else if (strcmp(key, "change_password_logged_in") == 0)
+ {
+ furi_string_set_str(result, change_password_logged_in);
+ }
+ else if (strcmp(key, "change_bio_logged_in") == 0)
+ {
+ furi_string_set_str(result, change_bio_logged_in);
+ }
+ else if (strcmp(key, "is_logged_in") == 0)
+ {
+ furi_string_set_str(result, is_logged_in);
+ }
+ else
+ {
+ FURI_LOG_E(TAG, "Invalid key");
+ furi_string_free(result);
+ return NULL;
+ }
+ return result;
+}
+
+bool is_logged_in_to_flip_social()
+{
+ // load flip social settings and check if logged in
+ FuriString *is_logged_in = flip_social_info("is_logged_in");
+ if (!is_logged_in)
+ {
+ FURI_LOG_E(TAG, "Failed to load is_logged_in");
+ return false;
+ }
+ if (furi_string_cmp(is_logged_in, "true") == 0)
+ {
+ // copy the logged_in FlipSocaial settings to FlipWorld
+ FuriString *username = flip_social_info("login_username_logged_in");
+ FuriString *password = flip_social_info("change_password_logged_in");
+ FuriString *wifi_password = flip_social_info("password");
+ FuriString *wifi_ssid = flip_social_info("ssid");
+ if (!username || !password || !wifi_password || !wifi_ssid)
+ {
+ furi_string_free(username);
+ furi_string_free(password);
+ furi_string_free(wifi_password);
+ furi_string_free(wifi_ssid);
+ return false;
+ }
+ save_settings(furi_string_get_cstr(wifi_ssid), furi_string_get_cstr(wifi_password), furi_string_get_cstr(username), furi_string_get_cstr(password));
+ furi_string_free(username);
+ furi_string_free(password);
+ furi_string_free(wifi_password);
+ furi_string_free(wifi_ssid);
+ furi_string_free(is_logged_in);
+ return true;
+ }
+ furi_string_free(is_logged_in);
+ return false;
+}
+
+bool is_logged_in()
+{
+ char is_logged_in[64];
+ if (load_char("is_logged_in", is_logged_in, sizeof(is_logged_in)))
+ {
+ return strcmp(is_logged_in, "true") == 0;
+ }
+ return false;
+}
+
+static bool load_flip_social_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 *change_bio_logged_in,
+ size_t change_bio_size,
+ char *is_logged_in,
+ size_t is_logged_in_size)
+{
+ Storage *storage = furi_record_open(RECORD_STORAGE);
+ File *file = storage_file_alloc(storage);
+
+ // file path from flipsocial
+ char file_path[128];
+ snprintf(file_path, sizeof(file_path), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_social/settings.bin");
+ if (!storage_file_open(file, file_path, FSAM_READ, FSOM_OPEN_EXISTING))
+ {
+ FURI_LOG_E(TAG, "Failed to open settings file for reading: %s", file_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
+ }
+
+ // Load the change_bio_logged_in
+ size_t change_bio_length;
+ if (storage_file_read(file, &change_bio_length, sizeof(size_t)) != sizeof(size_t) || change_bio_length > change_bio_size ||
+ storage_file_read(file, change_bio_logged_in, change_bio_length) != change_bio_length)
+ {
+ FURI_LOG_E(TAG, "Failed to read change_bio_logged_in");
+ // storage_file_close(file);
+ // storage_file_free(file);
+ // furi_record_close(RECORD_STORAGE);
+ // return false;
+ }
+ else
+ {
+ change_bio_logged_in[change_bio_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_world/flip_storage/storage.h b/flip_world/flip_storage/storage.h
new file mode 100644
index 000000000..8d13a84c7
--- /dev/null
+++ b/flip_world/flip_storage/storage.h
@@ -0,0 +1,41 @@
+#pragma once
+
+#include
+#include
+#include
+
+#define SETTINGS_PATH STORAGE_EXT_PATH_PREFIX "/apps_data/flip_world/settings.bin"
+
+void save_settings(
+ const char *wifi_ssid,
+ const char *wifi_password,
+ const char *username,
+ const char *password);
+
+bool load_settings(
+ char *wifi_ssid,
+ size_t wifi_ssid_size,
+ char *wifi_password,
+ size_t wifi_password_size,
+ char *username,
+ size_t username_size,
+ char *password,
+ size_t password_size);
+
+bool save_char(
+ const char *path_name, const char *value);
+
+bool load_char(
+ const char *path_name,
+ char *value,
+ size_t value_size);
+
+FuriString *load_furi_world(
+ const char *name);
+
+bool world_exists(
+ const char *name);
+
+bool save_world_names(const FuriString *json);
+bool is_logged_in_to_flip_social();
+bool is_logged_in();
\ No newline at end of file
diff --git a/flip_world/flip_world.c b/flip_world/flip_world.c
new file mode 100644
index 000000000..d30b779b5
--- /dev/null
+++ b/flip_world/flip_world.c
@@ -0,0 +1,9 @@
+#include