feat(travel): Refactor route handling to use direction instead of destination
This commit is contained in:
2
main/external/mtr/arrival.h
vendored
2
main/external/mtr/arrival.h
vendored
@@ -1,7 +1,5 @@
|
||||
#pragma once
|
||||
#include "external/mtr/arrival.h"
|
||||
#include "cJSON.h"
|
||||
#include "external/mtr/mtr.h"
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
|
||||
9
main/external/mtr/line_info.cpp
vendored
9
main/external/mtr/line_info.cpp
vendored
@@ -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 "";
|
||||
}
|
||||
|
||||
2
main/external/mtr/line_info.h
vendored
2
main/external/mtr/line_info.h
vendored
@@ -35,6 +35,8 @@ public:
|
||||
return &_stations;
|
||||
}
|
||||
|
||||
const char* get_station_name(const std::string& station_code) const;
|
||||
|
||||
friend class MTRNextTrainHandler;
|
||||
|
||||
private:
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,8 +28,9 @@ 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 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<ArrivalDisplay> arrivals; // List of upcoming trains to destination
|
||||
std::vector<ArrivalDisplay> arrivals; // List of upcoming trains in direction
|
||||
bool is_valid = false;
|
||||
std::string error_message;
|
||||
};
|
||||
|
||||
@@ -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<int>(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 {
|
||||
|
||||
@@ -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 <ctime>
|
||||
#include <iomanip>
|
||||
@@ -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<ArrivalInfo>* arrivals) {
|
||||
// Get all lines to find station positions
|
||||
std::vector<LineInfo> 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<ArrivalInfo>* 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;
|
||||
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);
|
||||
|
||||
@@ -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<MainUI> main_ui_;
|
||||
|
||||
@@ -256,8 +256,8 @@ esp_err_t WebHandler::settings_page_handler_(httpd_req_t* req) {
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>目的地</label>
|
||||
<select id="dest_select">
|
||||
<label>方向</label>
|
||||
<select id="direction_select">
|
||||
<option value="">請先選擇路線</option>
|
||||
</select>
|
||||
</div>
|
||||
@@ -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 = '<option value="">請先選擇路線</option>';
|
||||
destSelect.innerHTML = '<option value="">請先選擇路線</option>';
|
||||
directionSelect.innerHTML = '<option value="">請先選擇路線</option>';
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -343,7 +343,18 @@ esp_err_t WebHandler::settings_page_handler_(httpd_req_t* req) {
|
||||
).join('');
|
||||
|
||||
stationSelect.innerHTML = '<option value="">請選擇車站</option>' + stationsHtml;
|
||||
destSelect.innerHTML = '<option value="">請選擇目的地</option>' + stationsHtml;
|
||||
|
||||
const stations = line.stations;
|
||||
if (stations.length >= 2) {
|
||||
const firstStation = stations[0];
|
||||
const lastStation = stations[stations.length - 1];
|
||||
const directionsHtml =
|
||||
`<option value="${firstStation.code}">${firstStation.name}方向</option>` +
|
||||
`<option value="${lastStation.code}">${lastStation.name}方向</option>`;
|
||||
directionSelect.innerHTML = '<option value="">請選擇方向</option>' + directionsHtml;
|
||||
} else {
|
||||
directionSelect.innerHTML = '<option value="">路線車站不足</option>';
|
||||
}
|
||||
}
|
||||
|
||||
function renderRoutes() {
|
||||
@@ -357,7 +368,7 @@ esp_err_t WebHandler::settings_page_handler_(httpd_req_t* req) {
|
||||
<div class="route-item">
|
||||
<div class="route-info">
|
||||
<span class="route-line" style="background: ${route.line_color}">${route.line_name}</span>
|
||||
${route.station_name} → ${route.dest_name}
|
||||
${route.station_name} → ${route.direction_name}
|
||||
</div>
|
||||
<button class="danger" style="width: auto; padding: 5px 10px;"
|
||||
onclick="removeRoute(${index})">刪除</button>
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user