diff --git a/applications/debug/unit_tests/datetimelib/datetimelib_test.c b/applications/debug/unit_tests/datetimelib/datetimelib_test.c index 42bc7dbed7..bf8e6fabd7 100644 --- a/applications/debug/unit_tests/datetimelib/datetimelib_test.c +++ b/applications/debug/unit_tests/datetimelib/datetimelib_test.c @@ -112,7 +112,7 @@ MU_TEST_SUITE(test_datetime_validate_datetime) { MU_TEST(test_datetime_timestamp_to_datetime_min) { uint32_t test_value = 0; - DateTime min_datetime_expected = {0, 0, 0, 1, 1, 1970, 0}; + DateTime min_datetime_expected = {0, 0, 0, 1, 1, 1970, 4}; DateTime result = {0}; datetime_timestamp_to_datetime(test_value, &result); @@ -122,7 +122,7 @@ MU_TEST(test_datetime_timestamp_to_datetime_min) { MU_TEST(test_datetime_timestamp_to_datetime_max) { uint32_t test_value = UINT32_MAX; - DateTime max_datetime_expected = {6, 28, 15, 7, 2, 2106, 0}; + DateTime max_datetime_expected = {6, 28, 15, 7, 2, 2106, 7}; DateTime result = {0}; datetime_timestamp_to_datetime(test_value, &result); @@ -141,10 +141,26 @@ MU_TEST(test_datetime_timestamp_to_datetime_to_timestamp) { mu_assert_int_eq(test_value, result); } +MU_TEST(test_datetime_timestamp_to_datetime_weekday) { + uint32_t test_value = 1709748421; // Wed Mar 06 18:07:01 2024 UTC + + DateTime datetime = {0}; + datetime_timestamp_to_datetime(test_value, &datetime); + + mu_assert_int_eq(datetime.hour, 18); + mu_assert_int_eq(datetime.minute, 7); + mu_assert_int_eq(datetime.second, 1); + mu_assert_int_eq(datetime.day, 6); + mu_assert_int_eq(datetime.month, 3); + mu_assert_int_eq(datetime.weekday, 3); + mu_assert_int_eq(datetime.year, 2024); +} + MU_TEST_SUITE(test_datetime_timestamp_to_datetime_suite) { MU_RUN_TEST(test_datetime_timestamp_to_datetime_min); MU_RUN_TEST(test_datetime_timestamp_to_datetime_max); MU_RUN_TEST(test_datetime_timestamp_to_datetime_to_timestamp); + MU_RUN_TEST(test_datetime_timestamp_to_datetime_weekday); } MU_TEST(test_datetime_datetime_to_timestamp_min) { diff --git a/applications/main/infrared/infrared_app.c b/applications/main/infrared/infrared_app.c index e674cd13d7..51e92ed19f 100644 --- a/applications/main/infrared/infrared_app.c +++ b/applications/main/infrared/infrared_app.c @@ -6,7 +6,8 @@ #define TAG "InfraredApp" -#define INFRARED_TX_MIN_INTERVAL_MS 50U +#define INFRARED_TX_MIN_INTERVAL_MS (50U) +#define INFRARED_TASK_STACK_SIZE (2048UL) static const NotificationSequence* infrared_notification_sequences[InfraredNotificationMessageCount] = { @@ -128,6 +129,8 @@ static void infrared_find_vacant_remote_name(FuriString* name, const char* path) static InfraredApp* infrared_alloc() { InfraredApp* infrared = malloc(sizeof(InfraredApp)); + infrared->task_thread = + furi_thread_alloc_ex("InfraredTask", INFRARED_TASK_STACK_SIZE, NULL, infrared); infrared->file_path = furi_string_alloc(); infrared->button_name = furi_string_alloc(); @@ -213,6 +216,10 @@ static InfraredApp* infrared_alloc() { static void infrared_free(InfraredApp* infrared) { furi_assert(infrared); + + furi_thread_join(infrared->task_thread); + furi_thread_free(infrared->task_thread); + ViewDispatcher* view_dispatcher = infrared->view_dispatcher; InfraredAppState* app_state = &infrared->app_state; @@ -393,6 +400,18 @@ void infrared_tx_stop(InfraredApp* infrared) { infrared->app_state.last_transmit_time = furi_get_tick(); } +void infrared_blocking_task_start(InfraredApp* infrared, FuriThreadCallback callback) { + view_stack_add_view(infrared->view_stack, loading_get_view(infrared->loading)); + furi_thread_set_callback(infrared->task_thread, callback); + furi_thread_start(infrared->task_thread); +} + +bool infrared_blocking_task_finalize(InfraredApp* infrared) { + furi_thread_join(infrared->task_thread); + view_stack_remove_view(infrared->view_stack, loading_get_view(infrared->loading)); + return furi_thread_get_return_code(infrared->task_thread); +} + void infrared_text_store_set(InfraredApp* infrared, uint32_t bank, const char* fmt, ...) { va_list args; va_start(args, fmt); @@ -413,21 +432,6 @@ void infrared_play_notification_message( notification_message(infrared->notifications, infrared_notification_sequences[message]); } -void infrared_show_loading_popup(const InfraredApp* infrared, bool show) { - ViewStack* view_stack = infrared->view_stack; - Loading* loading = infrared->loading; - - if(show) { - // Raise timer priority so that animations can play - furi_timer_set_thread_priority(FuriTimerThreadPriorityElevated); - view_stack_add_view(view_stack, loading_get_view(loading)); - } else { - view_stack_remove_view(view_stack, loading_get_view(loading)); - // Restore default timer priority - furi_timer_set_thread_priority(FuriTimerThreadPriorityNormal); - } -} - void infrared_show_error_message(const InfraredApp* infrared, const char* fmt, ...) { va_list args; va_start(args, fmt); diff --git a/applications/main/infrared/infrared_app_i.h b/applications/main/infrared/infrared_app_i.h index a840f24cc4..cd5fec98d6 100644 --- a/applications/main/infrared/infrared_app_i.h +++ b/applications/main/infrared/infrared_app_i.h @@ -20,12 +20,13 @@ #include <gui/modules/button_panel.h> #include <gui/modules/variable_item_list.h> +#include <rpc/rpc_app.h> #include <storage/storage.h> #include <dialogs/dialogs.h> #include <notification/notification_messages.h> -#include <infrared_worker.h> +#include <infrared/worker/infrared_worker.h> #include "infrared_app.h" #include "infrared_remote.h" @@ -38,9 +39,6 @@ #include "views/infrared_debug_view.h" #include "views/infrared_move_view.h" -#include "rpc/rpc_app.h" -#include <furi_hal_infrared.h> - #define INFRARED_FILE_NAME_SIZE 100 #define INFRARED_TEXT_STORE_NUM 2 #define INFRARED_TEXT_STORE_SIZE 128 @@ -124,6 +122,7 @@ struct InfraredApp { Loading* loading; /**< Standard view for informing about long operations. */ InfraredProgressView* progress; /**< Custom view for showing brute force progress. */ + FuriThread* task_thread; /**< Pointer to a FuriThread instance for concurrent tasks. */ FuriString* file_path; /**< Full path to the currently loaded file. */ FuriString* button_name; /**< Name of the button requested in RPC mode. */ /** Arbitrary text storage for various inputs. */ @@ -216,6 +215,28 @@ void infrared_tx_start_button_index(InfraredApp* infrared, size_t button_index); */ void infrared_tx_stop(InfraredApp* infrared); +/** + * @brief Start a blocking task in a separate thread. + * + * If a ViewStack is currently on screen, a busy "Hourglass" animation + * will be shown and no input will be accepted until completion. + * + * @param[in,out] infrared pointer to the application instance. + * @param[in] callback pointer to the function to be run in the thread. + */ +void infrared_blocking_task_start(InfraredApp* infrared, FuriThreadCallback callback); + +/** + * @brief Wait for a blocking task to finish and receive the result. + * + * Upon the completion of a blocking task, the busy animation will be hidden + * and input will be accepted again. + * + * @param[in,out] infrared pointer to the application instance. + * @return true if the blocking task finished successfully, false otherwise. + */ +bool infrared_blocking_task_finalize(InfraredApp* infrared); + /** * @brief Set the internal text store with formatted text. * @@ -245,17 +266,6 @@ void infrared_play_notification_message( const InfraredApp* infrared, InfraredNotificationMessage message); -/** - * @brief Show a loading pop-up screen. - * - * In order for this to work, a Stack view must be currently active and - * the main view must be added to it. - * - * @param[in] infrared pointer to the application instance. - * @param[in] show whether to show or hide the pop-up. - */ -void infrared_show_loading_popup(const InfraredApp* infrared, bool show); - /** * @brief Show a formatted error messsage. * diff --git a/applications/main/infrared/infrared_custom_event.h b/applications/main/infrared/infrared_custom_event.h index 30bb0f729c..b53e52a2f3 100644 --- a/applications/main/infrared/infrared_custom_event.h +++ b/applications/main/infrared/infrared_custom_event.h @@ -14,6 +14,7 @@ enum InfraredCustomEventType { InfraredCustomEventTypePopupClosed, InfraredCustomEventTypeButtonSelected, InfraredCustomEventTypeBackPressed, + InfraredCustomEventTypeTaskFinished, InfraredCustomEventTypeRpcLoadFile, InfraredCustomEventTypeRpcExit, diff --git a/applications/main/infrared/scenes/common/infrared_scene_universal_common.c b/applications/main/infrared/scenes/common/infrared_scene_universal_common.c index 4967d19566..9fc48bd46b 100644 --- a/applications/main/infrared/scenes/common/infrared_scene_universal_common.c +++ b/applications/main/infrared/scenes/common/infrared_scene_universal_common.c @@ -32,9 +32,24 @@ static void infrared_scene_universal_common_hide_popup(InfraredApp* infrared) { infrared_play_notification_message(infrared, InfraredNotificationMessageBlinkStop); } +static int32_t infrared_scene_universal_common_task_callback(void* context) { + InfraredApp* infrared = context; + const bool success = infrared_brute_force_calculate_messages(infrared->brute_force); + view_dispatcher_send_custom_event( + infrared->view_dispatcher, + infrared_custom_event_pack(InfraredCustomEventTypeTaskFinished, 0)); + + return success; +} + void infrared_scene_universal_common_on_enter(void* context) { InfraredApp* infrared = context; + view_set_orientation(view_stack_get_view(infrared->view_stack), ViewOrientationVertical); view_stack_add_view(infrared->view_stack, button_panel_get_view(infrared->button_panel)); + view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewStack); + + // Load universal remote data in background + infrared_blocking_task_start(infrared, infrared_scene_universal_common_task_callback); } bool infrared_scene_universal_common_on_event(void* context, SceneManagerEvent event) { @@ -58,26 +73,34 @@ bool infrared_scene_universal_common_on_event(void* context, SceneManagerEvent e if(infrared_custom_event_get_type(event.event) == InfraredCustomEventTypeBackPressed) { infrared_brute_force_stop(brute_force); infrared_scene_universal_common_hide_popup(infrared); - consumed = true; } + consumed = true; } } else { if(event.type == SceneManagerEventTypeBack) { scene_manager_previous_scene(scene_manager); consumed = true; } else if(event.type == SceneManagerEventTypeCustom) { - if(infrared_custom_event_get_type(event.event) == - InfraredCustomEventTypeButtonSelected) { + uint16_t event_type; + int16_t event_value; + infrared_custom_event_unpack(event.event, &event_type, &event_value); + + if(event_type == InfraredCustomEventTypeButtonSelected) { uint32_t record_count; - if(infrared_brute_force_start( - brute_force, infrared_custom_event_get_value(event.event), &record_count)) { + if(infrared_brute_force_start(brute_force, event_value, &record_count)) { dolphin_deed(DolphinDeedIrSend); infrared_scene_universal_common_show_popup(infrared, record_count); } else { scene_manager_next_scene(scene_manager, InfraredSceneErrorDatabases); } - consumed = true; + } else if(event_type == InfraredCustomEventTypeTaskFinished) { + const bool task_success = infrared_blocking_task_finalize(infrared); + + if(!task_success) { + scene_manager_next_scene(infrared->scene_manager, InfraredSceneErrorDatabases); + } } + consumed = true; } } diff --git a/applications/main/infrared/scenes/infrared_scene_debug_settings.c b/applications/main/infrared/scenes/infrared_scene_debug_settings.c index e07dc1d6fb..8a64d36411 100644 --- a/applications/main/infrared/scenes/infrared_scene_debug_settings.c +++ b/applications/main/infrared/scenes/infrared_scene_debug_settings.c @@ -1,5 +1,7 @@ #include "../infrared_app_i.h" +#include <furi_hal_infrared.h> + uint8_t value_index_ir; #define DEB_PINS_COUNT (sizeof(infrared_debug_cfg_variables_text) / sizeof(char* const)) diff --git a/applications/main/infrared/scenes/infrared_scene_edit_delete.c b/applications/main/infrared/scenes/infrared_scene_edit_delete.c index 0cb88efdb6..8dc4ab6f9f 100644 --- a/applications/main/infrared/scenes/infrared_scene_edit_delete.c +++ b/applications/main/infrared/scenes/infrared_scene_edit_delete.c @@ -6,12 +6,33 @@ static void view_dispatcher_send_custom_event(infrared->view_dispatcher, result); } +static int32_t infrared_scene_edit_delete_task_callback(void* context) { + InfraredApp* infrared = context; + InfraredAppState* app_state = &infrared->app_state; + const InfraredEditTarget edit_target = app_state->edit_target; + + bool success; + if(edit_target == InfraredEditTargetButton) { + furi_assert(app_state->current_button_index != InfraredButtonIndexNone); + success = infrared_remote_delete_signal(infrared->remote, app_state->current_button_index); + } else if(edit_target == InfraredEditTargetRemote) { + success = infrared_remote_remove(infrared->remote); + } else { + furi_crash(); + } + + view_dispatcher_send_custom_event( + infrared->view_dispatcher, InfraredCustomEventTypeTaskFinished); + + return success; +} + void infrared_scene_edit_delete_on_enter(void* context) { InfraredApp* infrared = context; DialogEx* dialog_ex = infrared->dialog_ex; InfraredRemote* remote = infrared->remote; - const InfraredEditTarget edit_target = infrared->app_state.edit_target; + if(edit_target == InfraredEditTargetButton) { dialog_ex_set_header(dialog_ex, "Delete Button?", 64, 0, AlignCenter, AlignTop); @@ -84,39 +105,30 @@ bool infrared_scene_edit_delete_on_event(void* context, SceneManagerEvent event) if(event.type == SceneManagerEventTypeCustom) { if(event.event == DialogExResultLeft) { scene_manager_previous_scene(scene_manager); - consumed = true; } else if(event.event == DialogExResultRight) { - bool success = false; - InfraredRemote* remote = infrared->remote; + // Delete a button or a remote in a separate thread + infrared_blocking_task_start(infrared, infrared_scene_edit_delete_task_callback); + + } else if(event.event == InfraredCustomEventTypeTaskFinished) { + const bool task_success = infrared_blocking_task_finalize(infrared); + InfraredAppState* app_state = &infrared->app_state; - const InfraredEditTarget edit_target = app_state->edit_target; - - if(edit_target == InfraredEditTargetButton) { - furi_assert(app_state->current_button_index != InfraredButtonIndexNone); - infrared_show_loading_popup(infrared, true); - success = infrared_remote_delete_signal(remote, app_state->current_button_index); - infrared_show_loading_popup(infrared, false); - app_state->current_button_index = InfraredButtonIndexNone; - } else if(edit_target == InfraredEditTargetRemote) { - success = infrared_remote_remove(remote); - app_state->current_button_index = InfraredButtonIndexNone; - } else { - furi_crash(); - } - if(success) { + if(task_success) { scene_manager_next_scene(scene_manager, InfraredSceneEditDeleteDone); } else { - infrared_show_error_message( - infrared, - "Failed to\ndelete %s", - edit_target == InfraredEditTargetButton ? "button" : "file"); + const char* edit_target_text = + app_state->edit_target == InfraredEditTargetButton ? "button" : "file"; + infrared_show_error_message(infrared, "Failed to\ndelete %s", edit_target_text); + const uint32_t possible_scenes[] = {InfraredSceneRemoteList, InfraredSceneStart}; scene_manager_search_and_switch_to_previous_scene_one_of( scene_manager, possible_scenes, COUNT_OF(possible_scenes)); } - consumed = true; + + app_state->current_button_index = InfraredButtonIndexNone; } + consumed = true; } return consumed; diff --git a/applications/main/infrared/scenes/infrared_scene_edit_move.c b/applications/main/infrared/scenes/infrared_scene_edit_move.c index 4959a83109..500f3d791e 100644 --- a/applications/main/infrared/scenes/infrared_scene_edit_move.c +++ b/applications/main/infrared/scenes/infrared_scene_edit_move.c @@ -1,5 +1,17 @@ #include "../infrared_app_i.h" +static int32_t infrared_scene_edit_move_task_callback(void* context) { + InfraredApp* infrared = context; + const bool success = infrared_remote_move_signal( + infrared->remote, + infrared->app_state.prev_button_index, + infrared->app_state.current_button_index); + view_dispatcher_send_custom_event( + infrared->view_dispatcher, InfraredCustomEventTypeTaskFinished); + + return success; +} + static void infrared_scene_edit_move_button_callback( uint32_t index_old, uint32_t index_new, @@ -38,25 +50,21 @@ bool infrared_scene_edit_move_on_event(void* context, SceneManagerEvent event) { if(event.type == SceneManagerEventTypeCustom) { if(event.event == InfraredCustomEventTypeButtonSelected) { - infrared_show_loading_popup(infrared, true); - const bool button_moved = infrared_remote_move_signal( - infrared->remote, - infrared->app_state.prev_button_index, - infrared->app_state.current_button_index); - infrared_show_loading_popup(infrared, false); - - if(!button_moved) { - infrared_show_error_message( - infrared, - "Failed to move\n\"%s\"", - infrared_remote_get_signal_name( - infrared->remote, infrared->app_state.current_button_index)); + // Move the button in a separate thread + infrared_blocking_task_start(infrared, infrared_scene_edit_move_task_callback); + + } else if(event.event == InfraredCustomEventTypeTaskFinished) { + const bool task_success = infrared_blocking_task_finalize(infrared); + + if(!task_success) { + const char* signal_name = infrared_remote_get_signal_name( + infrared->remote, infrared->app_state.current_button_index); + infrared_show_error_message(infrared, "Failed to move\n\"%s\"", signal_name); scene_manager_search_and_switch_to_previous_scene( infrared->scene_manager, InfraredSceneRemoteList); } - - consumed = true; } + consumed = true; } return consumed; diff --git a/applications/main/infrared/scenes/infrared_scene_edit_rename.c b/applications/main/infrared/scenes/infrared_scene_edit_rename.c index 178690926d..2763c27773 100644 --- a/applications/main/infrared/scenes/infrared_scene_edit_rename.c +++ b/applications/main/infrared/scenes/infrared_scene_edit_rename.c @@ -3,6 +3,28 @@ #include <string.h> #include <toolbox/path.h> +static int32_t infrared_scene_edit_rename_task_callback(void* context) { + InfraredApp* infrared = context; + InfraredAppState* app_state = &infrared->app_state; + const InfraredEditTarget edit_target = app_state->edit_target; + + bool success; + if(edit_target == InfraredEditTargetButton) { + furi_assert(app_state->current_button_index != InfraredButtonIndexNone); + success = infrared_remote_rename_signal( + infrared->remote, app_state->current_button_index, infrared->text_store[0]); + } else if(edit_target == InfraredEditTargetRemote) { + success = infrared_rename_current_remote(infrared, infrared->text_store[0]); + } else { + furi_crash(); + } + + view_dispatcher_send_custom_event( + infrared->view_dispatcher, InfraredCustomEventTypeTaskFinished); + + return success; +} + void infrared_scene_edit_rename_on_enter(void* context) { InfraredApp* infrared = context; InfraredRemote* remote = infrared->remote; @@ -61,41 +83,31 @@ void infrared_scene_edit_rename_on_enter(void* context) { bool infrared_scene_edit_rename_on_event(void* context, SceneManagerEvent event) { InfraredApp* infrared = context; - InfraredRemote* remote = infrared->remote; SceneManager* scene_manager = infrared->scene_manager; - InfraredAppState* app_state = &infrared->app_state; bool consumed = false; if(event.type == SceneManagerEventTypeCustom) { if(event.event == InfraredCustomEventTypeTextEditDone) { - bool success = false; - const InfraredEditTarget edit_target = app_state->edit_target; - if(edit_target == InfraredEditTargetButton) { - const int32_t current_button_index = app_state->current_button_index; - furi_assert(current_button_index != InfraredButtonIndexNone); - infrared_show_loading_popup(infrared, true); - success = infrared_remote_rename_signal( - remote, current_button_index, infrared->text_store[0]); - infrared_show_loading_popup(infrared, false); - app_state->current_button_index = InfraredButtonIndexNone; - } else if(edit_target == InfraredEditTargetRemote) { - success = infrared_rename_current_remote(infrared, infrared->text_store[0]); - } else { - furi_crash(); - } + // Rename a button or a remote in a separate thread + infrared_blocking_task_start(infrared, infrared_scene_edit_rename_task_callback); - if(success) { + } else if(event.event == InfraredCustomEventTypeTaskFinished) { + const bool task_success = infrared_blocking_task_finalize(infrared); + InfraredAppState* app_state = &infrared->app_state; + + if(task_success) { scene_manager_next_scene(scene_manager, InfraredSceneEditRenameDone); } else { - infrared_show_error_message( - infrared, - "Failed to\nrename %s", - edit_target == InfraredEditTargetButton ? "button" : "file"); + const char* edit_target_text = + app_state->edit_target == InfraredEditTargetButton ? "button" : "file"; + infrared_show_error_message(infrared, "Failed to\nrename %s", edit_target_text); scene_manager_search_and_switch_to_previous_scene( scene_manager, InfraredSceneRemoteList); } - consumed = true; + + app_state->current_button_index = InfraredButtonIndexNone; } + consumed = true; } return consumed; diff --git a/applications/main/infrared/scenes/infrared_scene_remote_list.c b/applications/main/infrared/scenes/infrared_scene_remote_list.c index 2276e270a0..744409a7ab 100644 --- a/applications/main/infrared/scenes/infrared_scene_remote_list.c +++ b/applications/main/infrared/scenes/infrared_scene_remote_list.c @@ -1,41 +1,59 @@ #include "../infrared_app_i.h" -void infrared_scene_remote_list_on_enter(void* context) { +static int32_t infrared_scene_remote_list_task_callback(void* context) { InfraredApp* infrared = context; - SceneManager* scene_manager = infrared->scene_manager; - ViewDispatcher* view_dispatcher = infrared->view_dispatcher; - - view_set_orientation(view_stack_get_view(infrared->view_stack), ViewOrientationVertical); - view_dispatcher_switch_to_view(view_dispatcher, InfraredViewStack); + const bool success = + infrared_remote_load(infrared->remote, furi_string_get_cstr(infrared->file_path)); + view_dispatcher_send_custom_event( + infrared->view_dispatcher, InfraredCustomEventTypeTaskFinished); + return success; +} +static void infrared_scene_remote_list_select_and_load(InfraredApp* infrared) { DialogsFileBrowserOptions browser_options; dialog_file_browser_set_basic_options(&browser_options, INFRARED_APP_EXTENSION, &I_ir_10px); browser_options.base_path = INFRARED_APP_FOLDER; - while(dialog_file_browser_show( - infrared->dialogs, infrared->file_path, infrared->file_path, &browser_options)) { - const char* file_path = furi_string_get_cstr(infrared->file_path); + const bool file_selected = dialog_file_browser_show( + infrared->dialogs, infrared->file_path, infrared->file_path, &browser_options); - infrared_show_loading_popup(infrared, true); - const bool remote_loaded = infrared_remote_load(infrared->remote, file_path); - infrared_show_loading_popup(infrared, false); + if(file_selected) { + view_set_orientation(view_stack_get_view(infrared->view_stack), ViewOrientationVertical); + view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewStack); - if(remote_loaded) { - scene_manager_next_scene(scene_manager, InfraredSceneRemote); - return; - } else { - infrared_show_error_message(infrared, "Failed to load\n\"%s\"", file_path); - } + // Load the remote in a separate thread + infrared_blocking_task_start(infrared, infrared_scene_remote_list_task_callback); + + } else { + scene_manager_previous_scene(infrared->scene_manager); } +} - scene_manager_previous_scene(scene_manager); +void infrared_scene_remote_list_on_enter(void* context) { + InfraredApp* infrared = context; + infrared_scene_remote_list_select_and_load(infrared); } bool infrared_scene_remote_list_on_event(void* context, SceneManagerEvent event) { - UNUSED(context); - UNUSED(event); + InfraredApp* infrared = context; + bool consumed = false; + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == InfraredCustomEventTypeTaskFinished) { + const bool task_success = infrared_blocking_task_finalize(infrared); + + if(task_success) { + scene_manager_next_scene(infrared->scene_manager, InfraredSceneRemote); + } else { + infrared_show_error_message( + infrared, "Failed to load\n\"%s\"", furi_string_get_cstr(infrared->file_path)); + infrared_scene_remote_list_select_and_load(infrared); + } + } + consumed = true; + } + return consumed; } diff --git a/applications/main/infrared/scenes/infrared_scene_rpc.c b/applications/main/infrared/scenes/infrared_scene_rpc.c index f3408fba4d..03a2bff010 100644 --- a/applications/main/infrared/scenes/infrared_scene_rpc.c +++ b/applications/main/infrared/scenes/infrared_scene_rpc.c @@ -9,6 +9,15 @@ typedef enum { InfraredRpcStateSending, } InfraredRpcState; +static int32_t infrared_scene_rpc_task_callback(void* context) { + InfraredApp* infrared = context; + const bool success = + infrared_remote_load(infrared->remote, furi_string_get_cstr(infrared->file_path)); + view_dispatcher_send_custom_event( + infrared->view_dispatcher, InfraredCustomEventTypeTaskFinished); + return success; +} + void infrared_scene_rpc_on_enter(void* context) { InfraredApp* infrared = context; Popup* popup = infrared->popup; @@ -21,7 +30,8 @@ void infrared_scene_rpc_on_enter(void* context) { popup_set_context(popup, context); popup_set_callback(popup, infrared_popup_closed_callback); - view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewPopup); + view_stack_add_view(infrared->view_stack, popup_get_view(infrared->popup)); + view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewStack); scene_manager_set_scene_state(infrared->scene_manager, InfraredSceneRpc, InfraredRpcStateIdle); @@ -33,76 +43,88 @@ bool infrared_scene_rpc_on_event(void* context, SceneManagerEvent event) { bool consumed = false; if(event.type == SceneManagerEventTypeCustom) { - consumed = true; - InfraredRpcState state = + InfraredAppState* app_state = &infrared->app_state; + InfraredRpcState rpc_state = scene_manager_get_scene_state(infrared->scene_manager, InfraredSceneRpc); - if(event.event == InfraredCustomEventTypeBackPressed) { - view_dispatcher_stop(infrared->view_dispatcher); - } else if(event.event == InfraredCustomEventTypePopupClosed) { - view_dispatcher_stop(infrared->view_dispatcher); - } else if(event.event == InfraredCustomEventTypeRpcLoadFile) { - bool result = false; - if(state == InfraredRpcStateIdle) { - result = infrared_remote_load( - infrared->remote, furi_string_get_cstr(infrared->file_path)); - if(result) { - scene_manager_set_scene_state( - infrared->scene_manager, InfraredSceneRpc, InfraredRpcStateLoaded); - } + + if(event.event == InfraredCustomEventTypeRpcLoadFile) { + if(rpc_state == InfraredRpcStateIdle) { + // Load the remote in a separate thread + infrared_blocking_task_start(infrared, infrared_scene_rpc_task_callback); + } + + } else if(event.event == InfraredCustomEventTypeTaskFinished) { + const bool task_success = infrared_blocking_task_finalize(infrared); + + if(task_success) { + const char* remote_name = infrared_remote_get_name(infrared->remote); + infrared_text_store_set(infrared, 0, "loaded\n%s", remote_name); + scene_manager_set_scene_state( + infrared->scene_manager, InfraredSceneRpc, InfraredRpcStateLoaded); + + } else { + infrared_text_store_set( + infrared, 0, "failed to load\n%s", furi_string_get_cstr(infrared->file_path)); } - const char* remote_name = infrared_remote_get_name(infrared->remote); - infrared_text_store_set(infrared, 0, "loaded\n%s", remote_name); popup_set_text( infrared->popup, infrared->text_store[0], 89, 44, AlignCenter, AlignTop); - rpc_system_app_confirm(infrared->rpc_ctx, result); + rpc_system_app_confirm(infrared->rpc_ctx, task_success); + } else if( event.event == InfraredCustomEventTypeRpcButtonPressName || event.event == InfraredCustomEventTypeRpcButtonPressIndex) { bool result = false; - if(state == InfraredRpcStateLoaded) { + if(rpc_state == InfraredRpcStateLoaded) { if(event.event == InfraredCustomEventTypeRpcButtonPressName) { const char* button_name = furi_string_get_cstr(infrared->button_name); size_t index; const bool index_found = infrared_remote_get_signal_index(infrared->remote, button_name, &index); - infrared->app_state.current_button_index = - index_found ? (signed)index : InfraredButtonIndexNone; + app_state->current_button_index = index_found ? (signed)index : + InfraredButtonIndexNone; FURI_LOG_D(TAG, "Sending signal with name \"%s\"", button_name); } else { FURI_LOG_D( - TAG, - "Sending signal with index \"%ld\"", - infrared->app_state.current_button_index); + TAG, "Sending signal with index \"%ld\"", app_state->current_button_index); } if(infrared->app_state.current_button_index != InfraredButtonIndexNone) { - infrared_tx_start_button_index( - infrared, infrared->app_state.current_button_index); + infrared_tx_start_button_index(infrared, app_state->current_button_index); scene_manager_set_scene_state( infrared->scene_manager, InfraredSceneRpc, InfraredRpcStateSending); result = true; } } rpc_system_app_confirm(infrared->rpc_ctx, result); + } else if(event.event == InfraredCustomEventTypeRpcButtonRelease) { bool result = false; - if(state == InfraredRpcStateSending) { + + if(rpc_state == InfraredRpcStateSending) { infrared_tx_stop(infrared); result = true; scene_manager_set_scene_state( infrared->scene_manager, InfraredSceneRpc, InfraredRpcStateLoaded); } + rpc_system_app_confirm(infrared->rpc_ctx, result); - } else if(event.event == InfraredCustomEventTypeRpcExit) { - scene_manager_stop(infrared->scene_manager); - view_dispatcher_stop(infrared->view_dispatcher); - rpc_system_app_confirm(infrared->rpc_ctx, true); - } else if(event.event == InfraredCustomEventTypeRpcSessionClose) { + + } else if( + event.event == InfraredCustomEventTypeRpcExit || + event.event == InfraredCustomEventTypeRpcSessionClose || + event.event == InfraredCustomEventTypePopupClosed) { scene_manager_stop(infrared->scene_manager); view_dispatcher_stop(infrared->view_dispatcher); + + if(event.event == InfraredCustomEventTypeRpcExit) { + rpc_system_app_confirm(infrared->rpc_ctx, true); + } } + + consumed = true; } + return consumed; } @@ -112,5 +134,7 @@ void infrared_scene_rpc_on_exit(void* context) { InfraredRpcStateSending) { infrared_tx_stop(infrared); } + + view_stack_remove_view(infrared->view_stack, popup_get_view(infrared->popup)); popup_reset(infrared->popup); } diff --git a/applications/main/infrared/scenes/infrared_scene_universal_ac.c b/applications/main/infrared/scenes/infrared_scene_universal_ac.c index c691aef439..45f6b4ab65 100644 --- a/applications/main/infrared/scenes/infrared_scene_universal_ac.c +++ b/applications/main/infrared/scenes/infrared_scene_universal_ac.c @@ -3,8 +3,6 @@ #include "common/infrared_scene_universal_common.h" void infrared_scene_universal_ac_on_enter(void* context) { - infrared_scene_universal_common_on_enter(context); - InfraredApp* infrared = context; ButtonPanel* button_panel = infrared->button_panel; InfraredBruteForce* brute_force = infrared->brute_force; @@ -122,16 +120,7 @@ void infrared_scene_universal_ac_on_enter(void* context) { button_panel_add_label(button_panel, 22, 10, FontPrimary, "ACs"); - view_set_orientation(view_stack_get_view(infrared->view_stack), ViewOrientationVertical); - view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewStack); - - infrared_show_loading_popup(infrared, true); - bool success = infrared_brute_force_calculate_messages(brute_force); - infrared_show_loading_popup(infrared, false); - - if(!success) { - scene_manager_next_scene(infrared->scene_manager, InfraredSceneErrorDatabases); - } + infrared_scene_universal_common_on_enter(context); } bool infrared_scene_universal_ac_on_event(void* context, SceneManagerEvent event) { diff --git a/applications/main/infrared/scenes/infrared_scene_universal_audio.c b/applications/main/infrared/scenes/infrared_scene_universal_audio.c index 11f84acc44..9e90d859ef 100644 --- a/applications/main/infrared/scenes/infrared_scene_universal_audio.c +++ b/applications/main/infrared/scenes/infrared_scene_universal_audio.c @@ -3,8 +3,6 @@ #include "common/infrared_scene_universal_common.h" void infrared_scene_universal_audio_on_enter(void* context) { - infrared_scene_universal_common_on_enter(context); - InfraredApp* infrared = context; ButtonPanel* button_panel = infrared->button_panel; InfraredBruteForce* brute_force = infrared->brute_force; @@ -119,16 +117,7 @@ void infrared_scene_universal_audio_on_enter(void* context) { button_panel_add_label(button_panel, 18, 10, FontPrimary, "Audio"); button_panel_add_icon(button_panel, 34, 56, &I_vol_ac_text_30x30); - view_set_orientation(view_stack_get_view(infrared->view_stack), ViewOrientationVertical); - view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewStack); - - infrared_show_loading_popup(infrared, true); - bool success = infrared_brute_force_calculate_messages(brute_force); - infrared_show_loading_popup(infrared, false); - - if(!success) { - scene_manager_next_scene(infrared->scene_manager, InfraredSceneErrorDatabases); - } + infrared_scene_universal_common_on_enter(context); } bool infrared_scene_universal_audio_on_event(void* context, SceneManagerEvent event) { diff --git a/applications/main/infrared/scenes/infrared_scene_universal_digital_sign.c b/applications/main/infrared/scenes/infrared_scene_universal_digital_sign.c index 37f86e71b5..4b0d5cc5d0 100644 --- a/applications/main/infrared/scenes/infrared_scene_universal_digital_sign.c +++ b/applications/main/infrared/scenes/infrared_scene_universal_digital_sign.c @@ -3,8 +3,6 @@ #include "common/infrared_scene_universal_common.h" void infrared_scene_universal_digital_sign_on_enter(void* context) { - infrared_scene_universal_common_on_enter(context); - InfraredApp* infrared = context; ButtonPanel* button_panel = infrared->button_panel; InfraredBruteForce* brute_force = infrared->brute_force; @@ -71,16 +69,7 @@ void infrared_scene_universal_digital_sign_on_enter(void* context) { button_panel_add_label(button_panel, 1, 11, FontPrimary, "Digital Signs"); - view_set_orientation(view_stack_get_view(infrared->view_stack), ViewOrientationVertical); - view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewStack); - - infrared_show_loading_popup(infrared, true); - bool success = infrared_brute_force_calculate_messages(brute_force); - infrared_show_loading_popup(infrared, false); - - if(!success) { - scene_manager_next_scene(infrared->scene_manager, InfraredSceneErrorDatabases); - } + infrared_scene_universal_common_on_enter(context); } bool infrared_scene_universal_digital_sign_on_event(void* context, SceneManagerEvent event) { diff --git a/applications/main/infrared/scenes/infrared_scene_universal_fan.c b/applications/main/infrared/scenes/infrared_scene_universal_fan.c index ef3e819fdc..6f3a7d4b7d 100644 --- a/applications/main/infrared/scenes/infrared_scene_universal_fan.c +++ b/applications/main/infrared/scenes/infrared_scene_universal_fan.c @@ -3,8 +3,6 @@ #include "common/infrared_scene_universal_common.h" void infrared_scene_universal_fan_on_enter(void* context) { - infrared_scene_universal_common_on_enter(context); - InfraredApp* infrared = context; ButtonPanel* button_panel = infrared->button_panel; InfraredBruteForce* brute_force = infrared->brute_force; @@ -97,16 +95,7 @@ void infrared_scene_universal_fan_on_enter(void* context) { button_panel_add_label(button_panel, 20, 11, FontPrimary, "Fans"); button_panel_add_icon(button_panel, 34, 68, &I_speed_text_30x30); - view_set_orientation(view_stack_get_view(infrared->view_stack), ViewOrientationVertical); - view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewStack); - - infrared_show_loading_popup(infrared, true); - bool success = infrared_brute_force_calculate_messages(brute_force); - infrared_show_loading_popup(infrared, false); - - if(!success) { - scene_manager_next_scene(infrared->scene_manager, InfraredSceneErrorDatabases); - } + infrared_scene_universal_common_on_enter(context); } bool infrared_scene_universal_fan_on_event(void* context, SceneManagerEvent event) { diff --git a/applications/main/infrared/scenes/infrared_scene_universal_led.c b/applications/main/infrared/scenes/infrared_scene_universal_led.c index ca50a4a38e..1055f71e97 100644 --- a/applications/main/infrared/scenes/infrared_scene_universal_led.c +++ b/applications/main/infrared/scenes/infrared_scene_universal_led.c @@ -3,8 +3,6 @@ #include "common/infrared_scene_universal_common.h" void infrared_scene_universal_led_on_enter(void* context) { - infrared_scene_universal_common_on_enter(context); - InfraredApp* infrared = context; ButtonPanel* button_panel = infrared->button_panel; InfraredBruteForce* brute_force = infrared->brute_force; @@ -71,16 +69,7 @@ void infrared_scene_universal_led_on_enter(void* context) { button_panel_add_label(button_panel, 21, 11, FontPrimary, "LEDs"); - view_set_orientation(view_stack_get_view(infrared->view_stack), ViewOrientationVertical); - view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewStack); - - infrared_show_loading_popup(infrared, true); - bool success = infrared_brute_force_calculate_messages(brute_force); - infrared_show_loading_popup(infrared, false); - - if(!success) { - scene_manager_next_scene(infrared->scene_manager, InfraredSceneErrorDatabases); - } + infrared_scene_universal_common_on_enter(context); } bool infrared_scene_universal_led_on_event(void* context, SceneManagerEvent event) { diff --git a/applications/main/infrared/scenes/infrared_scene_universal_monitor.c b/applications/main/infrared/scenes/infrared_scene_universal_monitor.c index 65e2f790c9..da3d53999b 100644 --- a/applications/main/infrared/scenes/infrared_scene_universal_monitor.c +++ b/applications/main/infrared/scenes/infrared_scene_universal_monitor.c @@ -3,8 +3,6 @@ #include "common/infrared_scene_universal_common.h" void infrared_scene_universal_monitor_on_enter(void* context) { - infrared_scene_universal_common_on_enter(context); - InfraredApp* infrared = context; ButtonPanel* button_panel = infrared->button_panel; InfraredBruteForce* brute_force = infrared->brute_force; @@ -72,16 +70,7 @@ void infrared_scene_universal_monitor_on_enter(void* context) { button_panel_add_label(button_panel, 10, 11, FontPrimary, "Monitors"); - view_set_orientation(view_stack_get_view(infrared->view_stack), ViewOrientationVertical); - view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewStack); - - infrared_show_loading_popup(infrared, true); - bool success = infrared_brute_force_calculate_messages(brute_force); - infrared_show_loading_popup(infrared, false); - - if(!success) { - scene_manager_next_scene(infrared->scene_manager, InfraredSceneErrorDatabases); - } + infrared_scene_universal_common_on_enter(context); } bool infrared_scene_universal_monitor_on_event(void* context, SceneManagerEvent event) { diff --git a/applications/main/infrared/scenes/infrared_scene_universal_projector.c b/applications/main/infrared/scenes/infrared_scene_universal_projector.c index 0e6b462fd4..a90765c5fa 100644 --- a/applications/main/infrared/scenes/infrared_scene_universal_projector.c +++ b/applications/main/infrared/scenes/infrared_scene_universal_projector.c @@ -3,8 +3,6 @@ #include "common/infrared_scene_universal_common.h" void infrared_scene_universal_projector_on_enter(void* context) { - infrared_scene_universal_common_on_enter(context); - InfraredApp* infrared = context; ButtonPanel* button_panel = infrared->button_panel; InfraredBruteForce* brute_force = infrared->brute_force; @@ -94,16 +92,7 @@ void infrared_scene_universal_projector_on_enter(void* context) { button_panel_add_label(button_panel, 7, 11, FontPrimary, "Projectors"); button_panel_add_icon(button_panel, 34, 68, &I_vol_ac_text_30x30); - view_set_orientation(view_stack_get_view(infrared->view_stack), ViewOrientationVertical); - view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewStack); - - infrared_show_loading_popup(infrared, true); - bool success = infrared_brute_force_calculate_messages(brute_force); - infrared_show_loading_popup(infrared, false); - - if(!success) { - scene_manager_next_scene(infrared->scene_manager, InfraredSceneErrorDatabases); - } + infrared_scene_universal_common_on_enter(context); } bool infrared_scene_universal_projector_on_event(void* context, SceneManagerEvent event) { diff --git a/applications/main/infrared/scenes/infrared_scene_universal_tv.c b/applications/main/infrared/scenes/infrared_scene_universal_tv.c index c7e755848b..a1fa4b26f2 100644 --- a/applications/main/infrared/scenes/infrared_scene_universal_tv.c +++ b/applications/main/infrared/scenes/infrared_scene_universal_tv.c @@ -3,8 +3,6 @@ #include "common/infrared_scene_universal_common.h" void infrared_scene_universal_tv_on_enter(void* context) { - infrared_scene_universal_common_on_enter(context); - InfraredApp* infrared = context; ButtonPanel* button_panel = infrared->button_panel; InfraredBruteForce* brute_force = infrared->brute_force; @@ -95,16 +93,7 @@ void infrared_scene_universal_tv_on_enter(void* context) { button_panel_add_label(button_panel, 22, 10, FontPrimary, "TVs"); - view_set_orientation(view_stack_get_view(infrared->view_stack), ViewOrientationVertical); - view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewStack); - - infrared_show_loading_popup(infrared, true); - bool success = infrared_brute_force_calculate_messages(brute_force); - infrared_show_loading_popup(infrared, false); - - if(!success) { - scene_manager_next_scene(infrared->scene_manager, InfraredSceneErrorDatabases); - } + infrared_scene_universal_common_on_enter(context); } bool infrared_scene_universal_tv_on_event(void* context, SceneManagerEvent event) { diff --git a/lib/datetime/datetime.c b/lib/datetime/datetime.c index 7b1f73dd0f..73044fae60 100644 --- a/lib/datetime/datetime.c +++ b/lib/datetime/datetime.c @@ -71,6 +71,7 @@ void datetime_timestamp_to_datetime(uint32_t timestamp, DateTime* datetime) { uint32_t seconds_in_day = timestamp % SECONDS_PER_DAY; datetime->year = EPOCH_START_YEAR; + datetime->weekday = ((days + 3) % 7) + 1; while(days >= datetime_get_days_per_year(datetime->year)) { days -= datetime_get_days_per_year(datetime->year);