#include "ui/ui_handler.h" #include "ui/apps/registry.h" #include "esp_log.h" #include #define TAG "UIHandler" struct AppClickUserData { UIHandler* ui_handler; std::string app_name; }; UIHandler::~UIHandler() { deinit(); // Clean up all allocated AppClickUserData for (void* data : app_click_user_data_) { delete static_cast(data); } app_click_user_data_.clear(); } esp_err_t UIHandler::init(void) { lv_obj_t* screen = lv_scr_act(); esp_err_t ret = ESP_OK; // Create main screen layout ret = create_main_screen_(screen); if (ret != ESP_OK) { ESP_LOGE(TAG, "Failed to create main screen layout"); return ret; } // Initialize InteractionHandler with screen as parent (not app_container) // so keyboard survives app switches ret = interaction_handler_.init(screen); if (ret != ESP_OK) { ESP_LOGE(TAG, "Failed to initialize InteractionHandler"); return ret; } // Show the main screen lv_scr_load(screen); return ret; } esp_err_t UIHandler::deinit(void) { // Deinitialize current app if any if (active_descriptor_) { UIApp* app = active_descriptor_->get_app_instance(); if (app) { esp_err_t ret = app->deinit(); if (ret != ESP_OK) { ESP_LOGE("UIHandler", "Failed to deinitialize current app"); return ret; } } active_descriptor_ = nullptr; } // Destroy main screen layout esp_err_t ret = destroy_main_screen_(); if (ret != ESP_OK) { ESP_LOGE("UIHandler", "Failed to destroy main screen layout"); return ret; } // Deinitialize interaction handler ret = interaction_handler_.deinit(); if (ret != ESP_OK) { ESP_LOGE("UIHandler", "Failed to deinitialize InteractionHandler"); return ret; } return ESP_OK; } esp_err_t UIHandler::switch_app(AppDescriptor* app_descriptor) { if (!app_descriptor) { ESP_LOGE(TAG, "Invalid app descriptor"); return ESP_ERR_INVALID_ARG; } esp_err_t ret = ESP_OK; // Deinitialize current app if any if (active_descriptor_) { UIApp* current_app = active_descriptor_->get_app_instance(); if (current_app) { ret = current_app->deinit(); if (ret != ESP_OK) { ESP_LOGE(TAG, "Failed to deinitialize current app"); } } } // Clear the app container lv_obj_t* app_container = root_layout_.get_app_container(); if (app_container) { lv_obj_clean(app_container); } else { ESP_LOGE(TAG, "App container not available"); return ESP_ERR_INVALID_STATE; } // Set the new app as active active_descriptor_ = app_descriptor; // Initialize the new app UIApp* new_app = active_descriptor_->get_app_instance(); if (!new_app) { ESP_LOGE(TAG, "App instance not available"); active_descriptor_ = nullptr; return ESP_ERR_INVALID_STATE; } ret = new_app->init(app_container, &interaction_handler_); if (ret != ESP_OK) { ESP_LOGE(TAG, "Failed to initialize app: %s", new_app->get_name().c_str()); active_descriptor_ = nullptr; return ret; } // Update header with app name ret = update_header_title(new_app->get_name()); if (ret != ESP_OK) { ESP_LOGW(TAG, "Failed to update header title"); } // Show back button when in an app root_layout_.show_back_button(); ESP_LOGI(TAG, "Switched to app: %s", new_app->get_name().c_str()); return ESP_OK; } esp_err_t UIHandler::show_shutdown_screen(const std::string& message) { // Deinitialize current app if any if (active_descriptor_) { UIApp* app = active_descriptor_->get_app_instance(); if (app) { app->deinit(); } active_descriptor_ = nullptr; } // Clear the app container lv_obj_t* app_container = root_layout_.get_app_container(); if (app_container) { lv_obj_clean(app_container); // Create a simple shutdown message screen lv_obj_t* label = lv_label_create(app_container); if (message.empty()) { lv_label_set_text(label, "Shutting down..."); } else { lv_label_set_text(label, message.c_str()); } lv_obj_set_style_text_font(label, &lv_font_montserrat_14, 0); lv_obj_center(label); } // Update header update_header_title("System"); // Hide navigation buttons root_layout_.hide_back_button(); root_layout_.hide_home_button(); ESP_LOGI(TAG, "Showing shutdown screen: %s", message.c_str()); return ESP_OK; } esp_err_t UIHandler::return_to_main_screen(void) { esp_err_t ret = ESP_OK; // Deinitialize current app if any if (active_descriptor_) { UIApp* app = active_descriptor_->get_app_instance(); if (app) { ret = app->deinit(); if (ret != ESP_OK) { ESP_LOGE(TAG, "Failed to deinitialize app"); return ret; } } active_descriptor_ = nullptr; } // Clear the app container lv_obj_t* app_container = root_layout_.get_app_container(); if (app_container) { lv_obj_clean(app_container); // TODO: Display app launcher/home screen with app icons // For now, just show a placeholder message lv_obj_t* label = lv_label_create(app_container); lv_label_set_text(label, "Home Screen\n\nApp icons will go here"); lv_obj_set_style_text_align(label, LV_TEXT_ALIGN_CENTER, 0); lv_obj_center(label); } else { ESP_LOGE(TAG, "App container not available"); return ESP_ERR_INVALID_STATE; } // Update header ret = update_header_title("Home"); if (ret != ESP_OK) { ESP_LOGW(TAG, "Failed to update header title"); } // Hide back button on home screen root_layout_.hide_back_button(); ESP_LOGI(TAG, "Returned to main screen"); return ESP_OK; } esp_err_t UIHandler::update_header_title(const std::string& title) { return root_layout_.update_header(title); } // // Private methods // void UIHandler::on_back_button_pressed_(void) { if (active_descriptor_) { UIApp* app = active_descriptor_->get_app_instance(); if (app) { bool handled = app->on_back_button_pressed(); if (!handled) { // App didn't handle it, return to main screen return_to_main_screen(); } } } else { ESP_LOGW(TAG, "Back button pressed but no active app"); } } esp_err_t UIHandler::create_main_screen_(lv_obj_t* parent) { esp_err_t ret = ESP_OK; // Initialize root layout ret = root_layout_.init(parent, this); if (ret != ESP_OK) { ESP_LOGE(TAG, "Failed to initialize RootLayout"); return ret; } // render all apps for (const auto& [name, descriptor] : AppRegistry::instance()) { lv_obj_t* app_icon_container = lv_obj_create(root_layout_.get_app_container()); lv_obj_set_size(app_icon_container, 100, 100); lv_obj_set_style_pad_all(app_icon_container, 10, 0); lv_obj_set_flex_flow(app_icon_container, LV_FLEX_FLOW_COLUMN); lv_obj_set_flex_align(app_icon_container, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER); // Draw the app icon descriptor->draw_icon(app_icon_container); // App name label lv_obj_t* label = lv_label_create(app_icon_container); lv_label_set_text(label, name.c_str()); lv_obj_set_style_text_align(label, LV_TEXT_ALIGN_CENTER, 0); // Center the icon container lv_obj_center(app_icon_container); // Create and track user data for the callback auto* click_data = new AppClickUserData { this, name }; app_click_user_data_.push_back(click_data); // Register click event to switch to the app lv_obj_add_event_cb(app_icon_container, [](lv_event_t* e) { AppClickUserData* user_data = static_cast(lv_event_get_user_data(e)); UIHandler* ui_handler = user_data->ui_handler; std::string app_name = user_data->app_name; AppDescriptor* descriptor = AppRegistry::instance()[app_name]; if (descriptor) { ui_handler->switch_app(descriptor); } else { ESP_LOGE(TAG, "App descriptor not found for app: %s", app_name.c_str()); } }, LV_EVENT_CLICKED, click_data ); } // Register back button callback lv_event_dsc_t* back_event_dsc = nullptr; ret = root_layout_.register_back_button_callback( [](lv_event_t* e) { UIHandler* ui_handler = static_cast(lv_event_get_user_data(e)); ui_handler->on_back_button_pressed_(); }, this, &back_event_dsc ); if (ret != ESP_OK) { ESP_LOGE(TAG, "Failed to register back button callback"); return ret; } // Register home button callback lv_event_dsc_t* home_event_dsc = nullptr; ret = root_layout_.register_home_button_callback( [](lv_event_t* e) { UIHandler* ui_handler = static_cast(lv_event_get_user_data(e)); ui_handler->return_to_main_screen(); }, this, &home_event_dsc ); if (ret != ESP_OK) { ESP_LOGE(TAG, "Failed to register home button callback"); return ret; } ESP_LOGI(TAG, "Main screen layout created successfully"); return ESP_OK; } esp_err_t UIHandler::destroy_main_screen_(void) { esp_err_t ret = root_layout_.deinit(); if (ret != ESP_OK) { ESP_LOGE(TAG, "Failed to deinitialize RootLayout"); return ret; } ESP_LOGI(TAG, "Main screen layout destroyed successfully"); return ESP_OK; }