Add HARD MONEY detail screen with sparklines and high/low data

- New SCREEN_HARD_MONEY_DETAIL accessible by tapping 'HARD MONEY' header
- 3-column layout: Bitcoin | Gold | Silver with spot prices
- Day/Month/Year high/low values (color-coded green/red)
- Day high/low from intraday hourly data
- Month high/low from last 22 trading days
- Year high/low from 1-year daily data
- Togglable sparklines: D(5m/5d)/W(1h/7d)/M(1d/1mo)/Y(1wk/1y)/A(1mo/max)
- Default view is Monthly sparkline
- Auto-refresh on period change
This commit is contained in:
Chad 2026-04-17 21:07:20 -05:00
parent 7d932adb20
commit d0c8459a17

View file

@ -13,6 +13,7 @@
#include <WiFiClientSecure.h>
#include <HTTPClient.h>
#include <Preferences.h>
#include <time.h>
#define SCREEN_WIDTH 800
#define SCREEN_HEIGHT 480
@ -159,6 +160,11 @@ const unsigned long WIFI_CONNECT_TIMEOUT = 15000;
bool wifiConnecting = false;
unsigned long wifiConnectStartTime = 0;
bool timeSynced = false;
bool firstBoot = true;
const char* ntpServer = "pool.ntp.org";
bool displayFlipped = false;
#define HISTORY_DAYS 30
double btcHistory[HISTORY_DAYS] = {0};
double goldHistory[HISTORY_DAYS] = {0};
@ -168,12 +174,31 @@ bool goldHistoryLoaded = false;
bool silverHistoryLoaded = false;
unsigned long lastHistoryRefresh = 0;
#define HM_HISTORY_MAX 100
double btcHmHistory[HM_HISTORY_MAX] = {0};
double goldHmHistory[HM_HISTORY_MAX] = {0};
double silverHmHistory[HM_HISTORY_MAX] = {0};
int btcHmCount = 0;
int goldHmCount = 0;
int silverHmCount = 0;
bool btcHmLoaded = false;
bool goldHmLoaded = false;
bool silverHmLoaded = false;
double btcHighDay = 0, btcLowDay = 0, btcHighMonth = 0, btcLowMonth = 0, btcHighYear = 0, btcLowYear = 0;
double goldHighDay = 0, goldLowDay = 0, goldHighMonth = 0, goldLowMonth = 0, goldHighYear = 0, goldLowYear = 0;
double silverHighDay = 0, silverLowDay = 0, silverHighMonth = 0, silverLowMonth = 0, silverHighYear = 0, silverLowYear = 0;
int hmSparklinePeriod = 2;
bool hmNeedsRefresh = false;
unsigned long hmLastFetch = 0;
const unsigned long HM_FETCH_INTERVAL = 60000;
enum ScreenState {
SCREEN_DASHBOARD,
SCREEN_CUSTOMIZE_STOCKS,
SCREEN_CUSTOMIZE_INDICES,
SCREEN_WIFI_CONFIG,
SCREEN_WIFI_PASSWORD
SCREEN_WIFI_PASSWORD,
SCREEN_HARD_MONEY_DETAIL
};
ScreenState currentScreen = SCREEN_DASHBOARD;
@ -360,6 +385,223 @@ void fetchAllHistory() {
fetchHistory("SI=F", silverHistory, &silverHistoryLoaded);
}
bool fetchHmHistoryForSymbol(const char* symbol, double* history, int* count, int period) {
const char* interval;
const char* range;
switch (period) {
case 0: interval = "5m"; range = "5d"; break;
case 1: interval = "1h"; range = "7d"; break;
case 2: interval = "1d"; range = "1mo"; break;
case 3: interval = "1wk"; range = "1y"; break;
case 4: interval = "1mo"; range = "max"; break;
default: interval = "1d"; range = "1mo"; break;
}
WiFiClientSecure client;
client.setInsecure();
HTTPClient http;
String url = "https://query1.finance.yahoo.com/v8/finance/chart/" + String(symbol) + "?interval=" + String(interval) + "&range=" + String(range);
if (!http.begin(client, url)) return false;
http.setUserAgent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36");
http.setTimeout(15000);
int httpCode = http.GET();
if (httpCode != HTTP_CODE_OK) {
http.end();
return false;
}
String payload = http.getString();
http.end();
String arraySearch = "\"close\":[";
int arrayIdx = payload.indexOf(arraySearch);
if (arrayIdx == -1) return false;
int arrayStart = arrayIdx + arraySearch.length();
int arrayEnd = payload.indexOf("]", arrayStart);
if (arrayEnd == -1) return false;
String closeArray = payload.substring(arrayStart, arrayEnd);
*count = 0;
int pos = 0;
while (pos < closeArray.length() && *count < HM_HISTORY_MAX) {
while (pos < closeArray.length() && (closeArray[pos] == ' ' || closeArray[pos] == ',')) pos++;
if (pos >= closeArray.length()) break;
int valueStart = pos;
while (pos < closeArray.length() && closeArray[pos] != ',' && closeArray[pos] != ' ') pos++;
String valueStr = closeArray.substring(valueStart, pos);
if (!valueStr.equals("null") && valueStr.length() > 0) {
double value = valueStr.toDouble();
if (value > 0) {
history[*count] = value;
(*count)++;
}
}
pos++;
}
return *count > 0;
}
void fetchHmHighLowForSymbol(const char* symbol, double* highDay, double* lowDay, double* highMonth, double* lowMonth, double* highYear, double* lowYear) {
if (WiFi.status() != WL_CONNECTED) return;
WiFiClientSecure client;
client.setInsecure();
HTTPClient http;
// First API call: hourly data for today's intraday high/low
String url = "https://query1.finance.yahoo.com/v8/finance/chart/" + String(symbol) + "?interval=1h&range=1d";
if (http.begin(client, url)) {
http.setUserAgent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36");
http.setTimeout(15000);
int httpCode = http.GET();
if (httpCode == HTTP_CODE_OK) {
String payload = http.getString();
http.end();
String arraySearch = "\"close\":[";
int arrayIdx = payload.indexOf(arraySearch);
if (arrayIdx != -1) {
int arrayStart = arrayIdx + arraySearch.length();
int arrayEnd = payload.indexOf("]", arrayStart);
if (arrayEnd != -1) {
String closeArray = payload.substring(arrayStart, arrayEnd);
double minDay = 1e18, maxDay = 0;
int pos = 0;
while (pos < closeArray.length()) {
while (pos < closeArray.length() && (closeArray[pos] == ' ' || closeArray[pos] == ',')) pos++;
if (pos >= closeArray.length()) break;
int valueStart = pos;
while (pos < closeArray.length() && closeArray[pos] != ',' && closeArray[pos] != ' ') pos++;
String valueStr = closeArray.substring(valueStart, pos);
if (!valueStr.equals("null") && valueStr.length() > 0) {
double value = valueStr.toDouble();
if (value > 0) {
if (value < minDay) minDay = value;
if (value > maxDay) maxDay = value;
}
}
}
if (maxDay > 0) {
*highDay = maxDay;
*lowDay = minDay;
}
}
}
} else {
http.end();
}
}
// Second API call: daily data for month and year high/low
url = "https://query1.finance.yahoo.com/v8/finance/chart/" + String(symbol) + "?interval=1d&range=1y";
if (!http.begin(client, url)) return;
http.setUserAgent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36");
http.setTimeout(15000);
int httpCode = http.GET();
if (httpCode != HTTP_CODE_OK) {
http.end();
return;
}
String payload = http.getString();
http.end();
String arraySearch = "\"close\":[";
int arrayIdx = payload.indexOf(arraySearch);
if (arrayIdx == -1) return;
int arrayStart = arrayIdx + arraySearch.length();
int arrayEnd = payload.indexOf("]", arrayStart);
if (arrayEnd == -1) return;
String closeArray = payload.substring(arrayStart, arrayEnd);
// First pass: count values and find year high/low
double minYear = 1e18, maxYear = 0;
int valueCount = 0;
int pos = 0;
while (pos < closeArray.length()) {
while (pos < closeArray.length() && (closeArray[pos] == ' ' || closeArray[pos] == ',')) pos++;
if (pos >= closeArray.length()) break;
int valueStart = pos;
while (pos < closeArray.length() && closeArray[pos] != ',' && closeArray[pos] != ' ') pos++;
String valueStr = closeArray.substring(valueStart, pos);
if (!valueStr.equals("null") && valueStr.length() > 0) {
double value = valueStr.toDouble();
if (value > 0) {
valueCount++;
if (value < minYear) minYear = value;
if (value > maxYear) maxYear = value;
}
}
}
if (valueCount > 0) {
*highYear = maxYear;
*lowYear = minYear;
// Second pass: find month high/low (last 22 trading days)
int monthStart = valueCount > 22 ? valueCount - 22 : 0;
double minMonth = 1e18, maxMonth = 0;
int idx = 0;
pos = 0;
while (pos < closeArray.length()) {
while (pos < closeArray.length() && (closeArray[pos] == ' ' || closeArray[pos] == ',')) pos++;
if (pos >= closeArray.length()) break;
int valueStart = pos;
while (pos < closeArray.length() && closeArray[pos] != ',' && closeArray[pos] != ' ') pos++;
String valueStr = closeArray.substring(valueStart, pos);
if (!valueStr.equals("null") && valueStr.length() > 0) {
double value = valueStr.toDouble();
if (value > 0) {
if (idx >= monthStart) {
if (value < minMonth) minMonth = value;
if (value > maxMonth) maxMonth = value;
}
idx++;
}
}
}
*highMonth = maxMonth;
*lowMonth = minMonth;
}
}
void fetchAllHmData(int period) {
btcHmCount = goldHmCount = silverHmCount = 0;
btcHmLoaded = goldHmLoaded = silverHmLoaded = false;
fetchHmHistoryForSymbol("BTC-USD", btcHmHistory, &btcHmCount, period);
btcHmLoaded = btcHmCount > 0;
fetchHmHistoryForSymbol("GC=F", goldHmHistory, &goldHmCount, period);
goldHmLoaded = goldHmCount > 0;
fetchHmHistoryForSymbol("SI=F", silverHmHistory, &silverHmCount, period);
silverHmLoaded = silverHmCount > 0;
fetchHmHighLowForSymbol("BTC-USD", &btcHighDay, &btcLowDay, &btcHighMonth, &btcLowMonth, &btcHighYear, &btcLowYear);
fetchHmHighLowForSymbol("GC=F", &goldHighDay, &goldLowDay, &goldHighMonth, &goldLowMonth, &goldHighYear, &goldLowYear);
fetchHmHighLowForSymbol("SI=F", &silverHighDay, &silverLowDay, &silverHighMonth, &silverLowMonth, &silverHighYear, &silverLowYear);
hmLastFetch = millis();
}
void drawSparkline(int x, int y, int width, int height, double* history, bool loaded, uint16_t color) {
if (!loaded) return;
@ -437,6 +679,88 @@ void saveConfig() {
preferences.end();
}
int dayOfWeek(int year, int month, int day) {
if (month < 3) {
month += 12;
year--;
}
return (day + 13 * (month + 1) / 5 + year + year / 4 - year / 100 + year / 400) % 7;
}
bool isUSDST(int year, int month, int day, int hour) {
int springDOW = dayOfWeek(year, 3, 8);
int springStart = 8 + ((14 - springDOW) % 7);
int fallDOW = dayOfWeek(year, 11, 1);
int fallEnd = 1 + ((14 - fallDOW) % 7);
if (month < 3 || month > 11) return false;
if (month > 3 && month < 11) return true;
if (month == 3) {
if (day < springStart) return false;
if (day > springStart) return true;
return hour >= 2;
}
if (month == 11) {
if (day < fallEnd) return true;
if (day > fallEnd) return false;
return hour < 2;
}
return false;
}
bool isUSMarketOpen() {
if (!timeSynced) return firstBoot;
time_t now = time(nullptr);
struct tm* utc = gmtime(&now);
int utcHour = utc->tm_hour;
int utcMinute = utc->tm_min;
int year = utc->tm_year + 1900;
int month = utc->tm_mon + 1;
int day = utc->tm_mday;
int utcWday = utc->tm_wday;
bool dst = isUSDST(year, month, day, utcHour);
int etOffset = dst ? -4 : -5;
int etHour = utcHour + etOffset;
int etWday = utcWday;
if (etHour < 0) {
etHour += 24;
etWday = (etWday + 6) % 7;
} else if (etHour >= 24) {
etHour -= 24;
etWday = (etWday + 1) % 7;
}
if (etWday == 0 || etWday == 6) return false;
if (etHour < 9 || etHour >= 16) return false;
if (etHour == 9 && utcMinute < 30) return false;
return true;
}
void syncTime() {
configTime(0, 0, ntpServer);
int retries = 0;
while (time(nullptr) < 100000 && retries < 20) {
delay(500);
retries++;
}
if (time(nullptr) >= 100000) {
timeSynced = true;
}
}
void updatePrices() {
if (WiFi.status() != WL_CONNECTED) return;
@ -444,6 +768,7 @@ void updatePrices() {
fetchPriceYahoo("GC=F", &goldPrice);
fetchPriceYahoo("SI=F", &silverPrice);
if (isUSMarketOpen()) {
for (int i = 0; i < MAX_STOCKS; i++) {
if (strlen(stockTickers[i]) > 0) {
fetchPriceFinnhub(stockTickers[i], &stockPrices[i]);
@ -455,6 +780,11 @@ void updatePrices() {
fetchPriceYahoo(indexTickers[i], &indexPrices[i]);
}
}
}
if (firstBoot) {
firstBoot = false;
}
}
void drawKeyboard(int y) {
@ -714,6 +1044,42 @@ lcd.setTextColor(TFT_WHITE, TFT_ORANGE);
lcd.drawString("BACK", 125, SCREEN_HEIGHT - 30);
}
void drawGearIcon(int cx, int cy, int radius, uint16_t color) {
for (int i = 0; i < 8; i++) {
float angle = i * 45.0 * DEG_TO_RAD;
float halfWidth = 0.35;
float cosA = cos(angle);
float sinA = sin(angle);
int x0 = cx + cosA * radius;
int y0 = cy + sinA * radius;
int x1 = cx + cosA * (radius + 6);
int y1 = cy + sinA * (radius + 6);
int dx = halfWidth * (-sinA) * radius;
int dy = halfWidth * cosA * radius;
lcd.fillTriangle(x0 - dx, y0 - dy, x1 - dx, y1 - dy, x1 + dx, y1 + dy, color);
lcd.fillTriangle(x0 - dx, y0 - dy, x1 + dx, y1 + dy, x0 + dx, y0 + dy, color);
}
lcd.fillCircle(cx, cy, radius, color);
lcd.fillCircle(cx, cy, radius - 3, TFT_ORANGE);
}
void drawFlipIcon(int cx, int cy, int radius, uint16_t color) {
lcd.setColor(color);
lcd.drawArc(cx, cy, radius, radius - 3, 60, 300);
float arrowAngle = 60.0 * DEG_TO_RAD;
int arrowX = cx + cos(arrowAngle) * radius;
int arrowY = cy - sin(arrowAngle) * radius;
float tangentAngle = arrowAngle - PI / 2;
int tipX = arrowX + cos(tangentAngle) * 6;
int tipY = arrowY - sin(tangentAngle) * 6;
lcd.fillTriangle(
arrowX + cos(tangentAngle) * 2 - sin(tangentAngle) * 4,
arrowY - sin(tangentAngle) * 2 - cos(tangentAngle) * 4,
arrowX + cos(tangentAngle) * 2 + sin(tangentAngle) * 4,
arrowY - sin(tangentAngle) * 2 + cos(tangentAngle) * 4,
tipX, tipY, color);
}
void drawDashboard() {
lcd.fillScreen(TFT_BLACK);
@ -724,6 +1090,7 @@ void drawDashboard() {
lcd.drawString("HARD MONEY", 20, 22);
lcd.drawString("STOCKS", 280, 22);
lcd.drawString("INDICES", 540, 22);
drawFlipIcon(SCREEN_WIDTH - 32, 22, 12, TFT_WHITE);
int col1X = 20;
int col2X = 280;
@ -777,8 +1144,13 @@ indexY += 65;
String wifiStatus;
if (wifiConnecting) {
wifiStatus = "Connecting...";
} else if (WiFi.status() == WL_CONNECTED) {
wifiStatus = WiFi.SSID();
if (wifiStatus.length() > 20) {
wifiStatus = wifiStatus.substring(0, 17) + "...";
}
} else {
wifiStatus = WiFi.status() == WL_CONNECTED ? "OK" : "OFFLINE";
wifiStatus = "OFFLINE";
}
lcd.drawString("WiFi: " + wifiStatus, 20, SCREEN_HEIGHT - 20);
@ -791,8 +1163,7 @@ indexY += 65;
lcd.setTextDatum(textdatum_t::middle_center);
lcd.drawString(String(timeStr), SCREEN_WIDTH / 2, SCREEN_HEIGHT - 20);
lcd.fillSmoothRoundRect(SCREEN_WIDTH - 160, SCREEN_HEIGHT - 36, 140, 32, 6, TFT_DARKGRAY);
lcd.drawString("CUSTOMIZE", SCREEN_WIDTH - 90, SCREEN_HEIGHT - 20);
drawGearIcon(SCREEN_WIDTH - 32, SCREEN_HEIGHT - 20, 9, TFT_WHITE);
}
void drawFullKeyboard(int y, bool showSpecials) {
@ -1277,8 +1648,215 @@ void showRefreshingMessage() {
lcd.setTextDatum(textdatum_t::middle_center);
lcd.drawString("REFRESHING...", SCREEN_WIDTH / 2, SCREEN_HEIGHT - 20);
lcd.fillSmoothRoundRect(SCREEN_WIDTH - 160, SCREEN_HEIGHT - 36, 140, 32, 6, TFT_DARKGRAY);
lcd.drawString("CUSTOMIZE", SCREEN_WIDTH - 90, SCREEN_HEIGHT - 20);
drawGearIcon(SCREEN_WIDTH - 32, SCREEN_HEIGHT - 20, 9, TFT_WHITE);
}
void drawHardMoneyDetailScreen() {
lcd.fillScreen(TFT_BLACK);
lcd.fillSmoothRoundRect(0, 0, SCREEN_WIDTH, 45, 8, TFT_ORANGE);
lcd.setTextColor(TFT_WHITE, TFT_ORANGE);
lcd.setFont(&fonts::DejaVu24);
lcd.setTextDatum(textdatum_t::middle_center);
lcd.drawString("HARD MONEY", SCREEN_WIDTH / 2, 22);
int col1X = 50;
int col2X = 310;
int col3X = 570;
int colWidth = 180;
int headerY = 65;
int priceY = 90;
lcd.setTextColor(TFT_ORANGE, TFT_BLACK);
lcd.setFont(&fonts::Roboto_Thin_24);
lcd.setTextDatum(textdatum_t::middle_left);
lcd.drawString("BITCOIN", col1X, headerY);
lcd.drawString(formatPriceUS(btcPrice, 0), col1X, priceY);
lcd.setTextColor(TFT_GOLD, TFT_BLACK);
lcd.drawString("GOLD", col2X, headerY);
lcd.drawString(formatPriceUS(goldPrice, 2), col2X, priceY);
lcd.setTextColor(TFT_SILVER, TFT_BLACK);
lcd.drawString("SILVER", col3X, headerY);
lcd.drawString(formatPriceUS(silverPrice, 2), col3X, priceY);
int highLowY = priceY + 30;
int rowHeight = 22;
lcd.setFont(&fonts::DejaVu12);
lcd.setTextDatum(textdatum_t::middle_left);
lcd.setTextColor(TFT_GREEN, TFT_BLACK);
lcd.drawString("Day High:", col1X, highLowY);
lcd.drawString(formatPriceUS(btcHighDay, 0, true), col1X + 65, highLowY);
lcd.setTextColor(TFT_GREEN, TFT_BLACK);
lcd.drawString("Day High:", col2X, highLowY);
lcd.drawString(formatPriceUS(goldHighDay, 2, true), col2X + 65, highLowY);
lcd.drawString("Day High:", col3X, highLowY);
lcd.drawString(formatPriceUS(silverHighDay, 2, true), col3X + 65, highLowY);
lcd.setTextColor(0xF800, TFT_BLACK);
lcd.drawString("Day Low:", col1X, highLowY + rowHeight);
lcd.drawString(formatPriceUS(btcLowDay, 0, true), col1X + 65, highLowY + rowHeight);
lcd.drawString("Day Low:", col2X, highLowY + rowHeight);
lcd.drawString(formatPriceUS(goldLowDay, 2, true), col2X + 65, highLowY + rowHeight);
lcd.drawString("Day Low:", col3X, highLowY + rowHeight);
lcd.drawString(formatPriceUS(silverLowDay, 2, true), col3X + 65, highLowY + rowHeight);
lcd.setTextColor(TFT_GREEN, TFT_BLACK);
lcd.drawString("Mo High:", col1X, highLowY + rowHeight * 2);
lcd.drawString(formatPriceUS(btcHighMonth, 0, true), col1X + 65, highLowY + rowHeight * 2);
lcd.drawString("Mo High:", col2X, highLowY + rowHeight * 2);
lcd.drawString(formatPriceUS(goldHighMonth, 2, true), col2X + 65, highLowY + rowHeight * 2);
lcd.drawString("Mo High:", col3X, highLowY + rowHeight * 2);
lcd.drawString(formatPriceUS(silverHighMonth, 2, true), col3X + 65, highLowY + rowHeight * 2);
lcd.setTextColor(0xF800, TFT_BLACK);
lcd.drawString("Mo Low:", col1X, highLowY + rowHeight * 3);
lcd.drawString(formatPriceUS(btcLowMonth, 0, true), col1X + 65, highLowY + rowHeight * 3);
lcd.drawString("Mo Low:", col2X, highLowY + rowHeight * 3);
lcd.drawString(formatPriceUS(goldLowMonth, 2, true), col2X + 65, highLowY + rowHeight * 3);
lcd.drawString("Mo Low:", col3X, highLowY + rowHeight * 3);
lcd.drawString(formatPriceUS(silverLowMonth, 2, true), col3X + 65, highLowY + rowHeight * 3);
lcd.setTextColor(TFT_GREEN, TFT_BLACK);
lcd.drawString("Yr High:", col1X, highLowY + rowHeight * 4);
lcd.drawString(formatPriceUS(btcHighYear, 0, true), col1X + 65, highLowY + rowHeight * 4);
lcd.drawString("Yr High:", col2X, highLowY + rowHeight * 4);
lcd.drawString(formatPriceUS(goldHighYear, 2, true), col2X + 65, highLowY + rowHeight * 4);
lcd.drawString("Yr High:", col3X, highLowY + rowHeight * 4);
lcd.drawString(formatPriceUS(silverHighYear, 2, true), col3X + 65, highLowY + rowHeight * 4);
lcd.setTextColor(0xF800, TFT_BLACK);
lcd.drawString("Yr Low:", col1X, highLowY + rowHeight * 5);
lcd.drawString(formatPriceUS(btcLowYear, 0, true), col1X + 65, highLowY + rowHeight * 5);
lcd.drawString("Yr Low:", col2X, highLowY + rowHeight * 5);
lcd.drawString(formatPriceUS(goldLowYear, 2, true), col2X + 65, highLowY + rowHeight * 5);
lcd.drawString("Yr Low:", col3X, highLowY + rowHeight * 5);
lcd.drawString(formatPriceUS(silverLowYear, 2, true), col3X + 65, highLowY + rowHeight * 5);
int sparklineY = highLowY + rowHeight * 6 + 15;
const char* periodLabels[] = {"D", "W", "M", "Y", "A"};
int btnWidth = 50;
int btnHeight = 30;
int btnGap = 10;
int totalBtnWidth = 5 * btnWidth + 4 * btnGap;
int btnStartX = (SCREEN_WIDTH - totalBtnWidth) / 2;
for (int i = 0; i < 5; i++) {
int btnX = btnStartX + i * (btnWidth + btnGap);
uint16_t bgColor = (hmSparklinePeriod == i) ? TFT_ORANGE : TFT_DARKGRAY;
lcd.fillSmoothRoundRect(btnX, sparklineY, btnWidth, btnHeight, 6, bgColor);
lcd.setTextColor(TFT_WHITE, bgColor);
lcd.setFont(&fonts::DejaVu18);
lcd.setTextDatum(textdatum_t::middle_center);
lcd.drawString(periodLabels[i], btnX + btnWidth / 2, sparklineY + btnHeight / 2);
}
int sparklineDrawY = sparklineY + btnHeight + 15;
int sparklineHeight = 80;
if (btcHmLoaded && btcHmCount > 1) {
double minVal = btcHmHistory[0];
double maxVal = btcHmHistory[0];
for (int i = 0; i < btcHmCount; i++) {
if (btcHmHistory[i] < minVal) minVal = btcHmHistory[i];
if (btcHmHistory[i] > maxVal) maxVal = btcHmHistory[i];
}
double range = maxVal - minVal;
if (range < 1) range = 1;
for (int i = 0; i < btcHmCount - 1; i++) {
int x1 = col1X + (i * colWidth) / (btcHmCount - 1);
int x2 = col1X + ((i + 1) * colWidth) / (btcHmCount - 1);
int y1 = sparklineDrawY + sparklineHeight - (int)((btcHmHistory[i] - minVal) * sparklineHeight / range);
int y2 = sparklineDrawY + sparklineHeight - (int)((btcHmHistory[i + 1] - minVal) * sparklineHeight / range);
lcd.drawLine(x1, y1, x2, y2, TFT_ORANGE);
}
}
if (goldHmLoaded && goldHmCount > 1) {
double minVal = goldHmHistory[0];
double maxVal = goldHmHistory[0];
for (int i = 0; i < goldHmCount; i++) {
if (goldHmHistory[i] < minVal) minVal = goldHmHistory[i];
if (goldHmHistory[i] > maxVal) maxVal = goldHmHistory[i];
}
double range = maxVal - minVal;
if (range < 1) range = 1;
for (int i = 0; i < goldHmCount - 1; i++) {
int x1 = col2X + (i * colWidth) / (goldHmCount - 1);
int x2 = col2X + ((i + 1) * colWidth) / (goldHmCount - 1);
int y1 = sparklineDrawY + sparklineHeight - (int)((goldHmHistory[i] - minVal) * sparklineHeight / range);
int y2 = sparklineDrawY + sparklineHeight - (int)((goldHmHistory[i + 1] - minVal) * sparklineHeight / range);
lcd.drawLine(x1, y1, x2, y2, TFT_GOLD);
}
}
if (silverHmLoaded && silverHmCount > 1) {
double minVal = silverHmHistory[0];
double maxVal = silverHmHistory[0];
for (int i = 0; i < silverHmCount; i++) {
if (silverHmHistory[i] < minVal) minVal = silverHmHistory[i];
if (silverHmHistory[i] > maxVal) maxVal = silverHmHistory[i];
}
double range = maxVal - minVal;
if (range < 1) range = 1;
for (int i = 0; i < silverHmCount - 1; i++) {
int x1 = col3X + (i * colWidth) / (silverHmCount - 1);
int x2 = col3X + ((i + 1) * colWidth) / (silverHmCount - 1);
int y1 = sparklineDrawY + sparklineHeight - (int)((silverHmHistory[i] - minVal) * sparklineHeight / range);
int y2 = sparklineDrawY + sparklineHeight - (int)((silverHmHistory[i + 1] - minVal) * sparklineHeight / range);
lcd.drawLine(x1, y1, x2, y2, TFT_SILVER);
}
}
lcd.fillSmoothRoundRect(20, SCREEN_HEIGHT - 50, 150, 40, 8, TFT_GRAY);
lcd.setTextColor(TFT_WHITE, TFT_GRAY);
lcd.setFont(&fonts::DejaVu18);
lcd.setTextDatum(textdatum_t::middle_center);
lcd.drawString("BACK", 95, SCREEN_HEIGHT - 30);
}
bool handleHardMoneyTouch(int32_t x, int32_t y) {
if (x >= 20 && x < 170 && y >= SCREEN_HEIGHT - 50 && y < SCREEN_HEIGHT - 10) {
currentScreen = SCREEN_DASHBOARD;
needsRefresh = true;
return true;
}
int btnWidth = 50;
int btnHeight = 30;
int btnGap = 10;
int totalBtnWidth = 5 * btnWidth + 4 * btnGap;
int btnStartX = (SCREEN_WIDTH - totalBtnWidth) / 2;
int headerY = 55;
int priceY = 80;
int highLowY = priceY + 30;
int rowHeight = 22;
int sparklineY = highLowY + rowHeight * 6 + 15;
for (int i = 0; i < 5; i++) {
int btnX = btnStartX + i * (btnWidth + btnGap);
if (x >= btnX && x < btnX + btnWidth && y >= sparklineY && y < sparklineY + btnHeight) {
if (hmSparklinePeriod != i) {
hmSparklinePeriod = i;
btcHmLoaded = false;
goldHmLoaded = false;
silverHmLoaded = false;
hmNeedsRefresh = true;
}
return true;
}
}
return false;
}
void refreshDataAndShow() {
@ -1290,7 +1868,24 @@ void refreshDataAndShow() {
}
bool handleDashboardTouch(int32_t x, int32_t y) {
if (x >= SCREEN_WIDTH - 160 && x < SCREEN_WIDTH - 20 && y >= SCREEN_HEIGHT - 36 && y < SCREEN_HEIGHT - 4) {
if (x >= SCREEN_WIDTH - 55 && x < SCREEN_WIDTH - 10 && y >= 5 && y < 40) {
displayFlipped = !displayFlipped;
preferences.begin("dashboard", false);
preferences.putBool("flipped", displayFlipped);
preferences.end();
lcd.setRotation(displayFlipped ? 2 : 0);
needsRefresh = true;
return true;
}
if (x >= 20 && x < 200 && y >= 0 && y < 45) {
currentScreen = SCREEN_HARD_MONEY_DETAIL;
hmSparklinePeriod = 2;
hmNeedsRefresh = true;
return true;
}
if (x >= SCREEN_WIDTH - 50 && x < SCREEN_WIDTH - 14 && y >= SCREEN_HEIGHT - 32 && y < SCREEN_HEIGHT - 8) {
currentScreen = SCREEN_CUSTOMIZE_STOCKS;
selectedStockSlot = -1;
keyboardPos = 0;
@ -1322,7 +1917,12 @@ void setup() {
lcd.init();
lcd.setBrightness(255);
lcd.setRotation(0);
preferences.begin("dashboard", true);
displayFlipped = preferences.getBool("flipped", false);
preferences.end();
lcd.setRotation(displayFlipped ? 2 : 0);
lcd.fillScreen(TFT_WHITE);
delay(250);
@ -1420,6 +2020,9 @@ void loop() {
case SCREEN_WIFI_PASSWORD:
handleWifiPasswordTouch(x, y);
break;
case SCREEN_HARD_MONEY_DETAIL:
handleHardMoneyTouch(x, y);
break;
}
if (!needsRefresh) {
@ -1441,6 +2044,9 @@ void loop() {
case SCREEN_WIFI_PASSWORD:
drawWifiPasswordScreen();
break;
case SCREEN_HARD_MONEY_DETAIL:
drawHardMoneyDetailScreen();
break;
}
}
@ -1453,9 +2059,25 @@ void loop() {
drawFooterCountdown();
}
if (currentScreen == SCREEN_HARD_MONEY_DETAIL) {
if (hmNeedsRefresh || !btcHmLoaded || !goldHmLoaded || !silverHmLoaded) {
hmNeedsRefresh = false;
drawHardMoneyDetailScreen();
fetchAllHmData(hmSparklinePeriod);
drawHardMoneyDetailScreen();
}
if (millis() - hmLastFetch > HM_FETCH_INTERVAL) {
fetchAllHmData(hmSparklinePeriod);
drawHardMoneyDetailScreen();
}
}
if (wifiConnecting) {
if (WiFi.status() == WL_CONNECTED) {
wifiConnecting = false;
if (!timeSynced) {
syncTime();
}
if (currentScreen == SCREEN_DASHBOARD) {
drawDashboard();
}