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);