Files
KodeDot-FTP-Explorer/src/ui.cpp
T
Lago 44b6a89b57 fix: FTP storage backend, SD info display, time label alignment
- 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
2026-04-03 15:41:26 +02:00

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);
}
}