#include "apps/mtr_app.h" #include "external/mtr/arrival.h" #include "esp_log.h" #include "freertos/FreeRTOS.h" #include "freertos/task.h" #define TAG "MtrApp" // Event type for network ready #define EVENT_NETWORK_READY 1 MtrApp::MtrApp() { _mtr_handler = std::make_unique(); } esp_err_t MtrApp::init(lv_obj_t* container) { if (!container) { ESP_LOGE(TAG, "Container is null"); return ESP_ERR_INVALID_ARG; } _container = container; ESP_LOGI(TAG, "Initializing MTR app..."); // Create page stack _page_stack = std::make_unique(container); // Load all lines _all_lines = _mtr_handler->get_lines(); ESP_LOGI(TAG, "Loaded %zu MTR lines", _all_lines.size()); // Build initial line selection page _page_stack->push([this](lv_obj_t* page) { this->build_line_selection_page(page); }); ESP_LOGI(TAG, "MTR app initialized successfully"); return ESP_OK; } esp_err_t MtrApp::deinit(void) { ESP_LOGI(TAG, "Deinitializing MTR app"); // Clear page stack if (_page_stack) { _page_stack->clear(); _page_stack.reset(); } // Clear state _selected_line_code.clear(); _selected_station_code.clear(); _selected_line_info = nullptr; _all_lines.clear(); return ESP_OK; } std::string MtrApp::get_name(void) const { return "MTR"; } bool MtrApp::on_back_button_pressed(void) { if (_page_stack && _page_stack->depth() > 1) { _page_stack->pop(); return true; // Handled } return false; // Not handled, go back to main menu } void MtrApp::handle_event(uint32_t event_type, void* event_data) { if (event_type == EVENT_NETWORK_READY) { ESP_LOGI(TAG, "Network ready event received"); } } void MtrApp::build_line_selection_page(lv_obj_t* page_container) { ESP_LOGI(TAG, "Building line selection page"); // Title lv_obj_t* title = lv_label_create(page_container); lv_label_set_text(title, "選擇路綫 Select Line"); lv_obj_set_style_text_color(title, lv_color_black(), 0); lv_obj_align(title, LV_ALIGN_TOP_MID, 0, 10); // Scrollable container for line buttons lv_obj_t* scroll_container = lv_obj_create(page_container); lv_obj_set_size(scroll_container, lv_pct(95), lv_pct(85)); lv_obj_align(scroll_container, LV_ALIGN_TOP_MID, 0, 40); lv_obj_set_flex_flow(scroll_container, LV_FLEX_FLOW_COLUMN); lv_obj_set_flex_align(scroll_container, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER); lv_obj_set_style_pad_all(scroll_container, 5, 0); lv_obj_set_style_pad_row(scroll_container, 8, 0); // Create button for each line for (size_t i = 0; i < _all_lines.size(); i++) { LineInfo* line = &_all_lines[i]; lv_obj_t* btn = lv_btn_create(scroll_container); lv_obj_set_size(btn, lv_pct(95), 60); // Set button color based on line color uint32_t color = parse_color_hex(line->color()); lv_obj_set_style_bg_color(btn, lv_color_hex(color), 0); // Button label lv_obj_t* label = lv_label_create(btn); lv_label_set_text_fmt(label, "%s", line->code()); lv_obj_set_style_text_color(label, lv_color_white(), 0); lv_obj_center(label); // Store line pointer in user data lv_obj_add_event_cb(btn, line_button_event_cb, LV_EVENT_CLICKED, this); lv_obj_set_user_data(btn, (void*)line); } ESP_LOGI(TAG, "Created %zu line buttons", _all_lines.size()); } void MtrApp::build_station_selection_page(lv_obj_t* page_container) { ESP_LOGI(TAG, "Building station selection page for line: %s", _selected_line_code.c_str()); if (!_selected_line_info) { ESP_LOGE(TAG, "No line info selected"); return; } // Title with line code lv_obj_t* title = lv_label_create(page_container); lv_label_set_text_fmt(title, "%s 路綫車站", _selected_line_code.c_str()); lv_obj_set_style_text_color(title, lv_color_black(), 0); lv_obj_align(title, LV_ALIGN_TOP_MID, 0, 10); // Scrollable container for station buttons lv_obj_t* scroll_container = lv_obj_create(page_container); lv_obj_set_size(scroll_container, lv_pct(95), lv_pct(85)); lv_obj_align(scroll_container, LV_ALIGN_TOP_MID, 0, 40); lv_obj_set_flex_flow(scroll_container, LV_FLEX_FLOW_COLUMN); lv_obj_set_flex_align(scroll_container, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER); lv_obj_set_style_pad_all(scroll_container, 5, 0); lv_obj_set_style_pad_row(scroll_container, 6, 0); // Create button for each station const std::vector* stations = _selected_line_info->stations(); for (size_t i = 0; i < stations->size(); i++) { const StationInfo* station = &(*stations)[i]; lv_obj_t* btn = lv_btn_create(scroll_container); lv_obj_set_size(btn, lv_pct(95), 50); lv_obj_set_style_bg_color(btn, lv_color_hex(0x4CAF50), 0); // Button label with station name and code lv_obj_t* label = lv_label_create(btn); lv_label_set_text_fmt(label, "%s (%s)", station->name(), station->code()); lv_obj_set_style_text_color(label, lv_color_white(), 0); lv_obj_center(label); // Store station pointer in user data lv_obj_add_event_cb(btn, station_button_event_cb, LV_EVENT_CLICKED, this); lv_obj_set_user_data(btn, (void*)station); } ESP_LOGI(TAG, "Created %zu station buttons", stations->size()); } void MtrApp::build_arrival_page(lv_obj_t* page_container) { ESP_LOGI(TAG, "Building arrival page"); // Title lv_obj_t* title = lv_label_create(page_container); lv_label_set_text_fmt(title, "%s - %s", _selected_line_code.c_str(), _selected_station_code.c_str()); lv_obj_set_style_text_color(title, lv_color_black(), 0); lv_obj_align(title, LV_ALIGN_TOP_MID, 0, 10); // Loading message lv_obj_t* loading_label = lv_label_create(page_container); lv_label_set_text(loading_label, "載入中... Loading..."); lv_obj_set_style_text_color(loading_label, lv_color_black(), 0); lv_obj_center(loading_label); // Refresh button lv_obj_t* refresh_btn = lv_btn_create(page_container); lv_obj_set_size(refresh_btn, 120, 50); lv_obj_align(refresh_btn, LV_ALIGN_BOTTOM_MID, 0, -10); lv_obj_add_event_cb(refresh_btn, refresh_button_event_cb, LV_EVENT_CLICKED, this); lv_obj_t* refresh_label = lv_label_create(refresh_btn); lv_label_set_text(refresh_label, LV_SYMBOL_REFRESH " 重新整理"); lv_obj_set_style_text_color(refresh_label, lv_color_white(), 0); lv_obj_center(refresh_label); // Load arrival data asynchronously load_arrival_data(page_container); } void MtrApp::load_arrival_data(lv_obj_t* page_container) { if (!_network_handler) { ESP_LOGW(TAG, "Network handler not set, cannot fetch arrival data"); // Update UI to show error lv_obj_t* error_label = lv_label_create(page_container); lv_label_set_text(error_label, "網絡未就緒\nNetwork not ready"); lv_obj_set_style_text_color(error_label, lv_color_black(), 0); lv_obj_align(error_label, LV_ALIGN_CENTER, 0, -30); return; } ESP_LOGI(TAG, "Fetching arrival data for %s/%s", _selected_line_code.c_str(), _selected_station_code.c_str()); StationArrivalInfo* arrival_info = nullptr; MtrArrivalErrorCode error_code = _mtr_handler->get_next_arrival_info( _network_handler, _selected_line_code, _selected_station_code, arrival_info, Language::TC ); // Clear loading message lv_obj_clean(page_container); // Recreate title lv_obj_t* title = lv_label_create(page_container); lv_label_set_text_fmt(title, "%s - %s", _selected_line_code.c_str(), _selected_station_code.c_str()); lv_obj_set_style_text_color(title, lv_color_black(), 0); lv_obj_align(title, LV_ALIGN_TOP_MID, 0, 10); if (error_code != MtrArrivalErrorCode::NONE || !arrival_info) { ESP_LOGE(TAG, "Failed to fetch arrival info, error code: %d", (int)error_code); lv_obj_t* error_label = lv_label_create(page_container); lv_label_set_text(error_label, "無法取得班次資料\nFailed to fetch arrival data"); lv_obj_set_style_text_color(error_label, lv_color_black(), 0); lv_obj_center(error_label); return; } // Create scrollable container for arrivals lv_obj_t* scroll_container = lv_obj_create(page_container); lv_obj_set_size(scroll_container, lv_pct(95), lv_pct(75)); lv_obj_align(scroll_container, LV_ALIGN_TOP_MID, 0, 45); lv_obj_set_style_pad_all(scroll_container, 10, 0); int y_offset = 0; // Display UP direction trains lv_obj_t* up_header = lv_label_create(scroll_container); lv_label_set_text(up_header, "上行 UP:"); lv_obj_set_style_text_color(up_header, lv_color_black(), 0); lv_obj_set_pos(up_header, 0, y_offset); y_offset += 30; const std::vector* up_arrivals = arrival_info->up_arrivals(); if (up_arrivals->empty()) { lv_obj_t* no_train = lv_label_create(scroll_container); lv_label_set_text(no_train, " 暫無班次 No trains"); lv_obj_set_style_text_color(no_train, lv_color_hex(0x666666), 0); lv_obj_set_pos(no_train, 10, y_offset); y_offset += 25; } else { for (const auto& arrival : *up_arrivals) { lv_obj_t* arrival_label = lv_label_create(scroll_container); lv_label_set_text_fmt(arrival_label, " %s → %s", arrival.arrival_time(), arrival.destination()); lv_obj_set_style_text_color(arrival_label, lv_color_black(), 0); lv_obj_set_pos(arrival_label, 10, y_offset); y_offset += 25; } } y_offset += 10; // Display DOWN direction trains lv_obj_t* down_header = lv_label_create(scroll_container); lv_label_set_text(down_header, "下行 DOWN:"); lv_obj_set_style_text_color(down_header, lv_color_black(), 0); lv_obj_set_pos(down_header, 0, y_offset); y_offset += 30; const std::vector* down_arrivals = arrival_info->down_arrivals(); if (down_arrivals->empty()) { lv_obj_t* no_train = lv_label_create(scroll_container); lv_label_set_text(no_train, " 暫無班次 No trains"); lv_obj_set_style_text_color(no_train, lv_color_hex(0x666666), 0); lv_obj_set_pos(no_train, 10, y_offset); y_offset += 25; } else { for (const auto& arrival : *down_arrivals) { lv_obj_t* arrival_label = lv_label_create(scroll_container); lv_label_set_text_fmt(arrival_label, " %s → %s", arrival.arrival_time(), arrival.destination()); lv_obj_set_style_text_color(arrival_label, lv_color_black(), 0); lv_obj_set_pos(arrival_label, 10, y_offset); y_offset += 25; } } // Clean up if (arrival_info != nullptr) { delete arrival_info; } // Refresh button lv_obj_t* refresh_btn = lv_btn_create(page_container); lv_obj_set_size(refresh_btn, 120, 50); lv_obj_align(refresh_btn, LV_ALIGN_BOTTOM_MID, 0, -10); lv_obj_add_event_cb(refresh_btn, refresh_button_event_cb, LV_EVENT_CLICKED, this); lv_obj_t* refresh_label = lv_label_create(refresh_btn); lv_label_set_text(refresh_label, LV_SYMBOL_REFRESH " 重新整理"); lv_obj_set_style_text_color(refresh_label, lv_color_white(), 0); lv_obj_center(refresh_label); ESP_LOGI(TAG, "Arrival data displayed successfully"); } uint32_t MtrApp::parse_color_hex(const char* hex_str) { if (!hex_str || hex_str[0] != '#') { return 0x808080; // Default gray } // Skip the '#' character hex_str++; uint32_t color = 0; sscanf(hex_str, "%" SCNx32, &color); return color; } void MtrApp::line_button_event_cb(lv_event_t* e) { lv_event_code_t code = lv_event_get_code(e); if (code == LV_EVENT_CLICKED) { MtrApp* app = (MtrApp*)lv_event_get_user_data(e); lv_obj_t* btn = (lv_obj_t*)lv_event_get_target(e); LineInfo* line = (LineInfo*)lv_obj_get_user_data(btn); if (app && line) { ESP_LOGI(TAG, "Line selected: %s", line->code()); app->_selected_line_code = line->code(); app->_selected_line_info = line; // Push station selection page app->_page_stack->push([app](lv_obj_t* page) { app->build_station_selection_page(page); }); } } } void MtrApp::station_button_event_cb(lv_event_t* e) { lv_event_code_t code = lv_event_get_code(e); if (code == LV_EVENT_CLICKED) { MtrApp* app = (MtrApp*)lv_event_get_user_data(e); lv_obj_t* btn = (lv_obj_t*)lv_event_get_target(e); const StationInfo* station = (const StationInfo*)lv_obj_get_user_data(btn); if (app && station) { ESP_LOGI(TAG, "Station selected: %s (%s)", station->name(), station->code()); app->_selected_station_code = station->code(); // Push arrival page app->_page_stack->push([app](lv_obj_t* page) { app->build_arrival_page(page); }); } } } void MtrApp::refresh_button_event_cb(lv_event_t* e) { lv_event_code_t code = lv_event_get_code(e); if (code == LV_EVENT_CLICKED) { MtrApp* app = (MtrApp*)lv_event_get_user_data(e); if (app && app->_page_stack && app->_page_stack->current_page()) { ESP_LOGI(TAG, "Refresh button clicked"); app->load_arrival_data(app->_page_stack->current_page()); } } } // MtrAppDescriptor implementation MtrApp* MtrAppDescriptor::_app_instance = nullptr; MtrAppDescriptor::MtrAppDescriptor() : AppDescriptor("MTR", []() -> UIApp* { if (!MtrAppDescriptor::_app_instance) { MtrAppDescriptor::_app_instance = new MtrApp(); } return MtrAppDescriptor::_app_instance; }()) { // Register with AppRegistry AppRegistry::instance().register_app(this); ESP_LOGI(TAG, "MtrApp registered with AppRegistry"); } void MtrAppDescriptor::draw_icon(lv_obj_t* parent) { // Create MTR icon with train symbol lv_obj_t* icon_label = lv_label_create(parent); lv_label_set_text(icon_label, LV_SYMBOL_GPS "\nMTR"); lv_obj_set_style_text_color(icon_label, lv_color_white(), 0); lv_obj_set_style_text_align(icon_label, LV_TEXT_ALIGN_CENTER, 0); lv_obj_center(icon_label); }