#include "ui.h" #include /* ── 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); } }