346 lines
9.0 KiB
C++
346 lines
9.0 KiB
C++
#include "ui/ui_handler.h"
|
|
#include "ui/apps/registry.h"
|
|
#include "esp_log.h"
|
|
#include <algorithm>
|
|
|
|
#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<AppClickUserData*>(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<AppClickUserData*>(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<UIHandler*>(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<UIHandler*>(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;
|
|
}
|
|
|
|
|