Collapse UX to single Usage view + hold-to-pair + status line
Removes the Bluetooth screen — the device now has just splash + Usage. Keeps main's responsive Layout intact. - ui: drop the Bluetooth screen, ui_cycle_screen, and SCREEN_BLUETOOTH. The bottom spinner becomes a status line — Connected / Disconnected / Pairing… (via ble_has_bonds()) plus the whimsical messages when connected and idle. A "To pair / hold the power button / for 3 seconds, then release" hint shows while disconnected so the screen isn't empty. - main: hold-to-pair gesture (pair_tick) — hold PWR ~3s then RELEASE clears bonds and re-advertises. Clearing on release (not while held) means holding to power off (8s hardware shutdown) leaves the bond intact; disarms at 6s. PWR short-press now cycles brightness on the Usage view (animations on splash). - ble: add ble_has_bonds() for the status line. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.7
parent
ddb812ab5d
commit
2b4a0802fa
@@ -236,6 +236,10 @@ void ble_clear_bonds(void) {
|
|||||||
need_advertise = true;
|
need_advertise = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool ble_has_bonds(void) {
|
||||||
|
return NimBLEDevice::getNumBonds() > 0;
|
||||||
|
}
|
||||||
|
|
||||||
bool ble_has_data(void) {
|
bool ble_has_data(void) {
|
||||||
return data_ready;
|
return data_ready;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ ble_state_t ble_get_state(void);
|
|||||||
const char* ble_get_device_name(void);
|
const char* ble_get_device_name(void);
|
||||||
const char* ble_get_mac_address(void);
|
const char* ble_get_mac_address(void);
|
||||||
void ble_clear_bonds(void);
|
void ble_clear_bonds(void);
|
||||||
|
bool ble_has_bonds(void);
|
||||||
bool ble_has_data(void);
|
bool ble_has_data(void);
|
||||||
const char* ble_get_data(void);
|
const char* ble_get_data(void);
|
||||||
void ble_send_ack(void);
|
void ble_send_ack(void);
|
||||||
|
|||||||
+56
-3
@@ -11,6 +11,7 @@
|
|||||||
#include "usage_rate.h"
|
#include "usage_rate.h"
|
||||||
#include "idle.h"
|
#include "idle.h"
|
||||||
#include "idle_cfg.h"
|
#include "idle_cfg.h"
|
||||||
|
#include "brightness.h"
|
||||||
|
|
||||||
#include "hal/board_caps.h"
|
#include "hal/board_caps.h"
|
||||||
#include "hal/display_hal.h"
|
#include "hal/display_hal.h"
|
||||||
@@ -185,7 +186,8 @@ void setup() {
|
|||||||
|
|
||||||
display_hal_init();
|
display_hal_init();
|
||||||
display_hal_begin();
|
display_hal_begin();
|
||||||
idle_init(); // takes over brightness (DISPLAY_DEFAULT_BRIGHTNESS) and starts the idle timer
|
idle_init(); // takes over panel brightness and starts the idle timer
|
||||||
|
brightness_init(); // load the user's saved brightness level and apply via idle
|
||||||
|
|
||||||
power_hal_init();
|
power_hal_init();
|
||||||
imu_hal_init();
|
imu_hal_init();
|
||||||
@@ -226,6 +228,52 @@ void setup() {
|
|||||||
|
|
||||||
static ble_state_t last_ble_state = BLE_STATE_INIT;
|
static ble_state_t last_ble_state = BLE_STATE_INIT;
|
||||||
|
|
||||||
|
// Hold-to-pair gesture: hold the PWR button ~3s, then RELEASE → clear all BLE
|
||||||
|
// bonds and re-advertise. Clearing on *release* (not while held) is deliberate:
|
||||||
|
// holding to power the device OFF (AXP hardware shutdown at 8s) must not wipe
|
||||||
|
// the bond — a power-off hold never releases before shutdown. To stop a
|
||||||
|
// "chicken-out" release just before 8s from pairing, the gesture disarms at 6s.
|
||||||
|
//
|
||||||
|
// ~1.5s long-press edge → PENDING
|
||||||
|
// 3.0s (+1500) → ARMED (release from here clears bonds)
|
||||||
|
// 6.0s (+4500) → DISARMED (no clear; AXP powers off at 8s)
|
||||||
|
#define PAIR_ARM_AFTER_LONG_MS 1500 // 3.0s total
|
||||||
|
#define PAIR_DISARM_AFTER_LONG_MS 4500 // 6.0s total
|
||||||
|
enum pair_state_t { PAIR_IDLE, PAIR_PENDING, PAIR_ARMED };
|
||||||
|
static pair_state_t pair_state = PAIR_IDLE;
|
||||||
|
static uint32_t pair_long_seen_ms = 0;
|
||||||
|
|
||||||
|
static void pair_tick(void) {
|
||||||
|
if (pair_state == PAIR_IDLE && power_hal_pwr_long_pressed()) {
|
||||||
|
pair_state = PAIR_PENDING;
|
||||||
|
pair_long_seen_ms = millis();
|
||||||
|
(void)power_hal_pwr_released(); // drain any stale release edge
|
||||||
|
Serial.println("PWR long-press: hold to ~3s then release to pair");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (pair_state == PAIR_IDLE) return;
|
||||||
|
|
||||||
|
if (power_hal_pwr_released()) {
|
||||||
|
if (pair_state == PAIR_ARMED) {
|
||||||
|
Serial.println("Pair: released in window — clearing bonds, advertising");
|
||||||
|
ble_clear_bonds();
|
||||||
|
} else {
|
||||||
|
Serial.println("Pair: released too early — cancelled");
|
||||||
|
}
|
||||||
|
pair_state = PAIR_IDLE;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t held = millis() - pair_long_seen_ms;
|
||||||
|
if (pair_state == PAIR_PENDING && held >= PAIR_ARM_AFTER_LONG_MS) {
|
||||||
|
pair_state = PAIR_ARMED;
|
||||||
|
Serial.println("Pair: armed — release to pair");
|
||||||
|
} else if (pair_state == PAIR_ARMED && held >= PAIR_DISARM_AFTER_LONG_MS) {
|
||||||
|
pair_state = PAIR_IDLE; // power-off territory; don't pair
|
||||||
|
Serial.println("Pair: disarmed (holding toward power-off)");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void loop() {
|
void loop() {
|
||||||
idle_tick();
|
idle_tick();
|
||||||
lv_timer_handler();
|
lv_timer_handler();
|
||||||
@@ -242,7 +290,8 @@ void loop() {
|
|||||||
// ---- Physical buttons ----
|
// ---- Physical buttons ----
|
||||||
// PRIMARY → HID Space (Claude Code voice-mode PTT)
|
// PRIMARY → HID Space (Claude Code voice-mode PTT)
|
||||||
// SECONDARY → HID Shift+Tab (mode toggle; only if the board has one)
|
// SECONDARY → HID Shift+Tab (mode toggle; only if the board has one)
|
||||||
// PWR → cycle screens; on splash, cycle animations
|
// PWR → on splash: cycle animations; on usage: cycle brightness;
|
||||||
|
// hold ~3s + release: pairing mode
|
||||||
// First press from sleep is consumed as a wake-only event by
|
// First press from sleep is consumed as a wake-only event by
|
||||||
// idle_consume_wake_press(); the normal action fires from the second
|
// idle_consume_wake_press(); the normal action fires from the second
|
||||||
// press. Activity bookkeeping happens inside idle_consume_wake_press
|
// press. Activity bookkeeping happens inside idle_consume_wake_press
|
||||||
@@ -280,10 +329,14 @@ void loop() {
|
|||||||
|
|
||||||
if (power_hal_pwr_pressed()) {
|
if (power_hal_pwr_pressed()) {
|
||||||
if (!idle_consume_wake_press()) {
|
if (!idle_consume_wake_press()) {
|
||||||
|
// On splash: cycle animations. On the usage view: cycle
|
||||||
|
// screen brightness (single non-splash view, no more screens).
|
||||||
if (ui_get_current_screen() == SCREEN_SPLASH) splash_next();
|
if (ui_get_current_screen() == SCREEN_SPLASH) splash_next();
|
||||||
else ui_cycle_screen();
|
else brightness_cycle();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pair_tick();
|
||||||
}
|
}
|
||||||
|
|
||||||
ble_state_t bs = ble_get_state();
|
ble_state_t bs = ble_get_state();
|
||||||
|
|||||||
+83
-154
@@ -99,9 +99,11 @@ static void compute_layout(const BoardCaps& c) {
|
|||||||
#define COL_RED THEME_RED
|
#define COL_RED THEME_RED
|
||||||
#define COL_BAR_BG THEME_BAR_BG
|
#define COL_BAR_BG THEME_BAR_BG
|
||||||
|
|
||||||
// ---- Usage screen widgets ----
|
// ---- Usage screen widgets (single non-splash view) ----
|
||||||
static lv_obj_t* usage_container;
|
static lv_obj_t* usage_container;
|
||||||
static lv_obj_t* lbl_title;
|
static lv_obj_t* lbl_title;
|
||||||
|
static lv_obj_t* usage_group; // the two usage panels — shown when connected
|
||||||
|
static lv_obj_t* pair_group; // pairing hint — shown when disconnected
|
||||||
static lv_obj_t* bar_session;
|
static lv_obj_t* bar_session;
|
||||||
static lv_obj_t* lbl_session_pct;
|
static lv_obj_t* lbl_session_pct;
|
||||||
static lv_obj_t* lbl_session_label;
|
static lv_obj_t* lbl_session_label;
|
||||||
@@ -110,13 +112,7 @@ static lv_obj_t* bar_weekly;
|
|||||||
static lv_obj_t* lbl_weekly_pct;
|
static lv_obj_t* lbl_weekly_pct;
|
||||||
static lv_obj_t* lbl_weekly_label;
|
static lv_obj_t* lbl_weekly_label;
|
||||||
static lv_obj_t* lbl_weekly_reset;
|
static lv_obj_t* lbl_weekly_reset;
|
||||||
static lv_obj_t* lbl_anim;
|
static lv_obj_t* lbl_anim; // status line: connection state + whimsical idle
|
||||||
|
|
||||||
// ---- Bluetooth screen widgets ----
|
|
||||||
static lv_obj_t* ble_container;
|
|
||||||
static lv_obj_t* lbl_ble_status;
|
|
||||||
static lv_obj_t* lbl_ble_device;
|
|
||||||
static lv_obj_t* lbl_ble_mac;
|
|
||||||
|
|
||||||
// ---- Battery indicator (shared, on top) ----
|
// ---- Battery indicator (shared, on top) ----
|
||||||
static lv_obj_t* battery_img;
|
static lv_obj_t* battery_img;
|
||||||
@@ -126,6 +122,8 @@ static lv_image_dsc_t battery_dscs[5]; // empty, low, medium, full, charging
|
|||||||
// ---- Shared ----
|
// ---- Shared ----
|
||||||
static lv_image_dsc_t logo_dsc;
|
static lv_image_dsc_t logo_dsc;
|
||||||
static screen_t current_screen = SCREEN_USAGE;
|
static screen_t current_screen = SCREEN_USAGE;
|
||||||
|
static bool s_ble_connected = false; // cached BLE connection state
|
||||||
|
static uint32_t connected_at_ms = 0; // when we last entered CONNECTED ("Connected" dwell)
|
||||||
|
|
||||||
// Animation state
|
// Animation state
|
||||||
static uint32_t anim_last_ms = 0;
|
static uint32_t anim_last_ms = 0;
|
||||||
@@ -201,7 +199,6 @@ static void format_reset_time(int mins, char* buf, size_t len) {
|
|||||||
|
|
||||||
// Forward decls — callbacks defined near ui_show_screen below
|
// Forward decls — callbacks defined near ui_show_screen below
|
||||||
static void global_click_cb(lv_event_t* e);
|
static void global_click_cb(lv_event_t* e);
|
||||||
static void ble_reset_click_cb(lv_event_t* e);
|
|
||||||
|
|
||||||
static lv_obj_t* make_panel(lv_obj_t* parent, int x, int y, int w, int h) {
|
static lv_obj_t* make_panel(lv_obj_t* parent, int x, int y, int w, int h) {
|
||||||
lv_obj_t* panel = lv_obj_create(parent);
|
lv_obj_t* panel = lv_obj_create(parent);
|
||||||
@@ -235,15 +232,6 @@ static lv_obj_t* make_bar(lv_obj_t* parent, int x, int y, int w, int h) {
|
|||||||
return bar;
|
return bar;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void init_icon_dsc(lv_image_dsc_t* dsc, int w, int h, const uint16_t* data) {
|
|
||||||
dsc->header.w = w;
|
|
||||||
dsc->header.h = h;
|
|
||||||
dsc->header.cf = LV_COLOR_FORMAT_RGB565;
|
|
||||||
dsc->header.stride = w * 2;
|
|
||||||
dsc->data = (const uint8_t*)data;
|
|
||||||
dsc->data_size = w * h * 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void init_icon_dsc_rgb565a8(lv_image_dsc_t* dsc, int w, int h, const uint8_t* data) {
|
static void init_icon_dsc_rgb565a8(lv_image_dsc_t* dsc, int w, int h, const uint8_t* data) {
|
||||||
dsc->header.w = w;
|
dsc->header.w = w;
|
||||||
dsc->header.h = h;
|
dsc->header.h = h;
|
||||||
@@ -301,6 +289,39 @@ static void make_usage_panel(lv_obj_t* parent, int y, const char* pill_text,
|
|||||||
lv_obj_set_pos(*out_reset, 0, L.usage_reset_y);
|
lv_obj_set_pos(*out_reset, 0, L.usage_reset_y);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Pairing hint — shown when disconnected so the screen isn't empty and the
|
||||||
|
// user knows how to (re)pair. Wording matches the 3-second release gesture.
|
||||||
|
static void build_pair_group(lv_obj_t* parent) {
|
||||||
|
pair_group = lv_obj_create(parent);
|
||||||
|
lv_obj_set_size(pair_group, L.scr_w, L.scr_h - L.content_y);
|
||||||
|
lv_obj_set_pos(pair_group, 0, L.content_y);
|
||||||
|
lv_obj_set_style_bg_opa(pair_group, LV_OPA_TRANSP, 0);
|
||||||
|
lv_obj_set_style_border_width(pair_group, 0, 0);
|
||||||
|
lv_obj_set_style_pad_all(pair_group, 0, 0);
|
||||||
|
lv_obj_clear_flag(pair_group, LV_OBJ_FLAG_SCROLLABLE);
|
||||||
|
lv_obj_add_flag(pair_group, LV_OBJ_FLAG_EVENT_BUBBLE);
|
||||||
|
|
||||||
|
lv_obj_t* l1 = lv_label_create(pair_group);
|
||||||
|
lv_label_set_text(l1, "To pair");
|
||||||
|
lv_obj_set_style_text_font(l1, L.bt_status_font, 0);
|
||||||
|
lv_obj_set_style_text_color(l1, COL_TEXT, 0);
|
||||||
|
lv_obj_align(l1, LV_ALIGN_TOP_MID, 0, 40);
|
||||||
|
|
||||||
|
lv_obj_t* l2 = lv_label_create(pair_group);
|
||||||
|
lv_label_set_text(l2, "hold the power button");
|
||||||
|
lv_obj_set_style_text_font(l2, L.bt_device_font, 0);
|
||||||
|
lv_obj_set_style_text_color(l2, COL_DIM, 0);
|
||||||
|
lv_obj_align(l2, LV_ALIGN_TOP_MID, 0, 120);
|
||||||
|
|
||||||
|
lv_obj_t* l3 = lv_label_create(pair_group);
|
||||||
|
lv_label_set_text(l3, "for 3 seconds, then release");
|
||||||
|
lv_obj_set_style_text_font(l3, L.bt_device_font, 0);
|
||||||
|
lv_obj_set_style_text_color(l3, COL_DIM, 0);
|
||||||
|
lv_obj_align(l3, LV_ALIGN_TOP_MID, 0, 160);
|
||||||
|
|
||||||
|
lv_obj_add_flag(pair_group, LV_OBJ_FLAG_HIDDEN); // ui_update_ble_status decides
|
||||||
|
}
|
||||||
|
|
||||||
static void init_usage_screen(lv_obj_t* scr) {
|
static void init_usage_screen(lv_obj_t* scr) {
|
||||||
usage_container = lv_obj_create(scr);
|
usage_container = lv_obj_create(scr);
|
||||||
lv_obj_set_size(usage_container, L.scr_w, L.scr_h);
|
lv_obj_set_size(usage_container, L.scr_w, L.scr_h);
|
||||||
@@ -317,14 +338,28 @@ static void init_usage_screen(lv_obj_t* scr) {
|
|||||||
lv_obj_set_style_text_color(lbl_title, COL_TEXT, 0);
|
lv_obj_set_style_text_color(lbl_title, COL_TEXT, 0);
|
||||||
lv_obj_align(lbl_title, LV_ALIGN_TOP_MID, 16, L.title_y);
|
lv_obj_align(lbl_title, LV_ALIGN_TOP_MID, 16, L.title_y);
|
||||||
|
|
||||||
make_usage_panel(usage_container, L.content_y, "Current",
|
// Usage panels (shown when connected) live in a transparent full-size group
|
||||||
|
// so they can be toggled against the pairing hint as one unit.
|
||||||
|
usage_group = lv_obj_create(usage_container);
|
||||||
|
lv_obj_set_size(usage_group, L.scr_w, L.scr_h);
|
||||||
|
lv_obj_set_pos(usage_group, 0, 0);
|
||||||
|
lv_obj_set_style_bg_opa(usage_group, LV_OPA_TRANSP, 0);
|
||||||
|
lv_obj_set_style_border_width(usage_group, 0, 0);
|
||||||
|
lv_obj_set_style_pad_all(usage_group, 0, 0);
|
||||||
|
lv_obj_clear_flag(usage_group, LV_OBJ_FLAG_SCROLLABLE);
|
||||||
|
lv_obj_add_flag(usage_group, LV_OBJ_FLAG_EVENT_BUBBLE);
|
||||||
|
|
||||||
|
make_usage_panel(usage_group, L.content_y, "Current",
|
||||||
&lbl_session_pct, &lbl_session_label,
|
&lbl_session_pct, &lbl_session_label,
|
||||||
&bar_session, &lbl_session_reset);
|
&bar_session, &lbl_session_reset);
|
||||||
make_usage_panel(usage_container,
|
make_usage_panel(usage_group,
|
||||||
L.content_y + L.usage_panel_h + L.usage_panel_gap, "Weekly",
|
L.content_y + L.usage_panel_h + L.usage_panel_gap, "Weekly",
|
||||||
&lbl_weekly_pct, &lbl_weekly_label,
|
&lbl_weekly_pct, &lbl_weekly_label,
|
||||||
&bar_weekly, &lbl_weekly_reset);
|
&bar_weekly, &lbl_weekly_reset);
|
||||||
|
|
||||||
|
build_pair_group(usage_container);
|
||||||
|
|
||||||
|
// Status line — always visible on the usage view. Driven by ui_tick_anim().
|
||||||
lbl_anim = lv_label_create(usage_container);
|
lbl_anim = lv_label_create(usage_container);
|
||||||
lv_label_set_text(lbl_anim, "");
|
lv_label_set_text(lbl_anim, "");
|
||||||
lv_obj_set_style_text_font(lbl_anim, &font_mono_32, 0);
|
lv_obj_set_style_text_font(lbl_anim, &font_mono_32, 0);
|
||||||
@@ -332,91 +367,6 @@ static void init_usage_screen(lv_obj_t* scr) {
|
|||||||
lv_obj_align(lbl_anim, LV_ALIGN_BOTTOM_MID, 0, -15);
|
lv_obj_align(lbl_anim, LV_ALIGN_BOTTOM_MID, 0, -15);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ======== Bluetooth Screen ========
|
|
||||||
|
|
||||||
static void init_bluetooth_screen(lv_obj_t* scr) {
|
|
||||||
ble_container = lv_obj_create(scr);
|
|
||||||
lv_obj_set_size(ble_container, L.scr_w, L.scr_h);
|
|
||||||
lv_obj_set_pos(ble_container, 0, 0);
|
|
||||||
lv_obj_set_style_bg_opa(ble_container, LV_OPA_TRANSP, 0);
|
|
||||||
lv_obj_set_style_border_width(ble_container, 0, 0);
|
|
||||||
lv_obj_set_style_pad_all(ble_container, 0, 0);
|
|
||||||
lv_obj_clear_flag(ble_container, LV_OBJ_FLAG_SCROLLABLE);
|
|
||||||
lv_obj_add_event_cb(ble_container, global_click_cb, LV_EVENT_CLICKED, NULL);
|
|
||||||
|
|
||||||
lv_obj_t* lbl_ble_title = lv_label_create(ble_container);
|
|
||||||
lv_label_set_text(lbl_ble_title, "Bluetooth");
|
|
||||||
lv_obj_set_style_text_font(lbl_ble_title, L.bt_title_font, 0);
|
|
||||||
lv_obj_set_style_text_color(lbl_ble_title, COL_TEXT, 0);
|
|
||||||
lv_obj_align(lbl_ble_title, LV_ALIGN_TOP_MID, 16, L.title_y);
|
|
||||||
|
|
||||||
lv_obj_t* p_info = make_panel(ble_container, L.margin, L.content_y,
|
|
||||||
L.content_w, L.bt_info_panel_h);
|
|
||||||
|
|
||||||
static lv_image_dsc_t icon_bt_dsc;
|
|
||||||
init_icon_dsc(&icon_bt_dsc, ICON_BLUETOOTH_W, ICON_BLUETOOTH_H, icon_bluetooth_data);
|
|
||||||
|
|
||||||
lv_obj_t* bt_img = lv_image_create(p_info);
|
|
||||||
lv_image_set_src(bt_img, &icon_bt_dsc);
|
|
||||||
lv_obj_set_pos(bt_img, 0, 0);
|
|
||||||
|
|
||||||
lbl_ble_status = lv_label_create(p_info);
|
|
||||||
lv_label_set_text(lbl_ble_status, "Initializing...");
|
|
||||||
lv_obj_set_style_text_font(lbl_ble_status, L.bt_status_font, 0);
|
|
||||||
lv_obj_set_style_text_color(lbl_ble_status, COL_DIM, 0);
|
|
||||||
lv_obj_set_pos(lbl_ble_status, 56, 2);
|
|
||||||
|
|
||||||
lbl_ble_device = lv_label_create(p_info);
|
|
||||||
lv_label_set_text(lbl_ble_device, "Device: ---");
|
|
||||||
lv_obj_set_style_text_font(lbl_ble_device, L.bt_device_font, 0);
|
|
||||||
lv_obj_set_style_text_color(lbl_ble_device, COL_DIM, 0);
|
|
||||||
lv_obj_set_pos(lbl_ble_device, 0, 64);
|
|
||||||
|
|
||||||
lbl_ble_mac = lv_label_create(p_info);
|
|
||||||
lv_label_set_text(lbl_ble_mac, "Address: ---");
|
|
||||||
lv_obj_set_style_text_font(lbl_ble_mac, L.bt_device_font, 0);
|
|
||||||
lv_obj_set_style_text_color(lbl_ble_mac, COL_DIM, 0);
|
|
||||||
lv_obj_set_pos(lbl_ble_mac, 0, 100);
|
|
||||||
|
|
||||||
int reset_y = L.content_y + L.bt_info_panel_h + 16;
|
|
||||||
lv_obj_t* reset_zone = lv_obj_create(ble_container);
|
|
||||||
lv_obj_set_pos(reset_zone, L.margin, reset_y);
|
|
||||||
lv_obj_set_size(reset_zone, L.content_w, L.bt_reset_zone_h);
|
|
||||||
lv_obj_set_style_bg_color(reset_zone, COL_PANEL, 0);
|
|
||||||
lv_obj_set_style_bg_opa(reset_zone, LV_OPA_COVER, 0);
|
|
||||||
lv_obj_set_style_radius(reset_zone, 8, 0);
|
|
||||||
lv_obj_set_style_border_width(reset_zone, 0, 0);
|
|
||||||
lv_obj_set_style_pad_column(reset_zone, 14, 0);
|
|
||||||
lv_obj_set_flex_flow(reset_zone, LV_FLEX_FLOW_ROW);
|
|
||||||
lv_obj_set_flex_align(reset_zone, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER);
|
|
||||||
lv_obj_clear_flag(reset_zone, LV_OBJ_FLAG_SCROLLABLE);
|
|
||||||
lv_obj_add_event_cb(reset_zone, ble_reset_click_cb, LV_EVENT_CLICKED, NULL);
|
|
||||||
|
|
||||||
static lv_image_dsc_t icon_trash_dsc;
|
|
||||||
init_icon_dsc(&icon_trash_dsc, ICON_TRASH2_W, ICON_TRASH2_H, icon_trash2_data);
|
|
||||||
lv_obj_t* trash_img = lv_image_create(reset_zone);
|
|
||||||
lv_image_set_src(trash_img, &icon_trash_dsc);
|
|
||||||
|
|
||||||
lv_obj_t* reset_lbl = lv_label_create(reset_zone);
|
|
||||||
lv_label_set_text(reset_lbl, "Reset Bluetooth");
|
|
||||||
lv_obj_set_style_text_font(reset_lbl, L.bt_device_font, 0);
|
|
||||||
lv_obj_set_style_text_color(reset_lbl, COL_DIM, 0);
|
|
||||||
|
|
||||||
lv_obj_t* lbl_credit = lv_label_create(ble_container);
|
|
||||||
lv_label_set_text(lbl_credit, "Built by @hermannbjorgvin");
|
|
||||||
lv_obj_set_style_text_font(lbl_credit, L.bt_credit_1_font, 0);
|
|
||||||
lv_obj_set_style_text_color(lbl_credit, COL_DIM, 0);
|
|
||||||
lv_obj_align(lbl_credit, LV_ALIGN_BOTTOM_MID, 0, -46);
|
|
||||||
|
|
||||||
lv_obj_t* lbl_credit2 = lv_label_create(ble_container);
|
|
||||||
lv_label_set_text(lbl_credit2, "Clawd animation by @amaanbuilds");
|
|
||||||
lv_obj_set_style_text_font(lbl_credit2, L.bt_credit_2_font, 0);
|
|
||||||
lv_obj_set_style_text_color(lbl_credit2, COL_DIM, 0);
|
|
||||||
lv_obj_align(lbl_credit2, LV_ALIGN_BOTTOM_MID, 0, -20);
|
|
||||||
|
|
||||||
lv_obj_add_flag(ble_container, LV_OBJ_FLAG_HIDDEN);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ======== Public API ========
|
// ======== Public API ========
|
||||||
|
|
||||||
void ui_init(void) {
|
void ui_init(void) {
|
||||||
@@ -430,7 +380,6 @@ void ui_init(void) {
|
|||||||
init_battery_icons();
|
init_battery_icons();
|
||||||
|
|
||||||
init_usage_screen(scr);
|
init_usage_screen(scr);
|
||||||
init_bluetooth_screen(scr);
|
|
||||||
splash_init(scr);
|
splash_init(scr);
|
||||||
|
|
||||||
if (splash_get_root()) {
|
if (splash_get_root()) {
|
||||||
@@ -478,18 +427,27 @@ void ui_tick_anim(void) {
|
|||||||
anim_msg_start = now;
|
anim_msg_start = now;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (now - anim_last_ms >= spinner_ms[anim_spinner_idx]) {
|
if (now - anim_last_ms < spinner_ms[anim_spinner_idx]) return;
|
||||||
anim_last_ms = now;
|
anim_last_ms = now;
|
||||||
anim_phase = (anim_phase + 1) % SPINNER_PHASES;
|
anim_phase = (anim_phase + 1) % SPINNER_PHASES;
|
||||||
anim_spinner_idx = (anim_phase < SPINNER_COUNT) ? anim_phase
|
anim_spinner_idx = (anim_phase < SPINNER_COUNT) ? anim_phase
|
||||||
: (SPINNER_PHASES - anim_phase);
|
: (SPINNER_PHASES - anim_phase);
|
||||||
|
|
||||||
|
// Status text by priority. Whimsical messages only when connected & settled.
|
||||||
|
const char* text;
|
||||||
|
if (!s_ble_connected) {
|
||||||
|
text = ble_has_bonds() ? "Disconnected" : "Pairing";
|
||||||
|
} else if (now - connected_at_ms < 5000) {
|
||||||
|
text = "Connected";
|
||||||
|
} else {
|
||||||
|
text = anim_messages[anim_msg_idx];
|
||||||
|
}
|
||||||
|
|
||||||
|
// All states share the whimsical style: "<glyph> <Title-case word>…"
|
||||||
static char buf[80];
|
static char buf[80];
|
||||||
snprintf(buf, sizeof(buf), "%s %s\xE2\x80\xA6",
|
snprintf(buf, sizeof(buf), "%s %s\xE2\x80\xA6",
|
||||||
spinner_frames[anim_spinner_idx],
|
spinner_frames[anim_spinner_idx], text);
|
||||||
anim_messages[anim_msg_idx]);
|
|
||||||
lv_label_set_text(lbl_anim, buf);
|
lv_label_set_text(lbl_anim, buf);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static screen_t prev_non_splash_screen = SCREEN_USAGE;
|
static screen_t prev_non_splash_screen = SCREEN_USAGE;
|
||||||
@@ -505,20 +463,13 @@ static void global_click_cb(lv_event_t* e) {
|
|||||||
else ui_show_screen(SCREEN_SPLASH);
|
else ui_show_screen(SCREEN_SPLASH);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void ble_reset_click_cb(lv_event_t* e) {
|
|
||||||
(void)e;
|
|
||||||
ble_clear_bonds();
|
|
||||||
}
|
|
||||||
|
|
||||||
void ui_show_screen(screen_t screen) {
|
void ui_show_screen(screen_t screen) {
|
||||||
lv_obj_add_flag(usage_container, LV_OBJ_FLAG_HIDDEN);
|
lv_obj_add_flag(usage_container, LV_OBJ_FLAG_HIDDEN);
|
||||||
lv_obj_add_flag(ble_container, LV_OBJ_FLAG_HIDDEN);
|
|
||||||
splash_hide();
|
splash_hide();
|
||||||
|
|
||||||
switch (screen) {
|
switch (screen) {
|
||||||
case SCREEN_SPLASH: splash_show(); break;
|
case SCREEN_SPLASH: splash_show(); break;
|
||||||
case SCREEN_USAGE: lv_obj_clear_flag(usage_container, LV_OBJ_FLAG_HIDDEN); break;
|
case SCREEN_USAGE: lv_obj_clear_flag(usage_container, LV_OBJ_FLAG_HIDDEN); break;
|
||||||
case SCREEN_BLUETOOTH: lv_obj_clear_flag(ble_container, LV_OBJ_FLAG_HIDDEN); break;
|
|
||||||
default: break;
|
default: break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -532,16 +483,6 @@ void ui_show_screen(screen_t screen) {
|
|||||||
apply_battery_visibility();
|
apply_battery_visibility();
|
||||||
}
|
}
|
||||||
|
|
||||||
void ui_cycle_screen(void) {
|
|
||||||
screen_t next;
|
|
||||||
switch (current_screen) {
|
|
||||||
case SCREEN_USAGE: next = SCREEN_BLUETOOTH; break;
|
|
||||||
case SCREEN_BLUETOOTH: next = SCREEN_USAGE; break;
|
|
||||||
default: next = SCREEN_USAGE; break;
|
|
||||||
}
|
|
||||||
ui_show_screen(next);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ui_toggle_splash(void) {
|
void ui_toggle_splash(void) {
|
||||||
if (current_screen == SCREEN_SPLASH) ui_show_screen(prev_non_splash_screen);
|
if (current_screen == SCREEN_SPLASH) ui_show_screen(prev_non_splash_screen);
|
||||||
else ui_show_screen(SCREEN_SPLASH);
|
else ui_show_screen(SCREEN_SPLASH);
|
||||||
@@ -552,35 +493,23 @@ screen_t ui_get_current_screen(void) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void ui_update_ble_status(ble_state_t state, const char* name, const char* mac) {
|
void ui_update_ble_status(ble_state_t state, const char* name, const char* mac) {
|
||||||
switch (state) {
|
(void)name; (void)mac;
|
||||||
case BLE_STATE_CONNECTED:
|
bool was_connected = s_ble_connected;
|
||||||
lv_label_set_text(lbl_ble_status, "Connected");
|
s_ble_connected = (state == BLE_STATE_CONNECTED);
|
||||||
lv_obj_set_style_text_color(lbl_ble_status, COL_GREEN, 0);
|
|
||||||
break;
|
// Connected → usage panels; otherwise → pairing hint. The bottom status
|
||||||
case BLE_STATE_ADVERTISING:
|
// line carries the live state word (Connected / Disconnected / Pairing).
|
||||||
lv_label_set_text(lbl_ble_status, "Advertising...");
|
if (usage_group && pair_group) {
|
||||||
lv_obj_set_style_text_color(lbl_ble_status, COL_AMBER, 0);
|
if (s_ble_connected) {
|
||||||
break;
|
lv_obj_clear_flag(usage_group, LV_OBJ_FLAG_HIDDEN);
|
||||||
case BLE_STATE_DISCONNECTED:
|
lv_obj_add_flag(pair_group, LV_OBJ_FLAG_HIDDEN);
|
||||||
lv_label_set_text(lbl_ble_status, "Disconnected");
|
} else {
|
||||||
lv_obj_set_style_text_color(lbl_ble_status, COL_RED, 0);
|
lv_obj_add_flag(usage_group, LV_OBJ_FLAG_HIDDEN);
|
||||||
break;
|
lv_obj_clear_flag(pair_group, LV_OBJ_FLAG_HIDDEN);
|
||||||
default:
|
}
|
||||||
lv_label_set_text(lbl_ble_status, "Initializing...");
|
|
||||||
lv_obj_set_style_text_color(lbl_ble_status, COL_DIM, 0);
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (name) {
|
if (s_ble_connected && !was_connected) connected_at_ms = lv_tick_get();
|
||||||
static char nbuf[48];
|
|
||||||
snprintf(nbuf, sizeof(nbuf), "Device: %s", name);
|
|
||||||
lv_label_set_text(lbl_ble_device, nbuf);
|
|
||||||
}
|
|
||||||
if (mac) {
|
|
||||||
static char mbuf[48];
|
|
||||||
snprintf(mbuf, sizeof(mbuf), "Address: %s", mac);
|
|
||||||
lv_label_set_text(lbl_ble_mac, mbuf);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ui_update_battery(int percent, bool charging) {
|
void ui_update_battery(int percent, bool charging) {
|
||||||
|
|||||||
@@ -5,7 +5,6 @@
|
|||||||
enum screen_t {
|
enum screen_t {
|
||||||
SCREEN_SPLASH,
|
SCREEN_SPLASH,
|
||||||
SCREEN_USAGE,
|
SCREEN_USAGE,
|
||||||
SCREEN_BLUETOOTH,
|
|
||||||
SCREEN_COUNT,
|
SCREEN_COUNT,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -13,7 +12,6 @@ void ui_init(void);
|
|||||||
void ui_update(const UsageData* data);
|
void ui_update(const UsageData* data);
|
||||||
void ui_tick_anim(void);
|
void ui_tick_anim(void);
|
||||||
void ui_show_screen(screen_t screen);
|
void ui_show_screen(screen_t screen);
|
||||||
void ui_cycle_screen(void);
|
|
||||||
void ui_toggle_splash(void);
|
void ui_toggle_splash(void);
|
||||||
screen_t ui_get_current_screen(void);
|
screen_t ui_get_current_screen(void);
|
||||||
void ui_update_ble_status(ble_state_t state, const char* name, const char* mac);
|
void ui_update_ble_status(ble_state_t state, const char* name, const char* mac);
|
||||||
|
|||||||
Reference in New Issue
Block a user