adds initial support for companion app
This commit is contained in:
@@ -46,6 +46,8 @@ void CrossPointWebServerActivity::onEnter() {
|
||||
connectedIP.clear();
|
||||
connectedSSID.clear();
|
||||
lastHandleClientTime = 0;
|
||||
currentScreen = FileTransferScreen::WEB_BROWSER;
|
||||
lastStatsRefresh = 0;
|
||||
updateRequired = true;
|
||||
|
||||
xTaskCreate(&CrossPointWebServerActivity::taskTrampoline, "WebServerActivityTask",
|
||||
@@ -339,6 +341,48 @@ void CrossPointWebServerActivity::loop() {
|
||||
lastHandleClientTime = millis();
|
||||
}
|
||||
|
||||
// Handle screen switching with arrow buttons
|
||||
if (mappedInput.wasPressed(MappedInputManager::Button::PageForward)) {
|
||||
// Cycle to next screen: WEB_BROWSER -> COMPANION_APP -> COMPANION_APP_LIBRARY -> WEB_BROWSER
|
||||
switch (currentScreen) {
|
||||
case FileTransferScreen::WEB_BROWSER:
|
||||
currentScreen = FileTransferScreen::COMPANION_APP;
|
||||
break;
|
||||
case FileTransferScreen::COMPANION_APP:
|
||||
currentScreen = FileTransferScreen::COMPANION_APP_LIBRARY;
|
||||
break;
|
||||
case FileTransferScreen::COMPANION_APP_LIBRARY:
|
||||
currentScreen = FileTransferScreen::WEB_BROWSER;
|
||||
break;
|
||||
}
|
||||
updateRequired = true;
|
||||
Serial.printf("[%lu] [WEBACT] Switched to screen: %d\n", millis(), static_cast<int>(currentScreen));
|
||||
}
|
||||
|
||||
if (mappedInput.wasPressed(MappedInputManager::Button::PageBack)) {
|
||||
// Cycle to previous screen: WEB_BROWSER -> COMPANION_APP_LIBRARY -> COMPANION_APP -> WEB_BROWSER
|
||||
switch (currentScreen) {
|
||||
case FileTransferScreen::WEB_BROWSER:
|
||||
currentScreen = FileTransferScreen::COMPANION_APP_LIBRARY;
|
||||
break;
|
||||
case FileTransferScreen::COMPANION_APP:
|
||||
currentScreen = FileTransferScreen::WEB_BROWSER;
|
||||
break;
|
||||
case FileTransferScreen::COMPANION_APP_LIBRARY:
|
||||
currentScreen = FileTransferScreen::COMPANION_APP;
|
||||
break;
|
||||
}
|
||||
updateRequired = true;
|
||||
Serial.printf("[%lu] [WEBACT] Switched to screen: %d\n", millis(), static_cast<int>(currentScreen));
|
||||
}
|
||||
|
||||
// Refresh stats every 30 seconds
|
||||
if (millis() - lastStatsRefresh >= 30000) {
|
||||
lastStatsRefresh = millis();
|
||||
updateRequired = true;
|
||||
Serial.printf("[%lu] [WEBACT] Stats refresh triggered\n", millis());
|
||||
}
|
||||
|
||||
// Handle exit on Back button (also check outside loop)
|
||||
if (mappedInput.wasPressed(MappedInputManager::Button::Back)) {
|
||||
onGoBack();
|
||||
@@ -396,11 +440,111 @@ void drawQRCode(const GfxRenderer& renderer, const int x, const int y, const std
|
||||
}
|
||||
}
|
||||
|
||||
void CrossPointWebServerActivity::renderServerRunning() const {
|
||||
// Use consistent line spacing
|
||||
constexpr int LINE_SPACING = 28; // Space between lines
|
||||
// Helper to format bytes into human-readable sizes
|
||||
std::string formatBytes(size_t bytes) {
|
||||
if (bytes < 1024) {
|
||||
return std::to_string(bytes) + " B";
|
||||
} else if (bytes < 1024 * 1024) {
|
||||
char buf[32];
|
||||
snprintf(buf, sizeof(buf), "%.1f KB", bytes / 1024.0);
|
||||
return buf;
|
||||
} else {
|
||||
char buf[32];
|
||||
snprintf(buf, sizeof(buf), "%.1f MB", bytes / (1024.0 * 1024.0));
|
||||
return buf;
|
||||
}
|
||||
}
|
||||
|
||||
renderer.drawCenteredText(UI_12_FONT_ID, 15, "File Transfer", true, EpdFontFamily::BOLD);
|
||||
// Helper to format uptime in human-readable format
|
||||
std::string formatUptime(unsigned long seconds) {
|
||||
if (seconds < 60) {
|
||||
return std::to_string(seconds) + "s";
|
||||
} else if (seconds < 3600) {
|
||||
unsigned long mins = seconds / 60;
|
||||
unsigned long secs = seconds % 60;
|
||||
return std::to_string(mins) + "m " + std::to_string(secs) + "s";
|
||||
} else {
|
||||
unsigned long hours = seconds / 3600;
|
||||
unsigned long mins = (seconds % 3600) / 60;
|
||||
return std::to_string(hours) + "h " + std::to_string(mins) + "m";
|
||||
}
|
||||
}
|
||||
|
||||
std::string CrossPointWebServerActivity::getCompanionAppUrl() const {
|
||||
// Generate deep link URL for companion Android app - Device/Files tab
|
||||
// Format: crosspoint://files?host=<ip>&port=<port>&wsPort=<wsPort>
|
||||
std::string url = "crosspoint://files?host=" + connectedIP;
|
||||
if (webServer) {
|
||||
url += "&port=" + std::to_string(webServer->getPort());
|
||||
url += "&wsPort=" + std::to_string(webServer->getWsPort());
|
||||
}
|
||||
return url;
|
||||
}
|
||||
|
||||
std::string CrossPointWebServerActivity::getCompanionAppLibraryUrl() const {
|
||||
// Generate deep link URL for companion Android app - Library tab
|
||||
// Format: crosspoint://library?host=<ip>&port=<port>&wsPort=<wsPort>
|
||||
std::string url = "crosspoint://library?host=" + connectedIP;
|
||||
if (webServer) {
|
||||
url += "&port=" + std::to_string(webServer->getPort());
|
||||
url += "&wsPort=" + std::to_string(webServer->getWsPort());
|
||||
}
|
||||
return url;
|
||||
}
|
||||
|
||||
void CrossPointWebServerActivity::renderServerRunning() const {
|
||||
// Dispatch to the appropriate screen
|
||||
switch (currentScreen) {
|
||||
case FileTransferScreen::WEB_BROWSER:
|
||||
renderWebBrowserScreen();
|
||||
break;
|
||||
case FileTransferScreen::COMPANION_APP:
|
||||
renderCompanionAppScreen();
|
||||
break;
|
||||
case FileTransferScreen::COMPANION_APP_LIBRARY:
|
||||
renderCompanionAppLibraryScreen();
|
||||
break;
|
||||
}
|
||||
|
||||
// Render stats bar at the bottom (above button hints)
|
||||
renderStats();
|
||||
|
||||
// Draw button hints with arrow navigation
|
||||
const auto labels = mappedInput.mapLabels("« Exit", "", "«", "»");
|
||||
renderer.drawButtonHints(UI_10_FONT_ID, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
|
||||
}
|
||||
|
||||
void CrossPointWebServerActivity::renderStats() const {
|
||||
if (!webServer) return;
|
||||
|
||||
const int screenHeight = renderer.getScreenHeight();
|
||||
const int statsY = screenHeight - 70; // Position above button hints
|
||||
|
||||
// Get stats from web server
|
||||
const unsigned long uptime = webServer->getServerUptime();
|
||||
const size_t bytesUp = webServer->getTotalBytesUploaded();
|
||||
const size_t bytesDown = webServer->getTotalBytesDownloaded();
|
||||
const size_t filesUp = webServer->getTotalFilesUploaded();
|
||||
const size_t filesDown = webServer->getTotalFilesDownloaded();
|
||||
|
||||
// Format stats string
|
||||
std::string statsLine = "Up: " + formatUptime(uptime);
|
||||
statsLine += " | Recv: " + formatBytes(bytesUp);
|
||||
if (filesUp > 0) {
|
||||
statsLine += " (" + std::to_string(filesUp) + ")";
|
||||
}
|
||||
statsLine += " | Sent: " + formatBytes(bytesDown);
|
||||
if (filesDown > 0) {
|
||||
statsLine += " (" + std::to_string(filesDown) + ")";
|
||||
}
|
||||
|
||||
renderer.drawCenteredText(SMALL_FONT_ID, statsY, statsLine.c_str());
|
||||
}
|
||||
|
||||
void CrossPointWebServerActivity::renderWebBrowserScreen() const {
|
||||
constexpr int LINE_SPACING = 28;
|
||||
|
||||
renderer.drawCenteredText(UI_12_FONT_ID, 15, "File Transfer - Web Browser", true, EpdFontFamily::BOLD);
|
||||
|
||||
if (isApMode) {
|
||||
// AP mode display - center the content block
|
||||
@@ -415,7 +559,7 @@ void CrossPointWebServerActivity::renderServerRunning() const {
|
||||
|
||||
renderer.drawCenteredText(SMALL_FONT_ID, startY + LINE_SPACING * 3,
|
||||
"or scan QR code with your phone to connect to Wifi.");
|
||||
// Show QR code for URL
|
||||
// Show QR code for WiFi config
|
||||
const std::string wifiConfig = std::string("WIFI:S:") + connectedSSID + ";;";
|
||||
drawQRCode(renderer, (480 - 6 * 33) / 2, startY + LINE_SPACING * 4, wifiConfig);
|
||||
|
||||
@@ -433,8 +577,8 @@ void CrossPointWebServerActivity::renderServerRunning() const {
|
||||
renderer.drawCenteredText(SMALL_FONT_ID, startY + LINE_SPACING * 6, "or scan QR code with your phone:");
|
||||
drawQRCode(renderer, (480 - 6 * 33) / 2, startY + LINE_SPACING * 7, hostnameUrl);
|
||||
} else {
|
||||
// STA mode display (original behavior)
|
||||
const int startY = 65;
|
||||
// STA mode display
|
||||
const int startY = 55;
|
||||
|
||||
std::string ssidInfo = "Network: " + connectedSSID;
|
||||
if (ssidInfo.length() > 28) {
|
||||
@@ -455,11 +599,83 @@ void CrossPointWebServerActivity::renderServerRunning() const {
|
||||
|
||||
renderer.drawCenteredText(SMALL_FONT_ID, startY + LINE_SPACING * 4, "Open this URL in your browser");
|
||||
|
||||
renderer.drawCenteredText(SMALL_FONT_ID, startY + LINE_SPACING * 5, "or scan QR code with your phone:");
|
||||
|
||||
// Show QR code for URL
|
||||
drawQRCode(renderer, (480 - 6 * 33) / 2, startY + LINE_SPACING * 6, webInfo);
|
||||
renderer.drawCenteredText(SMALL_FONT_ID, startY + LINE_SPACING * 5, "or scan QR code with your phone:");
|
||||
}
|
||||
}
|
||||
|
||||
void CrossPointWebServerActivity::renderCompanionAppScreen() const {
|
||||
constexpr int LINE_SPACING = 28;
|
||||
|
||||
renderer.drawCenteredText(UI_12_FONT_ID, 15, "File Transfer - Companion App", true, EpdFontFamily::BOLD);
|
||||
|
||||
const int startY = 55;
|
||||
|
||||
// Show network info
|
||||
std::string ssidInfo = "Network: " + connectedSSID;
|
||||
if (ssidInfo.length() > 28) {
|
||||
ssidInfo.replace(25, ssidInfo.length() - 25, "...");
|
||||
}
|
||||
renderer.drawCenteredText(UI_10_FONT_ID, startY, ssidInfo.c_str());
|
||||
|
||||
if (isApMode) {
|
||||
renderer.drawCenteredText(SMALL_FONT_ID, startY + LINE_SPACING,
|
||||
"Connect to this WiFi first, then scan QR code");
|
||||
} else {
|
||||
renderer.drawCenteredText(SMALL_FONT_ID, startY + LINE_SPACING, "Scan QR code with the CrossPoint Companion app");
|
||||
}
|
||||
|
||||
const auto labels = mappedInput.mapLabels("« Exit", "", "", "");
|
||||
renderer.drawButtonHints(UI_10_FONT_ID, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
|
||||
// Show companion app URL prominently
|
||||
const std::string appUrl = getCompanionAppUrl();
|
||||
renderer.drawCenteredText(UI_10_FONT_ID, startY + LINE_SPACING * 2, appUrl.c_str(), true, EpdFontFamily::BOLD);
|
||||
|
||||
renderer.drawCenteredText(SMALL_FONT_ID, startY + LINE_SPACING * 3, "or scan QR code:");
|
||||
|
||||
// Show QR code for app URL (centered)
|
||||
drawQRCode(renderer, (480 - 6 * 33) / 2, startY + LINE_SPACING * 4, appUrl);
|
||||
|
||||
// Show HTTP URL for reference (smaller)
|
||||
const int refY = startY + LINE_SPACING * 4 + 6 * 33 + 15; // Below QR code
|
||||
renderer.drawCenteredText(SMALL_FONT_ID, refY, "Opens to: Device file browser");
|
||||
std::string webUrl = "http://" + connectedIP + "/";
|
||||
renderer.drawCenteredText(SMALL_FONT_ID, refY + 20, webUrl.c_str());
|
||||
}
|
||||
|
||||
void CrossPointWebServerActivity::renderCompanionAppLibraryScreen() const {
|
||||
constexpr int LINE_SPACING = 28;
|
||||
|
||||
renderer.drawCenteredText(UI_12_FONT_ID, 15, "File Transfer - Library Link", true, EpdFontFamily::BOLD);
|
||||
|
||||
const int startY = 55;
|
||||
|
||||
// Show network info
|
||||
std::string ssidInfo = "Network: " + connectedSSID;
|
||||
if (ssidInfo.length() > 28) {
|
||||
ssidInfo.replace(25, ssidInfo.length() - 25, "...");
|
||||
}
|
||||
renderer.drawCenteredText(UI_10_FONT_ID, startY, ssidInfo.c_str());
|
||||
|
||||
if (isApMode) {
|
||||
renderer.drawCenteredText(SMALL_FONT_ID, startY + LINE_SPACING,
|
||||
"Connect to this WiFi first, then scan QR code");
|
||||
} else {
|
||||
renderer.drawCenteredText(SMALL_FONT_ID, startY + LINE_SPACING, "Scan QR code with the CrossPoint Companion app");
|
||||
}
|
||||
|
||||
// Show companion app URL prominently
|
||||
const std::string appUrl = getCompanionAppLibraryUrl();
|
||||
renderer.drawCenteredText(UI_10_FONT_ID, startY + LINE_SPACING * 2, appUrl.c_str(), true, EpdFontFamily::BOLD);
|
||||
|
||||
renderer.drawCenteredText(SMALL_FONT_ID, startY + LINE_SPACING * 3, "or scan QR code:");
|
||||
|
||||
// Show QR code for app URL (centered)
|
||||
drawQRCode(renderer, (480 - 6 * 33) / 2, startY + LINE_SPACING * 4, appUrl);
|
||||
|
||||
// Show what this link opens to
|
||||
const int refY = startY + LINE_SPACING * 4 + 6 * 33 + 15; // Below QR code
|
||||
renderer.drawCenteredText(SMALL_FONT_ID, refY, "Opens to: Your local Library");
|
||||
std::string webUrl = "http://" + connectedIP + "/";
|
||||
renderer.drawCenteredText(SMALL_FONT_ID, refY + 20, webUrl.c_str());
|
||||
}
|
||||
|
||||
@@ -20,6 +20,13 @@ enum class WebServerActivityState {
|
||||
SHUTTING_DOWN // Shutting down server and WiFi
|
||||
};
|
||||
|
||||
// File transfer screen tabs
|
||||
enum class FileTransferScreen {
|
||||
WEB_BROWSER, // Default screen with HTTP URL and QR code
|
||||
COMPANION_APP, // Screen with crosspoint://files deep link URL and QR code
|
||||
COMPANION_APP_LIBRARY // Screen with crosspoint://library deep link URL and QR code
|
||||
};
|
||||
|
||||
/**
|
||||
* CrossPointWebServerActivity is the entry point for file transfer functionality.
|
||||
* It:
|
||||
@@ -51,10 +58,20 @@ class CrossPointWebServerActivity final : public ActivityWithSubactivity {
|
||||
// Performance monitoring
|
||||
unsigned long lastHandleClientTime = 0;
|
||||
|
||||
// Screen navigation
|
||||
FileTransferScreen currentScreen = FileTransferScreen::WEB_BROWSER;
|
||||
unsigned long lastStatsRefresh = 0;
|
||||
|
||||
static void taskTrampoline(void* param);
|
||||
[[noreturn]] void displayTaskLoop();
|
||||
void render() const;
|
||||
void renderServerRunning() const;
|
||||
void renderWebBrowserScreen() const;
|
||||
void renderCompanionAppScreen() const;
|
||||
void renderCompanionAppLibraryScreen() const;
|
||||
void renderStats() const;
|
||||
std::string getCompanionAppUrl() const;
|
||||
std::string getCompanionAppLibraryUrl() const;
|
||||
|
||||
void onNetworkModeSelected(NetworkMode mode);
|
||||
void onWifiSelectionComplete(bool connected);
|
||||
|
||||
Reference in New Issue
Block a user