From 3617a206ffd18623faa470096b2cf146fcdf92b5 Mon Sep 17 00:00:00 2001 From: GW_MC <72297530+GWMCwing@users.noreply.github.com> Date: Tue, 3 Feb 2026 20:11:16 +0800 Subject: [PATCH] feat(travel): Refactor route handling to use direction instead of destination --- main/external/mtr/arrival.h | 2 - main/external/mtr/line_info.cpp | 9 ++ main/external/mtr/line_info.h | 2 + .../apps/travel/settings/settings_handler.cpp | 19 +++-- main/ui/apps/travel/types.h | 17 ++-- main/ui/apps/travel/ui/main.cpp | 8 +- main/ui/apps/travel/ui/main_handler.cpp | 82 +++++++++++++++---- main/ui/apps/travel/ui/main_handler.h | 2 + main/ui/apps/travel/web/web_handlers.cpp | 53 +++++++----- 9 files changed, 137 insertions(+), 57 deletions(-) diff --git a/main/external/mtr/arrival.h b/main/external/mtr/arrival.h index a17abe1..83ec7c2 100644 --- a/main/external/mtr/arrival.h +++ b/main/external/mtr/arrival.h @@ -1,7 +1,5 @@ #pragma once -#include "external/mtr/arrival.h" #include "cJSON.h" -#include "external/mtr/mtr.h" #include #include diff --git a/main/external/mtr/line_info.cpp b/main/external/mtr/line_info.cpp index fe00a43..845efdd 100644 --- a/main/external/mtr/line_info.cpp +++ b/main/external/mtr/line_info.cpp @@ -51,3 +51,12 @@ LineInfo::LineInfo(cJSON* line_json) { ESP_LOGW(LINE_INFO_TAG, "Missing or invalid 'stations' array"); } } + +const char* LineInfo::get_station_name(const std::string& station_code) const { + for (const auto& station : _stations) { + if (std::string(station.code()) == station_code) { + return station.name(); + } + } + return ""; +} diff --git a/main/external/mtr/line_info.h b/main/external/mtr/line_info.h index d27edc6..d009b4b 100644 --- a/main/external/mtr/line_info.h +++ b/main/external/mtr/line_info.h @@ -35,6 +35,8 @@ public: return &_stations; } + const char* get_station_name(const std::string& station_code) const; + friend class MTRNextTrainHandler; private: diff --git a/main/ui/apps/travel/settings/settings_handler.cpp b/main/ui/apps/travel/settings/settings_handler.cpp index e4d2632..b57d993 100644 --- a/main/ui/apps/travel/settings/settings_handler.cpp +++ b/main/ui/apps/travel/settings/settings_handler.cpp @@ -28,7 +28,8 @@ void SettingHandler::load_settings() { } } - // Load routes + // Load routes - clear existing settings as per new format + routes_.clear(); std::string routes_json = storage_->get(NVS_KEY_ROUTES); if (!routes_json.empty()) { routes_from_json(routes_json); @@ -65,7 +66,7 @@ void SettingHandler::add_route(const RoutePair& route) { } routes_.push_back(route); - ESP_LOGI(TAG, "Added route: %s -> %s", route.station_name.c_str(), route.dest_name.c_str()); + ESP_LOGI(TAG, "Added route: %s -> %s", route.station_name.c_str(), route.direction_name.c_str()); } void SettingHandler::remove_route(size_t index) { @@ -100,8 +101,8 @@ std::string SettingHandler::routes_to_json() const { cJSON_AddStringToObject(route_obj, "line_color", route.line_color.c_str()); cJSON_AddStringToObject(route_obj, "station_code", route.station_code.c_str()); cJSON_AddStringToObject(route_obj, "station_name", route.station_name.c_str()); - cJSON_AddStringToObject(route_obj, "dest_code", route.dest_code.c_str()); - cJSON_AddStringToObject(route_obj, "dest_name", route.dest_name.c_str()); + cJSON_AddStringToObject(route_obj, "direction", route.direction.c_str()); + cJSON_AddStringToObject(route_obj, "direction_name", route.direction_name.c_str()); cJSON_AddItemToArray(root, route_obj); } @@ -152,13 +153,13 @@ void SettingHandler::routes_from_json(const std::string& json) { item = cJSON_GetObjectItem(route_obj, "station_name"); if (item && cJSON_IsString(item)) route.station_name = item->valuestring; - item = cJSON_GetObjectItem(route_obj, "dest_code"); - if (item && cJSON_IsString(item)) route.dest_code = item->valuestring; + item = cJSON_GetObjectItem(route_obj, "direction"); + if (item && cJSON_IsString(item)) route.direction = item->valuestring; - item = cJSON_GetObjectItem(route_obj, "dest_name"); - if (item && cJSON_IsString(item)) route.dest_name = item->valuestring; + item = cJSON_GetObjectItem(route_obj, "direction_name"); + if (item && cJSON_IsString(item)) route.direction_name = item->valuestring; - if (!route.line_code.empty() && !route.station_code.empty() && !route.dest_code.empty()) { + if (!route.line_code.empty() && !route.station_code.empty() && !route.direction.empty()) { routes_.push_back(route); } } diff --git a/main/ui/apps/travel/types.h b/main/ui/apps/travel/types.h index 122a07d..a79547e 100644 --- a/main/ui/apps/travel/types.h +++ b/main/ui/apps/travel/types.h @@ -6,7 +6,7 @@ namespace travel { /** - * @brief Structure representing a monitored route (station -> destination pair) + * @brief Structure representing a monitored route (station -> direction pair) */ struct RoutePair { std::string line_code; // Line code (e.g., "ISL", "TWL") @@ -14,13 +14,13 @@ struct RoutePair { std::string line_color; // Hex color code (e.g., "#007DC5") std::string station_code; // Station code (e.g., "CEN") std::string station_name; // Station name in TC (e.g., "中環") - std::string dest_code; // Destination station code (e.g., "CHW") - std::string dest_name; // Destination station name in TC (e.g., "柴灣") + std::string direction; // Direction terminal code (e.g., "CHW" for "柴灣方向") + std::string direction_name; // Direction name in TC (e.g., "柴灣方向") bool operator==(const RoutePair& other) const { return line_code == other.line_code && station_code == other.station_code && - dest_code == other.dest_code; + direction == other.direction; } }; @@ -28,9 +28,10 @@ struct RoutePair { * @brief Structure representing a single arrival display entry */ struct ArrivalDisplay { - std::string arrival_time; // Formatted arrival time (e.g., "2分鐘", "14:32") - std::string destination; // Destination station name - std::string platform; // Platform number if available + std::string arrival_time; // Formatted arrival time (e.g., "2分鐘") + std::string arrival_time_full; // Full time (e.g., "14:32") if available + std::string direction; // Direction terminal name (e.g., "柴灣方向") + std::string platform; // Platform number if available }; /** @@ -38,7 +39,7 @@ struct ArrivalDisplay { */ struct RouteArrivalData { RoutePair route; - std::vector arrivals; // List of upcoming trains to destination + std::vector arrivals; // List of upcoming trains in direction bool is_valid = false; std::string error_message; }; diff --git a/main/ui/apps/travel/ui/main.cpp b/main/ui/apps/travel/ui/main.cpp index 4f1a7d5..2bfb435 100644 --- a/main/ui/apps/travel/ui/main.cpp +++ b/main/ui/apps/travel/ui/main.cpp @@ -179,7 +179,7 @@ void MainUI::update_route_display_(RouteDisplay& display, const RouteArrivalData lv_obj_clear_flag(display.container, LV_OBJ_FLAG_HIDDEN); // Update header with line color - std::string header_text = data.route.station_name + " → " + data.route.dest_name; + std::string header_text = data.route.station_name + " → " + data.route.direction_name; lv_label_set_text(display.header, header_text.c_str()); if (!data.route.line_color.empty()) { @@ -191,7 +191,11 @@ void MainUI::update_route_display_(RouteDisplay& display, const RouteArrivalData for (int i = 0; i < MAX_ARRIVALS_PER_ROUTE; i++) { if (i < static_cast(data.arrivals.size())) { const auto& arrival = data.arrivals[i]; - std::string arrival_text = " " + arrival.arrival_time + " 往" + arrival.destination; + std::string arrival_text = " " + arrival.arrival_time; + if (!arrival.arrival_time_full.empty()) { + arrival_text += " (" + arrival.arrival_time_full + ")"; + } + arrival_text += " " + arrival.direction; lv_label_set_text(display.arrival_labels[i], arrival_text.c_str()); lv_obj_clear_flag(display.arrival_labels[i], LV_OBJ_FLAG_HIDDEN); } else { diff --git a/main/ui/apps/travel/ui/main_handler.cpp b/main/ui/apps/travel/ui/main_handler.cpp index 3cfde0d..b96535f 100644 --- a/main/ui/apps/travel/ui/main_handler.cpp +++ b/main/ui/apps/travel/ui/main_handler.cpp @@ -1,6 +1,6 @@ #include "ui/apps/travel/ui/main_handler.h" #include "display/lvgl_handler.h" -#include "external/mtr/arrival.h" +#include "external/mtr/line_info.h" #include "esp_log.h" #include #include @@ -153,28 +153,65 @@ void MainUIHandler::fetch_and_update_arrivals_() { ); if (error == MtrArrivalErrorCode::NONE && arrival_info) { - // Filter arrivals going to our destination + // Determine which direction (UP or DOWN) to filter based on terminal station const auto* up_arrivals = arrival_info->up_arrivals(); const auto* down_arrivals = arrival_info->down_arrivals(); - // Check both UP and DOWN directions for trains to our destination - auto filter_arrivals = [&](const std::vector* arrivals) { + // Get all lines to find station positions + std::vector lines = mtr_handler_->get_lines(); + + // Find current line and determine direction + bool filter_up = false; + bool filter_down = false; + + for (const auto& line : lines) { + if (std::string(line.code()) == line_code) { + const auto* stations = line.stations(); + if (stations) { + // Find index of current station and terminal station + size_t current_idx = SIZE_MAX; + size_t terminal_idx = SIZE_MAX; + + for (size_t i = 0; i < stations->size(); i++) { + if (std::string(stations->at(i).code()) == station_code) { + current_idx = i; + } + if (std::string(stations->at(i).code()) == route.direction) { + terminal_idx = i; + } + } + + // Determine direction: if terminal is at higher index, it's DOWN + if (current_idx != SIZE_MAX && terminal_idx != SIZE_MAX) { + if (terminal_idx > current_idx) { + filter_down = true; + } else { + filter_up = true; + } + } + } + break; + } + } + + // Filter arrivals based on direction + auto process_arrivals = [&](const std::vector* arrivals) { if (!arrivals) return; for (const auto& arrival : *arrivals) { - // Check if this train goes to our destination - std::string dest = arrival.destination(); - if (dest.find(route.dest_name) != std::string::npos || - dest.find(route.dest_code) != std::string::npos) { - ArrivalDisplay display; - display.arrival_time = format_arrival_time_(arrival.arrival_time()); - display.destination = dest; - data.arrivals.push_back(display); - } + ArrivalDisplay display; + display.arrival_time = format_arrival_time_(arrival.arrival_time()); + display.arrival_time_full = format_arrival_time_full_(arrival.arrival_time()); + display.direction = route.direction_name; + data.arrivals.push_back(display); } }; - filter_arrivals(up_arrivals); - filter_arrivals(down_arrivals); + if (filter_up) { + process_arrivals(up_arrivals); + } + if (filter_down) { + process_arrivals(down_arrivals); + } data.is_valid = true; @@ -224,6 +261,21 @@ std::string MainUIHandler::format_arrival_time_(const std::string& api_time) { return api_time; } +std::string MainUIHandler::format_arrival_time_full_(const std::string& api_time) { + // Returns absolute time for display (e.g., "14:32") + // Returns empty string for relative times + if (api_time.length() <= 2) { + return ""; + } + + size_t t_pos = api_time.find('T'); + if (t_pos != std::string::npos && api_time.length() > t_pos + 5) { + return api_time.substr(t_pos + 1, 5); + } + + return ""; +} + std::string MainUIHandler::get_current_time_string_() { auto now = std::time(nullptr); auto tm = *std::localtime(&now); diff --git a/main/ui/apps/travel/ui/main_handler.h b/main/ui/apps/travel/ui/main_handler.h index 1818f66..a5d20d5 100644 --- a/main/ui/apps/travel/ui/main_handler.h +++ b/main/ui/apps/travel/ui/main_handler.h @@ -3,6 +3,7 @@ #include "ui/apps/travel/ui/main.h" #include "ui/apps/travel/settings/settings_handler.h" #include "ui/interaction_handler.h" +#include "external/mtr/arrival.h" #include "external/mtr/mtr.h" #include "network/network.h" #include "freertos/FreeRTOS.h" @@ -47,6 +48,7 @@ private: void on_settings_button_clicked_(); void fetch_and_update_arrivals_(); std::string format_arrival_time_(const std::string& api_time); + std::string format_arrival_time_full_(const std::string& api_time); std::string get_current_time_string_(); std::unique_ptr main_ui_; diff --git a/main/ui/apps/travel/web/web_handlers.cpp b/main/ui/apps/travel/web/web_handlers.cpp index a856e45..ff9c88d 100644 --- a/main/ui/apps/travel/web/web_handlers.cpp +++ b/main/ui/apps/travel/web/web_handlers.cpp @@ -256,8 +256,8 @@ esp_err_t WebHandler::settings_page_handler_(httpd_req_t* req) {
- -
@@ -327,11 +327,11 @@ esp_err_t WebHandler::settings_page_handler_(httpd_req_t* req) { function updateStations() { const lineCode = document.getElementById('line_select').value; const stationSelect = document.getElementById('station_select'); - const destSelect = document.getElementById('dest_select'); + const directionSelect = document.getElementById('direction_select'); if (!lineCode) { stationSelect.innerHTML = ''; - destSelect.innerHTML = ''; + directionSelect.innerHTML = ''; return; } @@ -343,7 +343,18 @@ esp_err_t WebHandler::settings_page_handler_(httpd_req_t* req) { ).join(''); stationSelect.innerHTML = '' + stationsHtml; - destSelect.innerHTML = '' + stationsHtml; + + const stations = line.stations; + if (stations.length >= 2) { + const firstStation = stations[0]; + const lastStation = stations[stations.length - 1]; + const directionsHtml = + `` + + ``; + directionSelect.innerHTML = '' + directionsHtml; + } else { + directionSelect.innerHTML = ''; + } } function renderRoutes() { @@ -357,7 +368,7 @@ esp_err_t WebHandler::settings_page_handler_(httpd_req_t* req) {
${route.line_name} - ${route.station_name} → ${route.dest_name} + ${route.station_name} → ${route.direction_name}
@@ -368,21 +379,21 @@ esp_err_t WebHandler::settings_page_handler_(httpd_req_t* req) { async function addRoute() { const lineCode = document.getElementById('line_select').value; const stationCode = document.getElementById('station_select').value; - const destCode = document.getElementById('dest_select').value; + const directionCode = document.getElementById('direction_select').value; - if (!lineCode || !stationCode || !destCode) { - showStatus('請選擇路線、出發站和目的地', 'error'); + if (!lineCode || !stationCode || !directionCode) { + showStatus('請選擇路線、出發站和方向', 'error'); return; } - if (stationCode === destCode) { - showStatus('出發站和目的地不能相同', 'error'); + if (stationCode === directionCode) { + showStatus('出發站和方向不能相同', 'error'); return; } const line = linesData.find(l => l.code === lineCode); const station = line.stations.find(s => s.code === stationCode); - const dest = line.stations.find(s => s.code === destCode); + const direction = line.stations.find(s => s.code === directionCode); const route = { line_code: lineCode, @@ -390,8 +401,8 @@ esp_err_t WebHandler::settings_page_handler_(httpd_req_t* req) { line_color: line.color, station_code: stationCode, station_name: station.name, - dest_code: destCode, - dest_name: dest.name + direction: directionCode, + direction_name: direction.name + '方向' }; try { @@ -536,8 +547,8 @@ esp_err_t WebHandler::get_routes_handler_(httpd_req_t* req) { cJSON_AddStringToObject(route_obj, "line_color", route.line_color.c_str()); cJSON_AddStringToObject(route_obj, "station_code", route.station_code.c_str()); cJSON_AddStringToObject(route_obj, "station_name", route.station_name.c_str()); - cJSON_AddStringToObject(route_obj, "dest_code", route.dest_code.c_str()); - cJSON_AddStringToObject(route_obj, "dest_name", route.dest_name.c_str()); + cJSON_AddStringToObject(route_obj, "direction", route.direction.c_str()); + cJSON_AddStringToObject(route_obj, "direction_name", route.direction_name.c_str()); cJSON_AddItemToArray(routes_arr, route_obj); } cJSON_AddItemToObject(root, "routes", routes_arr); @@ -603,15 +614,15 @@ esp_err_t WebHandler::add_route_handler_(httpd_req_t* req) { item = cJSON_GetObjectItem(root, "station_name"); if (item && cJSON_IsString(item)) route.station_name = item->valuestring; - item = cJSON_GetObjectItem(root, "dest_code"); - if (item && cJSON_IsString(item)) route.dest_code = item->valuestring; + item = cJSON_GetObjectItem(root, "direction"); + if (item && cJSON_IsString(item)) route.direction = item->valuestring; - item = cJSON_GetObjectItem(root, "dest_name"); - if (item && cJSON_IsString(item)) route.dest_name = item->valuestring; + item = cJSON_GetObjectItem(root, "direction_name"); + if (item && cJSON_IsString(item)) route.direction_name = item->valuestring; cJSON_Delete(root); - if (route.line_code.empty() || route.station_code.empty() || route.dest_code.empty()) { + if (route.line_code.empty() || route.station_code.empty() || route.direction.empty()) { httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Missing required fields"); return ESP_FAIL; }