## 功能迁移清单(从 Dzbj_ESP32_S3_Key → Baji_Rtc_Toy) ### 1. 设备间BLE图片传输(GATT Client + 协议) - 新增 ble_transfer.c/h:发送方 GATT Client 扫描→连接→MTU协商→分包写入 - 接收方复用现有 GATT Server(IMAGE_WRITE 0x0B01),协议完全兼容 - 发送完成/失败自动跳转 Img 界面并关闭蓝牙 ### 2. APP传图显示 Update 界面 - 新增 ui_ScreenUpdate.c/h:更新进度界面(Gengxin背景 + Update_GIF动画) - dzbj_ble.c WRITE_EVT 中通过 ble_transfer_is_receiving() 区分 APP传图 vs 设备间传输 - APP传图 → ScreenUpdate,设备间传输 → ScreenReceiving ### 3. KEY2 按键功能入口(iot_button 单击/双击/长按) - KEY2 单击:开蓝牙 → Peiwang 配对界面(APP传图) - KEY2 双击:接收模式 → ScreenImageReception(等待配对) - KEY2 长按:发送模式 → ScreenImageShar(等待配对) - 按键参数与按键版对齐:long_press_time=1200ms, short_press_time=300ms ### 4. BOOT+KEY2 组合键模式切换(替代 BOOT 长按3秒) - BOOT 2秒长按 + KEY2 同时按下 → 触发模式切换 - 消除单键长按的误触发问题 - AI模式和吧唧模式均注册组合键 ### 5. 按键上下文状态机 - btn_context_t 枚举:HOME/IMG/SET/PEIWANG/IMAGE_SHAR/IMAGE_RECEPTION/SHARING/RECEIVING/UPDATE - 所有界面切换点(手势/按键/BLE自动跳转)同步设置 context - BOOT 单击按 context 分发:Home无操作、Img/Set返回Home、配对退出蓝牙、传输等待取消 ### 6. 新增 UI 界面(6个Screen + 7张图片) - ScreenPeiwang:蓝牙配对等待 - ScreenUpdate:APP传图更新中 - ScreenImageShar:发送方等待配对 - ScreenImageReception:接收方等待配对 - ScreenSharing:发送方传输中 - ScreenReceiving:接收方接收中 ### 7. 其他适配 - BLE 广播改为按需启动(dzbj_ble_start/stop/is_active) - sleep_mgr 移除 KEY2 唤醒(仅 BOOT 唤醒屏幕) - device_mode 新增模式切换按键抑制(防止重启后立即触发) - battery_ui 电池指示器组件 - sdkconfig 启用 BLE GATTC 支持 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
231 lines
8.8 KiB
C
231 lines
8.8 KiB
C
// This file was generated by SquareLine Studio
|
||
// SquareLine Studio version: SquareLine Studio 1.6.0
|
||
// LVGL version: 8.3.11
|
||
// Project name: Lcd_Pro
|
||
|
||
#include "../ui.h"
|
||
#include "ui_ScreenSet.h" // 引入ScreenSet的函数声明
|
||
#include "../../pages/include/pages.h" // 引入图片管理函数
|
||
#include "esp_log.h" // 用于日志输出
|
||
#include "dzbj_button.h"
|
||
|
||
extern void init_spiffs_image_list(void);
|
||
extern bool update_ui_ImgBle(const char *img_name);
|
||
extern void free_spiffs_image_list(void);
|
||
extern const char* get_next_image(void);
|
||
extern const char* get_prev_image(void);
|
||
|
||
lv_obj_t *ui_ScreenImg = NULL;
|
||
lv_obj_t *ui_ImgBle = NULL;
|
||
lv_obj_t *ui_ContainerDle = NULL;
|
||
lv_obj_t *ui_ImageDel = NULL;
|
||
lv_obj_t *ui_ImageReturn = NULL;
|
||
|
||
// 标志:是否首次加载
|
||
static bool first_load = true;
|
||
// 标志:是否需要显示 ContainerDle(只有从 ScreenSet 点击 ImgDelete 时才为 true)
|
||
static bool should_show_container = false;
|
||
|
||
// 显示 ContainerDle
|
||
void ui_ScreenImg_show_delete_container(void) {
|
||
should_show_container = true; // 设置标志,表示需要显示
|
||
if (ui_ContainerDle) {
|
||
lv_obj_clear_flag(ui_ContainerDle, LV_OBJ_FLAG_HIDDEN);
|
||
}
|
||
}
|
||
|
||
// 隐藏 ContainerDle
|
||
void ui_ScreenImg_hide_delete_container(void) {
|
||
should_show_container = false; // 清除标志
|
||
if (ui_ContainerDle) {
|
||
lv_obj_add_flag(ui_ContainerDle, LV_OBJ_FLAG_HIDDEN);
|
||
}
|
||
}
|
||
|
||
// ImageReturn 点击事件:隐藏容器
|
||
void ui_event_ImageReturn(lv_event_t * e) {
|
||
lv_event_code_t event_code = lv_event_get_code(e);
|
||
if (event_code == LV_EVENT_CLICKED) {
|
||
ui_ScreenImg_hide_delete_container();
|
||
}
|
||
}
|
||
|
||
// ImageDel 点击事件:删除当前图片
|
||
void ui_event_ImageDel(lv_event_t * e) {
|
||
lv_event_code_t event_code = lv_event_get_code(e);
|
||
if (event_code == LV_EVENT_CLICKED) {
|
||
// 删除当前图片
|
||
if (delete_current_image()) {
|
||
// 删除成功,隐藏容器
|
||
ui_ScreenImg_hide_delete_container();
|
||
|
||
// 获取下一张图片(内部已更新索引)
|
||
const char *next_img = get_current_image();
|
||
if (next_img) {
|
||
// 显示下一张图片
|
||
update_ui_ImgBle(next_img);
|
||
} else {
|
||
// 没有图片了,可以显示提示信息
|
||
// 这里暂不处理,保持当前界面
|
||
}
|
||
} else {
|
||
// 删除失败,仍然隐藏容器
|
||
ui_ScreenImg_hide_delete_container();
|
||
}
|
||
}
|
||
}
|
||
|
||
// event funtions
|
||
void ui_event_ScreenImg( lv_event_t * e) {
|
||
lv_event_code_t event_code = lv_event_get_code(e);
|
||
|
||
// 界面加载完成事件:每次进入都显示当前图片
|
||
if ( event_code == LV_EVENT_SCREEN_LOADED ) {
|
||
// 初始化图片列表(内部有guard,不会重复初始化)
|
||
init_spiffs_image_list();
|
||
|
||
// 每次进入界面都显示当前图片(支持BLE导航和手势切换回来)
|
||
const char *current_img = get_current_image();
|
||
if (current_img) {
|
||
update_ui_ImgBle(current_img);
|
||
} else {
|
||
ESP_LOGI("ScreenImg", "SPIFFS无可用图片,显示默认UI图片");
|
||
}
|
||
|
||
// 每次加载界面时检查是否需要显示 ContainerDle
|
||
// 只有从 ScreenSet 点击 ImgDelete 时 should_show_container 才为 true
|
||
if (should_show_container) {
|
||
// 需要显示,保持显示状态(已在 ui_ScreenImg_show_delete_container 中显示)
|
||
should_show_container = false; // 清除标志(下次进入默认不显示)
|
||
} else {
|
||
// 不需要显示,确保隐藏
|
||
ui_ScreenImg_hide_delete_container();
|
||
}
|
||
}
|
||
|
||
if ( event_code == LV_EVENT_GESTURE && lv_indev_get_gesture_dir(lv_indev_get_act()) == LV_DIR_TOP ) {
|
||
lv_indev_wait_release(lv_indev_get_act());
|
||
// 离开界面前清理资源和隐藏容器
|
||
#if LV_USE_GIF
|
||
pages_cleanup_gif();
|
||
#endif
|
||
ui_ScreenImg_hide_delete_container();
|
||
_ui_screen_change( &ui_ScreenHome, LV_SCR_LOAD_ANIM_NONE, 0, 0, &ui_ScreenHome_screen_init);
|
||
dzbj_button_set_context(BTN_CTX_HOME);
|
||
}
|
||
if ( event_code == LV_EVENT_GESTURE && lv_indev_get_gesture_dir(lv_indev_get_act()) == LV_DIR_BOTTOM ) {
|
||
lv_indev_wait_release(lv_indev_get_act());
|
||
// 离开界面前清理资源和隐藏容器
|
||
#if LV_USE_GIF
|
||
pages_cleanup_gif();
|
||
#endif
|
||
ui_ScreenImg_hide_delete_container();
|
||
// 设置返回到Img界面
|
||
ui_ScreenSet_set_previous(&ui_ScreenImg, &ui_ScreenImg_screen_init);
|
||
_ui_screen_change( &ui_ScreenSet, LV_SCR_LOAD_ANIM_NONE, 0, 0, &ui_ScreenSet_screen_init);
|
||
dzbj_button_set_context(BTN_CTX_SET);
|
||
}
|
||
if ( event_code == LV_EVENT_GESTURE && lv_indev_get_gesture_dir(lv_indev_get_act()) == LV_DIR_LEFT ) {
|
||
lv_indev_wait_release(lv_indev_get_act());
|
||
// 解码失败时自动跳过,最多尝试全部图片避免死循环
|
||
for(int try = 0; try < 10; try++) {
|
||
const char *next_img = get_next_image();
|
||
if(!next_img) break;
|
||
if(update_ui_ImgBle(next_img)) break;
|
||
ESP_LOGW("ScreenImg", "跳过无效图片,继续下一张");
|
||
}
|
||
}
|
||
if ( event_code == LV_EVENT_GESTURE && lv_indev_get_gesture_dir(lv_indev_get_act()) == LV_DIR_RIGHT ) {
|
||
lv_indev_wait_release(lv_indev_get_act());
|
||
// 解码失败时自动跳过,最多尝试全部图片避免死循环
|
||
for(int try = 0; try < 10; try++) {
|
||
const char *prev_img = get_prev_image();
|
||
if(!prev_img) break;
|
||
if(update_ui_ImgBle(prev_img)) break;
|
||
ESP_LOGW("ScreenImg", "跳过无效图片,继续上一张");
|
||
}
|
||
}
|
||
}
|
||
|
||
// build funtions
|
||
|
||
void ui_ScreenImg_screen_init(void)
|
||
{
|
||
ui_ScreenImg = lv_obj_create(NULL);
|
||
lv_obj_clear_flag( ui_ScreenImg, LV_OBJ_FLAG_SCROLLABLE ); /// Flags
|
||
|
||
ui_ImgBle = lv_img_create(ui_ScreenImg);
|
||
lv_img_set_src(ui_ImgBle, &ui_img_s1_png);
|
||
lv_obj_set_width( ui_ImgBle, 360);
|
||
lv_obj_set_height( ui_ImgBle, 360);
|
||
lv_obj_set_align( ui_ImgBle, LV_ALIGN_CENTER );
|
||
lv_obj_set_flex_flow(ui_ImgBle,LV_FLEX_FLOW_ROW);
|
||
lv_obj_set_flex_align(ui_ImgBle, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_START);
|
||
lv_obj_add_flag( ui_ImgBle, LV_OBJ_FLAG_ADV_HITTEST ); /// Flags
|
||
lv_obj_clear_flag( ui_ImgBle, LV_OBJ_FLAG_SCROLLABLE ); /// Flags
|
||
|
||
ui_ContainerDle = lv_obj_create(ui_ScreenImg);
|
||
lv_obj_remove_style_all(ui_ContainerDle);
|
||
lv_obj_set_width( ui_ContainerDle, 360);
|
||
lv_obj_set_height( ui_ContainerDle, 360); // 圆形容器
|
||
lv_obj_set_x( ui_ContainerDle, 0 );
|
||
lv_obj_set_y( ui_ContainerDle, 240 ); // 向下偏移 240px(显示顶部 1/3)
|
||
lv_obj_set_align( ui_ContainerDle, LV_ALIGN_CENTER );
|
||
lv_obj_clear_flag( ui_ContainerDle, LV_OBJ_FLAG_CLICKABLE | LV_OBJ_FLAG_SCROLLABLE );
|
||
// 设置圆形和裁剪
|
||
lv_obj_set_style_radius(ui_ContainerDle, 180, LV_PART_MAIN); // 圆角半径 180
|
||
lv_obj_set_style_clip_corner(ui_ContainerDle, true, LV_PART_MAIN); // 裁剪圆角外内容
|
||
lv_obj_set_style_bg_color(ui_ContainerDle, lv_color_hex(0x000000), LV_PART_MAIN);
|
||
lv_obj_set_style_bg_opa(ui_ContainerDle, 180, LV_PART_MAIN); // 半透明(180/255 约 70%)
|
||
lv_obj_add_flag( ui_ContainerDle, LV_OBJ_FLAG_HIDDEN ); // 默认隐藏
|
||
|
||
ui_ImageDel = lv_img_create(ui_ContainerDle);
|
||
lv_img_set_src(ui_ImageDel, &ui_img_s13_png);
|
||
lv_obj_set_width( ui_ImageDel, LV_SIZE_CONTENT);
|
||
lv_obj_set_height( ui_ImageDel, LV_SIZE_CONTENT);
|
||
lv_obj_set_x( ui_ImageDel, -60 ); // 左侧位置
|
||
lv_obj_set_y( ui_ImageDel, -120 ); // 容器顶部区域(屏幕底部可见区域)
|
||
lv_obj_set_align( ui_ImageDel, LV_ALIGN_CENTER );
|
||
lv_obj_add_flag( ui_ImageDel, LV_OBJ_FLAG_CLICKABLE ); // 可点击
|
||
lv_obj_clear_flag( ui_ImageDel, LV_OBJ_FLAG_SCROLLABLE );
|
||
|
||
ui_ImageReturn = lv_img_create(ui_ContainerDle);
|
||
lv_img_set_src(ui_ImageReturn, &ui_img_s14_png);
|
||
lv_obj_set_width( ui_ImageReturn, LV_SIZE_CONTENT);
|
||
lv_obj_set_height( ui_ImageReturn, LV_SIZE_CONTENT);
|
||
lv_obj_set_x( ui_ImageReturn, 60 ); // 右侧位置
|
||
lv_obj_set_y( ui_ImageReturn, -120 ); // 容器顶部区域(屏幕底部可见区域)
|
||
lv_obj_set_align( ui_ImageReturn, LV_ALIGN_CENTER );
|
||
lv_obj_add_flag( ui_ImageReturn, LV_OBJ_FLAG_CLICKABLE ); // 可点击
|
||
lv_obj_clear_flag( ui_ImageReturn, LV_OBJ_FLAG_SCROLLABLE );
|
||
|
||
// 添加点击事件回调
|
||
lv_obj_add_event_cb(ui_ImageDel, ui_event_ImageDel, LV_EVENT_ALL, NULL);
|
||
lv_obj_add_event_cb(ui_ImageReturn, ui_event_ImageReturn, LV_EVENT_ALL, NULL);
|
||
|
||
lv_obj_add_event_cb(ui_ScreenImg, ui_event_ScreenImg, LV_EVENT_ALL, NULL);
|
||
|
||
// 注意:不在此处加载图片,延迟到 LV_EVENT_SCREEN_LOADED 事件中加载
|
||
// 这样避免ui_init()时触发渲染导致开机闪烁
|
||
}
|
||
|
||
void ui_ScreenImg_screen_destroy(void)
|
||
{
|
||
#if LV_USE_GIF
|
||
pages_cleanup_gif();
|
||
#endif
|
||
if (ui_ScreenImg) lv_obj_del(ui_ScreenImg);
|
||
|
||
// NULL screen variables
|
||
ui_ScreenImg= NULL;
|
||
ui_ImgBle= NULL;
|
||
ui_ContainerDle= NULL;
|
||
ui_ImageDel= NULL;
|
||
ui_ImageReturn= NULL;
|
||
|
||
// 重置首次加载标志,下次进入时重新初始化
|
||
first_load = true;
|
||
|
||
free_spiffs_image_list();
|
||
}
|