From 7987917d85281066136d22d65baafc6f8041eb8a Mon Sep 17 00:00:00 2001 From: Georgii Surkov <37121527+gsurkov@users.noreply.github.com> Date: Wed, 20 Mar 2024 17:54:07 +0300 Subject: [PATCH] [FL-3496, FL-3523, FL-3767, FL-3790] Infrared fixes and more (#3515) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * FL-3496: do not hardcode universal library names in CLI * FL-3523: remove TODO, no changes necessary * FL-3767: remove TODO, no changes necessary * FL-3790: fix laggy TextInput by not adding it into a ViewStack * Improve documentation * Fix logical error in documentation Co-authored-by: あく --- applications/main/infrared/infrared_app.c | 12 +++-- applications/main/infrared/infrared_app_i.h | 12 +++-- applications/main/infrared/infrared_cli.c | 50 ++++++++++++++++--- .../common/infrared_scene_universal_common.c | 3 +- .../scenes/infrared_scene_edit_delete.c | 7 +-- .../scenes/infrared_scene_edit_move.c | 8 ++- .../scenes/infrared_scene_edit_rename.c | 15 ++---- .../scenes/infrared_scene_remote_list.c | 3 -- .../main/infrared/scenes/infrared_scene_rpc.c | 7 +-- applications/services/gui/view_dispatcher.h | 4 +- applications/services/gui/view_holder.h | 6 +++ applications/services/gui/view_stack.h | 4 +- applications/services/rpc/rpc.c | 2 - 13 files changed, 84 insertions(+), 49 deletions(-) diff --git a/applications/main/infrared/infrared_app.c b/applications/main/infrared/infrared_app.c index 7ccbb6cea..82b054478 100644 --- a/applications/main/infrared/infrared_app.c +++ b/applications/main/infrared/infrared_app.c @@ -189,6 +189,10 @@ static InfraredApp* infrared_alloc(void) { view_dispatcher_add_view( view_dispatcher, InfraredViewMove, infrared_move_view_get_view(infrared->move_view)); + infrared->loading = loading_alloc(); + view_dispatcher_add_view( + view_dispatcher, InfraredViewLoading, loading_get_view(infrared->loading)); + if(app_state->is_debug_enabled) { infrared->debug_view = infrared_debug_view_alloc(); view_dispatcher_add_view( @@ -198,7 +202,6 @@ static InfraredApp* infrared_alloc(void) { } infrared->button_panel = button_panel_alloc(); - infrared->loading = loading_alloc(); infrared->progress = infrared_progress_view_alloc(); return infrared; @@ -240,13 +243,15 @@ static void infrared_free(InfraredApp* infrared) { view_dispatcher_remove_view(view_dispatcher, InfraredViewMove); infrared_move_view_free(infrared->move_view); + view_dispatcher_remove_view(view_dispatcher, InfraredViewLoading); + loading_free(infrared->loading); + if(app_state->is_debug_enabled) { view_dispatcher_remove_view(view_dispatcher, InfraredViewDebugView); infrared_debug_view_free(infrared->debug_view); } button_panel_free(infrared->button_panel); - loading_free(infrared->loading); infrared_progress_view_free(infrared->progress); view_dispatcher_free(view_dispatcher); @@ -385,14 +390,13 @@ void infrared_tx_stop(InfraredApp* infrared) { } void infrared_blocking_task_start(InfraredApp* infrared, FuriThreadCallback callback) { - view_stack_add_view(infrared->view_stack, loading_get_view(infrared->loading)); + view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewLoading); 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); } diff --git a/applications/main/infrared/infrared_app_i.h b/applications/main/infrared/infrared_app_i.h index bccd58608..1c074323a 100644 --- a/applications/main/infrared/infrared_app_i.h +++ b/applications/main/infrared/infrared_app_i.h @@ -141,6 +141,7 @@ typedef enum { InfraredViewStack, InfraredViewDebugView, InfraredViewMove, + InfraredViewLoading, } InfraredView; /** @@ -213,8 +214,8 @@ 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. + * Before starting a blocking task, the current view will be replaced + * with a busy animation. All subsequent user input will be ignored. * * @param[in,out] infrared pointer to the application instance. * @param[in] callback pointer to the function to be run in the thread. @@ -222,10 +223,11 @@ void infrared_tx_stop(InfraredApp* infrared); void infrared_blocking_task_start(InfraredApp* infrared, FuriThreadCallback callback); /** - * @brief Wait for a blocking task to finish and receive the result. + * @brief Wait for a blocking task to finish and get the result. * - * Upon the completion of a blocking task, the busy animation will be hidden - * and input will be accepted again. + * The busy animation shown during the infrared_blocking_task_start() call + * will NOT be hidden and WILL remain on screen. If another view is needed + * (e.g. to display the results), the caller code MUST set it explicitly. * * @param[in,out] infrared pointer to the application instance. * @return true if the blocking task finished successfully, false otherwise. diff --git a/applications/main/infrared/infrared_cli.c b/applications/main/infrared/infrared_cli.c index b498fc600..123fe78d5 100644 --- a/applications/main/infrared/infrared_cli.c +++ b/applications/main/infrared/infrared_cli.c @@ -10,8 +10,10 @@ #include "infrared_signal.h" #include "infrared_brute_force.h" -#define INFRARED_CLI_BUF_SIZE 10 -#define INFRARED_ASSETS_FOLDER "infrared/assets" +#define INFRARED_CLI_BUF_SIZE (10U) +#define INFRARED_CLI_FILE_NAME_SIZE (256U) +#define INFRARED_FILE_EXTENSION ".ir" +#define INFRARED_ASSETS_FOLDER EXT_PATH("infrared/assets") #define INFRARED_BRUTE_FORCE_DUMMY_INDEX 0 DICT_DEF2(dict_signals, FuriString*, FURI_STRING_OPLIST, int, M_DEFAULT_OPLIST) @@ -66,6 +68,37 @@ static void signal_received_callback(void* context, InfraredWorkerSignal* receiv } } +static void infrared_cli_print_universal_remotes(void) { + Storage* storage = furi_record_open(RECORD_STORAGE); + File* dir = storage_file_alloc(storage); + + do { + if(!storage_dir_open(dir, INFRARED_ASSETS_FOLDER)) break; + + FileInfo file_info; + char file_name[INFRARED_CLI_FILE_NAME_SIZE]; + + while(storage_dir_read(dir, &file_info, file_name, sizeof(file_name))) { + if(file_info.flags & FSF_DIRECTORY) { + continue; + } + + char* file_ext = strstr(file_name, INFRARED_FILE_EXTENSION); + if((file_ext == NULL) || (strcmp(file_ext, INFRARED_FILE_EXTENSION) != 0)) { + continue; + } + + *file_ext = '\0'; + printf("%s ", file_name); + } + + printf("\r\n"); + } while(false); + + storage_file_free(dir); + furi_record_close(RECORD_STORAGE); +} + static void infrared_cli_print_usage(void) { printf("Usage:\r\n"); printf("\tir rx [raw]\r\n"); @@ -85,8 +118,9 @@ static void infrared_cli_print_usage(void) { printf("\tir decode []\r\n"); printf("\tir universal \r\n"); printf("\tir universal list \r\n"); - // TODO FL-3496: Do not hardcode universal remote names - printf("\tAvailable universal remotes: tv audio ac projector\r\n"); + printf("\tAvailable universal remotes: "); + + infrared_cli_print_universal_remotes(); } static void infrared_cli_start_ir_rx(Cli* cli, FuriString* args) { @@ -211,7 +245,6 @@ static bool infrared_cli_decode_raw_signal( size_t i; for(i = 0; i < raw_signal->timings_size; ++i) { - // TODO FL-3523: Any infrared_check_decoder_ready() magic? const InfraredMessage* message = infrared_decode(decoder, level, raw_signal->timings[i]); if(message) { @@ -365,7 +398,10 @@ static void infrared_cli_list_remote_signals(FuriString* remote_name) { Storage* storage = furi_record_open(RECORD_STORAGE); FlipperFormat* ff = flipper_format_buffered_file_alloc(storage); FuriString* remote_path = furi_string_alloc_printf( - "%s/%s.ir", EXT_PATH(INFRARED_ASSETS_FOLDER), furi_string_get_cstr(remote_name)); + "%s/%s%s", + INFRARED_ASSETS_FOLDER, + furi_string_get_cstr(remote_name), + INFRARED_FILE_EXTENSION); do { if(!flipper_format_buffered_file_open_existing(ff, furi_string_get_cstr(remote_path))) { @@ -413,7 +449,7 @@ static void infrared_cli_brute_force_signals(Cli* cli, FuriString* remote_name, FuriString* signal_name) { InfraredBruteForce* brute_force = infrared_brute_force_alloc(); FuriString* remote_path = furi_string_alloc_printf( - "%s/%s.ir", EXT_PATH(INFRARED_ASSETS_FOLDER), furi_string_get_cstr(remote_name)); + "%s/%s.ir", INFRARED_ASSETS_FOLDER, furi_string_get_cstr(remote_name)); infrared_brute_force_set_db_filename(brute_force, furi_string_get_cstr(remote_path)); infrared_brute_force_add_record( 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 9fc48bd46..9bdcdc4a8 100644 --- a/applications/main/infrared/scenes/common/infrared_scene_universal_common.c +++ b/applications/main/infrared/scenes/common/infrared_scene_universal_common.c @@ -46,7 +46,6 @@ 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); @@ -98,6 +97,8 @@ bool infrared_scene_universal_common_on_event(void* context, SceneManagerEvent e if(!task_success) { scene_manager_next_scene(infrared->scene_manager, InfraredSceneErrorDatabases); + } else { + view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewStack); } } consumed = true; diff --git a/applications/main/infrared/scenes/infrared_scene_edit_delete.c b/applications/main/infrared/scenes/infrared_scene_edit_delete.c index 8dc4ab6f9..90a2633d3 100644 --- a/applications/main/infrared/scenes/infrared_scene_edit_delete.c +++ b/applications/main/infrared/scenes/infrared_scene_edit_delete.c @@ -91,10 +91,7 @@ void infrared_scene_edit_delete_on_enter(void* context) { dialog_ex_set_result_callback(dialog_ex, infrared_scene_edit_delete_dialog_result_callback); dialog_ex_set_context(dialog_ex, context); - view_set_orientation(view_stack_get_view(infrared->view_stack), ViewOrientationHorizontal); - view_stack_add_view(infrared->view_stack, dialog_ex_get_view(infrared->dialog_ex)); - - view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewStack); + view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewDialogEx); } bool infrared_scene_edit_delete_on_event(void* context, SceneManagerEvent event) { @@ -136,5 +133,5 @@ bool infrared_scene_edit_delete_on_event(void* context, SceneManagerEvent event) void infrared_scene_edit_delete_on_exit(void* context) { InfraredApp* infrared = context; - view_stack_remove_view(infrared->view_stack, dialog_ex_get_view(infrared->dialog_ex)); + dialog_ex_reset(infrared->dialog_ex); } diff --git a/applications/main/infrared/scenes/infrared_scene_edit_move.c b/applications/main/infrared/scenes/infrared_scene_edit_move.c index 500f3d791..fcac1fc15 100644 --- a/applications/main/infrared/scenes/infrared_scene_edit_move.c +++ b/applications/main/infrared/scenes/infrared_scene_edit_move.c @@ -38,10 +38,7 @@ void infrared_scene_edit_move_on_enter(void* context) { infrared_move_view_set_callback( infrared->move_view, infrared_scene_edit_move_button_callback, infrared); - view_set_orientation(view_stack_get_view(infrared->view_stack), ViewOrientationHorizontal); - view_stack_add_view(infrared->view_stack, infrared_move_view_get_view(infrared->move_view)); - - view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewStack); + view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewMove); } bool infrared_scene_edit_move_on_event(void* context, SceneManagerEvent event) { @@ -62,6 +59,8 @@ bool infrared_scene_edit_move_on_event(void* context, SceneManagerEvent event) { infrared_show_error_message(infrared, "Failed to move\n\"%s\"", signal_name); scene_manager_search_and_switch_to_previous_scene( infrared->scene_manager, InfraredSceneRemoteList); + } else { + view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewMove); } } consumed = true; @@ -72,6 +71,5 @@ bool infrared_scene_edit_move_on_event(void* context, SceneManagerEvent event) { void infrared_scene_edit_move_on_exit(void* context) { InfraredApp* infrared = context; - view_stack_remove_view(infrared->view_stack, infrared_move_view_get_view(infrared->move_view)); infrared_move_view_reset(infrared->move_view); } diff --git a/applications/main/infrared/scenes/infrared_scene_edit_rename.c b/applications/main/infrared/scenes/infrared_scene_edit_rename.c index 2763c2777..a546ad6e5 100644 --- a/applications/main/infrared/scenes/infrared_scene_edit_rename.c +++ b/applications/main/infrared/scenes/infrared_scene_edit_rename.c @@ -75,10 +75,7 @@ void infrared_scene_edit_rename_on_enter(void* context) { enter_name_length, false); - view_set_orientation(view_stack_get_view(infrared->view_stack), ViewOrientationHorizontal); - view_stack_add_view(infrared->view_stack, text_input_get_view(infrared->text_input)); - - view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewStack); + view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewTextInput); } bool infrared_scene_edit_rename_on_event(void* context, SceneManagerEvent event) { @@ -117,12 +114,10 @@ void infrared_scene_edit_rename_on_exit(void* context) { InfraredApp* infrared = context; TextInput* text_input = infrared->text_input; - view_stack_remove_view(infrared->view_stack, text_input_get_view(text_input)); - - void* validator_context = text_input_get_validator_callback_context(text_input); - text_input_set_validator(text_input, NULL, NULL); - + ValidatorIsFile* validator_context = text_input_get_validator_callback_context(text_input); if(validator_context) { - validator_is_file_free((ValidatorIsFile*)validator_context); + validator_is_file_free(validator_context); } + + text_input_reset(text_input); } diff --git a/applications/main/infrared/scenes/infrared_scene_remote_list.c b/applications/main/infrared/scenes/infrared_scene_remote_list.c index 744409a7a..9c880ac2f 100644 --- a/applications/main/infrared/scenes/infrared_scene_remote_list.c +++ b/applications/main/infrared/scenes/infrared_scene_remote_list.c @@ -18,9 +18,6 @@ static void infrared_scene_remote_list_select_and_load(InfraredApp* infrared) { infrared->dialogs, infrared->file_path, infrared->file_path, &browser_options); if(file_selected) { - view_set_orientation(view_stack_get_view(infrared->view_stack), ViewOrientationVertical); - view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewStack); - // Load the remote in a separate thread infrared_blocking_task_start(infrared, infrared_scene_remote_list_task_callback); diff --git a/applications/main/infrared/scenes/infrared_scene_rpc.c b/applications/main/infrared/scenes/infrared_scene_rpc.c index 03a2bff01..4c263a117 100644 --- a/applications/main/infrared/scenes/infrared_scene_rpc.c +++ b/applications/main/infrared/scenes/infrared_scene_rpc.c @@ -30,11 +30,8 @@ void infrared_scene_rpc_on_enter(void* context) { popup_set_context(popup, context); popup_set_callback(popup, infrared_popup_closed_callback); - view_stack_add_view(infrared->view_stack, popup_get_view(infrared->popup)); - view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewStack); - + view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewPopup); scene_manager_set_scene_state(infrared->scene_manager, InfraredSceneRpc, InfraredRpcStateIdle); - notification_message(infrared->notifications, &sequence_display_backlight_on); } @@ -69,6 +66,7 @@ bool infrared_scene_rpc_on_event(void* context, SceneManagerEvent event) { popup_set_text( infrared->popup, infrared->text_store[0], 89, 44, AlignCenter, AlignTop); + view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewPopup); rpc_system_app_confirm(infrared->rpc_ctx, task_success); @@ -135,6 +133,5 @@ void infrared_scene_rpc_on_exit(void* context) { infrared_tx_stop(infrared); } - view_stack_remove_view(infrared->view_stack, popup_get_view(infrared->popup)); popup_reset(infrared->popup); } diff --git a/applications/services/gui/view_dispatcher.h b/applications/services/gui/view_dispatcher.h index 80cd9cbb0..f8567ea1a 100644 --- a/applications/services/gui/view_dispatcher.h +++ b/applications/services/gui/view_dispatcher.h @@ -1,6 +1,8 @@ /** * @file view_dispatcher.h - * GUI: ViewDispatcher API + * @brief GUI: ViewDispatcher API + * + * @warning Views added to a ViewDispatcher MUST NOT be in a ViewStack at the same time. */ #pragma once diff --git a/applications/services/gui/view_holder.h b/applications/services/gui/view_holder.h index e4646af9c..bc313a926 100644 --- a/applications/services/gui/view_holder.h +++ b/applications/services/gui/view_holder.h @@ -1,3 +1,9 @@ +/** + * @file view_holder.h + * @brief GUI: ViewHolder API + * + * @warning View added to a ViewHolder MUST NOT be in a ViewStack at the same time. + */ #pragma once #include diff --git a/applications/services/gui/view_stack.h b/applications/services/gui/view_stack.h index ed17f682f..7387038b0 100644 --- a/applications/services/gui/view_stack.h +++ b/applications/services/gui/view_stack.h @@ -1,11 +1,13 @@ /** * @file view_stack.h - * GUI: ViewStack API + * @brief GUI: ViewStack API * * ViewStack accumulates several Views in one stack. * Draw callbacks are called sequenctially starting from * first added. Input callbacks are called in reverse order. * Consumed input is not passed on underlying layers. + * + * @warning Views added to a ViewStack MUST NOT be in a ViewDispatcher or a ViewHolder at the same time. */ #pragma once diff --git a/applications/services/rpc/rpc.c b/applications/services/rpc/rpc.c index b2700c713..054a0286f 100644 --- a/applications/services/rpc/rpc.c +++ b/applications/services/rpc/rpc.c @@ -190,8 +190,6 @@ bool rpc_pb_stream_read(pb_istream_t* istream, pb_byte_t* buf, size_t count) { furi_assert(session); furi_assert(istream->bytes_left); - /* TODO FL-3768 this function may be called after - marking the worker for termination */ if(session->terminate) { return false; }