44b6a89b57
- Fix FTP using FFAT instead of SD_MMC: define both NETWORK_TYPE (6) and STORAGE_TYPE (10) with numeric values to bypass FtpServerKey.h guard - Fix SD capacity showing 'f/f GB': use integer math instead of floats (LVGL builtin sprintf lacks %f support) - Fix time/date labels tilting 45°: set fixed width + LV_TEXT_ALIGN_CENTER - Switch LVGL sprintf to C stdlib for future float compatibility
325 lines
16 KiB
C++
325 lines
16 KiB
C++
#include "ui.h"
|
|
#include <Arduino.h>
|
|
|
|
/* ── colour palette (AMOLED-optimised) ───────────────────────────── */
|
|
#define CLR_BG 0x0A0A0F
|
|
#define CLR_PANEL_BG 0x141420
|
|
#define CLR_PANEL_BRD 0x2A2A3A
|
|
#define CLR_WHITE 0xFFFFFF
|
|
#define CLR_GRAY 0x888888
|
|
#define CLR_CYAN 0x00BCD4
|
|
#define CLR_GREEN 0x4CAF50
|
|
#define CLR_RED 0xF44336
|
|
#define CLR_BLUE 0x2196F3
|
|
#define CLR_PURPLE 0xAB47BC
|
|
#define CLR_BAR_BG 0x1A1A2E
|
|
#define CLR_BAR_FILL 0x4CAF50
|
|
#define CLR_ERROR_BG 0x2D0A0A
|
|
#define CLR_DIMMED 0x666677
|
|
#define CLR_SEPARATOR 0x1E1E2E
|
|
#define CLR_TITLE_ACCENT 0x00BCD4
|
|
|
|
/* ── screen dimensions ───────────────────────────────────────────── */
|
|
#define SCR_W 410
|
|
#define SCR_H 502
|
|
|
|
/* ── static widget handles ───────────────────────────────────────── */
|
|
static lv_obj_t *lbl_title;
|
|
static lv_obj_t *line_sep;
|
|
static lv_obj_t *lbl_wifi;
|
|
static lv_obj_t *lbl_ip;
|
|
static lv_obj_t *panel_ftp;
|
|
static lv_obj_t *dot_ftp_status;
|
|
static lv_obj_t *lbl_ftp_title;
|
|
static lv_obj_t *lbl_ftp_status;
|
|
static lv_obj_t *lbl_ftp_url;
|
|
static lv_obj_t *lbl_ftp_creds;
|
|
static lv_obj_t *lbl_storage_title;
|
|
static lv_obj_t *bar_sd;
|
|
static lv_obj_t *lbl_sd_info;
|
|
static lv_obj_t *lbl_sd_pct;
|
|
static lv_obj_t *lbl_time;
|
|
static lv_obj_t *lbl_date;
|
|
static lv_obj_t *obj_error_bar;
|
|
static lv_obj_t *lbl_error;
|
|
|
|
/* ── cached IP for FTP URL ───────────────────────────────────────── */
|
|
static char cached_ip[48] = "0.0.0.0";
|
|
|
|
/* ── helper: create a styled label ───────────────────────────────── */
|
|
static lv_obj_t *make_label(lv_obj_t *parent, const lv_font_t *font,
|
|
uint32_t color, lv_align_t align,
|
|
int32_t x_ofs, int32_t y_ofs,
|
|
const char *text)
|
|
{
|
|
lv_obj_t *lbl = lv_label_create(parent);
|
|
lv_label_set_text(lbl, text);
|
|
lv_obj_set_style_text_font(lbl, font, 0);
|
|
lv_obj_set_style_text_color(lbl, lv_color_hex(color), 0);
|
|
lv_obj_align(lbl, align, x_ofs, y_ofs);
|
|
return lbl;
|
|
}
|
|
|
|
/* ── public API ──────────────────────────────────────────────────── */
|
|
|
|
void ui_init()
|
|
{
|
|
lv_obj_t *scr = lv_screen_active();
|
|
|
|
/* ── screen background: pure AMOLED black ────────────────────── */
|
|
lv_obj_set_style_bg_color(scr, lv_color_hex(CLR_BG), 0);
|
|
lv_obj_set_style_bg_opa(scr, LV_OPA_COVER, 0);
|
|
|
|
/* ────────────────────────────────────────────────────────────── */
|
|
/* HEADER (y = 0 … 60) */
|
|
/* ────────────────────────────────────────────────────────────── */
|
|
lbl_title = make_label(scr, &lv_font_montserrat_24, CLR_WHITE,
|
|
LV_ALIGN_TOP_MID, 0, 16, "FTP Explorer");
|
|
lv_obj_set_style_text_letter_space(lbl_title, 2, 0);
|
|
|
|
/* subtle separator line */
|
|
static lv_point_precise_t line_pts[] = {{40, 0}, {370, 0}};
|
|
line_sep = lv_line_create(scr);
|
|
lv_line_set_points(line_sep, line_pts, 2);
|
|
lv_obj_set_style_line_color(line_sep, lv_color_hex(CLR_SEPARATOR), 0);
|
|
lv_obj_set_style_line_width(line_sep, 1, 0);
|
|
lv_obj_set_style_line_opa(line_sep, LV_OPA_70, 0);
|
|
lv_obj_align(line_sep, LV_ALIGN_TOP_LEFT, 0, 55);
|
|
|
|
/* ────────────────────────────────────────────────────────────── */
|
|
/* WI-FI SECTION (y = 65 … 140) */
|
|
/* ────────────────────────────────────────────────────────────── */
|
|
lbl_wifi = make_label(scr, &lv_font_montserrat_16, CLR_GREEN,
|
|
LV_ALIGN_TOP_LEFT, 28, 68,
|
|
LV_SYMBOL_WIFI " Disconnected");
|
|
|
|
lbl_ip = make_label(scr, &lv_font_montserrat_30, CLR_CYAN,
|
|
LV_ALIGN_TOP_LEFT, 28, 96, "0.0.0.0");
|
|
|
|
/* ────────────────────────────────────────────────────────────── */
|
|
/* FTP SERVER PANEL (y = 148 … 288) */
|
|
/* ────────────────────────────────────────────────────────────── */
|
|
panel_ftp = lv_obj_create(scr);
|
|
lv_obj_set_size(panel_ftp, SCR_W - 40, 138);
|
|
lv_obj_align(panel_ftp, LV_ALIGN_TOP_MID, 0, 148);
|
|
lv_obj_set_style_bg_color(panel_ftp, lv_color_hex(CLR_PANEL_BG), 0);
|
|
lv_obj_set_style_bg_opa(panel_ftp, LV_OPA_COVER, 0);
|
|
lv_obj_set_style_border_color(panel_ftp, lv_color_hex(CLR_PANEL_BRD), 0);
|
|
lv_obj_set_style_border_width(panel_ftp, 1, 0);
|
|
lv_obj_set_style_border_opa(panel_ftp, LV_OPA_80, 0);
|
|
lv_obj_set_style_radius(panel_ftp, 14, 0);
|
|
lv_obj_set_style_pad_left(panel_ftp, 16, 0);
|
|
lv_obj_set_style_pad_right(panel_ftp, 16, 0);
|
|
lv_obj_set_style_pad_top(panel_ftp, 14, 0);
|
|
lv_obj_set_style_pad_bottom(panel_ftp, 10, 0);
|
|
lv_obj_clear_flag(panel_ftp, LV_OBJ_FLAG_SCROLLABLE);
|
|
/* subtle vertical gradient on the panel */
|
|
lv_obj_set_style_bg_grad_color(panel_ftp, lv_color_hex(0x1A1A30), 0);
|
|
lv_obj_set_style_bg_grad_dir(panel_ftp, LV_GRAD_DIR_VER, 0);
|
|
|
|
/* panel title */
|
|
lbl_ftp_title = lv_label_create(panel_ftp);
|
|
lv_label_set_text(lbl_ftp_title, "FTP Server");
|
|
lv_obj_set_style_text_font(lbl_ftp_title, &lv_font_montserrat_18, 0);
|
|
lv_obj_set_style_text_color(lbl_ftp_title, lv_color_hex(CLR_WHITE), 0);
|
|
lv_obj_align(lbl_ftp_title, LV_ALIGN_TOP_LEFT, 0, 0);
|
|
|
|
/* status dot (8x8 circle) */
|
|
dot_ftp_status = lv_obj_create(panel_ftp);
|
|
lv_obj_set_size(dot_ftp_status, 8, 8);
|
|
lv_obj_set_style_radius(dot_ftp_status, 4, 0);
|
|
lv_obj_set_style_bg_color(dot_ftp_status, lv_color_hex(CLR_GREEN), 0);
|
|
lv_obj_set_style_bg_opa(dot_ftp_status, LV_OPA_COVER, 0);
|
|
lv_obj_set_style_border_width(dot_ftp_status, 0, 0);
|
|
lv_obj_clear_flag(dot_ftp_status, LV_OBJ_FLAG_SCROLLABLE);
|
|
lv_obj_align(dot_ftp_status, LV_ALIGN_TOP_LEFT, 0, 33);
|
|
|
|
/* status label next to dot */
|
|
lbl_ftp_status = lv_label_create(panel_ftp);
|
|
lv_label_set_text(lbl_ftp_status, "Idle");
|
|
lv_obj_set_style_text_font(lbl_ftp_status, &lv_font_montserrat_14, 0);
|
|
lv_obj_set_style_text_color(lbl_ftp_status, lv_color_hex(CLR_GREEN), 0);
|
|
lv_obj_align(lbl_ftp_status, LV_ALIGN_TOP_LEFT, 14, 28);
|
|
|
|
/* FTP URL */
|
|
lbl_ftp_url = lv_label_create(panel_ftp);
|
|
lv_label_set_text(lbl_ftp_url, "ftp://0.0.0.0");
|
|
lv_obj_set_style_text_font(lbl_ftp_url, &lv_font_montserrat_16, 0);
|
|
lv_obj_set_style_text_color(lbl_ftp_url, lv_color_hex(CLR_DIMMED), 0);
|
|
lv_obj_align(lbl_ftp_url, LV_ALIGN_TOP_LEFT, 0, 54);
|
|
|
|
/* credentials */
|
|
lbl_ftp_creds = lv_label_create(panel_ftp);
|
|
lv_label_set_text(lbl_ftp_creds, "User: kode Pass: kode");
|
|
lv_obj_set_style_text_font(lbl_ftp_creds, &lv_font_montserrat_12, 0);
|
|
lv_obj_set_style_text_color(lbl_ftp_creds, lv_color_hex(CLR_GRAY), 0);
|
|
lv_obj_align(lbl_ftp_creds, LV_ALIGN_TOP_LEFT, 0, 82);
|
|
|
|
/* ────────────────────────────────────────────────────────────── */
|
|
/* STORAGE SECTION (y = 300 … 370) */
|
|
/* ────────────────────────────────────────────────────────────── */
|
|
lbl_storage_title = make_label(scr, &lv_font_montserrat_18, CLR_WHITE,
|
|
LV_ALIGN_TOP_LEFT, 28, 300, "Storage");
|
|
|
|
bar_sd = lv_bar_create(scr);
|
|
lv_obj_set_size(bar_sd, SCR_W - 56, 14);
|
|
lv_obj_align(bar_sd, LV_ALIGN_TOP_LEFT, 28, 330);
|
|
lv_bar_set_range(bar_sd, 0, 100);
|
|
lv_bar_set_value(bar_sd, 0, LV_ANIM_OFF);
|
|
/* bar track */
|
|
lv_obj_set_style_bg_color(bar_sd, lv_color_hex(CLR_BAR_BG), 0);
|
|
lv_obj_set_style_bg_opa(bar_sd, LV_OPA_COVER, 0);
|
|
lv_obj_set_style_radius(bar_sd, 7, 0);
|
|
/* bar indicator */
|
|
lv_obj_set_style_bg_color(bar_sd, lv_color_hex(CLR_BAR_FILL),
|
|
LV_PART_INDICATOR);
|
|
lv_obj_set_style_bg_opa(bar_sd, LV_OPA_COVER, LV_PART_INDICATOR);
|
|
lv_obj_set_style_radius(bar_sd, 7, LV_PART_INDICATOR);
|
|
|
|
/* size text left-aligned */
|
|
lbl_sd_info = make_label(scr, &lv_font_montserrat_14, CLR_GRAY,
|
|
LV_ALIGN_TOP_LEFT, 28, 350, "0.0 / 0.0 GB");
|
|
|
|
/* percentage right-aligned */
|
|
lbl_sd_pct = make_label(scr, &lv_font_montserrat_14, CLR_GRAY,
|
|
LV_ALIGN_TOP_RIGHT, -28, 350, "0%");
|
|
|
|
/* ────────────────────────────────────────────────────────────── */
|
|
/* CLOCK AREA (y = 385 … 490) */
|
|
/* ────────────────────────────────────────────────────────────── */
|
|
lbl_time = lv_label_create(scr);
|
|
lv_label_set_text(lbl_time, "--:--:--");
|
|
lv_obj_set_style_text_font(lbl_time, &lv_font_montserrat_40, 0);
|
|
lv_obj_set_style_text_color(lbl_time, lv_color_hex(CLR_WHITE), 0);
|
|
lv_obj_set_width(lbl_time, SCR_W);
|
|
lv_obj_set_style_text_align(lbl_time, LV_TEXT_ALIGN_CENTER, 0);
|
|
lv_obj_align(lbl_time, LV_ALIGN_TOP_MID, 0, 398);
|
|
|
|
lbl_date = lv_label_create(scr);
|
|
lv_label_set_text(lbl_date, "--- --, ----");
|
|
lv_obj_set_style_text_font(lbl_date, &lv_font_montserrat_18, 0);
|
|
lv_obj_set_style_text_color(lbl_date, lv_color_hex(CLR_GRAY), 0);
|
|
lv_obj_set_width(lbl_date, SCR_W);
|
|
lv_obj_set_style_text_align(lbl_date, LV_TEXT_ALIGN_CENTER, 0);
|
|
lv_obj_align(lbl_date, LV_ALIGN_TOP_MID, 0, 456);
|
|
|
|
/* ────────────────────────────────────────────────────────────── */
|
|
/* ERROR OVERLAY (hidden by default) */
|
|
/* ────────────────────────────────────────────────────────────── */
|
|
obj_error_bar = lv_obj_create(scr);
|
|
lv_obj_set_size(obj_error_bar, SCR_W - 40, LV_SIZE_CONTENT);
|
|
lv_obj_align(obj_error_bar, LV_ALIGN_BOTTOM_MID, 0, -6);
|
|
lv_obj_set_style_bg_color(obj_error_bar, lv_color_hex(CLR_ERROR_BG), 0);
|
|
lv_obj_set_style_bg_opa(obj_error_bar, LV_OPA_COVER, 0);
|
|
lv_obj_set_style_border_color(obj_error_bar, lv_color_hex(CLR_RED), 0);
|
|
lv_obj_set_style_border_width(obj_error_bar, 1, 0);
|
|
lv_obj_set_style_border_opa(obj_error_bar, LV_OPA_60, 0);
|
|
lv_obj_set_style_radius(obj_error_bar, 10, 0);
|
|
lv_obj_set_style_pad_all(obj_error_bar, 10, 0);
|
|
lv_obj_clear_flag(obj_error_bar, LV_OBJ_FLAG_SCROLLABLE);
|
|
lv_obj_add_flag(obj_error_bar, LV_OBJ_FLAG_HIDDEN);
|
|
|
|
lbl_error = lv_label_create(obj_error_bar);
|
|
lv_label_set_text(lbl_error, "");
|
|
lv_obj_set_width(lbl_error, SCR_W - 72);
|
|
lv_obj_set_style_text_font(lbl_error, &lv_font_montserrat_14, 0);
|
|
lv_obj_set_style_text_color(lbl_error, lv_color_hex(CLR_RED), 0);
|
|
lv_obj_set_style_text_align(lbl_error, LV_TEXT_ALIGN_CENTER, 0);
|
|
lv_label_set_long_mode(lbl_error, LV_LABEL_LONG_WRAP);
|
|
lv_obj_align(lbl_error, LV_ALIGN_CENTER, 0, 0);
|
|
}
|
|
|
|
/* ── ui_set_wifi ─────────────────────────────────────────────────── */
|
|
void ui_set_wifi(const char *ssid, const char *ip)
|
|
{
|
|
if (ssid && ssid[0]) {
|
|
lv_label_set_text_fmt(lbl_wifi, LV_SYMBOL_WIFI " %s", ssid);
|
|
lv_obj_set_style_text_color(lbl_wifi, lv_color_hex(CLR_GREEN), 0);
|
|
} else {
|
|
lv_label_set_text(lbl_wifi, LV_SYMBOL_WIFI " Disconnected");
|
|
lv_obj_set_style_text_color(lbl_wifi, lv_color_hex(CLR_RED), 0);
|
|
}
|
|
|
|
if (ip && ip[0]) {
|
|
lv_label_set_text(lbl_ip, ip);
|
|
snprintf(cached_ip, sizeof(cached_ip), "%s", ip);
|
|
} else {
|
|
lv_label_set_text(lbl_ip, "0.0.0.0");
|
|
snprintf(cached_ip, sizeof(cached_ip), "0.0.0.0");
|
|
}
|
|
|
|
/* keep FTP URL in sync */
|
|
lv_label_set_text_fmt(lbl_ftp_url, "ftp://%s", cached_ip);
|
|
}
|
|
|
|
/* ── ui_set_ftp ──────────────────────────────────────────────────── */
|
|
void ui_set_ftp(const char *status)
|
|
{
|
|
if (!status) return;
|
|
|
|
lv_label_set_text(lbl_ftp_status, status);
|
|
|
|
/* colour the dot + text based on status keyword */
|
|
uint32_t clr = CLR_GREEN; /* default: Idle */
|
|
if (strstr(status, "Connect")) clr = CLR_BLUE;
|
|
if (strstr(status, "Transfer")) clr = CLR_PURPLE;
|
|
if (strstr(status, "Error")) clr = CLR_RED;
|
|
|
|
lv_obj_set_style_bg_color(dot_ftp_status, lv_color_hex(clr), 0);
|
|
lv_obj_set_style_text_color(lbl_ftp_status, lv_color_hex(clr), 0);
|
|
}
|
|
|
|
/* ── ui_set_time ─────────────────────────────────────────────────── */
|
|
void ui_set_time(const char *time_str)
|
|
{
|
|
lv_label_set_text(lbl_time, time_str);
|
|
}
|
|
|
|
/* ── ui_set_date ─────────────────────────────────────────────────── */
|
|
void ui_set_date(const char *date_str)
|
|
{
|
|
lv_label_set_text(lbl_date, date_str);
|
|
}
|
|
|
|
/* ── ui_set_sd ───────────────────────────────────────────────────── */
|
|
void ui_set_sd(uint64_t used_mb, uint64_t total_mb)
|
|
{
|
|
int pct = 0;
|
|
if (total_mb > 0) {
|
|
pct = (int)((used_mb * 100ULL) / total_mb);
|
|
if (pct > 100) pct = 100;
|
|
}
|
|
lv_bar_set_value(bar_sd, pct, LV_ANIM_OFF);
|
|
|
|
/* Format with one decimal using integer math (LVGL builtin sprintf
|
|
may lack %f support) */
|
|
uint32_t used_gb_i = (uint32_t)(used_mb / 1024);
|
|
uint32_t used_gb_f = (uint32_t)((used_mb % 1024) * 10 / 1024);
|
|
uint32_t total_gb_i = (uint32_t)(total_mb / 1024);
|
|
uint32_t total_gb_f = (uint32_t)((total_mb % 1024) * 10 / 1024);
|
|
lv_label_set_text_fmt(lbl_sd_info, "%lu.%lu / %lu.%lu GB",
|
|
used_gb_i, used_gb_f, total_gb_i, total_gb_f);
|
|
lv_label_set_text_fmt(lbl_sd_pct, "%d%%", pct);
|
|
|
|
/* bar turns red when storage > 90% */
|
|
if (pct > 90) {
|
|
lv_obj_set_style_bg_color(bar_sd, lv_color_hex(CLR_RED),
|
|
LV_PART_INDICATOR);
|
|
} else {
|
|
lv_obj_set_style_bg_color(bar_sd, lv_color_hex(CLR_BAR_FILL),
|
|
LV_PART_INDICATOR);
|
|
}
|
|
}
|
|
|
|
/* ── ui_set_error ────────────────────────────────────────────────── */
|
|
void ui_set_error(const char *msg)
|
|
{
|
|
if (msg && msg[0]) {
|
|
lv_label_set_text(lbl_error, msg);
|
|
lv_obj_clear_flag(obj_error_bar, LV_OBJ_FLAG_HIDDEN);
|
|
} else {
|
|
lv_label_set_text(lbl_error, "");
|
|
lv_obj_add_flag(obj_error_bar, LV_OBJ_FLAG_HIDDEN);
|
|
}
|
|
}
|