chore: migrate project into clean repository

This commit is contained in:
yuuux
2026-08-13 16:50:52 +08:00
commit d1d25a09e7
27405 changed files with 9422808 additions and 0 deletions

View File

@@ -0,0 +1,25 @@
add_subdirectory(widgets)
add_subdirectory(setting)
target_sources(
${LISA_UI_LIB_NAME}
PRIVATE
info_view.c
setting_view.c
lisa_ui_llm_base.c
lisa_ui_llm_primary.c
lisa_ui_keyboard_input.c
alarm_success_view.c
alarm_ring_view.c
quota_qrcode_view.c
)
if(CONFIG_OTA)
target_sources(${LISA_UI_LIB_NAME} PRIVATE ota_view.c)
endif()
target_include_directories(
${LISA_UI_LIB_NAME}
PRIVATE
./
)

View File

@@ -0,0 +1,180 @@
/**
* @file alarm_ring_view.c
* @brief Alarm ring page view implementation
*/
#include "alarm_ring_view.h"
#include "lisa_ui.h"
#include "lisa_ui_assets.h"
#include "lisa_ui_fonts.h"
#include <stdio.h>
#define TAG "alarm_ring_view"
/* 声明闹钟PNG图标 */
LV_IMG_DECLARE(icons_Icon_alarm_png);
typedef struct {
lv_obj_t obj;
lv_obj_t *title_label;
lv_obj_t *time_label;
lv_obj_t *date_label;
lv_obj_t *ring_icon;
lv_obj_t *stop_btn;
lv_timer_t *blink_timer;
bool is_blinking;
} alarm_ring_view_t;
static void blink_timer_cb(lv_timer_t *timer)
{
lv_obj_t *obj = (lv_obj_t *)timer->user_data;
if (!obj) return;
alarm_ring_view_t *view = (alarm_ring_view_t *)obj;
if (!view->ring_icon) return;
view->is_blinking = !view->is_blinking;
if (view->is_blinking) {
lv_obj_set_style_opa(view->ring_icon, LV_OPA_30, LV_PART_MAIN);
} else {
lv_obj_set_style_opa(view->ring_icon, LV_OPA_COVER, LV_PART_MAIN);
}
}
static void alarm_ring_view_constructor(const lv_obj_class_t *class_p, lv_obj_t *obj)
{
LV_UNUSED(class_p);
alarm_ring_view_t *view = (alarm_ring_view_t *)obj;
view->blink_timer = NULL;
view->is_blinking = false;
lv_obj_set_size(obj, LV_PCT(100), LV_PCT(100));
lv_obj_set_style_bg_color(obj, lv_color_black(), LV_PART_MAIN);
lv_obj_set_style_bg_opa(obj, LV_OPA_COVER, LV_PART_MAIN);
lv_obj_set_style_border_width(obj, 0, LV_PART_MAIN);
lv_obj_clear_flag(obj, LV_OBJ_FLAG_SCROLLABLE);
view->title_label = lv_label_create(obj);
if (!view->title_label) {
LISA_UI_LOGE("Failed to create title label");
return;
}
lv_label_set_text(view->title_label, _("Alarm Reminder"));
lv_obj_set_style_text_color(view->title_label, lv_color_white(), LV_PART_MAIN);
lv_obj_set_style_text_font(view->title_label, &lv_font_chinese_16, LV_PART_MAIN);
lv_obj_align(view->title_label, LV_ALIGN_TOP_MID, 0, 20);
view->time_label = lv_label_create(obj);
if (!view->time_label) {
LISA_UI_LOGE("Failed to create time label");
return;
}
lv_label_set_text(view->time_label, "00:00");
lv_obj_set_style_text_color(view->time_label, lv_color_white(), LV_PART_MAIN);
lv_obj_set_style_text_font(view->time_label, &lv_font_rubik_bold_64, LV_PART_MAIN);
lv_obj_align(view->time_label, LV_ALIGN_CENTER, 0, -40);
view->date_label = lv_label_create(obj);
if (!view->date_label) {
LISA_UI_LOGE("Failed to create date label");
return;
}
lv_label_set_text(view->date_label, "");
lv_obj_set_style_text_color(view->date_label, lv_color_white(), LV_PART_MAIN);
lv_obj_set_style_text_font(view->date_label, &lv_font_chinese_16, LV_PART_MAIN);
lv_obj_align_to(view->date_label, view->time_label, LV_ALIGN_OUT_BOTTOM_MID, -50, 5);
view->ring_icon = lv_img_create(obj);
if (!view->ring_icon) {
LISA_UI_LOGE("Failed to create ring icon");
return;
}
lv_img_set_src(view->ring_icon, &icons_Icon_alarm_png);
lv_obj_align(view->ring_icon, LV_ALIGN_TOP_RIGHT, -20, 20);
view->stop_btn = lv_btn_create(obj);
if (!view->stop_btn) {
LISA_UI_LOGE("Failed to create stop button");
return;
}
lv_obj_set_size(view->stop_btn, 200, 40);
lv_obj_set_style_bg_color(view->stop_btn, lv_color_hex(0xFF6B6B), LV_PART_MAIN);
lv_obj_set_style_radius(view->stop_btn, 25, LV_PART_MAIN);
lv_obj_set_style_border_width(view->stop_btn, 0, LV_PART_MAIN);
lv_obj_align(view->stop_btn, LV_ALIGN_BOTTOM_MID, 0, -10);
lv_obj_t *btn_label = lv_label_create(view->stop_btn);
if (!btn_label) {
LISA_UI_LOGE("Failed to create button label");
return;
}
lv_label_set_text(btn_label, "唤醒或按键可终止闹铃");
lv_obj_set_style_text_color(btn_label, lv_color_white(), LV_PART_MAIN);
lv_obj_set_style_text_font(btn_label, &lv_font_chinese_16, LV_PART_MAIN);
lv_obj_align(btn_label, LV_ALIGN_CENTER, 0, -3);
view->blink_timer = lv_timer_create(blink_timer_cb, 500, obj);
if (!view->blink_timer) {
LISA_UI_LOGE("Failed to create blink timer");
return;
}
view->is_blinking = false;
}
static void alarm_ring_view_destructor(const lv_obj_class_t *class_p, lv_obj_t *obj)
{
LV_UNUSED(class_p);
alarm_ring_view_t *view = (alarm_ring_view_t *)obj;
if (view->blink_timer) {
lv_timer_del(view->blink_timer);
view->blink_timer = NULL;
}
}
const lv_obj_class_t alarm_ring_view_class = {
.constructor_cb = alarm_ring_view_constructor,
.destructor_cb = alarm_ring_view_destructor,
.instance_size = sizeof(alarm_ring_view_t),
.base_class = &lv_obj_class
};
lv_obj_t *alarm_ring_view_create(lv_obj_t *parent)
{
lv_obj_t *obj = lv_obj_class_create_obj(&alarm_ring_view_class, parent);
lv_obj_class_init_obj(obj);
return obj;
}
void alarm_ring_view_set_time(lv_obj_t *obj, const char *time_str)
{
if (!obj || !time_str) return;
alarm_ring_view_t *view = (alarm_ring_view_t *)obj;
if (view->time_label) {
lv_label_set_text(view->time_label, time_str);
}
}
void alarm_ring_view_set_date(lv_obj_t *obj, const char *date_str)
{
if (!obj || !date_str) return;
alarm_ring_view_t *view = (alarm_ring_view_t *)obj;
if (view->date_label) {
lv_label_set_text(view->date_label, date_str);
}
}
void alarm_ring_view_set_stop_cb(lv_obj_t *obj, lv_event_cb_t cb, void *user_data)
{
if (!obj || !cb) return;
alarm_ring_view_t *view = (alarm_ring_view_t *)obj;
if (view->stop_btn) {
lv_obj_add_event_cb(view->stop_btn, cb, LV_EVENT_CLICKED, user_data);
}
}

View File

@@ -0,0 +1,55 @@
/**
* @file alarm_ring_view.h
* @brief Alarm ring page view header
*/
#ifndef __ALARM_RING_VIEW_H__
#define __ALARM_RING_VIEW_H__
#ifdef __cplusplus
extern "C" {
#endif
#include "lvgl.h"
/** Alarm ring view class definition */
extern const lv_obj_class_t alarm_ring_view_class;
/**
* @brief Create alarm ring view object
*
* @param parent Parent object, NULL for current screen
* @return lv_obj_t* Created view object, NULL on failure
*/
lv_obj_t *alarm_ring_view_create(lv_obj_t *parent);
/**
* @brief Set alarm time text
*
* @param obj View object
* @param time_str Time string (e.g. "16:30")
*/
void alarm_ring_view_set_time(lv_obj_t *obj, const char *time_str);
/**
* @brief Set alarm date text
*
* @param obj View object
* @param date_str Date string (e.g. "12月29日 周日")
*/
void alarm_ring_view_set_date(lv_obj_t *obj, const char *date_str);
/**
* @brief Set stop button callback
*
* @param obj View object
* @param cb Callback function
* @param user_data User data passed to callback
*/
void alarm_ring_view_set_stop_cb(lv_obj_t *obj, lv_event_cb_t cb, void *user_data);
#ifdef __cplusplus
}
#endif
#endif /* __ALARM_RING_VIEW_H__ */

View File

@@ -0,0 +1,89 @@
/**
* @file alarm_success_view.c
* @brief Alarm success page view implementation
*/
#include "alarm_success_view.h"
#include "lisa_ui.h"
#include "lisa_ui_assets.h"
#include "lisa_ui_fonts.h"
#define TAG "alarm_success_view"
typedef struct {
lv_obj_t obj;
lv_obj_t *title_label;
lv_obj_t *time_label;
lv_obj_t *date_label;
} alarm_success_view_t;
static void alarm_success_view_constructor(const lv_obj_class_t *class_p, lv_obj_t *obj)
{
LV_UNUSED(class_p);
alarm_success_view_t *view = (alarm_success_view_t *)obj;
lv_obj_set_size(obj, LV_PCT(100), LV_PCT(100));
lv_obj_set_style_bg_color(obj, lv_color_black(), LV_PART_MAIN);
lv_obj_set_style_bg_opa(obj, LV_OPA_COVER, LV_PART_MAIN);
lv_obj_set_style_border_width(obj, 0, LV_PART_MAIN);
lv_obj_clear_flag(obj, LV_OBJ_FLAG_SCROLLABLE);
view->title_label = lv_label_create(obj);
lv_label_set_text(view->title_label, _("Alarm set successfully"));
lv_obj_set_style_text_color(view->title_label, lv_color_white(), LV_PART_MAIN);
lv_obj_set_style_text_font(view->title_label, &lv_font_chinese_16, LV_PART_MAIN);
lv_obj_align(view->title_label, LV_ALIGN_TOP_MID, 0, 30);
view->time_label = lv_label_create(obj);
lv_label_set_text(view->time_label, "00:00");
lv_obj_set_style_text_color(view->time_label, lv_color_white(), LV_PART_MAIN);
lv_obj_set_style_text_font(view->time_label, &lv_font_rubik_bold_64, LV_PART_MAIN);
lv_obj_align(view->time_label, LV_ALIGN_CENTER, 0, -30);
view->date_label = lv_label_create(obj);
lv_label_set_text(view->date_label, "");
lv_obj_set_style_text_color(view->date_label, lv_color_white(), LV_PART_MAIN);
lv_obj_set_style_text_font(view->date_label, &lv_font_chinese_16, LV_PART_MAIN);
lv_obj_align_to(view->date_label, view->time_label, LV_ALIGN_OUT_BOTTOM_MID, -50, 30);
}
static void alarm_success_view_destructor(const lv_obj_class_t *class_p, lv_obj_t *obj)
{
LV_UNUSED(class_p);
LV_UNUSED(obj);
}
const lv_obj_class_t alarm_success_view_class = {
.constructor_cb = alarm_success_view_constructor,
.destructor_cb = alarm_success_view_destructor,
.instance_size = sizeof(alarm_success_view_t),
.base_class = &lv_obj_class
};
lv_obj_t *alarm_success_view_create(lv_obj_t *parent)
{
lv_obj_t *obj = lv_obj_class_create_obj(&alarm_success_view_class, parent);
lv_obj_class_init_obj(obj);
return obj;
}
void alarm_success_view_set_time(lv_obj_t *obj, const char *time_str)
{
if (!obj || !time_str) return;
alarm_success_view_t *view = (alarm_success_view_t *)obj;
if (view->time_label) {
lv_label_set_text(view->time_label, time_str);
}
}
void alarm_success_view_set_date(lv_obj_t *obj, const char *date_str)
{
if (!obj || !date_str) return;
alarm_success_view_t *view = (alarm_success_view_t *)obj;
if (view->date_label) {
lv_label_set_text(view->date_label, date_str);
}
}

View File

@@ -0,0 +1,46 @@
/**
* @file alarm_success_view.h
* @brief Alarm success page view header
*/
#ifndef __ALARM_SUCCESS_VIEW_H__
#define __ALARM_SUCCESS_VIEW_H__
#ifdef __cplusplus
extern "C" {
#endif
#include "lvgl.h"
/** Alarm success view class definition */
extern const lv_obj_class_t alarm_success_view_class;
/**
* @brief Create alarm success view object
*
* @param parent Parent object, NULL for current screen
* @return lv_obj_t* Created view object, NULL on failure
*/
lv_obj_t *alarm_success_view_create(lv_obj_t *parent);
/**
* @brief Set alarm time text
*
* @param obj View object
* @param time_str Time string (e.g. "16:30")
*/
void alarm_success_view_set_time(lv_obj_t *obj, const char *time_str);
/**
* @brief Set alarm date text
*
* @param obj View object
* @param date_str Date string (e.g. "12月29日 周日")
*/
void alarm_success_view_set_date(lv_obj_t *obj, const char *date_str);
#ifdef __cplusplus
}
#endif
#endif /* __ALARM_SUCCESS_VIEW_H__ */

View File

@@ -0,0 +1,186 @@
/**
* @file info_view.c
* @brief Info/QR Code page view implementation
*
* Pure UI layer implementation for info/qrcode display page.
* This view displays QR codes (BLE or network) and informational text.
*/
#define TAG "info_view"
#include "info_view.h"
#include "lisa_ui.h"
#include "lisa_ui_llm_base.h"
#include "lisa_ui_res.h"
#include "lisa_ui_fonts.h"
#include <string.h>
/** Default text displayed when no specific info is set */
#define DEFAULT_INFO_TEXT "Please scan QR code"
/**
* @brief Info view structure
* Inherits from lisa_ui_llm_base and adds QR code display elements
*/
struct lisa_ui_info_view {
lisa_ui_llm_base_t base_obj; /**< Base object */
/* Content elements */
lv_obj_t *top_label; /**< Top instruction text */
lv_obj_t *qr_img; /**< QR code image */
lv_obj_t *bottom_label; /**< Bottom information text */
/* QR image descriptor for RGB565 data */
lv_img_dsc_t qr_img_desc; /**< Image descriptor */
};
typedef struct lisa_ui_info_view lisa_ui_info_view_t;
static void lisa_ui_info_view_class_constructor(const lv_obj_class_t *class_p, lv_obj_t *obj);
static void lisa_ui_info_view_class_destructor(const lv_obj_class_t *class_p, lv_obj_t *obj);
const lv_obj_class_t lisa_ui_info_view_class = {
.base_class = &lisa_ui_llm_base_class,
.instance_size = sizeof(lisa_ui_info_view_t),
.constructor_cb = lisa_ui_info_view_class_constructor,
.destructor_cb = lisa_ui_info_view_class_destructor,
};
static void lisa_ui_info_view_class_constructor(const lv_obj_class_t *class_p, lv_obj_t *obj)
{
(void)class_p;
lisa_ui_info_view_t *info_view = (lisa_ui_info_view_t *)obj;
// Hide the bar to allow content to start from the very top
lv_obj_t *bar = lisa_ui_llm_base_bar_get(obj);
if (bar) {
lv_obj_add_flag(bar, LV_OBJ_FLAG_HIDDEN);
}
lv_obj_t *container = lisa_ui_llm_base_container_get(obj);
if (NULL == container) {
LISA_UI_LOGE("Failed to get base container");
return;
}
// Create QR code image in center
info_view->qr_img = lv_img_create(container);
#ifdef CONFIG_BOARD_ARCS_MINI_DOLL_V2
lv_obj_set_size(info_view->qr_img, 160, 160);
lv_obj_align(info_view->qr_img, LV_ALIGN_CENTER, -80, 0);
#else
lv_obj_set_size(info_view->qr_img, 148, 148);
lv_obj_align(info_view->qr_img, LV_ALIGN_CENTER, 0, 5);
#endif
// Create top label above QR code
info_view->top_label = lv_label_create(container);
lv_label_set_text(info_view->top_label, "");
lv_obj_set_style_text_color(info_view->top_label, lv_color_white(), LV_PART_MAIN);
lv_obj_set_style_text_font(info_view->top_label, &lv_font_chinese_16, LV_PART_MAIN);
lv_obj_set_style_text_align(info_view->top_label, LV_TEXT_ALIGN_CENTER, LV_PART_MAIN);
lv_obj_set_width(info_view->top_label, LV_PCT(90));
lv_label_set_long_mode(info_view->top_label, LV_LABEL_LONG_WRAP);
lv_obj_align_to(info_view->top_label, info_view->qr_img, LV_ALIGN_OUT_TOP_MID, 0, -25);
// Create bottom label below QR code
info_view->bottom_label = lv_label_create(container);
lv_label_set_text(info_view->bottom_label, "");
lv_obj_set_style_text_color(info_view->bottom_label, lv_color_white(), LV_PART_MAIN);
lv_obj_set_style_text_font(info_view->bottom_label, &lv_font_chinese_16, LV_PART_MAIN);
lv_obj_set_style_text_align(info_view->bottom_label, LV_TEXT_ALIGN_CENTER, LV_PART_MAIN);
lv_obj_set_width(info_view->bottom_label, LV_PCT(90));
lv_label_set_long_mode(info_view->bottom_label, LV_LABEL_LONG_WRAP);
lv_obj_align_to(info_view->bottom_label, info_view->qr_img, LV_ALIGN_OUT_BOTTOM_MID, 0, 10);
}
static void lisa_ui_info_view_class_destructor(const lv_obj_class_t *class_p, lv_obj_t *obj)
{
(void)class_p;
(void)obj;
// Child objects auto-deleted by LVGL
}
// ===================== Public API Implementation =====================
lv_obj_t *lisa_ui_info_view_create(lv_obj_t *parent)
{
lv_obj_t *obj = lv_obj_class_create_obj(&lisa_ui_info_view_class, parent);
lv_obj_class_init_obj(obj);
// Set page title
lisa_ui_llm_base_set_title(obj, "Info");
return obj;
}
void lisa_ui_info_view_set_top_text(lv_obj_t *obj, const char *text)
{
if (!obj || !lv_obj_has_class(obj, &lisa_ui_info_view_class)) {
return;
}
lisa_ui_info_view_t *info_view = (lisa_ui_info_view_t *)obj;
if (info_view->top_label) {
lv_label_set_text(info_view->top_label, text ? text : DEFAULT_INFO_TEXT);
}
}
void lisa_ui_info_view_set_bottom_text(lv_obj_t *obj, const char *text)
{
if (!obj || !lv_obj_has_class(obj, &lisa_ui_info_view_class)) {
return;
}
lisa_ui_info_view_t *info_view = (lisa_ui_info_view_t *)obj;
if (info_view->bottom_label) {
lv_label_set_text(info_view->bottom_label, text ? text : "");
}
}
void lisa_ui_info_view_set_qr_image(lv_obj_t *obj, const void *src)
{
if (!obj || !lv_obj_has_class(obj, &lisa_ui_info_view_class)) {
return;
}
lisa_ui_info_view_t *info_view = (lisa_ui_info_view_t *)obj;
if (info_view->qr_img && src) {
lv_img_set_src(info_view->qr_img, src);
}
}
lv_obj_t *lisa_ui_info_view_get_qr_image(lv_obj_t *obj)
{
if (!obj || !lv_obj_has_class(obj, &lisa_ui_info_view_class)) {
return NULL;
}
lisa_ui_info_view_t *info_view = (lisa_ui_info_view_t *)obj;
return info_view->qr_img;
}
void lisa_ui_info_view_set_qr_rgb565(lv_obj_t *obj, const uint16_t *data, uint16_t width, uint16_t height, uint32_t data_size)
{
if (!obj || !lv_obj_has_class(obj, &lisa_ui_info_view_class) || !data) {
return;
}
lisa_ui_info_view_t *info_view = (lisa_ui_info_view_t *)obj;
if (!info_view->qr_img) {
return;
}
// Setup image descriptor for RGB565 format
memset(&info_view->qr_img_desc, 0, sizeof(lv_img_dsc_t));
info_view->qr_img_desc.header.always_zero = 0;
info_view->qr_img_desc.header.w = width;
info_view->qr_img_desc.header.h = height;
info_view->qr_img_desc.header.cf = LV_IMG_CF_TRUE_COLOR; // RGB565 format
info_view->qr_img_desc.data_size = data_size;
info_view->qr_img_desc.data = (const uint8_t *)data;
// Set image source to descriptor
lv_img_set_src(info_view->qr_img, &info_view->qr_img_desc);
}

View File

@@ -0,0 +1,76 @@
/**
* @file info_view.h
* @brief Info/QR Code page view header
*
* Provides view interface for info/qrcode display page.
*/
#ifndef __LISA_UI_INFO_VIEW_H__
#define __LISA_UI_INFO_VIEW_H__
#ifdef __cplusplus
extern "C" {
#endif
#include "lvgl.h"
#include "lisa_ui_llm_base.h"
/** Info view class definition */
extern const lv_obj_class_t lisa_ui_info_view_class;
/**
* @brief Create info view object
*
* @param parent Parent object, NULL for current screen
* @return lv_obj_t* Created info view object, NULL on failure
*/
lv_obj_t *lisa_ui_info_view_create(lv_obj_t *parent);
/**
* @brief Set top instruction text
*
* @param obj Info view object
* @param text Text to display, NULL for default text
*/
void lisa_ui_info_view_set_top_text(lv_obj_t *obj, const char *text);
/**
* @brief Set bottom information text
*
* @param obj Info view object
* @param text Text to display, NULL to clear
*/
void lisa_ui_info_view_set_bottom_text(lv_obj_t *obj, const char *text);
/**
* @brief Set QR code image source
*
* @param obj Info view object
* @param src Image source (file path, C array, or symbol)
*/
void lisa_ui_info_view_set_qr_image(lv_obj_t *obj, const void *src);
/**
* @brief Get QR code image object
*
* @param obj Info view object
* @return lv_obj_t* QR image object for advanced operations
*/
lv_obj_t *lisa_ui_info_view_get_qr_image(lv_obj_t *obj);
/**
* @brief Set QR code image from RGB565 data
*
* @param obj Info view object
* @param data RGB565 image data pointer
* @param width Image width in pixels
* @param height Image height in pixels
* @param data_size Total data size in bytes
*/
void lisa_ui_info_view_set_qr_rgb565(lv_obj_t *obj, const uint16_t *data, uint16_t width, uint16_t height, uint32_t data_size);
#ifdef __cplusplus
}
#endif
#endif /* __LISA_UI_INFO_VIEW_H__ */

View File

@@ -0,0 +1,348 @@
/**
* @file lisa_ui_keyboard_input.c
* @brief 通用键盘输入组件实现
*/
#include <stdio.h>
#include <string.h>
#include "lisa_ui_keyboard_input.h"
#include "lisa_ui.h"
#include "lisa_ui_assets.h"
#include "lisa_ui_fonts.h"
#define TAG "keyboard_input"
typedef struct {
lv_obj_t *panel; /* 全屏面板 */
lv_obj_t *title_label; /* 标题标签 */
lv_obj_t *input_label; /* 输入框标签 (如"密码:") */
lv_obj_t *input; /* 文本输入框 */
lv_obj_t *keyboard; /* 键盘 */
lisa_ui_keyboard_input_confirm_cb_t confirm_cb; /* 确认回调 */
void *confirm_user_data; /* 确认回调用户数据 */
lisa_ui_keyboard_input_cancel_cb_t cancel_cb; /* 取消回调 */
void *cancel_user_data; /* 取消回调用户数据 */
bool switching_keyboard; /* 是否正在切换键盘 */
} lisa_ui_keyboard_input_t;
/* 键盘按钮映射 - 小写字母 */
static const char *keyboard_map_lc[] = {
"1", "2", "3", "4", "5", "6", "7", "8", "9", "0", "\n",
"q", "w", "e", "r", "t", "y", "u", "i", "o", "p", "\n",
"a", "s", "d", "f", "g", "h", "j", "k", "l", "\n",
"z", "x", "c", "v", "b", "n", "m", LV_SYMBOL_BACKSPACE, "\n",
"ABC", "#+=", " ", " ", "enter", ""
};
/* 键盘按钮映射 - 大写字母 */
static const char *keyboard_map_uc[] = {
"1", "2", "3", "4", "5", "6", "7", "8", "9", "0", "\n",
"Q", "W", "E", "R", "T", "Y", "U", "I", "O", "P", "\n",
"A", "S", "D", "F", "G", "H", "J", "K", "L", "\n",
"Z", "X", "C", "V", "B", "N", "M", LV_SYMBOL_BACKSPACE, "\n",
"abc", "#+=", " ", " ", "enter", ""
};
/* 键盘按钮映射 - 符号 */
static const char *keyboard_map_special[] = {
"1", "2", "3", "4", "5", "6", "7", "8", "9", "0", "\n",
"!", "@", "#", "$", "%", "^", "&", "*", "(", ")", "\n",
"-", "_", "=", "+", "[", "]", "{", "}", "\n",
";", ":", "'", "\"", ",", ".", "/", LV_SYMBOL_BACKSPACE, "\n",
"abc", "ABC", " ", " ", "enter", ""
};
/* 键盘事件处理 */
static void keyboard_event_cb(lv_event_t *e)
{
lv_event_code_t code = lv_event_get_code(e);
if (code != LV_EVENT_PRESSED) {
return;
}
lv_obj_t *keyboard = lv_event_get_target(e);
lisa_ui_keyboard_input_t *kb_input = (lisa_ui_keyboard_input_t *)lv_event_get_user_data(e);
uint16_t btn_id = lv_btnmatrix_get_selected_btn(keyboard);
if (btn_id == LV_BTNMATRIX_BTN_NONE) {
return;
}
const char *txt = lv_btnmatrix_get_btn_text(keyboard, btn_id);
if (!txt) {
return;
}
/* 处理删除键 */
if (strcmp(txt, LV_SYMBOL_BACKSPACE) == 0) {
lv_textarea_del_char(kb_input->input);
return;
}
/* 处理确定按钮 */
if (strcmp(txt, "enter") == 0) {
if (kb_input->confirm_cb) {
const char *text = lv_textarea_get_text(kb_input->input);
kb_input->confirm_cb(text, kb_input->confirm_user_data);
}
return;
}
/* 处理键盘切换 */
if (strcmp(txt, "abc") == 0) {
lv_btnmatrix_set_map(keyboard, keyboard_map_lc);
return;
}
if (strcmp(txt, "ABC") == 0) {
lv_btnmatrix_set_map(keyboard, keyboard_map_uc);
return;
}
if (strcmp(txt, "#+=") == 0) {
lv_btnmatrix_set_map(keyboard, keyboard_map_special);
return;
}
/* 处理普通字符输入 */
lv_textarea_add_text(kb_input->input, txt);
}
/* 返回按钮事件处理 */
static void back_event_cb(lv_event_t *e)
{
lv_event_code_t code = lv_event_get_code(e);
if (code != LV_EVENT_CLICKED) {
return;
}
lisa_ui_keyboard_input_t *kb_input = (lisa_ui_keyboard_input_t *)lv_event_get_user_data(e);
/* 调用取消回调 */
if (kb_input->cancel_cb) {
kb_input->cancel_cb(kb_input->cancel_user_data);
}
/* 隐藏界面 */
lisa_ui_keyboard_input_hide((lv_obj_t *)kb_input);
}
lv_obj_t *lisa_ui_keyboard_input_create(lv_obj_t *parent)
{
if (!parent) {
parent = lv_scr_act();
}
/* 分配内存 */
lisa_ui_keyboard_input_t *kb_input = lv_mem_alloc(sizeof(lisa_ui_keyboard_input_t));
if (!kb_input) {
LISA_UI_LOGE("Failed to allocate memory for keyboard input");
return NULL;
}
memset(kb_input, 0, sizeof(lisa_ui_keyboard_input_t));
kb_input->switching_keyboard = false;
/* 创建全屏面板 */
kb_input->panel = lv_obj_create(parent);
lv_obj_set_size(kb_input->panel, LV_PCT(100), LV_PCT(100));
lv_obj_align(kb_input->panel, LV_ALIGN_CENTER, 0, 0);
lv_obj_set_style_bg_color(kb_input->panel, lv_color_hex(0x000000), LV_PART_MAIN);
lv_obj_set_style_bg_opa(kb_input->panel, LV_OPA_COVER, LV_PART_MAIN);
lv_obj_set_style_border_width(kb_input->panel, 0, LV_PART_MAIN);
lv_obj_set_style_pad_all(kb_input->panel, 0, LV_PART_MAIN);
lv_obj_add_flag(kb_input->panel, LV_OBJ_FLAG_HIDDEN); /* 默认隐藏 */
/* 保存结构体指针到panel的user_data */
lv_obj_set_user_data(kb_input->panel, kb_input);
/* 创建返回按钮 */
lv_obj_t *back_btn = lv_btn_create(kb_input->panel);
lv_obj_set_size(back_btn, 40, 40);
lv_obj_align(back_btn, LV_ALIGN_TOP_LEFT, 5, 5);
lv_obj_set_style_bg_opa(back_btn, LV_OPA_TRANSP, LV_PART_MAIN);
lv_obj_set_style_border_width(back_btn, 0, LV_PART_MAIN);
lv_obj_set_style_shadow_width(back_btn, 0, LV_PART_MAIN);
lv_obj_t *back_icon = lv_img_create(back_btn);
lv_img_set_src(back_icon, &icons_icon_back_png);
lv_obj_center(back_icon);
lv_obj_add_event_cb(back_btn, back_event_cb, LV_EVENT_CLICKED, kb_input);
/* 创建标题标签 */
kb_input->title_label = lv_label_create(kb_input->panel);
lv_obj_set_style_text_font(kb_input->title_label, &lv_font_chinese_16, LV_PART_MAIN);
lv_obj_set_style_text_color(kb_input->title_label, lv_color_white(), LV_PART_MAIN);
lv_obj_align(kb_input->title_label, LV_ALIGN_TOP_MID, 0, 10);
lv_label_set_text(kb_input->title_label, "");
/* 创建输入框容器 */
lv_obj_t *input_container = lv_obj_create(kb_input->panel);
lv_obj_set_size(input_container, LV_PCT(90), 40);
lv_obj_align(input_container, LV_ALIGN_TOP_MID, 0, 50);
lv_obj_set_style_bg_color(input_container, lv_color_hex(0x2a2a2a), LV_PART_MAIN);
lv_obj_set_style_border_color(input_container, lv_color_hex(0x4a4a4a), LV_PART_MAIN);
lv_obj_set_style_border_width(input_container, 2, LV_PART_MAIN);
lv_obj_set_style_radius(input_container, 8, LV_PART_MAIN);
lv_obj_set_style_pad_all(input_container, 10, LV_PART_MAIN);
/* 创建输入框 */
kb_input->input = lv_textarea_create(input_container);
lv_obj_set_size(kb_input->input, LV_PCT(100), LV_PCT(100));
lv_obj_align(kb_input->input, LV_ALIGN_CENTER, 0, 0);
// lv_obj_set_style_text_font(kb_input->input, &lv_font_chinese_16, LV_PART_MAIN);
lv_obj_set_style_text_color(kb_input->input, lv_color_white(), LV_PART_MAIN);
lv_obj_set_style_bg_color(kb_input->input, lv_color_hex(0x2a2a2a), LV_PART_MAIN);
lv_obj_set_style_bg_opa(kb_input->input, LV_OPA_TRANSP, LV_PART_MAIN);
lv_obj_set_style_border_width(kb_input->input, 0, LV_PART_MAIN);
lv_obj_set_style_pad_all(kb_input->input, 8, LV_PART_MAIN);
lv_textarea_set_one_line(kb_input->input, true);
lv_textarea_set_placeholder_text(kb_input->input, "please input");
/* 输入框标签(保留变量以兼容set_label函数,但设为隐藏) */
kb_input->input_label = lv_label_create(kb_input->panel);
lv_obj_add_flag(kb_input->input_label, LV_OBJ_FLAG_HIDDEN);
/* 创建键盘 */
kb_input->keyboard = lv_btnmatrix_create(kb_input->panel);
lv_obj_set_size(kb_input->keyboard, LV_PCT(100), 150); /* 降低键盘高度从180到150 */
lv_obj_align(kb_input->keyboard, LV_ALIGN_BOTTOM_MID, 0, 0);
lv_obj_set_style_bg_color(kb_input->keyboard, lv_color_hex(0x1a1a1a), LV_PART_MAIN);
lv_obj_set_style_border_width(kb_input->keyboard, 0, LV_PART_MAIN);
lv_obj_set_style_pad_all(kb_input->keyboard, 2, LV_PART_MAIN);
lv_obj_set_style_pad_gap(kb_input->keyboard, 2, LV_PART_MAIN);
// lv_obj_set_style_text_font(kb_input->keyboard, &lv_font_chinese_16, LV_PART_MAIN); /* 字体从18降到16 */
/* 按钮样式 */
lv_obj_set_style_bg_color(kb_input->keyboard, lv_color_hex(0x3a3a3a), LV_PART_ITEMS);
lv_obj_set_style_bg_color(kb_input->keyboard, lv_color_hex(0x5a5a5a), LV_PART_ITEMS | LV_STATE_PRESSED);
lv_obj_set_style_text_color(kb_input->keyboard, lv_color_white(), LV_PART_ITEMS);
lv_obj_set_style_radius(kb_input->keyboard, 4, LV_PART_ITEMS);
lv_btnmatrix_set_map(kb_input->keyboard, keyboard_map_lc);
lv_obj_add_event_cb(kb_input->keyboard, keyboard_event_cb, LV_EVENT_PRESSED, kb_input);
LISA_UI_LOGD("Keyboard input created");
return (lv_obj_t *)kb_input;
}
void lisa_ui_keyboard_input_set_title(lv_obj_t *obj, const char *title)
{
if (!obj || !title) {
return;
}
lisa_ui_keyboard_input_t *kb_input = (lisa_ui_keyboard_input_t *)obj;
lv_label_set_text(kb_input->title_label, title);
}
void lisa_ui_keyboard_input_set_placeholder(lv_obj_t *obj, const char *placeholder)
{
if (!obj || !placeholder) {
return;
}
lisa_ui_keyboard_input_t *kb_input = (lisa_ui_keyboard_input_t *)obj;
lv_textarea_set_placeholder_text(kb_input->input, placeholder);
}
void lisa_ui_keyboard_input_set_label(lv_obj_t *obj, const char *label)
{
if (!obj || !label) {
return;
}
lisa_ui_keyboard_input_t *kb_input = (lisa_ui_keyboard_input_t *)obj;
lv_label_set_text(kb_input->input_label, label);
}
void lisa_ui_keyboard_input_set_password_mode(lv_obj_t *obj, bool enable)
{
if (!obj) {
return;
}
lisa_ui_keyboard_input_t *kb_input = (lisa_ui_keyboard_input_t *)obj;
lv_textarea_set_password_mode(kb_input->input, enable);
}
void lisa_ui_keyboard_input_set_confirm_cb(lv_obj_t *obj,
lisa_ui_keyboard_input_confirm_cb_t confirm_cb,
void *user_data)
{
if (!obj) {
return;
}
lisa_ui_keyboard_input_t *kb_input = (lisa_ui_keyboard_input_t *)obj;
kb_input->confirm_cb = confirm_cb;
kb_input->confirm_user_data = user_data;
}
void lisa_ui_keyboard_input_set_cancel_cb(lv_obj_t *obj,
lisa_ui_keyboard_input_cancel_cb_t cancel_cb,
void *user_data)
{
if (!obj) {
return;
}
lisa_ui_keyboard_input_t *kb_input = (lisa_ui_keyboard_input_t *)obj;
kb_input->cancel_cb = cancel_cb;
kb_input->cancel_user_data = user_data;
}
void lisa_ui_keyboard_input_show(lv_obj_t *obj)
{
if (!obj) {
return;
}
lisa_ui_keyboard_input_t *kb_input = (lisa_ui_keyboard_input_t *)obj;
/* 清空输入框 */
lv_textarea_set_text(kb_input->input, "");
/* 重置键盘为小写模式 */
lv_btnmatrix_set_map(kb_input->keyboard, keyboard_map_lc);
/* 显示面板 */
lv_obj_clear_flag(kb_input->panel, LV_OBJ_FLAG_HIDDEN);
LISA_UI_LOGD("Keyboard input shown");
}
void lisa_ui_keyboard_input_hide(lv_obj_t *obj)
{
if (!obj) {
return;
}
lisa_ui_keyboard_input_t *kb_input = (lisa_ui_keyboard_input_t *)obj;
lv_obj_add_flag(kb_input->panel, LV_OBJ_FLAG_HIDDEN);
LISA_UI_LOGD("Keyboard input hidden");
}
const char *lisa_ui_keyboard_input_get_text(lv_obj_t *obj)
{
if (!obj) {
return "";
}
lisa_ui_keyboard_input_t *kb_input = (lisa_ui_keyboard_input_t *)obj;
return lv_textarea_get_text(kb_input->input);
}
void lisa_ui_keyboard_input_clear(lv_obj_t *obj)
{
if (!obj) {
return;
}
lisa_ui_keyboard_input_t *kb_input = (lisa_ui_keyboard_input_t *)obj;
lv_textarea_set_text(kb_input->input, "");
}

View File

@@ -0,0 +1,113 @@
/**
* @file lisa_ui_keyboard_input.h
* @brief 通用键盘输入组件
* @description 提供一个全屏的键盘输入界面,支持密码输入模式
*/
#ifndef __LISA_UI_KEYBOARD_INPUT_H__
#define __LISA_UI_KEYBOARD_INPUT_H__
#ifdef __cplusplus
extern "C" {
#endif
#include "lvgl.h"
/**
* @brief 键盘输入确认回调函数类型
* @param text 输入的文本内容
* @param user_data 用户数据
*/
typedef void (*lisa_ui_keyboard_input_confirm_cb_t)(const char *text, void *user_data);
/**
* @brief 键盘输入取消回调函数类型
* @param user_data 用户数据
*/
typedef void (*lisa_ui_keyboard_input_cancel_cb_t)(void *user_data);
/**
* @brief 创建键盘输入组件
* @param parent 父对象
* @return lv_obj_t* 创建的键盘输入对象
*/
lv_obj_t *lisa_ui_keyboard_input_create(lv_obj_t *parent);
/**
* @brief 设置标题文本
* @param obj 键盘输入对象
* @param title 标题文本
*/
void lisa_ui_keyboard_input_set_title(lv_obj_t *obj, const char *title);
/**
* @brief 设置输入框提示文本
* @param obj 键盘输入对象
* @param placeholder 提示文本
*/
void lisa_ui_keyboard_input_set_placeholder(lv_obj_t *obj, const char *placeholder);
/**
* @brief 设置输入框标签文本 (如"密码:"、"用户名:"等)
* @param obj 键盘输入对象
* @param label 标签文本
*/
void lisa_ui_keyboard_input_set_label(lv_obj_t *obj, const char *label);
/**
* @brief 设置密码模式
* @param obj 键盘输入对象
* @param enable true=密码模式, false=普通模式
*/
void lisa_ui_keyboard_input_set_password_mode(lv_obj_t *obj, bool enable);
/**
* @brief 设置确认回调
* @param obj 键盘输入对象
* @param confirm_cb 确认回调函数
* @param user_data 用户数据
*/
void lisa_ui_keyboard_input_set_confirm_cb(lv_obj_t *obj,
lisa_ui_keyboard_input_confirm_cb_t confirm_cb,
void *user_data);
/**
* @brief 设置取消回调
* @param obj 键盘输入对象
* @param cancel_cb 取消回调函数
* @param user_data 用户数据
*/
void lisa_ui_keyboard_input_set_cancel_cb(lv_obj_t *obj,
lisa_ui_keyboard_input_cancel_cb_t cancel_cb,
void *user_data);
/**
* @brief 显示键盘输入界面
* @param obj 键盘输入对象
*/
void lisa_ui_keyboard_input_show(lv_obj_t *obj);
/**
* @brief 隐藏键盘输入界面
* @param obj 键盘输入对象
*/
void lisa_ui_keyboard_input_hide(lv_obj_t *obj);
/**
* @brief 获取输入的文本
* @param obj 键盘输入对象
* @return const char* 输入的文本
*/
const char *lisa_ui_keyboard_input_get_text(lv_obj_t *obj);
/**
* @brief 清空输入框
* @param obj 键盘输入对象
*/
void lisa_ui_keyboard_input_clear(lv_obj_t *obj);
#ifdef __cplusplus
}
#endif
#endif /* __LISA_UI_KEYBOARD_INPUT_H__ */

View File

@@ -0,0 +1,175 @@
#include "lvgl.h"
#include "lisa_ui_llm_base.h"
#include <string.h>
static void lisa_ui_llm_base_class_constructor(const lv_obj_class_t *class_p, lv_obj_t *obj);
const lv_obj_class_t lisa_ui_llm_base_class = {
.base_class = &lv_obj_class,
.width_def = LV_PCT(100),
.height_def = LV_PCT(100),
.instance_size = sizeof(lisa_ui_llm_base_t),
.constructor_cb = lisa_ui_llm_base_class_constructor,
};
static lv_style_t lisa_ui_base_style;
static uint8_t lisa_ui_base_style_inited = 0;
static void lisa_ui_base_style_init(void)
{
lv_style_init(&lisa_ui_base_style);
lv_style_set_bg_color(&lisa_ui_base_style, lv_color_hex(LISA_UI_LLM_BASE_DEFAULT_BG_COLOR));
lv_style_set_bg_opa(&lisa_ui_base_style, LV_OPA_COVER);
lv_style_set_border_width(&lisa_ui_base_style, 0);
lv_style_set_border_opa(&lisa_ui_base_style, LV_OPA_COVER);
}
static void lisa_ui_llm_base_class_constructor(const lv_obj_class_t *class_p, lv_obj_t *obj)
{
if (!lisa_ui_base_style_inited) {
lisa_ui_base_style_init();
lisa_ui_base_style_inited = 1;
}
lv_obj_add_style(obj, &lisa_ui_base_style, LV_PART_MAIN);
lv_obj_set_size(obj, LV_PCT(100), LV_PCT(100));
// 禁用主对象的拖动/滚动功能
lv_obj_clear_flag(obj, LV_OBJ_FLAG_SCROLLABLE);
lv_obj_set_scrollbar_mode(obj, LV_SCROLLBAR_MODE_OFF);
lv_obj_set_scroll_dir(obj, LV_DIR_NONE);
// 设置flex布局和对齐方式
lv_obj_set_layout(obj, LV_LAYOUT_FLEX);
lv_obj_set_style_flex_flow(obj, LV_FLEX_FLOW_COLUMN, LV_PART_MAIN);
lv_obj_set_style_flex_main_place(obj, LV_FLEX_ALIGN_START, LV_PART_MAIN);
lv_obj_set_style_flex_cross_place(obj, LV_FLEX_ALIGN_START, LV_PART_MAIN);
lv_obj_set_style_bg_color(obj, lv_color_hex(0x000000), LV_PART_MAIN);
lv_obj_set_style_bg_opa(obj, LV_OPA_COVER, LV_PART_MAIN);
// 清除所有间距
lv_obj_set_style_pad_all(obj, 0, LV_PART_MAIN);
lv_obj_set_style_pad_gap(obj, 12, LV_PART_MAIN);
lisa_ui_llm_base_t *llm_base = (lisa_ui_llm_base_t *)obj;
// 创建任务栏
llm_base->bar = lv_obj_create(obj);
lv_obj_set_width(llm_base->bar, LV_PCT(100));
lv_obj_set_height(llm_base->bar, LISA_UI_LLM_BASE_DEFAULT_BAR_HEIGHT);
lv_obj_set_style_bg_color(llm_base->bar, lv_color_hex(LISA_UI_LLM_BASE_DEFAULT_BAR_COLOR), LV_PART_MAIN);
lv_obj_set_style_bg_opa(llm_base->bar, LV_OPA_COVER, LV_PART_MAIN);
lv_obj_set_style_border_width(llm_base->bar, 0, LV_PART_MAIN);
lv_obj_set_style_pad_all(llm_base->bar, 0, LV_PART_MAIN);
lv_obj_set_style_radius(llm_base->bar, 0, LV_PART_MAIN);
// 禁用任务栏的拖动/滚动功能
lv_obj_clear_flag(llm_base->bar, LV_OBJ_FLAG_SCROLLABLE);
lv_obj_set_scrollbar_mode(llm_base->bar, LV_SCROLLBAR_MODE_OFF);
lv_obj_set_scroll_dir(llm_base->bar, LV_DIR_NONE);
// 创建容器
llm_base->container = lv_obj_create(obj);
lv_obj_set_width(llm_base->container, LV_PCT(100));
lv_obj_set_style_bg_color(llm_base->container, lv_color_hex(0x000000), LV_PART_MAIN);
lv_obj_set_style_bg_opa(llm_base->container, LV_OPA_COVER, LV_PART_MAIN);
lv_obj_set_style_border_width(llm_base->container, 0, LV_PART_MAIN);
lv_obj_set_style_pad_all(llm_base->container, 0, LV_PART_MAIN);
lv_obj_set_style_radius(llm_base->container, 0, LV_PART_MAIN);
lv_obj_set_flex_grow(llm_base->container, 1);
// 禁用滑动功能
lv_obj_clear_flag(llm_base->container, LV_OBJ_FLAG_SCROLLABLE);
// 或者设置滑动条模式为关闭
lv_obj_set_scrollbar_mode(llm_base->container, LV_SCROLLBAR_MODE_OFF);
// 设置滑动方向为无
lv_obj_set_scroll_dir(llm_base->container, LV_DIR_NONE);
// 初始化字符串
memset(llm_base->title, 0, sizeof(llm_base->title));
}
// ===================== 公共API实现 =====================
lv_obj_t *lisa_ui_llm_base_bar_get(lv_obj_t *obj)
{
if (obj == NULL) {
// 添加错误日志
return NULL;
}
if (!LISA_UI_LLM_BASE_CLASS_CHECK(obj)) {
// 添加类型检查失败的错误日志
return NULL;
}
lisa_ui_llm_base_t *llm_base = (lisa_ui_llm_base_t *)obj;
return llm_base->bar;
}
lv_obj_t *lisa_ui_llm_base_container_get(lv_obj_t *obj)
{
if (!lisa_ui_llm_base_is_valid(obj)) {
return NULL;
}
lisa_ui_llm_base_t *llm_base = (lisa_ui_llm_base_t *)obj;
return llm_base->container;
}
void lisa_ui_llm_base_set_title(lv_obj_t *obj, const char *title)
{
if (!lisa_ui_llm_base_is_valid(obj)) {
return;
}
lisa_ui_llm_base_t *llm_base = (lisa_ui_llm_base_t *)obj;
if (title) {
strncpy(llm_base->title, title, sizeof(llm_base->title) - 1);
llm_base->title[sizeof(llm_base->title) - 1] = '\0';
} else {
memset(llm_base->title, 0, sizeof(llm_base->title));
}
}
const char *lisa_ui_llm_base_get_title(lv_obj_t *obj)
{
if (!lisa_ui_llm_base_is_valid(obj)) {
return NULL;
}
lisa_ui_llm_base_t *llm_base = (lisa_ui_llm_base_t *)obj;
return llm_base->title;
}
void lisa_ui_llm_base_set_bar_color(lv_obj_t *obj, lv_color_t color)
{
if (!lisa_ui_llm_base_is_valid(obj)) {
return;
}
lisa_ui_llm_base_t *llm_base = (lisa_ui_llm_base_t *)obj;
if (llm_base->bar) {
lv_obj_set_style_bg_color(llm_base->bar, color, LV_PART_MAIN);
}
}
void lisa_ui_llm_base_set_bar_height(lv_obj_t *obj, lv_coord_t height)
{
if (!lisa_ui_llm_base_is_valid(obj)) {
return;
}
lisa_ui_llm_base_t *llm_base = (lisa_ui_llm_base_t *)obj;
if (llm_base->bar && height > 0) {
lv_obj_set_height(llm_base->bar, height);
}
}
void lisa_ui_llm_base_clear_container(lv_obj_t *obj)
{
if (!lisa_ui_llm_base_is_valid(obj)) {
return;
}
lisa_ui_llm_base_t *llm_base = (lisa_ui_llm_base_t *)obj;
if (llm_base->container) {
lv_obj_clean(llm_base->container);
}
}

View File

@@ -0,0 +1,159 @@
/**
* @file lisa_ui_llm_base.h
* @brief LLM UI基础组件头文件
*
* 提供LLM聊天界面的基础UI组件包含任务栏和内容容器的布局管理。
* 该组件基于LVGL图形库提供了完整的创建、管理和样式设置API。
*
* @version 1.0.0
* @date 2025
* @author Lisa UI Team
*/
#ifndef __LISA_UI_LLM_BASE_H__
#define __LISA_UI_LLM_BASE_H__
#ifdef __cplusplus
extern "C" {
#endif
/*===========================================
* 包含文件
*==========================================*/
#include "lvgl.h"
#include <stdint.h>
#include <stdbool.h>
/** 默认配置参数 */
#define LISA_UI_LLM_BASE_DEFAULT_BAR_HEIGHT 36 /*!< 默认任务栏高度 */
#define LISA_UI_LLM_BASE_DEFAULT_BAR_COLOR 0x000000 /*!< 默认任务栏颜色 */
#define LISA_UI_LLM_BASE_DEFAULT_BG_COLOR 0x000000 /*!< 默认背景颜色 */
#define LISA_UI_LLM_BASE_MAX_TITLE_LENGTH 16 /*!< 最大标题长度 */
/** 类型检查宏 */
#define LISA_UI_LLM_BASE_CLASS_CHECK(obj) \
(lv_obj_has_class(obj, &lisa_ui_llm_base_class))
/**
* @brief LLM UI基础组件结构体
*
* 该结构体定义了LLM UI基础组件的数据成员包含LVGL对象、
* 任务栏、内容容器和标题信息。
*/
struct lisa_ui_llm_base {
lv_obj_t obj; /*!< LVGL基础对象 */
lv_obj_t *bar; /*!< 任务栏对象 */
lv_obj_t *container; /*!< 页面容器对象 */
char title[LISA_UI_LLM_BASE_MAX_TITLE_LENGTH]; /*!< 页面标题 */
};
/** LLM UI基础组件类型定义 */
typedef struct lisa_ui_llm_base lisa_ui_llm_base_t;
/*===========================================
* 全局变量声明
*==========================================*/
/** LLM UI基础组件类定义 */
extern const lv_obj_class_t lisa_ui_llm_base_class;
/*===========================================
* 函数声明
*==========================================*/
/**
* @brief 获取任务栏对象
*
* @param obj LLM UI基础组件对象
* @return lv_obj_t* 任务栏对象失败返回NULL
*
* @note 返回的对象可用于添加标题标签、按钮等UI元素
*/
lv_obj_t *lisa_ui_llm_base_bar_get(lv_obj_t *obj);
/**
* @brief 获取内容容器对象
*
* @param obj LLM UI基础组件对象
* @return lv_obj_t* 内容容器对象失败返回NULL
*
* @note 返回的容器对象用于放置聊天消息、输入框等内容
*/
lv_obj_t *lisa_ui_llm_base_container_get(lv_obj_t *obj);
/**
* @brief 设置页面标题
*
* @param obj LLM UI基础组件对象
* @param title 标题字符串如果为NULL则清空标题
*
* @note 标题长度限制为LISA_UI_LLM_BASE_MAX_TITLE_LENGTH-1个字符
*/
void lisa_ui_llm_base_set_title(lv_obj_t *obj, const char *title);
/**
* @brief 获取页面标题
*
* @param obj LLM UI基础组件对象
* @return const char* 标题字符串失败返回NULL
*/
const char *lisa_ui_llm_base_get_title(lv_obj_t *obj);
/**
* @brief 设置任务栏背景颜色
*
* @param obj LLM UI基础组件对象
* @param color 颜色值
*
* @code
* // 设置任务栏为绿色
* lisa_ui_llm_base_set_bar_color(llm_ui, lv_color_hex(0x4CAF50));
* @endcode
*/
void lisa_ui_llm_base_set_bar_color(lv_obj_t *obj, lv_color_t color);
/**
* @brief 设置任务栏高度
*
* @param obj LLM UI基础组件对象
* @param height 高度值(像素)
*
* @note 高度改变会影响整体布局,建议在组件创建后立即设置
*/
void lisa_ui_llm_base_set_bar_height(lv_obj_t *obj, lv_coord_t height);
/**
* @brief 清空内容容器
*
* @param obj LLM UI基础组件对象
*
* @note 该函数会删除容器中的所有子对象
*/
void lisa_ui_llm_base_clear_container(lv_obj_t *obj);
/*===========================================
* 内联函数
*==========================================*/
/**
* @brief 检查对象是否为LLM UI基础组件类型
*
* @param obj 要检查的对象
* @return true 是LLM UI基础组件类型
* @return false 不是LLM UI基础组件类型
*/
static inline bool lisa_ui_llm_base_is_valid(lv_obj_t *obj)
{
if (obj == NULL) {
return false;
}
// 检查是否为基类或其派生类
return LISA_UI_LLM_BASE_CLASS_CHECK(obj);
}
#ifdef __cplusplus
} /* extern "C" */
#endif
#endif /* __LISA_UI_LLM_BASE_H__ */

View File

@@ -0,0 +1,502 @@
#include <string.h>
#include "lisa_ui.h"
#include "lisa_ui_llm_base.h"
#include "lisa_ui_llm_primary.h"
#include "lisa_ui_assets.h"
#include "lisa_ui_fonts.h"
#include "lisa_ui_anim_ext.h"
LV_IMG_DECLARE(icons_ic_status_full_duplex_png);
LV_IMG_DECLARE(icons_ic_status_alarm_png);
static void lisa_ui_llm_primary_class_constructor(const lv_obj_class_t *class_p, lv_obj_t *obj);
const lv_obj_class_t lisa_ui_llm_primary_class = {
.base_class = &lisa_ui_llm_base_class,
.instance_size = sizeof(lisa_ui_llm_primary_t),
.constructor_cb = lisa_ui_llm_primary_class_constructor,
};
static void lisa_ui_llm_primary_class_constructor(const lv_obj_class_t *class_p, lv_obj_t *obj)
{
(void)class_p;
lisa_ui_llm_primary_t *llm_primary = (lisa_ui_llm_primary_t *)obj;
lv_obj_t *bar = lisa_ui_llm_base_bar_get(obj);
if (NULL == bar) {
LISA_UI_LOGE("Failed to get base bar");
return;
}
lv_obj_t *container = lisa_ui_llm_base_container_get(obj);
if (NULL == container) {
LISA_UI_LOGE("Failed to get base container");
return;
}
// 设置任务栏布局
lv_obj_set_style_pad_left(bar, 8, LV_PART_MAIN);
lv_obj_set_style_pad_right(bar, 8, LV_PART_MAIN);
lv_obj_set_style_pad_top(bar, 8, LV_PART_MAIN);
lv_obj_set_style_pad_bottom(bar, 8, LV_PART_MAIN);
lv_obj_set_style_min_height(bar, 36, LV_PART_MAIN);
lv_obj_set_style_base_dir(bar, LV_BASE_DIR_LTR, LV_PART_MAIN);
#ifndef CONFIG_BOARD_ARCS_MINI
// 创建设置图标(左边)
llm_primary->settings_icon = lv_img_create(bar);
lv_obj_add_flag(llm_primary->settings_icon, LV_OBJ_FLAG_CLICKABLE);
#endif
// 创建WiFi图标右边
llm_primary->wifi_icon = lv_img_create(bar);
// 创建交互模式图标(全双工)
llm_primary->full_duplex_icon = lv_img_create(bar);
// 创建闹钟图标
llm_primary->alarm_icon = lv_img_create(bar);
// 创建电量图标
llm_primary->battery_icon = lv_img_create(bar);
// 创建状态文本标签(中间)
llm_primary->status_label = lv_label_create(bar);
// 设置主容器为垂直布局
lv_obj_set_layout(container, LV_LAYOUT_FLEX);
lv_obj_set_style_flex_flow(container, LV_FLEX_FLOW_COLUMN, LV_PART_MAIN);
lv_obj_set_style_flex_main_place(container, LV_FLEX_ALIGN_CENTER, LV_PART_MAIN);
lv_obj_set_style_flex_cross_place(container, LV_FLEX_ALIGN_CENTER, LV_PART_MAIN);
// 创建emoji动画容器上半部分
llm_primary->emoji_container = lv_obj_create(container);
// 创建emoji动画图片
llm_primary->emoji_anim = lisa_ui_anim_ext_create(llm_primary->emoji_container);
// 创建拍照图片覆盖在emoji上层默认隐藏
llm_primary->img = lv_img_create(container);
// 创建图片提示文本标签(覆盖在图片下方,默认隐藏)
llm_primary->img_hint = lv_label_create(obj);
// 创建内容文本容器(下半部分)
llm_primary->content_container = lv_obj_create(container);
// 创建内容文本标签
llm_primary->content_label = lv_textarea_create(llm_primary->content_container);
#ifdef CONFIG_BOARD_ARCS_MINI_DOLL_V2
lv_obj_add_flag(bar, LV_OBJ_FLAG_HIDDEN);
lv_obj_add_flag(llm_primary->content_container, LV_OBJ_FLAG_HIDDEN);
#endif
}
// ===================== 公共API实现 =====================
lv_obj_t *lisa_ui_llm_primary_create(lv_obj_t *parent)
{
lv_obj_t *obj = lv_obj_class_create_obj(&lisa_ui_llm_primary_class, parent);
lv_obj_class_init_obj(obj);
lisa_ui_llm_primary_t *llm_primary = (lisa_ui_llm_primary_t *)obj;
#ifndef CONFIG_BOARD_ARCS_MINI
// 设置图标初始化和位置(左边)
lv_img_set_src(llm_primary->settings_icon, &icons_ic_launch_setting_png);
lv_obj_align(llm_primary->settings_icon, LV_ALIGN_LEFT_MID, 0, 0);
#endif
// 设置WiFi图标位置右边
lv_img_set_src(llm_primary->wifi_icon, &icons_ic_status_wifi_no_connect_png);
lv_obj_align(llm_primary->wifi_icon, LV_ALIGN_LEFT_MID, 0, 0);
// 设置交互模式图标(默认隐藏)
lv_img_set_src(llm_primary->full_duplex_icon, &icons_ic_status_full_duplex_png);
lv_obj_align(llm_primary->full_duplex_icon, LV_ALIGN_LEFT_MID, 20, 0);
lv_obj_add_flag(llm_primary->full_duplex_icon, LV_OBJ_FLAG_HIDDEN);
// 设置闹钟图标(默认隐藏)
lv_img_set_src(llm_primary->alarm_icon, &icons_ic_status_alarm_png);
lv_obj_align(llm_primary->alarm_icon, LV_ALIGN_RIGHT_MID, -30, 0);
lv_obj_add_flag(llm_primary->alarm_icon, LV_OBJ_FLAG_HIDDEN);
// 设置电量图标(默认隐藏)
lv_img_set_src(llm_primary->battery_icon, &icons_ic_status_power0_png);
lv_obj_align(llm_primary->battery_icon, LV_ALIGN_RIGHT_MID, 0, 0);
lv_obj_add_flag(llm_primary->battery_icon, LV_OBJ_FLAG_HIDDEN);
// 设置状态文本标签
lv_obj_set_style_text_letter_space(llm_primary->status_label, 1, LV_PART_MAIN);
lv_obj_set_style_text_color(llm_primary->status_label, lv_color_hex(0xFFFFFF), LV_PART_MAIN);
lv_obj_set_style_text_font(llm_primary->status_label, &lv_font_chinese_16, LV_PART_MAIN);
lv_obj_set_style_text_align(llm_primary->status_label, LV_TEXT_ALIGN_CENTER, LV_PART_MAIN);
lv_obj_set_style_text_line_space(llm_primary->status_label, 0, LV_PART_MAIN);
lv_obj_align(llm_primary->status_label, LV_ALIGN_CENTER, 0, 0);
// 设置emoji动画容器
lv_obj_set_style_bg_opa(llm_primary->emoji_container, LV_OPA_TRANSP, LV_PART_MAIN);
lv_obj_set_style_border_width(llm_primary->emoji_container, 0, LV_PART_MAIN);
#ifdef CONFIG_BOARD_ARCS_MINI_DOLL_V2
lv_obj_set_size(llm_primary->emoji_container, LV_PCT(100), LV_PCT(100));
#else
lv_obj_set_size(llm_primary->emoji_container, LV_PCT(100), 104);
#endif
lv_obj_set_style_pad_all(llm_primary->emoji_container, 0, LV_PART_MAIN);
#ifndef CONFIG_BOARD_ARCS_MINI
lv_obj_set_style_pad_left(llm_primary->emoji_container, 55, LV_PART_MAIN); // 左边距50像素表情右移30像素
lv_obj_set_style_pad_right(llm_primary->emoji_container, 20, LV_PART_MAIN);
#endif
lv_obj_update_layout(llm_primary->emoji_container);
// 设置emoji容器为居中对齐
lv_obj_set_layout(llm_primary->emoji_container, LV_LAYOUT_FLEX);
lv_obj_set_style_flex_flow(llm_primary->emoji_container, LV_FLEX_FLOW_COLUMN, LV_PART_MAIN);
lv_obj_set_style_flex_main_place(llm_primary->emoji_container, LV_FLEX_ALIGN_CENTER, LV_PART_MAIN);
lv_obj_set_style_flex_cross_place(llm_primary->emoji_container, LV_FLEX_ALIGN_CENTER, LV_PART_MAIN);
// 设置emoji动画图片
lv_obj_set_size(llm_primary->emoji_anim, LV_SIZE_CONTENT, LV_SIZE_CONTENT);
#ifdef CONFIG_BOARD_ARCS_MINI
lv_obj_add_flag(llm_primary->emoji_anim, LV_OBJ_FLAG_FLOATING);
lv_obj_center(llm_primary->emoji_anim);
#endif
lv_obj_set_size(llm_primary->img, LV_SIZE_CONTENT, LV_SIZE_CONTENT);
lv_obj_add_flag(llm_primary->img, LV_OBJ_FLAG_HIDDEN);
lv_obj_add_flag(llm_primary->img, LV_OBJ_FLAG_FLOATING); /* Make img ignore flex layout from the start */
lv_label_set_text(llm_primary->img_hint, "图片可在小聆AI小程序中查看");
lv_obj_set_style_text_color(llm_primary->img_hint, lv_color_hex(0xFFFFFF), LV_PART_MAIN);
lv_obj_set_style_text_font(llm_primary->img_hint, &lv_font_chinese_16, LV_PART_MAIN);
lv_obj_set_style_text_align(llm_primary->img_hint, LV_TEXT_ALIGN_CENTER, LV_PART_MAIN);
lv_obj_add_flag(llm_primary->img_hint, LV_OBJ_FLAG_HIDDEN);
lv_obj_add_flag(llm_primary->img_hint, LV_OBJ_FLAG_IGNORE_LAYOUT);
lv_obj_align(llm_primary->img_hint, LV_ALIGN_BOTTOM_MID, 0, -5);
lv_obj_move_foreground(llm_primary->img_hint);
// 设置内容文本容器
lv_obj_set_style_bg_opa(llm_primary->content_container, LV_OPA_TRANSP, LV_PART_MAIN);
lv_obj_set_style_border_width(llm_primary->content_container, 0, LV_PART_MAIN);
lv_obj_set_size(llm_primary->content_container, LV_PCT(100), 85);
lv_obj_set_style_pad_all(llm_primary->content_container, 10, LV_PART_MAIN);
// 设置内容容器为居中对齐
lv_obj_set_layout(llm_primary->content_container, LV_LAYOUT_FLEX);
lv_obj_set_style_flex_flow(llm_primary->content_container, LV_FLEX_FLOW_COLUMN, LV_PART_MAIN);
lv_obj_set_style_flex_main_place(llm_primary->content_container, LV_FLEX_ALIGN_CENTER, LV_PART_MAIN);
lv_obj_set_style_flex_cross_place(llm_primary->content_container, LV_FLEX_ALIGN_CENTER, LV_PART_MAIN);
// 设置内容文本区域
lv_textarea_set_text(llm_primary->content_label, "请唤醒我");
lv_obj_set_style_text_color(llm_primary->content_label, lv_color_hex(0xFFFFFF), LV_PART_MAIN);
lv_obj_set_style_text_font(llm_primary->content_label, &lv_font_chinese_16, LV_PART_MAIN);
lv_obj_set_style_text_align(llm_primary->content_label, LV_TEXT_ALIGN_CENTER, LV_PART_MAIN);
// 隐藏边框和背景
lv_obj_set_style_border_width(llm_primary->content_label, 0, LV_PART_MAIN); // 隐藏边框
lv_obj_set_style_bg_opa(llm_primary->content_label, LV_OPA_TRANSP, LV_PART_MAIN); // 背景透明
lv_obj_set_style_outline_width(llm_primary->content_label, 0, LV_PART_MAIN); // 隐藏轮廓
// 设置滚动和交互
lv_textarea_set_cursor_click_pos(llm_primary->content_label, false); // 禁用光标点击
lv_obj_add_flag(llm_primary->content_label, LV_OBJ_FLAG_SCROLLABLE); // 启用滚动
lv_obj_set_scrollbar_mode(llm_primary->content_label, LV_SCROLLBAR_MODE_AUTO); // 自动显示滚动条
lv_obj_clear_flag(llm_primary->content_label, LV_OBJ_FLAG_CLICKABLE); // 禁用点击
// 设置尺寸和位置 - 使用更大的高度以便滚动
lv_obj_set_width(llm_primary->content_label, LV_PCT(100));
lv_obj_set_height(llm_primary->content_label, LV_PCT(100));
// 调整文本样式
lv_obj_set_style_text_line_space(llm_primary->content_label, 0, LV_PART_MAIN); // 使用字体自身line_height
lv_obj_set_style_text_letter_space(llm_primary->content_label, 1, LV_PART_MAIN); // 字符间距(字符与字符之间)
lv_obj_set_style_pad_top(llm_primary->content_label, 20, LV_PART_MAIN); // 上边距
lv_obj_set_style_pad_bottom(llm_primary->content_label, 20, LV_PART_MAIN); // 下边距
lv_obj_set_style_pad_left(llm_primary->content_label, 12, LV_PART_MAIN); // 左边距
lv_obj_set_style_pad_right(llm_primary->content_label, 12, LV_PART_MAIN); // 右边距
return obj;
}
#ifndef CONFIG_BOARD_ARCS_MINI
lv_obj_t *lisa_ui_llm_primary_settings_icon_get(lv_obj_t *obj)
{
if (!lisa_ui_llm_primary_is_valid(obj)) {
return NULL;
}
lisa_ui_llm_primary_t *llm_primary = (lisa_ui_llm_primary_t *)obj;
return llm_primary->settings_icon;
}
#endif
lv_obj_t *lisa_ui_llm_primary_wifi_icon_get(lv_obj_t *obj)
{
if (!lisa_ui_llm_primary_is_valid(obj)) {
return NULL;
}
lisa_ui_llm_primary_t *llm_primary = (lisa_ui_llm_primary_t *)obj;
return llm_primary->wifi_icon;
}
lv_obj_t *lisa_ui_llm_primary_battery_icon_get(lv_obj_t *obj)
{
if (!lisa_ui_llm_primary_is_valid(obj)) {
return NULL;
}
lisa_ui_llm_primary_t *llm_primary = (lisa_ui_llm_primary_t *)obj;
return llm_primary->battery_icon;
}
lv_obj_t *lisa_ui_llm_primary_status_label_get(lv_obj_t *obj)
{
if (!lisa_ui_llm_primary_is_valid(obj)) {
return NULL;
}
lisa_ui_llm_primary_t *llm_primary = (lisa_ui_llm_primary_t *)obj;
return llm_primary->status_label;
}
lv_obj_t *lisa_ui_llm_primary_content_label_get(lv_obj_t *obj)
{
if (!lisa_ui_llm_primary_is_valid(obj)) {
return NULL;
}
lisa_ui_llm_primary_t *llm_primary = (lisa_ui_llm_primary_t *)obj;
return llm_primary->content_label;
}
void lisa_ui_llm_primary_set_status_text(lv_obj_t *obj, const char *status)
{
if (!lisa_ui_llm_primary_is_valid(obj)) {
return;
}
lisa_ui_llm_primary_t *llm_primary = (lisa_ui_llm_primary_t *)obj;
if (llm_primary->status_label) {
lv_label_set_text(llm_primary->status_label, status);
}
}
void lisa_ui_llm_primary_set_content_text(lv_obj_t *obj, const char *content)
{
if (!lisa_ui_llm_primary_is_valid(obj)) {
return;
}
lisa_ui_llm_primary_t *llm_primary = (lisa_ui_llm_primary_t *)obj;
if (llm_primary->content_label) {
lv_textarea_set_text(llm_primary->content_label, content);
}
}
void lisa_ui_llm_primary_add_content_text(lv_obj_t *obj, const char *content)
{
if (!lisa_ui_llm_primary_is_valid(obj)) {
return;
}
lisa_ui_llm_primary_t *llm_primary = (lisa_ui_llm_primary_t *)obj;
if (llm_primary->content_label) {
lv_textarea_add_text(llm_primary->content_label, content);
}
}
void lisa_ui_llm_primary_set_wifi_img(lv_obj_t *obj, const void *img_path)
{
if (!lisa_ui_llm_primary_is_valid(obj)) {
return;
}
lisa_ui_llm_primary_t *llm_primary = (lisa_ui_llm_primary_t *)obj;
if (llm_primary->wifi_icon && img_path) {
lv_img_set_src(llm_primary->wifi_icon, img_path);
}
}
void lisa_ui_llm_primary_set_battery_img(lv_obj_t *obj, const void *img_path)
{
if (!lisa_ui_llm_primary_is_valid(obj)) {
return;
}
lisa_ui_llm_primary_t *llm_primary = (lisa_ui_llm_primary_t *)obj;
if (!llm_primary->battery_icon) {
return;
}
if (img_path) {
lv_img_set_src(llm_primary->battery_icon, img_path);
lv_obj_clear_flag(llm_primary->battery_icon, LV_OBJ_FLAG_HIDDEN);
} else {
lv_obj_add_flag(llm_primary->battery_icon, LV_OBJ_FLAG_HIDDEN);
}
}
void lisa_ui_llm_primary_set_full_duplex_icon_visible(lv_obj_t *obj, bool visible)
{
if (!lisa_ui_llm_primary_is_valid(obj)) {
return;
}
lisa_ui_llm_primary_t *llm_primary = (lisa_ui_llm_primary_t *)obj;
if (!llm_primary->full_duplex_icon) {
return;
}
if (visible) {
lv_obj_clear_flag(llm_primary->full_duplex_icon, LV_OBJ_FLAG_HIDDEN);
} else {
lv_obj_add_flag(llm_primary->full_duplex_icon, LV_OBJ_FLAG_HIDDEN);
}
}
void lisa_ui_llm_primary_set_alarm_icon_visible(lv_obj_t *obj, bool visible)
{
if (!lisa_ui_llm_primary_is_valid(obj)) {
return;
}
lisa_ui_llm_primary_t *llm_primary = (lisa_ui_llm_primary_t *)obj;
if (!llm_primary->alarm_icon) {
return;
}
if (visible) {
lv_obj_clear_flag(llm_primary->alarm_icon, LV_OBJ_FLAG_HIDDEN);
} else {
lv_obj_add_flag(llm_primary->alarm_icon, LV_OBJ_FLAG_HIDDEN);
}
}
#ifndef CONFIG_BOARD_ARCS_MINI
void lisa_ui_llm_primary_set_settings_icon_event_cb(lv_obj_t *obj, lv_event_cb_t event_cb, void *user_data)
{
if (!lisa_ui_llm_primary_is_valid(obj)) {
return;
}
lisa_ui_llm_primary_t *llm_primary = (lisa_ui_llm_primary_t *)obj;
if (llm_primary->settings_icon && event_cb) {
lv_obj_add_event_cb(llm_primary->settings_icon, event_cb, LV_EVENT_CLICKED, user_data);
}
}
#endif
lv_obj_t *lisa_ui_llm_primary_emoji_anim_get(lv_obj_t *obj)
{
lisa_ui_llm_primary_t *llm_primary = (lisa_ui_llm_primary_t *)obj;
return llm_primary->emoji_anim;
}
static void camera_img_anim_ready_cb(lv_anim_t *a)
{
lisa_ui_llm_primary_t *llm_primary = (lisa_ui_llm_primary_t *)a->user_data;
lv_obj_add_flag(llm_primary->img, LV_OBJ_FLAG_HIDDEN);
lv_obj_set_style_opa(llm_primary->img, LV_OPA_COVER, 0); /* Reset opacity for next use */
lv_obj_clear_flag(llm_primary->emoji_anim, LV_OBJ_FLAG_HIDDEN);
#ifndef CONFIG_BOARD_ARCS_MINI_DOLL_V2
lv_obj_clear_flag(llm_primary->content_container, LV_OBJ_FLAG_HIDDEN);
#endif
}
static void camera_img_hide_timer_cb(lv_timer_t *timer)
{
lisa_ui_llm_primary_t *llm_primary = (lisa_ui_llm_primary_t *)timer->user_data;
lv_anim_t a;
lv_obj_add_flag(llm_primary->img, LV_OBJ_FLAG_HIDDEN);
lv_obj_clear_flag(llm_primary->emoji_anim, LV_OBJ_FLAG_HIDDEN);
#ifndef CONFIG_BOARD_ARCS_MINI_DOLL_V2
lv_obj_clear_flag(llm_primary->content_container, LV_OBJ_FLAG_HIDDEN);
#endif
lv_timer_del(timer);
}
void lisa_ui_llm_primary_img_hide(lv_obj_t *obj)
{
lisa_ui_llm_primary_t *llm_primary = (lisa_ui_llm_primary_t *)obj;
lv_obj_add_flag(llm_primary->img, LV_OBJ_FLAG_HIDDEN);
lv_obj_add_flag(llm_primary->img_hint, LV_OBJ_FLAG_HIDDEN);
lv_obj_clear_flag(llm_primary->emoji_anim, LV_OBJ_FLAG_HIDDEN);
#ifndef CONFIG_BOARD_ARCS_MINI_DOLL_V2
lv_obj_clear_flag(llm_primary->content_container, LV_OBJ_FLAG_HIDDEN);
#endif
}
void lisa_ui_llm_primary_img_show(lv_obj_t *obj, void *img)
{
lisa_ui_llm_primary_t *llm_primary = (lisa_ui_llm_primary_t *)obj;
bool enable_zoom = true;
if (llm_primary->img_hint) {
lv_obj_add_flag(llm_primary->img_hint, LV_OBJ_FLAG_HIDDEN);
}
if (llm_primary->content_label) {
lv_textarea_set_text(llm_primary->content_label, "");
}
lv_img_set_src(llm_primary->img, img);
/* URL网络图当前是LV_IMG_CF_RAW很多平台在RAW路径不支持zoom变换 */
if (lv_img_src_get_type(img) == LV_IMG_SRC_VARIABLE) {
const lv_img_dsc_t *dsc = (const lv_img_dsc_t *)img;
if (dsc && (dsc->header.cf == LV_IMG_CF_RAW ||
dsc->header.cf == LV_IMG_CF_RAW_ALPHA ||
dsc->header.cf == LV_IMG_CF_RAW_CHROMA_KEYED)) {
enable_zoom = false;
}
}
/* 128 = 256 * 0.5 (50% scale) */
lv_img_set_zoom(llm_primary->img, enable_zoom ? 128 : LV_IMG_ZOOM_NONE);
lv_obj_add_flag(llm_primary->img, LV_OBJ_FLAG_FLOATING);
lv_obj_update_layout(llm_primary->img);
lv_obj_align(llm_primary->img, LV_ALIGN_CENTER, 0, -25);
lv_obj_clear_flag(llm_primary->img, LV_OBJ_FLAG_HIDDEN);
lv_obj_set_style_opa(llm_primary->img, LV_OPA_COVER, 0); /* Ensure fully visible */
lv_obj_add_flag(llm_primary->emoji_anim, LV_OBJ_FLAG_HIDDEN);
}
void lisa_ui_llm_primary_img_hint_show(lv_obj_t *obj, const char *text)
{
if (!lisa_ui_llm_primary_is_valid(obj)) {
return;
}
lisa_ui_llm_primary_t *llm_primary = (lisa_ui_llm_primary_t *)obj;
if (!llm_primary->img_hint) {
return;
}
lv_label_set_text(llm_primary->img_hint, text ? text : "");
lv_obj_align(llm_primary->img_hint, LV_ALIGN_BOTTOM_MID, 0, -5);
lv_obj_move_foreground(llm_primary->img_hint);
lv_obj_clear_flag(llm_primary->img_hint, LV_OBJ_FLAG_HIDDEN);
}
void lisa_ui_llm_primary_img_hint_hide(lv_obj_t *obj)
{
if (!lisa_ui_llm_primary_is_valid(obj)) {
return;
}
lisa_ui_llm_primary_t *llm_primary = (lisa_ui_llm_primary_t *)obj;
if (!llm_primary->img_hint) {
return;
}
lv_obj_add_flag(llm_primary->img_hint, LV_OBJ_FLAG_HIDDEN);
}

View File

@@ -0,0 +1,297 @@
/**
* @file lisa_ui_llm_primary.h
* @brief LLM UI主要组件头文件
*
* 继承自LLM UI基础组件提供具体的任务栏元素实现
* 包括WiFi图标、状态文本以及容器中的emoji动画和文本显示。
*
* @version 1.0.0
* @date 2024
* @author Lisa UI Team
*/
#ifndef __LISA_UI_LLM_PRIMARY_H__
#define __LISA_UI_LLM_PRIMARY_H__
#ifdef __cplusplus
extern "C" {
#endif
/*===========================================
* 包含文件
*==========================================*/
#include "lisa_ui_llm_base.h"
#include "lvgl.h"
#include <stdint.h>
#include <stdbool.h>
/*===========================================
* 宏定义
*==========================================*/
/** 状态文本最大长度 */
#define LISA_UI_LLM_PRIMARY_MAX_STATUS_LENGTH 32
/** 内容文本最大长度 */
#define LISA_UI_LLM_PRIMARY_MAX_CONTENT_LENGTH 256
/** 类型检查宏 */
#define LISA_UI_LLM_PRIMARY_CLASS_CHECK(obj) (lv_obj_has_class(obj, &lisa_ui_llm_primary_class))
/** 根据每帧时长计算总动画时长的辅助宏 */
#define LISA_UI_LLM_DURATION_BY_FRAME(frame_duration_ms, frame_count) ((frame_duration_ms) * (frame_count))
/**
* @brief LLM UI主要组件结构体
*
* 继承自LLM UI基础组件添加具体的任务栏元素和容器内容
*/
struct lisa_ui_llm_primary {
lisa_ui_llm_base_t base_obj; /*!< 基础对象 */
/* 任务栏元素 */
#ifndef CONFIG_BOARD_ARCS_MINI
lv_obj_t *settings_icon; /*!< 设置图标 */
#endif
lv_obj_t *wifi_icon; /*!< WiFi图标 */
lv_obj_t *full_duplex_icon; /*!< 交互模式图标(全双工) */
lv_obj_t *alarm_icon; /*!< 闹钟图标 */
lv_obj_t *battery_icon; /*!< 电量图标 */
lv_obj_t *status_label; /*!< 状态文本标签 */
/* 容器内容元素 */
lv_obj_t *emoji_container; /*!< emoji 动画容器 */
lv_obj_t *emoji_anim; /*!< emoji 动画图片 */
lv_obj_t *content_container; /*!< 内容文本容器 */
lv_obj_t *content_label; /*!< 内容文本标签 */
lv_obj_t *img;
lv_obj_t *img_hint;
};
/** LLM UI主要组件类型定义 */
typedef struct lisa_ui_llm_primary lisa_ui_llm_primary_t;
/*===========================================
* 全局变量声明
*==========================================*/
/** LLM UI主要组件类定义 */
extern const lv_obj_class_t lisa_ui_llm_primary_class;
/*===========================================
* 函数声明
*==========================================*/
/**
* @brief 创建LLM UI主要组件
*
* @param parent 父对象如果为NULL则使用当前活动屏幕
* @return lv_obj_t* 创建的LLM UI主要组件对象失败返回NULL
*
* @note 该函数会自动创建任务栏的所有元素和容器内容
*
* @code
* // 创建LLM UI主要组件
* lv_obj_t *llm_ui = lisa_ui_llm_primary_create(lv_scr_act());
* if (llm_ui != NULL) {
* // 组件创建成功,包含完整的任务栏和内容区域
* }
* @endcode
*/
lv_obj_t *lisa_ui_llm_primary_create(lv_obj_t *parent);
#ifndef CONFIG_BOARD_ARCS_MINI
/**
* @brief 获取设置图标对象
*
* @param obj LLM UI主要组件对象
* @return lv_obj_t* 设置图标对象失败返回NULL
*/
lv_obj_t *lisa_ui_llm_primary_settings_icon_get(lv_obj_t *obj);
#endif
/**
* @brief 获取WiFi图标对象
*
* @param obj LLM UI主要组件对象
* @return lv_obj_t* WiFi图标对象失败返回NULL
*/
lv_obj_t *lisa_ui_llm_primary_wifi_icon_get(lv_obj_t *obj);
/**
* @brief 获取状态文本标签对象
*
* @param obj LLM UI主要组件对象
* @return lv_obj_t* 状态文本标签对象失败返回NULL
*/
lv_obj_t *lisa_ui_llm_primary_status_label_get(lv_obj_t *obj);
/**
* @brief 获取电量图标对象
*
* @param obj LLM UI主要组件对象
* @return lv_obj_t* 电量图标对象失败返回NULL
*/
lv_obj_t *lisa_ui_llm_primary_battery_icon_get(lv_obj_t *obj);
/**
* @brief 获取emoji动画图片对象
*
* @param obj LLM UI主要组件对象
* @return lv_obj_t* emoji动画图片对象失败返回NULL
*/
lv_obj_t *lisa_ui_llm_primary_emoji_img_get(lv_obj_t *obj);
/**
* @brief 获取内容文本标签对象
*
* @param obj LLM UI主要组件对象
* @return lv_obj_t* 内容文本标签对象失败返回NULL
*/
lv_obj_t *lisa_ui_llm_primary_content_label_get(lv_obj_t *obj);
/**
* @brief 设置状态文本
*
* @param obj LLM UI主要组件对象
* @param status 状态文本如果为NULL则清空状态
*
*/
void lisa_ui_llm_primary_set_status_text(lv_obj_t *obj, const char *status);
/**
* @brief 设置内容文本
*
* @param obj LLM UI主要组件对象
* @param content 内容文本如果为NULL则清空内容
*
*/
void lisa_ui_llm_primary_set_content_text(lv_obj_t *obj, const char *content);
/**
* @brief 设置内容文本(追加)
*
* @param obj LLM UI主要组件对象
* @param content 内容文本如果为NULL则清空内容
*
*/
void lisa_ui_llm_primary_add_content_text(lv_obj_t *obj, const char *content);
/**
* @brief 设置WiFi图标显示状态
*
* @param obj LLM UI主要组件对象
* @param img_path 图片路径如果为NULL则不显示WiFi图标
*/
void lisa_ui_llm_primary_set_wifi_img(lv_obj_t *obj, const void *img_path);
/**
* @brief 设置交互模式图标显示状态
*
* @param obj LLM UI主要组件对象
* @param visible true显示false隐藏
*/
void lisa_ui_llm_primary_set_full_duplex_icon_visible(lv_obj_t *obj, bool visible);
/**
* @brief 设置闹钟图标显示状态
*
* @param obj LLM UI主要组件对象
* @param visible true显示false隐藏
*/
void lisa_ui_llm_primary_set_alarm_icon_visible(lv_obj_t *obj, bool visible);
/**
* @brief 设置电量图标显示状态
*
* @param obj LLM UI主要组件对象
* @param img_path 图片路径如果为NULL则隐藏图标
*/
void lisa_ui_llm_primary_set_battery_img(lv_obj_t *obj, const void *img_path);
/**
* @brief 启动emoji动画
*
* @param obj LLM UI主要组件对象
*/
void lisa_ui_llm_primary_start_emoji_animation(lv_obj_t *obj);
/**
* @brief 停止emoji动画
*
* @param obj LLM UI主要组件对象
*/
void lisa_ui_llm_primary_stop_emoji_animation(lv_obj_t *obj);
/**
* @brief 设置自定义emoji动画图片
*
* @param obj LLM UI主要组件对象
* @param images 图片数组指针
* @param images_count 图片数量
* @param duration 动画时长(毫秒)
* @param first_frame_delay 第一帧停留时间(毫秒)0表示无延迟
*/
void lisa_ui_llm_primary_set_custom_emoji_animation(lv_obj_t *obj, const void **images, uint32_t images_count,
uint32_t duration, uint32_t first_frame_delay);
/**
* @brief 设置emoji动画循环参数
*
* @param obj LLM UI主要组件对象
* @param loop_count 循环次数0表示无限循环
*/
void lisa_ui_llm_primary_set_loop_count(lv_obj_t *obj, uint32_t loop_count);
#ifndef CONFIG_BOARD_ARCS_MINI
/**
* @brief 设置设置图标事件回调
*
* @param obj LLM UI主要组件对象
* @param event_cb 事件回调函数
* @param user_data 用户数据
*/
void lisa_ui_llm_primary_set_settings_icon_event_cb(lv_obj_t *obj, lv_event_cb_t event_cb, void *user_data);
#endif
/**
* @brief 显示拍照图片到表情容器
*
* @param obj LLM UI主要组件对象
* @param rgb565_data RGB565图片数据
* @param width 图片宽度
* @param height 图片高度
*/
void lisa_ui_llm_primary_show_camera_image(lv_obj_t *obj, const uint16_t *rgb565_data, uint32_t width, uint32_t height);
/**
* @brief 隐藏拍照图片
*
* @param obj LLM UI主要组件对象
*/
void lisa_ui_llm_primary_hide_camera_image(lv_obj_t *obj);
lv_obj_t *lisa_ui_llm_primary_emoji_anim_get(lv_obj_t *obj);
/*===========================================
* 内联函数
*==========================================*/
/**
* @brief 检查对象是否为LLM UI主要组件类型
*
* @param obj 要检查的对象
* @return true 是LLM UI主要组件类型
* @return false 不是LLM UI主要组件类型
*/
static inline bool lisa_ui_llm_primary_is_valid(lv_obj_t *obj)
{
return (obj != NULL) && LISA_UI_LLM_PRIMARY_CLASS_CHECK(obj);
}
void lisa_ui_llm_primary_img_show(lv_obj_t *obj, void *img);
void lisa_ui_llm_primary_img_hide(lv_obj_t *obj);
void lisa_ui_llm_primary_img_hint_show(lv_obj_t *obj, const char *text);
void lisa_ui_llm_primary_img_hint_hide(lv_obj_t *obj);
#ifdef __cplusplus
} /* extern "C" */
#endif
#endif /* __LISA_UI_LLM_PRIMARY_H__ */

View File

@@ -0,0 +1,186 @@
/**
* @file ota_view.c
* @brief OTA status view implementation
*/
#define TAG "ota_view"
#include "ota_view.h"
#include "lisa_ui.h"
#include "lisa_ui_llm_base.h"
#include "lisa_ui_fonts.h"
#include <stdio.h>
typedef struct {
lisa_ui_llm_base_t base;
lv_obj_t *status_label;
lv_obj_t *progress_label;
lv_obj_t *eta_label;
char status_text[48];
char progress_text[32];
char eta_text[32];
} lisa_ui_ota_view_t;
static void ota_view_constructor(const lv_obj_class_t *class_p, lv_obj_t *obj);
static void ota_view_destructor(const lv_obj_class_t *class_p, lv_obj_t *obj);
const lv_obj_class_t lisa_ui_ota_view_class = {
.base_class = &lisa_ui_llm_base_class,
.instance_size = sizeof(lisa_ui_ota_view_t),
.constructor_cb = ota_view_constructor,
.destructor_cb = ota_view_destructor,
};
static void ota_view_constructor(const lv_obj_class_t *class_p, lv_obj_t *obj)
{
LV_UNUSED(class_p);
lisa_ui_ota_view_t *view = (lisa_ui_ota_view_t *)obj;
lv_obj_t *bar = lisa_ui_llm_base_bar_get(obj);
if (bar) {
lv_obj_add_flag(bar, LV_OBJ_FLAG_HIDDEN);
}
lv_obj_t *container = lisa_ui_llm_base_container_get(obj);
if (!container) {
return;
}
view->status_label = lv_label_create(container);
lv_label_set_text(view->status_label, "正在检查更新…");
lv_obj_set_style_text_color(view->status_label, lv_color_white(), 0);
lv_obj_set_style_text_font(view->status_label, &lv_font_chinese_16, 0);
lv_obj_set_width(view->status_label, lv_pct(100));
lv_obj_set_style_text_align(view->status_label, LV_TEXT_ALIGN_CENTER, 0);
lv_obj_align(view->status_label, LV_ALIGN_CENTER, 0, -16);
view->progress_label = lv_label_create(container);
lv_label_set_text(view->progress_label, "");
lv_obj_set_style_text_color(view->progress_label, lv_color_white(), 0);
lv_obj_set_style_text_font(view->progress_label, &lv_font_chinese_16, 0);
lv_obj_set_style_text_opa(view->progress_label, LV_OPA_70, 0);
lv_obj_set_width(view->progress_label, lv_pct(100));
lv_obj_set_style_text_align(view->progress_label, LV_TEXT_ALIGN_CENTER, 0);
lv_obj_align(view->progress_label, LV_ALIGN_CENTER, 0, 8);
view->eta_label = lv_label_create(container);
lv_label_set_text(view->eta_label, "");
lv_obj_set_style_text_color(view->eta_label, lv_color_white(), 0);
lv_obj_set_style_text_font(view->eta_label, &lv_font_chinese_16, 0);
lv_obj_set_style_text_opa(view->eta_label, LV_OPA_50, 0);
lv_obj_set_width(view->eta_label, lv_pct(100));
lv_obj_set_style_text_align(view->eta_label, LV_TEXT_ALIGN_CENTER, 0);
lv_obj_align(view->eta_label, LV_ALIGN_BOTTOM_MID, 0, -12);
}
static void ota_view_destructor(const lv_obj_class_t *class_p, lv_obj_t *obj)
{
(void)class_p;
(void)obj;
}
lv_obj_t *lisa_ui_ota_view_create(lv_obj_t *parent)
{
lv_obj_t *obj = lv_obj_class_create_obj(&lisa_ui_ota_view_class, parent);
lv_obj_class_init_obj(obj);
return obj;
}
static const char *ota_target_name(ota_target_e target)
{
switch (target) {
case OTA_TARGET_WAKE_WORD:
return "唤醒词";
case OTA_TARGET_PROMPT_TONE:
return "提示音";
case OTA_TARGET_EMOJI:
return "表情";
default:
return "资源";
}
}
static void ota_view_format_size(char *buf, size_t buf_size, uint32_t bytes)
{
if (bytes >= 1024 * 1024) {
snprintf(buf, buf_size, "%.1fMB", (float)bytes / (1024.0f * 1024.0f));
} else {
snprintf(buf, buf_size, "%uKB", bytes / 1024);
}
}
void lisa_ui_ota_view_update(lv_obj_t *obj, const ota_state_t *state)
{
if (!obj || !lv_obj_has_class(obj, &lisa_ui_ota_view_class)) {
return;
}
lisa_ui_ota_view_t *view = (lisa_ui_ota_view_t *)obj;
switch (state->state) {
case OTA_STATE_UPDATING: {
// 第一行N/M 正在更新XXX…
if (state->update_total > 0) {
snprintf(view->status_text, sizeof(view->status_text), "%u/%u 正在更新%s…",
state->update_index, state->update_total, ota_target_name(state->target));
} else {
snprintf(view->status_text, sizeof(view->status_text), "正在更新%s…",
ota_target_name(state->target));
}
lv_label_set_text_static(view->status_label, view->status_text);
// 第二行:已下载 / 总大小
if (state->bytes_total > 0) {
char processed_str[16], total_str[16];
ota_view_format_size(processed_str, sizeof(processed_str), state->bytes_processed);
ota_view_format_size(total_str, sizeof(total_str), state->bytes_total);
snprintf(view->progress_text, sizeof(view->progress_text), "%s / %s", processed_str, total_str);
lv_label_set_text_static(view->progress_label, view->progress_text);
} else {
lv_label_set_text_static(view->progress_label, "");
}
// 第三行:预计剩余时间
if (state->bytes_processed < state->bytes_total) {
if (state->bytes_processed > 0 && state->elapsed_ms > 0) {
uint32_t remaining_bytes = state->bytes_total - state->bytes_processed;
float speed = (float)state->bytes_processed / (float)state->elapsed_ms;
uint32_t eta_sec = (uint32_t)((float)remaining_bytes / speed / 1000.0f);
uint32_t eta_min = (eta_sec + 59) / 60;
if (eta_min <= 1) {
snprintf(view->eta_text, sizeof(view->eta_text), "预计不到1分钟");
} else {
snprintf(view->eta_text, sizeof(view->eta_text), "预计%u分钟", eta_min);
}
} else {
snprintf(view->eta_text, sizeof(view->eta_text), "预计约1分钟");
}
lv_label_set_text_static(view->eta_label, view->eta_text);
} else {
lv_label_set_text_static(view->eta_label, "");
}
break;
}
case OTA_STATE_SUCCESSED:
lv_label_set_text_static(view->status_label, "更新完毕");
lv_label_set_text_static(view->progress_label, "");
if (state->reboot == OTA_REBOOT_STRATEGY_AUTO) {
lv_label_set_text_static(view->eta_label, "正在重启…");
} else {
lv_label_set_text_static(view->eta_label, "请手动重启设备");
}
break;
case OTA_STATE_FAILED:
lv_label_set_text_static(view->status_label, "更新失败");
lv_label_set_text_static(view->progress_label, "");
if (state->reboot == OTA_REBOOT_STRATEGY_AUTO) {
lv_label_set_text_static(view->eta_label, "正在重启…");
} else {
lv_label_set_text_static(view->eta_label, "请手动重启设备");
}
break;
default:
break;
}
}

View File

@@ -0,0 +1,10 @@
#ifndef __LISA_UI_OTA_VIEW_H__
#define __LISA_UI_OTA_VIEW_H__
#include "lvgl.h"
#include "ota_manager.h"
lv_obj_t *lisa_ui_ota_view_create(lv_obj_t *parent);
void lisa_ui_ota_view_update(lv_obj_t *obj, const ota_state_t *state);
#endif

View File

@@ -0,0 +1,102 @@
#include "quota_qrcode_view.h"
#include "lisa_ui_llm_base.h"
#include "lisa_ui_fonts.h"
#define TAG "quota_qrcode_view"
typedef struct {
lv_obj_t obj;
lv_obj_t *qr_img;
lv_obj_t *title_label;
lv_obj_t *message_label;
} quota_qrcode_view_t;
static void quota_qrcode_view_constructor(const lv_obj_class_t *class_p, lv_obj_t *obj);
static void quota_qrcode_view_destructor(const lv_obj_class_t *class_p, lv_obj_t *obj);
const lv_obj_class_t lisa_ui_quota_qrcode_view_class = {
.constructor_cb = quota_qrcode_view_constructor,
.destructor_cb = quota_qrcode_view_destructor,
.instance_size = sizeof(quota_qrcode_view_t),
.base_class = &lv_obj_class
};
static void quota_qrcode_view_constructor(const lv_obj_class_t *class_p, lv_obj_t *obj)
{
LV_UNUSED(class_p);
LV_TRACE_OBJ_CREATE("begin");
quota_qrcode_view_t *view = (quota_qrcode_view_t *)obj;
lv_obj_clear_flag(obj, LV_OBJ_FLAG_SCROLLABLE);
lv_obj_set_size(obj, LV_PCT(100), LV_PCT(100));
lv_obj_set_style_bg_color(obj, lv_color_black(), 0);
lv_obj_set_style_bg_opa(obj, LV_OPA_COVER, 0);
view->qr_img = lv_img_create(obj);
#ifdef CONFIG_BOARD_ARCS_MINI_DOLL_V2
lv_obj_set_size(view->qr_img, 160, 160);
lv_obj_align(view->qr_img, LV_ALIGN_CENTER, -80, 0);
#else
lv_obj_set_size(view->qr_img, 148, 148);
lv_obj_align(view->qr_img, LV_ALIGN_CENTER, 0, 5);
#endif
view->title_label = lv_label_create(obj);
lv_obj_set_style_text_color(view->title_label, lv_color_white(), 0);
lv_obj_set_style_text_font(view->title_label, &lv_font_chinese_16, 0);
lv_obj_set_style_text_align(view->title_label, LV_TEXT_ALIGN_CENTER, 0);
lv_obj_set_width(view->title_label, LV_PCT(90));
lv_label_set_long_mode(view->title_label, LV_LABEL_LONG_WRAP);
lv_label_set_text(view->title_label, "");
lv_obj_align_to(view->title_label, view->qr_img, LV_ALIGN_OUT_TOP_MID, 0, -25);
view->message_label = lv_label_create(obj);
lv_obj_set_style_text_color(view->message_label, lv_color_white(), 0);
lv_obj_set_style_text_font(view->message_label, &lv_font_chinese_16, 0);
lv_obj_set_style_text_align(view->message_label, LV_TEXT_ALIGN_CENTER, 0);
lv_obj_set_width(view->message_label, LV_PCT(100));
lv_label_set_long_mode(view->message_label, LV_LABEL_LONG_WRAP);
lv_label_set_text(view->message_label, "");
lv_obj_align_to(view->message_label, view->qr_img, LV_ALIGN_OUT_BOTTOM_MID, 0, 0);
LV_TRACE_OBJ_CREATE("finished");
}
static void quota_qrcode_view_destructor(const lv_obj_class_t *class_p, lv_obj_t *obj)
{
LV_UNUSED(class_p);
LV_TRACE_OBJ_CREATE("begin");
LV_TRACE_OBJ_CREATE("finished");
}
lv_obj_t *lisa_ui_quota_qrcode_view_create(lv_obj_t *parent)
{
lv_obj_t *obj = lv_obj_class_create_obj(&lisa_ui_quota_qrcode_view_class, parent);
lv_obj_class_init_obj(obj);
return obj;
}
void lisa_ui_quota_qrcode_view_set_title(lv_obj_t *view, const char *title)
{
quota_qrcode_view_t *v = (quota_qrcode_view_t *)view;
if (v && v->title_label && title) {
lv_label_set_text(v->title_label, title);
}
}
void lisa_ui_quota_qrcode_view_set_message(lv_obj_t *view, const char *message)
{
quota_qrcode_view_t *v = (quota_qrcode_view_t *)view;
if (v && v->message_label && message) {
lv_label_set_text(v->message_label, message);
}
}
void lisa_ui_quota_qrcode_view_set_qr_image(lv_obj_t *view, const void *img_src)
{
quota_qrcode_view_t *v = (quota_qrcode_view_t *)view;
if (v && v->qr_img && img_src) {
lv_img_set_src(v->qr_img, img_src);
}
}

View File

@@ -0,0 +1,11 @@
#ifndef LISA_UI_QUOTA_QRCODE_VIEW_H
#define LISA_UI_QUOTA_QRCODE_VIEW_H
#include "lvgl.h"
lv_obj_t *lisa_ui_quota_qrcode_view_create(lv_obj_t *parent);
void lisa_ui_quota_qrcode_view_set_title(lv_obj_t *view, const char *title);
void lisa_ui_quota_qrcode_view_set_message(lv_obj_t *view, const char *message);
void lisa_ui_quota_qrcode_view_set_qr_image(lv_obj_t *view, const void *img_src);
#endif

View File

@@ -0,0 +1,14 @@
target_sources(
${LISA_UI_LIB_NAME}
PRIVATE
setting_common_view.c
setting_wifi_view.c
setting_wakeup_view.c
setting_clock_view.c
)
target_include_directories(
${LISA_UI_LIB_NAME}
PRIVATE
./
)

View File

@@ -0,0 +1,387 @@
/**
* @file setting_clock_view.c
* @brief Clock/Alarm settings view implementation
*/
#include "setting_clock_view.h"
#include "lisa_ui.h"
#include "lisa_ui_llm_base.h"
#include "lisa_ui_nav_scr.h"
#include "lisa_ui_nav_scr_ids.h"
#include "lisa_ui_assets.h"
#include "lisa_ui_fonts.h"
#include <time.h>
#include <stdio.h>
#define TAG "setting_clock_view"
typedef struct {
lisa_ui_llm_base_t base;
lv_obj_t *back_btn;
lv_obj_t *scroll_container;
lv_obj_t *empty_label;
lisa_ui_setting_clock_back_cb_t back_cb; /* 返回按钮回调函数 */
void *back_user_data; /* 回调函数用户数据 */
lisa_ui_setting_clock_delete_cb_t delete_cb; /* 删除闹钟回调函数 */
alarm_item_t alarm_items[20]; /* 使用统一的alarm结构体 */
int alarm_count;
} lisa_ui_setting_clock_view_t;
static void lisa_ui_setting_clock_view_class_constructor(const lv_obj_class_t *class_p, lv_obj_t *obj);
const lv_obj_class_t lisa_ui_setting_clock_view_class = {
.base_class = &lisa_ui_llm_base_class,
.constructor_cb = lisa_ui_setting_clock_view_class_constructor,
.instance_size = sizeof(lisa_ui_setting_clock_view_t),
};
static void back_btn_event_cb(lv_event_t *e)
{
lv_event_code_t code = lv_event_get_code(e);
if (code == LV_EVENT_CLICKED) {
lisa_ui_setting_clock_view_t *view = (lisa_ui_setting_clock_view_t *)lv_event_get_user_data(e);
LISA_UI_LOGD("Clock back button clicked");
/* 调用回调函数将页面导航逻辑交给presenter层处理 */
if (view && view->back_cb) {
view->back_cb(view->back_user_data);
}
}
}
static void delete_btn_event_cb(lv_event_t *e)
{
lv_event_code_t code = lv_event_get_code(e);
if (code == LV_EVENT_CLICKED) {
lv_obj_t *btn = lv_event_get_target(e);
lv_obj_t *view_obj = lv_obj_get_parent(lv_obj_get_parent(btn));
while (view_obj && !lv_obj_check_type(view_obj, &lisa_ui_setting_clock_view_class)) {
view_obj = lv_obj_get_parent(view_obj);
}
if (!view_obj) {
return;
}
lisa_ui_setting_clock_view_t *view = (lisa_ui_setting_clock_view_t *)view_obj;
if (!view || !view->delete_cb) {
return;
}
uint64_t *timestamp = (uint64_t *)lv_event_get_user_data(e);
if (timestamp) {
view->delete_cb(*timestamp);
}
}
}
/* 手动将timestamp转换为日期时间不依赖localtime() */
static void timestamp_to_datetime(uint64_t timestamp, int *year, int *month, int *day, int *hour, int *minute,
int *second)
{
/* 时区偏移UTC+8 (28800秒) */
const int timezone_offset = 8 * 3600;
time_t t = (time_t)timestamp + timezone_offset;
/* 计算天数 */
int days = t / 86400;
int remaining = t % 86400;
/* 计算时分秒 */
*hour = remaining / 3600;
*minute = (remaining % 3600) / 60;
*second = remaining % 60;
/* 计算年月日 (从1970年1月1日开始) */
int y = 1970;
while (1) {
int days_in_year = (y % 4 == 0 && (y % 100 != 0 || y % 400 == 0)) ? 366 : 365;
if (days < days_in_year) {
break;
}
days -= days_in_year;
y++;
}
*year = y;
/* 计算月份 */
int days_in_month[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
if (*year % 4 == 0 && (*year % 100 != 0 || *year % 400 == 0)) {
days_in_month[1] = 29;
}
int m = 0;
while (m < 12 && days >= days_in_month[m]) {
days -= days_in_month[m];
m++;
}
*month = m + 1;
*day = days + 1;
}
/* 使用蔡勒公式计算星期几 */
static int calculate_weekday_zeller(int year, int month, int day)
{
/* 蔡勒公式W = (d + (13*(m+1))/5 + y + y/4 - y/100 + y/400) % 7 */
/* 注意1月和2月要当作上一年的13月和14月来计算 */
if (month < 3) {
month += 12;
year -= 1;
}
int century = year / 100;
int year_of_century = year % 100;
int w = (day + (13 * (month + 1)) / 5 + year_of_century + year_of_century / 4 + century / 4 - 2 * century) % 7;
/* 蔡勒公式结果0=周六, 1=周日, 2=周一, 3=周二, 4=周三, 5=周四, 6=周五 */
/* 转换为0=周日, 1=周一, 2=周二, 3=周三, 4=周四, 5=周五, 6=周六 */
int weekday = (w + 6) % 7;
return weekday;
}
static void format_alarm_time_string(uint64_t timestamp, char *time_str, char *date_str)
{
int year, month, day, hour, minute, second;
LISA_UI_LOGI("Format alarm time - timestamp: %llu", (unsigned long long)timestamp);
/* 检查timestamp是否有效负数或过大的值都是无效的 */
if ((int64_t)timestamp < 0 || timestamp > 4102444800ULL) {
LISA_UI_LOGE(" Invalid timestamp: %lld (negative or too large)", (int64_t)timestamp);
snprintf(time_str, 16, "--:--");
snprintf(date_str, 32, _("alarm invalid"));
return;
}
/* 手动转换时间戳 */
timestamp_to_datetime(timestamp, &year, &month, &day, &hour, &minute, &second);
LISA_UI_LOGI(" Converted: year=%d, mon=%d, mday=%d, hour=%d, min=%d, sec=%d", year, month, day, hour, minute,
second);
/* 格式化时间 */
snprintf(time_str, 16, "%02d:%02d", hour, minute);
/* 使用蔡勒公式计算正确的星期几 */
int weekday = calculate_weekday_zeller(year, month, day);
LISA_UI_LOGI(" Zeller weekday: %d", weekday);
const char *weekdays[] = {"周日", "周一", "周二", "周三", "周四", "周五", "周六"};
snprintf(date_str, 32, "%d月%d日 %s", month, day, weekdays[weekday]);
LISA_UI_LOGI(" Formatted: time='%s', date='%s'", time_str, date_str);
}
static lv_obj_t *create_alarm_item(lv_obj_t *parent, const alarm_item_t *alarm_item, int y_offset)
{
lv_obj_t *alarm_container = lv_obj_create(parent);
lv_obj_set_size(alarm_container, LV_PCT(95), 80);
lv_obj_align(alarm_container, LV_ALIGN_TOP_MID, 0, y_offset);
lv_obj_set_style_bg_color(alarm_container, lv_color_make(0x4A, 0x4A, 0x4A), 0);
lv_obj_set_style_border_width(alarm_container, 0, 0);
lv_obj_set_style_radius(alarm_container, 8, 0);
lv_obj_set_style_pad_all(alarm_container, 10, 0);
char time_str[16];
char date_str[32];
format_alarm_time_string(alarm_item->timestamp, time_str, date_str);
lv_obj_t *time_label = lv_label_create(alarm_container);
lv_label_set_text(time_label, time_str);
lv_obj_set_style_text_color(time_label, lv_color_white(), 0);
lv_obj_set_style_text_font(time_label, &lv_font_chinese_16, 0);
lv_obj_align(time_label, LV_ALIGN_TOP_LEFT, 5, 8);
lv_obj_t *date_label = lv_label_create(alarm_container);
lv_label_set_text(date_label, date_str);
lv_obj_set_style_text_color(date_label, lv_color_make(0xAA, 0xAA, 0xAA), 0);
lv_obj_set_style_text_font(date_label, &lv_font_chinese_16, 0);
lv_obj_align(date_label, LV_ALIGN_BOTTOM_LEFT, 5, -8);
lv_obj_t *delete_btn = lv_btn_create(alarm_container);
lv_obj_set_size(delete_btn, 50, 30);
lv_obj_align(delete_btn, LV_ALIGN_RIGHT_MID, -5, 0);
lv_obj_set_style_bg_color(delete_btn, lv_color_make(0xFF, 0x44, 0x44), 0);
lv_obj_set_style_border_width(delete_btn, 0, 0);
lv_obj_set_style_radius(delete_btn, 4, 0);
lv_obj_t *delete_label = lv_label_create(delete_btn);
lv_label_set_text(delete_label, _("cancel"));
lv_obj_set_style_text_color(delete_label, lv_color_white(), 0);
lv_obj_set_style_text_font(delete_label, &lv_font_chinese_16, 0);
lv_obj_center(delete_label);
lv_obj_add_event_cb(delete_btn, delete_btn_event_cb, LV_EVENT_CLICKED, (void *)&alarm_item->timestamp);
return alarm_container;
}
static void lisa_ui_setting_clock_view_class_constructor(const lv_obj_class_t *class_p, lv_obj_t *obj)
{
LV_UNUSED(class_p);
lisa_ui_setting_clock_view_t *view = (lisa_ui_setting_clock_view_t *)obj;
// Hide the bar to allow content to start from the very top
lv_obj_t *bar = lisa_ui_llm_base_bar_get(obj);
if (bar) {
lv_obj_add_flag(bar, LV_OBJ_FLAG_HIDDEN);
}
lv_obj_t *container = lisa_ui_llm_base_container_get(obj);
if (NULL == container) {
LISA_UI_LOGE("Failed to get base container");
return;
}
// Back button at the very top
view->back_btn = lv_btn_create(container);
lv_obj_set_size(view->back_btn, 30, 30);
lv_obj_align(view->back_btn, LV_ALIGN_TOP_LEFT, 5, 0);
lv_obj_set_style_bg_opa(view->back_btn, LV_OPA_TRANSP, LV_PART_MAIN);
lv_obj_set_style_border_width(view->back_btn, 0, LV_PART_MAIN);
lv_obj_t *back_icon = lv_img_create(view->back_btn);
lv_img_set_src(back_icon, &icons_icon_back_png);
lv_obj_center(back_icon);
lv_obj_add_event_cb(view->back_btn, back_btn_event_cb, LV_EVENT_CLICKED, view);
/* 在返回按钮旁边创建标题文本 */
lv_obj_t *title = lv_label_create(container);
lv_label_set_text(title, _("alarm setting"));
lv_obj_set_style_text_font(title, &lv_font_chinese_16, LV_PART_MAIN);
lv_obj_set_style_text_color(title, lv_color_white(), LV_PART_MAIN);
lv_obj_align(title, LV_ALIGN_TOP_LEFT, 45, 8); /* 紧贴返回按钮右侧 */
/* 初始化列表容器为NULL等待刷新时创建 */
view->scroll_container = NULL;
view->empty_label = NULL;
view->delete_cb = NULL;
view->alarm_count = 0;
}
lv_obj_t *lisa_ui_setting_clock_view_create(lv_obj_t *parent)
{
lv_obj_t *obj = lv_obj_class_create_obj(&lisa_ui_setting_clock_view_class, parent);
lv_obj_class_init_obj(obj);
/* 初始化回调函数为NULL */
lisa_ui_setting_clock_view_t *view = (lisa_ui_setting_clock_view_t *)obj;
view->back_cb = NULL;
view->back_user_data = NULL;
view->delete_cb = NULL;
return obj;
}
void lisa_ui_setting_clock_view_set_alarms(lv_obj_t *obj, const alarm_item_t *alarms, uint32_t count)
{
if (!obj) {
return;
}
lisa_ui_setting_clock_view_t *view = (lisa_ui_setting_clock_view_t *)obj;
/* 限制最大数量 */
uint32_t copy_count = (count > 20) ? 20 : count;
/* 复制闹钟数据到view */
for (uint32_t i = 0; i < copy_count; i++) {
view->alarm_items[i].timestamp = alarms[i].timestamp;
LISA_UI_LOGI("set alarms, timestamp: %llu", view->alarm_items[i].timestamp);
}
view->alarm_count = copy_count;
LISA_UI_LOGD("Set %u alarms to view", copy_count);
}
void lisa_ui_setting_clock_view_refresh_list(lv_obj_t *obj)
{
if (!obj) {
return;
}
lisa_ui_setting_clock_view_t *view = (lisa_ui_setting_clock_view_t *)obj;
lv_obj_t *container = lisa_ui_llm_base_container_get(obj);
if (!container) {
return;
}
/* 清理旧的UI元素 */
if (view->scroll_container) {
lv_obj_del(view->scroll_container);
view->scroll_container = NULL;
}
if (view->empty_label) {
lv_obj_del(view->empty_label);
view->empty_label = NULL;
}
LISA_UI_LOGD("Refreshing alarm list, count=%d", view->alarm_count);
if (view->alarm_count == 0) {
LISA_UI_LOGD("No alarms to display, showing empty state");
view->empty_label = lv_label_create(container);
lv_label_set_text(view->empty_label, _("no alarm"));
lv_obj_set_style_text_color(view->empty_label, lv_color_make(0x88, 0x88, 0x88), 0);
lv_obj_set_style_text_font(view->empty_label, &lv_font_chinese_16, 0);
lv_obj_set_style_text_align(view->empty_label, LV_TEXT_ALIGN_CENTER, 0);
lv_obj_align(view->empty_label, LV_ALIGN_CENTER, 0, 0);
lv_obj_set_width(view->empty_label, lv_pct(100));
lv_obj_set_height(view->empty_label, LV_SIZE_CONTENT);
lv_label_set_long_mode(view->empty_label, LV_LABEL_LONG_WRAP);
return;
}
/* 创建滚动容器 */
view->scroll_container = lv_obj_create(container);
lv_obj_set_size(view->scroll_container, LV_PCT(100), LV_PCT(75));
lv_obj_align(view->scroll_container, LV_ALIGN_BOTTOM_MID, 0, 0);
lv_obj_set_style_bg_color(view->scroll_container, lv_color_black(), 0);
lv_obj_set_style_border_width(view->scroll_container, 0, 0);
lv_obj_set_style_pad_all(view->scroll_container, 10, 0);
/* 创建UI元素 */
int y_offset = 10;
for (int i = 0; i < view->alarm_count; i++) {
/* 跳过无效的时间戳 */
if (view->alarm_items[i].timestamp == 0) {
LISA_UI_LOGD("Skipping alarm with timestamp=0 at index %d", i);
continue;
}
LISA_UI_LOGI("Creating alarm item %d: timestamp=%llu", i, (unsigned long long)view->alarm_items[i].timestamp);
lv_obj_t *item = create_alarm_item(view->scroll_container, &view->alarm_items[i], y_offset);
if (item) {
y_offset += 90;
}
}
}
void lisa_ui_setting_clock_view_set_back_cb(lv_obj_t *obj, lisa_ui_setting_clock_back_cb_t cb, void *user_data)
{
if (!obj) {
return;
}
/* 设置返回按钮回调函数和用户数据 */
lisa_ui_setting_clock_view_t *view = (lisa_ui_setting_clock_view_t *)obj;
view->back_cb = cb;
view->back_user_data = user_data;
}
void lisa_ui_setting_clock_view_set_delete_cb(lv_obj_t *obj, lisa_ui_setting_clock_delete_cb_t cb)
{
if (!obj) {
return;
}
lisa_ui_setting_clock_view_t *view = (lisa_ui_setting_clock_view_t *)obj;
view->delete_cb = cb;
}

View File

@@ -0,0 +1,40 @@
/**
* @file setting_clock_view.h
* @brief Clock/Alarm settings view
*/
#ifndef __SETTING_CLOCK_VIEW_H__
#define __SETTING_CLOCK_VIEW_H__
#ifdef __cplusplus
extern "C" {
#endif
#include "lvgl.h"
#include "lisa_ui_llm_base.h"
#include <stdint.h>
/**
* @brief Alarm item structure for display
*/
typedef struct {
uint64_t timestamp;
char text[128];
} alarm_item_t;
extern const lv_obj_class_t lisa_ui_setting_clock_view_class;
typedef void (*lisa_ui_setting_clock_back_cb_t)(void *user_data);
typedef void (*lisa_ui_setting_clock_delete_cb_t)(uint64_t timestamp);
lv_obj_t *lisa_ui_setting_clock_view_create(lv_obj_t *parent);
void lisa_ui_setting_clock_view_set_back_cb(lv_obj_t *obj, lisa_ui_setting_clock_back_cb_t cb, void *user_data);
void lisa_ui_setting_clock_view_set_delete_cb(lv_obj_t *obj, lisa_ui_setting_clock_delete_cb_t cb);
void lisa_ui_setting_clock_view_set_alarms(lv_obj_t *obj, const alarm_item_t *alarms, uint32_t count);
void lisa_ui_setting_clock_view_refresh_list(lv_obj_t *obj);
#ifdef __cplusplus
}
#endif
#endif /* __SETTING_CLOCK_VIEW_H__ */

View File

@@ -0,0 +1,245 @@
/**
* @file setting_common_view.c
* @brief Common settings view implementation (Volume, Brightness)
*/
#define TAG "setting_common_view"
#include "setting_common_view.h"
#include "lisa_ui.h"
#include "lisa_ui_llm_base.h"
#include "lisa_ui_nav_scr.h"
#include "lisa_ui_nav_scr_ids.h"
#include "lisa_ui_assets.h"
#include "stdio.h"
#include "lisa_ui_fonts.h"
typedef struct {
lisa_ui_llm_base_t base;
lv_obj_t *back_btn;
lv_obj_t *volume_label;
lv_obj_t *brightness_label;
lv_obj_t *volume_slider;
lv_obj_t *brightness_slider;
lisa_ui_setting_common_back_cb_t back_cb;
void *back_user_data;
lisa_ui_setting_common_volume_changed_cb_t volume_changed_cb;
void *volume_user_data;
lisa_ui_setting_common_brightness_changed_cb_t brightness_changed_cb;
void *brightness_user_data;
} lisa_ui_setting_common_view_t;
static void lisa_ui_setting_common_view_class_constructor(const lv_obj_class_t *class_p, lv_obj_t *obj);
const lv_obj_class_t lisa_ui_setting_common_view_class = {
.base_class = &lisa_ui_llm_base_class,
.constructor_cb = lisa_ui_setting_common_view_class_constructor,
.instance_size = sizeof(lisa_ui_setting_common_view_t),
};
static void back_btn_event_cb(lv_event_t *e)
{
lv_event_code_t code = lv_event_get_code(e);
if (code == LV_EVENT_CLICKED) {
lisa_ui_setting_common_view_t *view = (lisa_ui_setting_common_view_t *)lv_event_get_user_data(e);
LISA_UI_LOGD("Back button clicked");
if (view && view->back_cb) {
view->back_cb(view->back_user_data);
}
}
}
static void volume_slider_event_cb(lv_event_t *e)
{
lv_event_code_t code = lv_event_get_code(e);
if (code == LV_EVENT_VALUE_CHANGED) {
lv_obj_t *slider = lv_event_get_target(e);
int32_t value = lv_slider_get_value(slider);
lisa_ui_setting_common_view_t *view = (lisa_ui_setting_common_view_t *)lv_event_get_user_data(e);
if (view && view->volume_label && lv_obj_is_valid(view->volume_label)) {
static char volume_text[32];
snprintf(volume_text, sizeof(volume_text), _("volume: %d%%"), (int)value);
lv_label_set_text(view->volume_label, volume_text);
}
LISA_UI_LOGD("Volume changed to: %d", (int)value);
/* 调用业务逻辑回调 */
if (view && view->volume_changed_cb) {
view->volume_changed_cb((uint8_t)value, view->volume_user_data);
}
}
}
static void brightness_slider_event_cb(lv_event_t *e)
{
lv_event_code_t code = lv_event_get_code(e);
if (code == LV_EVENT_VALUE_CHANGED) {
lv_obj_t *slider = lv_event_get_target(e);
int32_t value = lv_slider_get_value(slider);
lisa_ui_setting_common_view_t *view = (lisa_ui_setting_common_view_t *)lv_event_get_user_data(e);
if (view && view->brightness_label && lv_obj_is_valid(view->brightness_label)) {
static char brightness_text[32];
snprintf(brightness_text, sizeof(brightness_text), _("brightness: %d%%"), (int)value);
lv_label_set_text(view->brightness_label, brightness_text);
}
LISA_UI_LOGD("Brightness changed to: %d", (int)value);
/* 调用业务逻辑回调 */
if (view && view->brightness_changed_cb) {
view->brightness_changed_cb((uint8_t)value, view->brightness_user_data);
}
}
}
static void lisa_ui_setting_common_view_class_constructor(const lv_obj_class_t *class_p, lv_obj_t *obj)
{
LV_UNUSED(class_p);
lisa_ui_setting_common_view_t *view = (lisa_ui_setting_common_view_t *)obj;
// Hide the bar to allow content to start from the very top
lv_obj_t *bar = lisa_ui_llm_base_bar_get(obj);
if (bar) {
lv_obj_add_flag(bar, LV_OBJ_FLAG_HIDDEN);
}
lv_obj_t *container = lisa_ui_llm_base_container_get(obj);
if (NULL == container) {
LISA_UI_LOGE("Failed to get base container");
return;
}
// Back button at the very top
view->back_btn = lv_btn_create(container);
lv_obj_set_size(view->back_btn, 30, 30);
lv_obj_align(view->back_btn, LV_ALIGN_TOP_LEFT, 5, 0);
lv_obj_set_style_bg_opa(view->back_btn, LV_OPA_TRANSP, LV_PART_MAIN);
lv_obj_set_style_border_width(view->back_btn, 0, LV_PART_MAIN);
lv_obj_t *back_icon = lv_img_create(view->back_btn);
lv_img_set_src(back_icon, &icons_icon_back_png);
lv_obj_center(back_icon);
lv_obj_add_event_cb(view->back_btn, back_btn_event_cb, LV_EVENT_CLICKED, view);
/* 在返回按钮旁边创建标题文本 */
lv_obj_t *title = lv_label_create(container);
lv_label_set_text(title, _("common setting"));
lv_obj_set_style_text_font(title, &lv_font_chinese_16, LV_PART_MAIN);
lv_obj_set_style_text_color(title, lv_color_white(), LV_PART_MAIN);
lv_obj_align(title, LV_ALIGN_TOP_LEFT, 45, 8); /* 紧贴返回按钮右侧 */
view->volume_label = lv_label_create(container);
lv_obj_set_style_text_color(view->volume_label, lv_color_white(), LV_PART_MAIN);
lv_obj_set_style_text_font(view->volume_label, &lv_font_chinese_16, LV_PART_MAIN);
lv_obj_align(view->volume_label, LV_ALIGN_TOP_LEFT, 20, 80);
view->volume_slider = lv_slider_create(container);
lv_obj_set_width(view->volume_slider, 280);
lv_obj_align_to(view->volume_slider, view->volume_label, LV_ALIGN_OUT_BOTTOM_LEFT, 0, 10);
lv_slider_set_range(view->volume_slider, 0, 100);
lv_slider_set_value(view->volume_slider, 50, LV_ANIM_OFF);
lv_obj_add_event_cb(view->volume_slider, volume_slider_event_cb, LV_EVENT_VALUE_CHANGED, view);
view->brightness_label = lv_label_create(container);
lv_obj_set_style_text_color(view->brightness_label, lv_color_white(), LV_PART_MAIN);
lv_obj_set_style_text_font(view->brightness_label, &lv_font_chinese_16, LV_PART_MAIN);
lv_obj_align_to(view->brightness_label, view->volume_slider, LV_ALIGN_OUT_BOTTOM_LEFT, 0, 30);
view->brightness_slider = lv_slider_create(container);
lv_obj_set_width(view->brightness_slider, 280);
lv_obj_align_to(view->brightness_slider, view->brightness_label, LV_ALIGN_OUT_BOTTOM_LEFT, 0, 10);
lv_slider_set_range(view->brightness_slider, 0, 100);
lv_slider_set_value(view->brightness_slider, 50, LV_ANIM_OFF);
lv_obj_add_event_cb(view->brightness_slider, brightness_slider_event_cb, LV_EVENT_VALUE_CHANGED, view);
}
lv_obj_t *lisa_ui_setting_common_view_create(lv_obj_t *parent)
{
lv_obj_t *obj = lv_obj_class_create_obj(&lisa_ui_setting_common_view_class, parent);
lv_obj_class_init_obj(obj);
lisa_ui_setting_common_view_t *view = (lisa_ui_setting_common_view_t *)obj;
view->back_cb = NULL;
view->back_user_data = NULL;
view->volume_changed_cb = NULL;
view->volume_user_data = NULL;
view->brightness_changed_cb = NULL;
view->brightness_user_data = NULL;
return obj;
}
void lisa_ui_setting_common_view_set_volume(lv_obj_t *obj, uint8_t volume)
{
if (!obj || volume > 100) {
return;
}
lisa_ui_setting_common_view_t *view = (lisa_ui_setting_common_view_t *)obj;
if (view->volume_slider && lv_obj_is_valid(view->volume_slider)) {
lv_slider_set_value(view->volume_slider, volume, LV_ANIM_OFF);
}
if (view->volume_label && lv_obj_is_valid(view->volume_label)) {
static char volume_text[32];
snprintf(volume_text, sizeof(volume_text), _("volume: %d%%"), volume);
lv_label_set_text(view->volume_label, volume_text);
}
}
void lisa_ui_setting_common_view_set_brightness(lv_obj_t *obj, uint8_t brightness)
{
if (!obj || brightness > 100) {
return;
}
lisa_ui_setting_common_view_t *view = (lisa_ui_setting_common_view_t *)obj;
if (view->brightness_slider && lv_obj_is_valid(view->brightness_slider)) {
lv_slider_set_value(view->brightness_slider, brightness, LV_ANIM_OFF);
}
if (view->brightness_label && lv_obj_is_valid(view->brightness_label)) {
static char brightness_text[32];
snprintf(brightness_text, sizeof(brightness_text), _("brightness: %d%%"), brightness);
lv_label_set_text(view->brightness_label, brightness_text);
}
}
void lisa_ui_setting_common_view_set_back_cb(lv_obj_t *obj, lisa_ui_setting_common_back_cb_t cb, void *user_data)
{
if (!obj) {
return;
}
lisa_ui_setting_common_view_t *view = (lisa_ui_setting_common_view_t *)obj;
view->back_cb = cb;
view->back_user_data = user_data;
}
void lisa_ui_setting_common_view_set_volume_changed_cb(lv_obj_t *obj, lisa_ui_setting_common_volume_changed_cb_t cb, void *user_data)
{
if (!obj) {
return;
}
lisa_ui_setting_common_view_t *view = (lisa_ui_setting_common_view_t *)obj;
view->volume_changed_cb = cb;
view->volume_user_data = user_data;
}
void lisa_ui_setting_common_view_set_brightness_changed_cb(lv_obj_t *obj, lisa_ui_setting_common_brightness_changed_cb_t cb, void *user_data)
{
if (!obj) {
return;
}
lisa_ui_setting_common_view_t *view = (lisa_ui_setting_common_view_t *)obj;
view->brightness_changed_cb = cb;
view->brightness_user_data = user_data;
}

View File

@@ -0,0 +1,88 @@
/**
* @file setting_common_view.h
* @brief Common settings view (Volume, Brightness)
*/
#ifndef __SETTING_COMMON_VIEW_H__
#define __SETTING_COMMON_VIEW_H__
#ifdef __cplusplus
extern "C" {
#endif
#include "lvgl.h"
#include "lisa_ui_llm_base.h"
/** Common settings view class definition */
extern const lv_obj_class_t lisa_ui_setting_common_view_class;
/**
* @brief Back button click callback
* @param user_data User data
*/
typedef void (*lisa_ui_setting_common_back_cb_t)(void *user_data);
/**
* @brief Volume changed callback
* @param volume New volume value (0-100)
* @param user_data User data
*/
typedef void (*lisa_ui_setting_common_volume_changed_cb_t)(uint8_t volume, void *user_data);
/**
* @brief Brightness changed callback
* @param brightness New brightness value (0-100)
* @param user_data User data
*/
typedef void (*lisa_ui_setting_common_brightness_changed_cb_t)(uint8_t brightness, void *user_data);
/**
* @brief Create common settings view (Volume/Brightness sliders)
* @param parent Parent object
* @return lv_obj_t* Created view object
*/
lv_obj_t *lisa_ui_setting_common_view_create(lv_obj_t *parent);
/**
* @brief Set volume value and update display
* @param obj View object
* @param volume Volume level (0-100)
*/
void lisa_ui_setting_common_view_set_volume(lv_obj_t *obj, uint8_t volume);
/**
* @brief Set brightness value and update display
* @param obj View object
* @param brightness Brightness level (0-100)
*/
void lisa_ui_setting_common_view_set_brightness(lv_obj_t *obj, uint8_t brightness);
/**
* @brief Set back button click callback
* @param obj View object
* @param cb Callback function
* @param user_data User data passed to callback
*/
void lisa_ui_setting_common_view_set_back_cb(lv_obj_t *obj, lisa_ui_setting_common_back_cb_t cb, void *user_data);
/**
* @brief Set volume changed callback
* @param obj View object
* @param cb Callback function
* @param user_data User data passed to callback
*/
void lisa_ui_setting_common_view_set_volume_changed_cb(lv_obj_t *obj, lisa_ui_setting_common_volume_changed_cb_t cb, void *user_data);
/**
* @brief Set brightness changed callback
* @param obj View object
* @param cb Callback function
* @param user_data User data passed to callback
*/
void lisa_ui_setting_common_view_set_brightness_changed_cb(lv_obj_t *obj, lisa_ui_setting_common_brightness_changed_cb_t cb, void *user_data);
#ifdef __cplusplus
}
#endif
#endif /* __SETTING_COMMON_VIEW_H__ */

View File

@@ -0,0 +1,281 @@
/**
* @file setting_wakeup_view.c
* @brief Wakeup settings view implementation
*/
#include "setting_wakeup_view.h"
#include "lisa_ui.h"
#include "lisa_ui_llm_base.h"
#include "lisa_ui_nav_scr.h"
#include "lisa_ui_nav_scr_ids.h"
#include "lisa_ui_assets.h"
#include "lisa_ui_fonts.h"
#define TAG "setting_wakeup_view"
#define WAKEUP_MODE_MAX 3
typedef struct {
lv_obj_t *radio_bg;
lv_obj_t *radio;
uint16_t radio_id;
lv_obj_t *tip_text;
void *view;
} radio_group_item_t;
typedef struct {
lisa_ui_llm_base_t base;
lv_obj_t *back_btn;
lv_obj_t *title_label;
lv_obj_t *radio_group;
radio_group_item_t radio_items[WAKEUP_MODE_MAX];
uint16_t current_mode;
uint16_t radio_group_index;
lisa_ui_setting_wakeup_back_cb_t back_cb; /* 返回按钮回调函数 */
void *back_user_data; /* 回调函数用户数据 */
lisa_ui_setting_wakeup_mode_changed_cb_t mode_changed_cb;
void *mode_changed_user_data;
} lisa_ui_setting_wakeup_view_t;
static void lisa_ui_setting_wakeup_view_class_constructor(const lv_obj_class_t *class_p, lv_obj_t *obj);
const lv_obj_class_t lisa_ui_setting_wakeup_view_class = {
.base_class = &lisa_ui_llm_base_class,
.constructor_cb = lisa_ui_setting_wakeup_view_class_constructor,
.instance_size = sizeof(lisa_ui_setting_wakeup_view_t),
};
static lv_style_t style_radio;
static void back_btn_event_cb(lv_event_t *e)
{
lv_event_code_t code = lv_event_get_code(e);
if (code == LV_EVENT_CLICKED) {
lisa_ui_setting_wakeup_view_t *view = (lisa_ui_setting_wakeup_view_t *)lv_event_get_user_data(e);
LISA_UI_LOGD("Wakeup back button clicked");
/* 调用回调函数将页面导航逻辑交给presenter层处理 */
if (view && view->back_cb) {
view->back_cb(view->back_user_data);
}
}
}
static void update_radio_buttons(lisa_ui_setting_wakeup_view_t *view, uint16_t selected_mode)
{
if (!view || selected_mode >= WAKEUP_MODE_MAX) {
return;
}
for (int i = 0; i < view->radio_group_index && i < WAKEUP_MODE_MAX; i++) {
if (view->radio_items[i].radio && lv_obj_is_valid(view->radio_items[i].radio)) {
lv_obj_clear_state(view->radio_items[i].radio, LV_STATE_CHECKED);
}
}
if (selected_mode < view->radio_group_index &&
view->radio_items[selected_mode].radio &&
lv_obj_is_valid(view->radio_items[selected_mode].radio)) {
lv_obj_add_state(view->radio_items[selected_mode].radio, LV_STATE_CHECKED);
}
view->current_mode = selected_mode;
}
static void radio_group_bg_event_handler(lv_event_t *e)
{
lv_event_code_t code = lv_event_get_code(e);
if (code == LV_EVENT_CLICKED) {
radio_group_item_t *item = (radio_group_item_t *)lv_event_get_user_data(e);
if (!item || !item->view) {
return;
}
if (item->radio_id >= WAKEUP_MODE_MAX) {
return;
}
lisa_ui_setting_wakeup_view_t *view = (lisa_ui_setting_wakeup_view_t *)item->view;
update_radio_buttons(view, item->radio_id);
LISA_UI_LOGD("Wakeup mode changed to: %d", item->radio_id);
if (view->mode_changed_cb) {
view->mode_changed_cb(item->radio_id, view->mode_changed_user_data);
}
}
}
static lv_obj_t *add_radio_group_item(lv_obj_t *group, lisa_ui_setting_wakeup_view_t *view,
const char *txt, const char *tips_text, uint16_t radio_id)
{
if (!group || !txt || !tips_text || !view || radio_id >= WAKEUP_MODE_MAX) {
return NULL;
}
lv_obj_t *radio_bg = lv_obj_create(group);
if (!radio_bg) {
return NULL;
}
lv_obj_clear_flag(radio_bg, LV_OBJ_FLAG_SCROLLABLE);
lv_obj_set_size(radio_bg, LV_PCT(100), LV_SIZE_CONTENT);
lv_obj_set_style_border_width(radio_bg, 0, LV_PART_MAIN | LV_STATE_DEFAULT);
lv_obj_set_style_bg_color(radio_bg, lv_color_hex(0x24242f), LV_PART_MAIN | LV_STATE_DEFAULT);
lv_obj_set_style_radius(radio_bg, 8, LV_PART_MAIN | LV_STATE_DEFAULT);
lv_obj_set_style_pad_all(radio_bg, 15, LV_PART_MAIN | LV_STATE_DEFAULT);
view->radio_items[radio_id].radio_bg = radio_bg;
view->radio_items[radio_id].radio_id = radio_id;
view->radio_items[radio_id].view = view;
lv_obj_add_event_cb(radio_bg, radio_group_bg_event_handler, LV_EVENT_CLICKED,
(void *)&view->radio_items[radio_id]);
lv_obj_set_style_text_color(radio_bg, lv_color_hex(0xFFFFFF), LV_PART_MAIN | LV_STATE_DEFAULT);
lv_obj_t *checkbox = lv_checkbox_create(radio_bg);
if (!checkbox) {
lv_obj_del(radio_bg);
return NULL;
}
view->radio_items[radio_id].radio = checkbox;
lv_obj_set_size(checkbox, LV_PCT(80), LV_SIZE_CONTENT);
lv_checkbox_set_text(checkbox, txt);
lv_obj_set_style_text_font(checkbox, &lv_font_chinese_16, LV_PART_MAIN | LV_STATE_DEFAULT);
lv_obj_add_flag(checkbox, LV_OBJ_FLAG_EVENT_BUBBLE);
lv_obj_add_style(checkbox, &style_radio, LV_PART_INDICATOR);
lv_obj_clear_flag(checkbox, LV_OBJ_FLAG_CLICKABLE);
lv_obj_t *tip_text = lv_label_create(radio_bg);
if (!tip_text) {
lv_obj_del(radio_bg);
return NULL;
}
view->radio_items[radio_id].tip_text = tip_text;
lv_obj_align_to(tip_text, checkbox, LV_ALIGN_OUT_BOTTOM_LEFT, 30, 5);
lv_obj_set_size(tip_text, LV_PCT(95), LV_SIZE_CONTENT);
lv_label_set_text(tip_text, tips_text);
lv_obj_set_style_text_font(tip_text, &lv_font_chinese_16, LV_PART_MAIN | LV_STATE_DEFAULT);
lv_obj_set_style_text_color(tip_text, lv_color_hex(0xBCBCBC), LV_PART_MAIN | LV_STATE_DEFAULT);
lv_obj_clear_flag(tip_text, LV_OBJ_FLAG_SCROLLABLE);
lv_obj_set_scrollbar_mode(tip_text, LV_SCROLLBAR_MODE_OFF);
lv_obj_set_scroll_dir(tip_text, LV_DIR_NONE);
if (view->radio_group_index < WAKEUP_MODE_MAX) {
view->radio_group_index++;
}
return checkbox;
}
static void lisa_ui_setting_wakeup_view_class_constructor(const lv_obj_class_t *class_p, lv_obj_t *obj)
{
LV_UNUSED(class_p);
lisa_ui_setting_wakeup_view_t *view = (lisa_ui_setting_wakeup_view_t *)obj;
lv_obj_t *bar = lisa_ui_llm_base_bar_get(obj);
if (bar) {
lv_obj_add_flag(bar, LV_OBJ_FLAG_HIDDEN);
}
lv_obj_t *container = lisa_ui_llm_base_container_get(obj);
if (NULL == container) {
LISA_UI_LOGE("Failed to get base container");
return;
}
lv_obj_set_style_bg_color(container, lv_color_hex(0x000000), LV_PART_MAIN);
view->back_btn = lv_btn_create(container);
lv_obj_set_size(view->back_btn, 30, 30);
lv_obj_align(view->back_btn, LV_ALIGN_TOP_LEFT, 5, 0);
lv_obj_set_style_bg_opa(view->back_btn, LV_OPA_TRANSP, LV_PART_MAIN);
lv_obj_set_style_border_width(view->back_btn, 0, LV_PART_MAIN);
lv_obj_t *back_icon = lv_img_create(view->back_btn);
lv_img_set_src(back_icon, &icons_icon_back_png);
lv_obj_center(back_icon);
lv_obj_add_event_cb(view->back_btn, back_btn_event_cb, LV_EVENT_CLICKED, view);
view->title_label = lv_label_create(container);
lv_label_set_text(view->title_label, _("wakeup setting"));
lv_obj_set_style_text_color(view->title_label, lv_color_white(), LV_PART_MAIN);
lv_obj_set_style_text_font(view->title_label, &lv_font_chinese_16, LV_PART_MAIN);
lv_obj_align(view->title_label, LV_ALIGN_TOP_LEFT, 45, 8);
lv_style_init(&style_radio);
lv_style_set_radius(&style_radio, LV_RADIUS_CIRCLE);
view->radio_group = lv_obj_create(container);
if (!view->radio_group) {
LISA_UI_LOGE("Failed to create radio_group");
return;
}
lv_obj_set_size(view->radio_group, LV_PCT(100), LV_PCT(80));
lv_obj_align(view->radio_group, LV_ALIGN_BOTTOM_MID, 0, 0);
lv_obj_set_flex_flow(view->radio_group, LV_FLEX_FLOW_COLUMN);
lv_obj_set_flex_align(view->radio_group, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_START);
lv_obj_set_style_border_width(view->radio_group, 0, LV_PART_MAIN | LV_STATE_DEFAULT);
lv_obj_set_style_bg_color(view->radio_group, lv_color_hex(0x000000), LV_PART_MAIN | LV_STATE_DEFAULT);
lv_obj_set_style_pad_gap(view->radio_group, 10, LV_PART_MAIN | LV_STATE_DEFAULT);
view->radio_group_index = 0;
add_radio_group_item(view->radio_group, view, _("button wakeup"),
_("button wakeup description"), 0);
add_radio_group_item(view->radio_group, view, _("voice wakeup(single)"),
_("voice wakeup single description"), 1);
add_radio_group_item(view->radio_group, view, _("voice wakeup(multiple)"),
_("voice wakeup multiple description"), 2);
view->current_mode = 0;
update_radio_buttons(view, 0);
}
lv_obj_t *lisa_ui_setting_wakeup_view_create(lv_obj_t *parent)
{
lv_obj_t *obj = lv_obj_class_create_obj(&lisa_ui_setting_wakeup_view_class, parent);
lv_obj_class_init_obj(obj);
/* 初始化回调函数为NULL */
lisa_ui_setting_wakeup_view_t *view = (lisa_ui_setting_wakeup_view_t *)obj;
view->back_cb = NULL;
view->back_user_data = NULL;
view->mode_changed_cb = NULL;
view->mode_changed_user_data = NULL;
return obj;
}
void lisa_ui_setting_wakeup_view_set_back_cb(lv_obj_t *obj, lisa_ui_setting_wakeup_back_cb_t cb, void *user_data)
{
if (!obj) {
return;
}
/* 设置返回按钮回调函数和用户数据 */
lisa_ui_setting_wakeup_view_t *view = (lisa_ui_setting_wakeup_view_t *)obj;
view->back_cb = cb;
view->back_user_data = user_data;
}
void lisa_ui_setting_wakeup_view_set_mode_changed_cb(lv_obj_t *obj, lisa_ui_setting_wakeup_mode_changed_cb_t cb, void *user_data)
{
if (!obj) {
return;
}
lisa_ui_setting_wakeup_view_t *view = (lisa_ui_setting_wakeup_view_t *)obj;
view->mode_changed_cb = cb;
view->mode_changed_user_data = user_data;
}
void lisa_ui_setting_wakeup_view_set_mode(lv_obj_t *obj, uint16_t mode)
{
if (!obj) {
return;
}
lisa_ui_setting_wakeup_view_t *view = (lisa_ui_setting_wakeup_view_t *)obj;
update_radio_buttons(view, mode);
}

View File

@@ -0,0 +1,47 @@
/**
* @file setting_wakeup_view.h
* @brief Wakeup settings view
*/
#ifndef __SETTING_WAKEUP_VIEW_H__
#define __SETTING_WAKEUP_VIEW_H__
#ifdef __cplusplus
extern "C" {
#endif
#include "lvgl.h"
#include "lisa_ui_llm_base.h"
/** Wakeup settings view class definition */
extern const lv_obj_class_t lisa_ui_setting_wakeup_view_class;
/**
* @brief 返回按钮点击回调函数类型
* @param user_data 用户数据
*/
typedef void (*lisa_ui_setting_wakeup_back_cb_t)(void *user_data);
typedef void (*lisa_ui_setting_wakeup_mode_changed_cb_t)(uint16_t mode, void *user_data);
/**
* @brief Create wakeup settings view
* @param parent Parent object
* @return lv_obj_t* Created view object
*/
lv_obj_t *lisa_ui_setting_wakeup_view_create(lv_obj_t *parent);
/**
* @brief 设置返回按钮点击回调
* @param obj Wakeup view对象
* @param cb 回调函数
* @param user_data 传递给回调的用户数据
*/
void lisa_ui_setting_wakeup_view_set_back_cb(lv_obj_t *obj, lisa_ui_setting_wakeup_back_cb_t cb, void *user_data);
void lisa_ui_setting_wakeup_view_set_mode_changed_cb(lv_obj_t *obj, lisa_ui_setting_wakeup_mode_changed_cb_t cb, void *user_data);
void lisa_ui_setting_wakeup_view_set_mode(lv_obj_t *obj, uint16_t mode);
#ifdef __cplusplus
}
#endif
#endif /* __SETTING_WAKEUP_VIEW_H__ */

View File

@@ -0,0 +1,563 @@
/**
* @file setting_wifi_view.c
* @brief WiFi settings view implementation
*/
#include <stdio.h>
#include "setting_wifi_view.h"
#include "lisa_ui.h"
#include "lisa_ui_llm_base.h"
#include "lisa_ui_nav_scr.h"
#include "lisa_ui_nav_scr_ids.h"
#include "lisa_ui_assets.h"
#include "lisa_ui_fonts.h"
#include "../lisa_ui_keyboard_input.h"
#define TAG "setting_wifi_view"
#define WIFI_LIST_UPDATE_THROTTLE_MS 500 /* 列表更新限流时间(ms) */
#define WIFI_LIST_BATCH_CREATE_SIZE 5 /* 批量创建列表项数量 */
typedef struct {
lisa_ui_llm_base_t base;
lv_obj_t *back_btn;
lv_obj_t *wifi_switch;
lv_obj_t *wifi_list; /* WiFi 列表容器 */
lv_obj_t *info_label; /* 提示信息标签 */
lisa_ui_setting_wifi_back_cb_t back_cb; /* 返回按钮回调函数 */
void *back_user_data; /* 回调函数用户数据 */
lisa_ui_setting_wifi_item_cb_t item_cb; /* WiFi 项点击回调 */
void *item_user_data; /* WiFi 项回调用户数据 */
/* 密码输入界面相关 */
lv_obj_t *keyboard_input; /* 键盘输入组件 */
char current_ssid[32]; /* 当前选中的SSID */
lisa_ui_setting_wifi_password_confirm_cb_t password_confirm_cb; /* 密码确认回调 */
lisa_ui_setting_wifi_password_cancel_cb_t password_cancel_cb; /* 密码取消回调 */
void *password_user_data; /* 密码输入回调用户数据 */
/* 性能优化相关 */
lv_style_t item_style; /* WiFi项样式(复用) */
lv_style_t item_pressed_style; /* WiFi项按下样式(复用) */
uint32_t last_update_time; /* 上次更新时间戳 */
} lisa_ui_setting_wifi_view_t;
static void lisa_ui_setting_wifi_view_class_constructor(const lv_obj_class_t *class_p, lv_obj_t *obj);
const lv_obj_class_t lisa_ui_setting_wifi_view_class = {
.base_class = &lisa_ui_llm_base_class,
.constructor_cb = lisa_ui_setting_wifi_view_class_constructor,
.instance_size = sizeof(lisa_ui_setting_wifi_view_t),
};
static void back_btn_event_cb(lv_event_t *e)
{
lv_event_code_t code = lv_event_get_code(e);
if (code == LV_EVENT_CLICKED) {
lisa_ui_setting_wifi_view_t *view = (lisa_ui_setting_wifi_view_t *)lv_event_get_user_data(e);
LISA_UI_LOGD("WiFi back button clicked");
/* 调用回调函数将页面导航逻辑交给presenter层处理 */
if (view && view->back_cb) {
view->back_cb(view->back_user_data);
}
}
}
static void lisa_ui_setting_wifi_view_class_constructor(const lv_obj_class_t *class_p, lv_obj_t *obj)
{
LV_UNUSED(class_p);
lisa_ui_setting_wifi_view_t *view = (lisa_ui_setting_wifi_view_t *)obj;
// Hide the bar to allow content to start from the very top
lv_obj_t *bar = lisa_ui_llm_base_bar_get(obj);
if (bar) {
lv_obj_add_flag(bar, LV_OBJ_FLAG_HIDDEN);
}
lv_obj_t *container = lisa_ui_llm_base_container_get(obj);
if (NULL == container) {
LISA_UI_LOGE("Failed to get base container");
return;
}
/* 初始化可复用的样式 */
lv_style_init(&view->item_style);
lv_style_set_bg_color(&view->item_style, lv_color_hex(0x1a1a1a));
lv_style_set_border_width(&view->item_style, 0);
lv_style_set_radius(&view->item_style, 8);
lv_style_set_pad_all(&view->item_style, 12);
lv_style_set_shadow_width(&view->item_style, 4);
lv_style_set_shadow_color(&view->item_style, lv_color_black());
lv_style_set_shadow_opa(&view->item_style, LV_OPA_20);
lv_style_init(&view->item_pressed_style);
lv_style_set_bg_color(&view->item_pressed_style, lv_color_hex(0x2a2a2a));
view->last_update_time = 0;
/* ============ 标题栏区域 ============ */
// 创建标题栏容器
lv_obj_t *header = lv_obj_create(container);
lv_obj_set_size(header, LV_PCT(100), 45);
lv_obj_align(header, LV_ALIGN_TOP_MID, 0, 0);
lv_obj_set_style_bg_opa(header, LV_OPA_TRANSP, LV_PART_MAIN);
lv_obj_set_style_border_width(header, 0, LV_PART_MAIN);
lv_obj_set_style_pad_all(header, 0, LV_PART_MAIN);
// 返回按钮
view->back_btn = lv_btn_create(header);
lv_obj_set_size(view->back_btn, 35, 35);
lv_obj_align(view->back_btn, LV_ALIGN_LEFT_MID, 5, 0);
lv_obj_set_style_bg_opa(view->back_btn, LV_OPA_TRANSP, LV_PART_MAIN);
lv_obj_set_style_border_width(view->back_btn, 0, LV_PART_MAIN);
lv_obj_set_style_shadow_width(view->back_btn, 0, LV_PART_MAIN);
lv_obj_t *back_icon = lv_img_create(view->back_btn);
lv_img_set_src(back_icon, &icons_icon_back_png);
lv_obj_center(back_icon);
lv_obj_add_event_cb(view->back_btn, back_btn_event_cb, LV_EVENT_CLICKED, view);
// 标题文本
lv_obj_t *title = lv_label_create(header);
lv_label_set_text(title, _("wifi setting"));
lv_obj_set_style_text_font(title, &lv_font_chinese_16, LV_PART_MAIN);
lv_obj_set_style_text_color(title, lv_color_white(), LV_PART_MAIN);
lv_obj_align(title, LV_ALIGN_LEFT_MID, 50, 0);
// /* ============ WiFi 开关区域 ============ */
// // 创建 WiFi 开关容器
// lv_obj_t *switch_container = lv_obj_create(container);
// lv_obj_set_size(switch_container, LV_PCT(100), 50);
// lv_obj_align(switch_container, LV_ALIGN_TOP_MID, 0, 50);
// lv_obj_set_style_bg_color(switch_container, lv_color_hex(0x1a1a1a), LV_PART_MAIN);
// lv_obj_set_style_border_width(switch_container, 0, LV_PART_MAIN);
// lv_obj_set_style_radius(switch_container, 8, LV_PART_MAIN);
// lv_obj_set_style_pad_all(switch_container, 15, LV_PART_MAIN);
// lv_obj_clear_flag(switch_container, LV_OBJ_FLAG_SCROLLABLE);
// // WiFi 标签
// lv_obj_t *wifi_label = lv_label_create(switch_container);
// lv_label_set_text(wifi_label, _("AutoScan"));
// lv_obj_set_style_text_font(wifi_label, &lv_font_chinese_16, LV_PART_MAIN);
// lv_obj_set_style_text_color(wifi_label, lv_color_white(), LV_PART_MAIN);
// lv_obj_align(wifi_label, LV_ALIGN_LEFT_MID, 0, 0);
// // WiFi 开关
// view->wifi_switch = lv_switch_create(switch_container);
// lv_obj_set_size(view->wifi_switch, 50, 26);
// lv_obj_align(view->wifi_switch, LV_ALIGN_RIGHT_MID, 0, 0);
/* ============ 列表区域 ============ */
// WiFi 列表容器(可滚动)
view->wifi_list = lv_obj_create(container);
lv_obj_set_size(view->wifi_list, LV_PCT(100), 180);
lv_obj_align(view->wifi_list, LV_ALIGN_TOP_MID, 0, 50);
lv_obj_set_style_bg_opa(view->wifi_list, LV_OPA_TRANSP, LV_PART_MAIN);
lv_obj_set_style_border_width(view->wifi_list, 0, LV_PART_MAIN);
lv_obj_set_style_pad_all(view->wifi_list, 0, LV_PART_MAIN);
lv_obj_set_style_pad_row(view->wifi_list, 8, LV_PART_MAIN);
lv_obj_set_flex_flow(view->wifi_list, LV_FLEX_FLOW_COLUMN);
lv_obj_set_flex_align(view->wifi_list, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_START);
lv_obj_set_scrollbar_mode(view->wifi_list, LV_SCROLLBAR_MODE_AUTO);
lv_obj_set_scroll_dir(view->wifi_list, LV_DIR_VER);
// 提示信息标签(默认隐藏)
view->info_label = lv_label_create(container);
lv_label_set_text(view->info_label, _("scaning"));
lv_obj_set_style_text_font(view->info_label, &lv_font_chinese_16, LV_PART_MAIN);
lv_obj_set_style_text_color(view->info_label, lv_color_hex(0x888888), LV_PART_MAIN);
lv_obj_align(view->info_label, LV_ALIGN_CENTER, 0, 20);
lv_obj_add_flag(view->info_label, LV_OBJ_FLAG_HIDDEN);
}
lv_obj_t *lisa_ui_setting_wifi_view_create(lv_obj_t *parent)
{
lv_obj_t *obj = lv_obj_class_create_obj(&lisa_ui_setting_wifi_view_class, parent);
lv_obj_class_init_obj(obj);
/* 初始化回调函数为NULL */
lisa_ui_setting_wifi_view_t *view = (lisa_ui_setting_wifi_view_t *)obj;
view->back_cb = NULL;
view->back_user_data = NULL;
view->item_cb = NULL;
view->item_user_data = NULL;
view->keyboard_input = NULL;
view->current_ssid[0] = '\0';
view->password_confirm_cb = NULL;
view->password_cancel_cb = NULL;
view->password_user_data = NULL;
return obj;
}
lv_obj_t *lisa_ui_setting_wifi_view_get_switch(lv_obj_t *obj)
{
if (!obj) {
return NULL;
}
lisa_ui_setting_wifi_view_t *view = (lisa_ui_setting_wifi_view_t *)obj;
return view->wifi_switch;
}
void lisa_ui_setting_wifi_view_set_back_cb(lv_obj_t *obj, lisa_ui_setting_wifi_back_cb_t cb, void *user_data)
{
if (!obj) {
return;
}
/* 设置返回按钮回调函数和用户数据 */
lisa_ui_setting_wifi_view_t *view = (lisa_ui_setting_wifi_view_t *)obj;
view->back_cb = cb;
view->back_user_data = user_data;
}
void lisa_ui_setting_wifi_view_set_item_cb(lv_obj_t *obj, lisa_ui_setting_wifi_item_cb_t cb, void *user_data)
{
if (!obj) {
return;
}
lisa_ui_setting_wifi_view_t *view = (lisa_ui_setting_wifi_view_t *)obj;
view->item_cb = cb;
view->item_user_data = user_data;
}
static void wifi_item_event_cb(lv_event_t *e)
{
lv_event_code_t code = lv_event_get_code(e);
if (code == LV_EVENT_CLICKED) {
lv_obj_t *item = lv_event_get_target(e);
lisa_ui_setting_wifi_view_t *view = (lisa_ui_setting_wifi_view_t *)lv_event_get_user_data(e);
if (view && view->item_cb) {
/* 从 item 的 user_data 获取 SSID */
const char *ssid = (const char *)lv_obj_get_user_data(item);
if (ssid) {
view->item_cb(ssid, view->item_user_data);
}
}
}
}
/* 获取信号强度图标 */
static const char *get_signal_icon(int rssi)
{
if (rssi >= -50) {
return LV_SYMBOL_WIFI; /* 强信号 */
} else if (rssi >= -70) {
return LV_SYMBOL_WIFI; /* 中等信号 */
} else {
return LV_SYMBOL_WIFI; /* 弱信号 */
}
}
/* 创建单个WiFi列表项(优化版) */
static lv_obj_t *create_wifi_item(lisa_ui_setting_wifi_view_t *view, const lisa_ui_wifi_ap_info_t *ap_info)
{
/* 创建列表项容器 */
lv_obj_t *item = lv_obj_create(view->wifi_list);
lv_obj_set_size(item, LV_PCT(100), 55);
/* 使用预定义样式,避免重复设置 */
lv_obj_add_style(item, &view->item_style, LV_PART_MAIN);
lv_obj_add_style(item, &view->item_pressed_style, LV_PART_MAIN | LV_STATE_PRESSED);
lv_obj_add_flag(item, LV_OBJ_FLAG_CLICKABLE);
/* 保存 SSID 到 item 的 user_data (需要动态分配内存) */
char *ssid_copy = lv_mem_alloc(strlen(ap_info->ssid) + 1);
if (ssid_copy) {
strcpy(ssid_copy, ap_info->ssid);
lv_obj_set_user_data(item, ssid_copy);
}
/* 连接状态打勾标记 */
if (ap_info->is_connected) {
lv_obj_t *check_icon = lv_label_create(item);
lv_label_set_text(check_icon, LV_SYMBOL_OK);
lv_obj_set_style_text_color(check_icon, lv_color_hex(0x4CAF50), LV_PART_MAIN);
lv_obj_set_style_text_font(check_icon, &lv_font_montserrat_14, LV_PART_MAIN);
lv_obj_align(check_icon, LV_ALIGN_LEFT_MID, 0, 0);
}
/* SSID 标签 - 统一左边距,保持对齐 */
lv_obj_t *ssid_label = lv_label_create(item);
lv_label_set_text(ssid_label, ap_info->ssid);
lv_obj_set_style_text_font(ssid_label, &lv_font_chinese_16, LV_PART_MAIN);
lv_obj_set_style_text_color(ssid_label, lv_color_white(), LV_PART_MAIN);
lv_label_set_long_mode(ssid_label, LV_LABEL_LONG_SCROLL_CIRCULAR);
lv_obj_set_width(ssid_label, 125);
lv_obj_align(ssid_label, LV_ALIGN_LEFT_MID, 25, 0);
/* 信号强度显示 */
lv_obj_t *signal_label = lv_label_create(item);
char signal_buf[16];
/* 根据信号强度设置颜色 */
lv_color_t signal_color;
if (ap_info->rssi >= -50) {
signal_color = lv_color_hex(0x4CAF50); /* 强信号 - 绿色 */
} else if (ap_info->rssi >= -70) {
signal_color = lv_color_hex(0xFFC107); /* 中等信号 - 黄色 */
} else {
signal_color = lv_color_hex(0xFF5722); /* 弱信号 - 橙红色 */
}
snprintf(signal_buf, sizeof(signal_buf), "%ddBm", ap_info->rssi);
lv_label_set_text(signal_label, signal_buf);
lv_obj_set_style_text_font(signal_label, &lv_font_chinese_16, LV_PART_MAIN);
lv_obj_set_style_text_color(signal_label, signal_color, LV_PART_MAIN);
lv_obj_align(signal_label, LV_ALIGN_RIGHT_MID, 0, 0);
/* 注册点击事件 */
lv_obj_add_event_cb(item, wifi_item_event_cb, LV_EVENT_CLICKED, view);
return item;
}
/* 批量创建任务的定时器回调 */
typedef struct {
lisa_ui_setting_wifi_view_t *view;
const lisa_ui_wifi_ap_info_t *ap_list;
int total_count;
int current_index;
} wifi_list_create_data_t;
static void batch_create_timer_cb(lv_timer_t *timer)
{
wifi_list_create_data_t *data = (wifi_list_create_data_t *)timer->user_data;
if (!data || !data->view || !data->ap_list) {
lv_timer_del(timer);
if (data) {
if (data->ap_list) lv_mem_free((void *)data->ap_list);
lv_mem_free(data);
}
return;
}
/* 批量创建一部分列表项 */
int end_index = data->current_index + WIFI_LIST_BATCH_CREATE_SIZE;
if (end_index > data->total_count) {
end_index = data->total_count;
}
for (int i = data->current_index; i < end_index; i++) {
create_wifi_item(data->view, &data->ap_list[i]);
}
data->current_index = end_index;
/* 如果所有项都已创建,删除定时器并释放内存 */
if (data->current_index >= data->total_count) {
LISA_UI_LOGD("Batch creation completed: %d items", data->total_count);
lv_mem_free((void *)data->ap_list);
lv_mem_free(data);
lv_timer_del(timer);
}
}
void lisa_ui_setting_wifi_view_update_list(lv_obj_t *obj, const lisa_ui_wifi_ap_info_t *ap_list, int ap_count)
{
if (!obj || !ap_list || ap_count <= 0) {
return;
}
lisa_ui_setting_wifi_view_t *view = (lisa_ui_setting_wifi_view_t *)obj;
/* 限流检查:避免过于频繁的更新 */
uint32_t current_time = lv_tick_get();
if ((current_time - view->last_update_time) < WIFI_LIST_UPDATE_THROTTLE_MS) {
LISA_UI_LOGD("Update throttled, skipping");
return;
}
view->last_update_time = current_time;
/* 释放之前动态分配的内存 */
uint32_t child_count = lv_obj_get_child_cnt(view->wifi_list);
for (uint32_t i = 0; i < child_count; i++) {
lv_obj_t *child = lv_obj_get_child(view->wifi_list, i);
if (child) {
char *ssid_copy = (char *)lv_obj_get_user_data(child);
if (ssid_copy) {
lv_mem_free(ssid_copy);
}
}
}
/* 清空现有列表 */
lv_obj_clean(view->wifi_list);
/* 隐藏提示信息 */
lv_obj_add_flag(view->info_label, LV_OBJ_FLAG_HIDDEN);
/* 性能优化:对于大量WiFi列表,使用批量创建 */
if (ap_count > WIFI_LIST_BATCH_CREATE_SIZE * 2) {
/* 先创建一批可见项,其余延迟创建 */
int initial_count = WIFI_LIST_BATCH_CREATE_SIZE;
for (int i = 0; i < initial_count && i < ap_count; i++) {
create_wifi_item(view, &ap_list[i]);
}
/* 如果还有更多项,使用定时器批量创建 */
if (ap_count > initial_count) {
/* 复制AP列表数据 */
lisa_ui_wifi_ap_info_t *ap_copy = lv_mem_alloc(sizeof(lisa_ui_wifi_ap_info_t) * ap_count);
if (ap_copy) {
memcpy(ap_copy, ap_list, sizeof(lisa_ui_wifi_ap_info_t) * ap_count);
wifi_list_create_data_t *data = lv_mem_alloc(sizeof(wifi_list_create_data_t));
if (data) {
data->view = view;
data->ap_list = ap_copy;
data->total_count = ap_count;
data->current_index = initial_count;
/* 创建定时器,每50ms创建一批 */
lv_timer_t *timer = lv_timer_create(batch_create_timer_cb, 50, data);
if (!timer) {
lv_mem_free(data);
lv_mem_free(ap_copy);
LISA_UI_LOGE("Failed to create batch timer");
}
} else {
lv_mem_free(ap_copy);
}
}
}
LISA_UI_LOGD("Started batch creation for %d WiFi items", ap_count);
} else {
/* 对于少量WiFi,直接一次性创建 */
for (int i = 0; i < ap_count; i++) {
create_wifi_item(view, &ap_list[i]);
}
LISA_UI_LOGD("Updated WiFi list with %d items", ap_count);
}
}
void lisa_ui_setting_wifi_view_clear_list(lv_obj_t *obj)
{
if (!obj) {
return;
}
lisa_ui_setting_wifi_view_t *view = (lisa_ui_setting_wifi_view_t *)obj;
/* 释放之前动态分配的内存 */
uint32_t child_count = lv_obj_get_child_cnt(view->wifi_list);
for (uint32_t i = 0; i < child_count; i++) {
lv_obj_t *child = lv_obj_get_child(view->wifi_list, i);
if (child) {
char *ssid_copy = (char *)lv_obj_get_user_data(child);
if (ssid_copy) {
lv_mem_free(ssid_copy);
}
}
}
/* 清空列表 */
lv_obj_clean(view->wifi_list);
/* 显示提示信息 */
lv_label_set_text(view->info_label, _("no network"));
lv_obj_clear_flag(view->info_label, LV_OBJ_FLAG_HIDDEN);
LISA_UI_LOGD("Cleared WiFi list");
}
/* 密码输入确认回调的包装函数 */
static void password_confirm_wrapper(const char *text, void *user_data)
{
lisa_ui_setting_wifi_view_t *view = (lisa_ui_setting_wifi_view_t *)user_data;
if (view) {
/* 恢复当前页面的显示 */
lv_obj_clear_flag((lv_obj_t *)view, LV_OBJ_FLAG_HIDDEN);
if (view->password_confirm_cb) {
view->password_confirm_cb(view->current_ssid, text, view->password_user_data);
}
}
}
/* 密码输入取消回调的包装函数 */
static void password_cancel_wrapper(void *user_data)
{
lisa_ui_setting_wifi_view_t *view = (lisa_ui_setting_wifi_view_t *)user_data;
if (view) {
/* 恢复当前页面的显示 */
lv_obj_clear_flag((lv_obj_t *)view, LV_OBJ_FLAG_HIDDEN);
if (view->password_cancel_cb) {
view->password_cancel_cb(view->password_user_data);
}
}
}
void lisa_ui_setting_wifi_view_show_password_input(lv_obj_t *obj, const char *ssid,
lisa_ui_setting_wifi_password_confirm_cb_t confirm_cb,
lisa_ui_setting_wifi_password_cancel_cb_t cancel_cb,
void *user_data)
{
if (!obj || !ssid) {
return;
}
lisa_ui_setting_wifi_view_t *view = (lisa_ui_setting_wifi_view_t *)obj;
/* 保存回调函数和SSID */
view->password_confirm_cb = confirm_cb;
view->password_cancel_cb = cancel_cb;
view->password_user_data = user_data;
strncpy(view->current_ssid, ssid, sizeof(view->current_ssid) - 1);
view->current_ssid[sizeof(view->current_ssid) - 1] = '\0';
/* 如果键盘输入组件不存在,创建它 */
if (!view->keyboard_input) {
view->keyboard_input = lisa_ui_keyboard_input_create(lv_scr_act());
if (!view->keyboard_input) {
LISA_UI_LOGE("Failed to create keyboard input");
return;
}
lisa_ui_keyboard_input_set_placeholder(view->keyboard_input, "Please input password");
lisa_ui_keyboard_input_set_password_mode(view->keyboard_input, false);
/* 设置回调 - 使用包装函数 */
lisa_ui_keyboard_input_set_confirm_cb(view->keyboard_input, password_confirm_wrapper, view);
lisa_ui_keyboard_input_set_cancel_cb(view->keyboard_input, password_cancel_wrapper, view);
}
/* 设置标题 */
char title_text[64];
snprintf(title_text, sizeof(title_text), "%s", ssid);
lisa_ui_keyboard_input_set_title(view->keyboard_input, title_text);
/* 隐藏当前页面 */
lv_obj_add_flag(obj, LV_OBJ_FLAG_HIDDEN);
/* 显示键盘输入界面 */
lisa_ui_keyboard_input_show(view->keyboard_input);
LISA_UI_LOGD("Showing password input for SSID: %s", ssid);
}
void lisa_ui_setting_wifi_view_hide_password_input(lv_obj_t *obj)
{
if (!obj) {
return;
}
lisa_ui_setting_wifi_view_t *view = (lisa_ui_setting_wifi_view_t *)obj;
if (view->keyboard_input) {
lisa_ui_keyboard_input_hide(view->keyboard_input);
LISA_UI_LOGD("Password input hidden");
}
/* 恢复当前页面的显示 */
lv_obj_clear_flag(obj, LV_OBJ_FLAG_HIDDEN);
}

View File

@@ -0,0 +1,123 @@
/**
* @file setting_wifi_view.h
* @brief WiFi settings view
*/
#ifndef __SETTING_WIFI_VIEW_H__
#define __SETTING_WIFI_VIEW_H__
#ifdef __cplusplus
extern "C" {
#endif
#include "lvgl.h"
#include "lisa_ui_llm_base.h"
/** WiFi settings view class definition */
extern const lv_obj_class_t lisa_ui_setting_wifi_view_class;
/**
* @brief 返回按钮点击回调函数类型
* @param user_data 用户数据
*/
typedef void (*lisa_ui_setting_wifi_back_cb_t)(void *user_data);
/**
* @brief Create WiFi settings view
* @param parent Parent object
* @return lv_obj_t* Created view object
*/
lv_obj_t *lisa_ui_setting_wifi_view_create(lv_obj_t *parent);
/**
* @brief Get WiFi switch object
* @param obj WiFi view object
* @return lv_obj_t* WiFi switch object
*/
lv_obj_t *lisa_ui_setting_wifi_view_get_switch(lv_obj_t *obj);
/**
* @brief 设置返回按钮点击回调
* @param obj WiFi view对象
* @param cb 回调函数
* @param user_data 传递给回调的用户数据
*/
void lisa_ui_setting_wifi_view_set_back_cb(lv_obj_t *obj, lisa_ui_setting_wifi_back_cb_t cb, void *user_data);
/**
* @brief WiFi AP 信息结构
*/
typedef struct {
char ssid[32];
int rssi;
int channel;
bool is_connected; /* 是否已连接 */
} lisa_ui_wifi_ap_info_t;
/**
* @brief WiFi 项点击回调函数类型
* @param ssid WiFi SSID
* @param user_data 用户数据
*/
typedef void (*lisa_ui_setting_wifi_item_cb_t)(const char *ssid, void *user_data);
/**
* @brief WiFi 密码确认回调函数类型
* @param ssid WiFi SSID
* @param password WiFi 密码
* @param user_data 用户数据
*/
typedef void (*lisa_ui_setting_wifi_password_confirm_cb_t)(const char *ssid, const char *password, void *user_data);
/**
* @brief WiFi 密码取消回调函数类型
* @param user_data 用户数据
*/
typedef void (*lisa_ui_setting_wifi_password_cancel_cb_t)(void *user_data);
/**
* @brief 更新 WiFi 扫描列表
* @param obj WiFi view对象
* @param ap_list AP 信息数组
* @param ap_count AP 数量
*/
void lisa_ui_setting_wifi_view_update_list(lv_obj_t *obj, const lisa_ui_wifi_ap_info_t *ap_list, int ap_count);
/**
* @brief 设置 WiFi 项点击回调
* @param obj WiFi view对象
* @param cb 回调函数
* @param user_data 传递给回调的用户数据
*/
void lisa_ui_setting_wifi_view_set_item_cb(lv_obj_t *obj, lisa_ui_setting_wifi_item_cb_t cb, void *user_data);
/**
* @brief 清空 WiFi 列表
* @param obj WiFi view对象
*/
void lisa_ui_setting_wifi_view_clear_list(lv_obj_t *obj);
/**
* @brief 显示密码输入界面
* @param obj WiFi view对象
* @param ssid WiFi SSID
* @param confirm_cb 确认回调
* @param cancel_cb 取消回调
* @param user_data 用户数据
*/
void lisa_ui_setting_wifi_view_show_password_input(lv_obj_t *obj, const char *ssid,
lisa_ui_setting_wifi_password_confirm_cb_t confirm_cb,
lisa_ui_setting_wifi_password_cancel_cb_t cancel_cb,
void *user_data);
/**
* @brief 隐藏密码输入界面
* @param obj WiFi view对象
*/
void lisa_ui_setting_wifi_view_hide_password_input(lv_obj_t *obj);
#ifdef __cplusplus
}
#endif
#endif /* __SETTING_WIFI_VIEW_H__ */

View File

@@ -0,0 +1,222 @@
/**
* @file setting_view.c
* @brief Setting main page view implementation
*
* Pure UI layer for settings menu with 2x2 grid card layout.
*/
#define TAG "setting_view"
#include "setting_view.h"
#include "lisa_ui.h"
#include "lisa_ui_llm_base.h"
#include "lisa_ui_nav_scr.h"
#include "lisa_ui_nav_scr_ids.h"
#include "lisa_ui_assets.h"
#include "lisa_ui_fonts.h"
#include <string.h>
/**
* @brief Setting view structure
*/
struct lisa_ui_setting_view {
lisa_ui_llm_base_t base_obj; /**< Base object */
lv_obj_t *back_btn; /**< Back button (X icon) */
lv_obj_t *list; /**< Setting items container */
lisa_ui_setting_item_click_cb_t click_cb;
void *click_user_data;
lisa_ui_setting_back_cb_t back_cb;
void *back_user_data;
};
typedef struct lisa_ui_setting_view lisa_ui_setting_view_t;
static void lisa_ui_setting_view_class_constructor(const lv_obj_class_t *class_p, lv_obj_t *obj);
static void lisa_ui_setting_view_class_destructor(const lv_obj_class_t *class_p, lv_obj_t *obj);
const lv_obj_class_t lisa_ui_setting_view_class = {
.base_class = &lisa_ui_llm_base_class,
.instance_size = sizeof(lisa_ui_setting_view_t),
.constructor_cb = lisa_ui_setting_view_class_constructor,
.destructor_cb = lisa_ui_setting_view_class_destructor,
};
static void list_btn_event_handler(lv_event_t *e)
{
lv_event_code_t code = lv_event_get_code(e);
if (code == LV_EVENT_CLICKED) {
lisa_ui_setting_view_t *setting = (lisa_ui_setting_view_t *)lv_event_get_user_data(e);
if (setting->click_cb) {
lv_obj_t *card = lv_event_get_target(e);
// Get text label (second child of card)
uint32_t child_cnt = lv_obj_get_child_cnt(card);
if (child_cnt >= 2) {
lv_obj_t *text_label = lv_obj_get_child(card, 1);
const char *name = lv_label_get_text(text_label);
setting->click_cb(name, setting->click_user_data);
}
}
}
}
static void back_btn_event_cb(lv_event_t *e)
{
lv_event_code_t code = lv_event_get_code(e);
if (code == LV_EVENT_CLICKED) {
lisa_ui_setting_view_t *setting = (lisa_ui_setting_view_t *)lv_event_get_user_data(e);
LISA_UI_LOGD("Setting back button clicked");
if (setting && setting->back_cb) {
setting->back_cb(setting->back_user_data);
}
}
}
static void lisa_ui_setting_view_class_constructor(const lv_obj_class_t *class_p, lv_obj_t *obj)
{
LV_UNUSED(class_p);
lisa_ui_setting_view_t *setting = (lisa_ui_setting_view_t *)obj;
// Hide the bar to allow content to start from the very top
lv_obj_t *bar = lisa_ui_llm_base_bar_get(obj);
if (bar) {
lv_obj_add_flag(bar, LV_OBJ_FLAG_HIDDEN);
}
lv_obj_t *container = lisa_ui_llm_base_container_get(obj);
if (NULL == container) {
LISA_UI_LOGE("Failed to get base container");
return;
}
// Create back button (X icon) in top-left corner at the very top
setting->back_btn = lv_btn_create(container);
lv_obj_set_size(setting->back_btn, 30, 30);
lv_obj_align(setting->back_btn, LV_ALIGN_TOP_LEFT, 5, 0);
lv_obj_set_style_bg_opa(setting->back_btn, LV_OPA_TRANSP, LV_PART_MAIN);
lv_obj_set_style_border_width(setting->back_btn, 0, LV_PART_MAIN);
lv_obj_set_style_shadow_width(setting->back_btn, 0, LV_PART_MAIN);
lv_obj_add_flag(setting->back_btn, LV_OBJ_FLAG_CLICKABLE); // Ensure clickable
lv_obj_t *back_icon = lv_img_create(setting->back_btn);
lv_img_set_src(back_icon, &icons_icon_closs_png);
lv_obj_center(back_icon);
lv_obj_add_event_cb(setting->back_btn, back_btn_event_cb, LV_EVENT_CLICKED, setting);
LISA_UI_LOGD("Setting back button created and event added");
// Create transparent container for cards (no grid, use absolute positioning like original)
setting->list = lv_obj_create(container);
lv_obj_set_size(setting->list, LV_PCT(100), LV_PCT(100));
lv_obj_set_style_bg_opa(setting->list, LV_OPA_TRANSP, LV_PART_MAIN);
lv_obj_set_style_border_width(setting->list, 0, LV_PART_MAIN);
lv_obj_set_style_pad_all(setting->list, 0, LV_PART_MAIN);
lv_obj_clear_flag(setting->list, LV_OBJ_FLAG_CLICKABLE); // List not clickable, only cards
// Move back button to front so it's not covered by cards
lv_obj_move_foreground(setting->back_btn);
}
static void lisa_ui_setting_view_class_destructor(const lv_obj_class_t *class_p, lv_obj_t *obj)
{
(void)class_p;
(void)obj;
// Child objects auto-deleted by LVGL
}
// ===================== Public API Implementation =====================
lv_obj_t *lisa_ui_setting_view_create(lv_obj_t *parent)
{
lv_obj_t *obj = lv_obj_class_create_obj(&lisa_ui_setting_view_class, parent);
lv_obj_class_init_obj(obj);
lisa_ui_setting_view_t *setting = (lisa_ui_setting_view_t *)obj;
setting->click_cb = NULL;
setting->click_user_data = NULL;
setting->back_cb = NULL;
setting->back_user_data = NULL;
// Set page title
lisa_ui_llm_base_set_title(obj, "Settings");
return obj;
}
void lisa_ui_setting_view_item_add(lv_obj_t *obj, const char *label, const void *icon_src, int col, int row)
{
if (!obj || !lv_obj_has_class(obj, &lisa_ui_setting_view_class)) {
return;
}
lisa_ui_setting_view_t *setting = (lisa_ui_setting_view_t *)obj;
// Calculate position like original UI (2x2 grid with absolute positioning)
lv_coord_t card_width = 140;
lv_coord_t card_height = 55;
lv_coord_t margin = 10;
lv_coord_t start_x = (320 - (2 * card_width + margin)) / 2; // Center horizontally
lv_coord_t start_y = 35; // Start below back button (30px height + 5px margin)
lv_coord_t x = start_x + col * (card_width + margin);
lv_coord_t y = start_y + row * (card_height + margin);
// Create card (exactly like original UI)
lv_obj_t *card = lv_obj_create(setting->list);
lv_obj_set_size(card, card_width, card_height);
lv_obj_set_pos(card, x, y);
lv_obj_set_style_bg_color(card, lv_color_hex(0x404040), LV_PART_MAIN);
lv_obj_set_style_border_width(card, 0, LV_PART_MAIN);
lv_obj_set_style_radius(card, 15, LV_PART_MAIN);
lv_obj_add_flag(card, LV_OBJ_FLAG_CLICKABLE);
// Icon on left (PNG image from assets, like original UI)
lv_obj_t *icon = lv_img_create(card);
lv_img_set_src(icon, icon_src);
lv_obj_align(icon, LV_ALIGN_LEFT_MID, 10, 0);
// Text on right (like original UI)
lv_obj_t *text = lv_label_create(card);
lv_label_set_text(text, label);
lv_obj_set_style_text_color(text, lv_color_white(), LV_PART_MAIN);
lv_obj_set_style_text_font(text, &lv_font_chinese_16, LV_PART_MAIN);
lv_obj_align(text, LV_ALIGN_RIGHT_MID, -10, 0);
// Add click event
lv_obj_add_event_cb(card, list_btn_event_handler, LV_EVENT_CLICKED, setting);
}
void lisa_ui_setting_view_set_click_cb(lv_obj_t *obj, lisa_ui_setting_item_click_cb_t cb, void *user_data)
{
if (!obj || !lv_obj_has_class(obj, &lisa_ui_setting_view_class)) {
return;
}
lisa_ui_setting_view_t *setting = (lisa_ui_setting_view_t *)obj;
setting->click_cb = cb;
setting->click_user_data = user_data;
}
void lisa_ui_setting_view_clear(lv_obj_t *obj)
{
if (!obj || !lv_obj_has_class(obj, &lisa_ui_setting_view_class)) {
return;
}
lisa_ui_setting_view_t *setting = (lisa_ui_setting_view_t *)obj;
lv_obj_clean(setting->list);
}
void lisa_ui_setting_view_set_back_cb(lv_obj_t *obj, lisa_ui_setting_back_cb_t cb, void *user_data)
{
if (!obj || !lv_obj_has_class(obj, &lisa_ui_setting_view_class)) {
return;
}
lisa_ui_setting_view_t *setting = (lisa_ui_setting_view_t *)obj;
setting->back_cb = cb;
setting->back_user_data = user_data;
}

View File

@@ -0,0 +1,75 @@
/**
* @file setting_view.h
* @brief Setting main page view header
*/
#ifndef __LISA_UI_SETTING_VIEW_H__
#define __LISA_UI_SETTING_VIEW_H__
#ifdef __cplusplus
extern "C" {
#endif
#include "lvgl.h"
#include "lisa_ui_llm_base.h"
/** Setting view class definition */
extern const lv_obj_class_t lisa_ui_setting_view_class;
/**
* @brief Setting item click callback
* @param name Item name that was clicked
* @param user_data User data
*/
typedef void (*lisa_ui_setting_item_click_cb_t)(const char *name, void *user_data);
/**
* @brief Back button click callback
* @param user_data User data
*/
typedef void (*lisa_ui_setting_back_cb_t)(void *user_data);
/**
* @brief Create setting view object
* @param parent Parent object, NULL for current screen
* @return lv_obj_t* Created setting view object, NULL on failure
*/
lv_obj_t *lisa_ui_setting_view_create(lv_obj_t *parent);
/**
* @brief Add setting item card to 2x2 grid
* @param obj Setting view object
* @param label Item label text
* @param icon_src LVGL image descriptor for icon
* @param col Grid column (0 or 1)
* @param row Grid row (0 or 1)
*/
void lisa_ui_setting_view_item_add(lv_obj_t *obj, const char *label, const void *icon_src, int col, int row);
/**
* @brief Set item click callback
* @param obj Setting view object
* @param cb Callback function
* @param user_data User data passed to callback
*/
void lisa_ui_setting_view_set_click_cb(lv_obj_t *obj, lisa_ui_setting_item_click_cb_t cb, void *user_data);
/**
* @brief Clear all setting items
* @param obj Setting view object
*/
void lisa_ui_setting_view_clear(lv_obj_t *obj);
/**
* @brief Set back button click callback
* @param obj Setting view object
* @param cb Callback function
* @param user_data User data passed to callback
*/
void lisa_ui_setting_view_set_back_cb(lv_obj_t *obj, lisa_ui_setting_back_cb_t cb, void *user_data);
#ifdef __cplusplus
}
#endif
#endif /* __LISA_UI_SETTING_VIEW_H__ */

View File

@@ -0,0 +1,13 @@
target_sources(
${LISA_UI_LIB_NAME}
PRIVATE
lisa_ui_anim_ext.c
lisa_ui_anim.c
lisa_ui_toast.c
)
target_include_directories(
${LISA_UI_LIB_NAME}
PRIVATE
./
)

View File

@@ -0,0 +1,318 @@
#define TAG "LISA_UI_ANIM"
#include "lisa_ui.h"
#include "lisa_ui_anim.h"
#include <string.h>
#define MY_CLASS &lisa_ui_anim_class
#define ANIM_DEFAULT_DELAY_MS 100
static void lisa_ui_anim_constructor(const lv_obj_class_t *class_p, lv_obj_t *obj);
static void lisa_ui_anim_destructor(const lv_obj_class_t *class_p, lv_obj_t *obj);
static void timer_cb(lv_timer_t *timer);
static uint32_t get_frame_delay(lisa_ui_anim_t *anim, uint16_t frame_idx);
static void update_frame(lisa_ui_anim_t *anim);
const lv_obj_class_t lisa_ui_anim_class = {.constructor_cb = lisa_ui_anim_constructor,
.destructor_cb = lisa_ui_anim_destructor,
.event_cb = NULL,
.width_def = LV_SIZE_CONTENT,
.height_def = LV_SIZE_CONTENT,
.instance_size = sizeof(lisa_ui_anim_t),
.base_class = &lv_img_class};
static void lisa_ui_anim_constructor(const lv_obj_class_t *class_p, lv_obj_t *obj)
{
LV_UNUSED(class_p);
LV_TRACE_OBJ_CREATE("begin");
lisa_ui_anim_t *anim = (lisa_ui_anim_t *)obj;
anim->frames = NULL;
anim->delays = NULL;
anim->frame_count = 0;
anim->current_frame = 0;
anim->default_delay = ANIM_DEFAULT_DELAY_MS;
anim->timer = NULL;
anim->loop = -1;
anim->playing = 0;
anim->timer = lv_timer_create(timer_cb, anim->default_delay, obj);
if (anim->timer == NULL) {
LISA_UI_LOGE("Failed to create timer");
return;
}
lv_timer_pause(anim->timer);
LISA_UI_LOGI("Animation widget created");
LV_TRACE_OBJ_CREATE("finished");
}
static void lisa_ui_anim_destructor(const lv_obj_class_t *class_p, lv_obj_t *obj)
{
LV_UNUSED(class_p);
LV_TRACE_OBJ_CREATE("begin");
lisa_ui_anim_t *anim = (lisa_ui_anim_t *)obj;
if (anim->timer != NULL) {
lv_timer_del(anim->timer);
anim->timer = NULL;
}
if (anim->delays != NULL) {
lv_mem_free(anim->delays);
anim->delays = NULL;
}
LISA_UI_LOGI("Animation widget destroyed");
LV_TRACE_OBJ_CREATE("finished");
}
static void timer_cb(lv_timer_t *timer)
{
lv_obj_t *obj = (lv_obj_t *)timer->user_data;
if (obj == NULL) {
return;
}
lisa_ui_anim_t *anim = (lisa_ui_anim_t *)obj;
if (anim->frames == NULL || anim->frame_count == 0) {
return;
}
anim->current_frame++;
if (anim->current_frame >= anim->frame_count) {
if (anim->loop < 0) {
anim->current_frame = 0;
if (anim->loop_cb) {
anim->loop_cb(obj);
}
} else if (anim->loop > 0) {
anim->current_frame = 0;
anim->loop--;
if (anim->loop_cb) {
anim->loop_cb(obj);
}
} else if (anim->loop == 0) {
anim->current_frame = anim->frame_count - 1;
anim->playing = 0;
lv_timer_pause(timer);
if (anim->stop_cb) {
anim->stop_cb(obj);
}
LISA_UI_LOGD("Animation finished");
return;
}
}
update_frame(anim);
uint32_t next_delay = get_frame_delay(anim, anim->current_frame);
lv_timer_set_period(timer, next_delay);
}
static uint32_t get_frame_delay(lisa_ui_anim_t *anim, uint16_t frame_idx)
{
uint32_t delay = anim->default_delay;
if (anim->delays != NULL && frame_idx < anim->frame_count) {
delay = anim->delays[frame_idx];
}
if (delay > 10 * 1000) {
LISA_UI_LOGW("Delay too long: %d, idx=%d", delay, frame_idx);
} else if (delay == 0) {
LISA_UI_LOGW("Delay too short: %d, idx=%d", delay, frame_idx);
}
return delay;
}
static void update_frame(lisa_ui_anim_t *anim)
{
if (anim->current_frame < anim->frame_count && anim->frames != NULL) {
lv_img_set_src(&anim->img.obj, &anim->frames[anim->current_frame]);
LISA_UI_LOGD("Frame %u/%u displayed", anim->current_frame + 1, anim->frame_count);
}
}
lv_obj_t *lisa_ui_anim_create(lv_obj_t *parent)
{
LV_LOG_INFO("begin");
lv_obj_t *obj = lv_obj_class_create_obj(MY_CLASS, parent);
lv_obj_class_init_obj(obj);
return obj;
}
int lisa_ui_anim_set_config(lv_obj_t *obj, const lisa_ui_anim_config_t *config)
{
LV_ASSERT_OBJ(obj, MY_CLASS);
if (config == NULL || config->frames == NULL || config->frame_count == 0) {
LISA_UI_LOGE("Invalid parameters");
return -1;
}
lisa_ui_anim_t *anim = (lisa_ui_anim_t *)obj;
if (anim->playing) {
lisa_ui_anim_pause(obj);
}
if (anim->delays != NULL) {
lv_mem_free(anim->delays);
anim->delays = NULL;
}
anim->frames = config->frames;
anim->frame_count = config->frame_count;
anim->default_delay = config->default_delay > 0 ? config->default_delay : ANIM_DEFAULT_DELAY_MS;
anim->loop = config->loop;
anim->current_frame = 0;
if (config->delays != NULL) {
anim->delays = (uint32_t *)lv_mem_alloc(sizeof(uint32_t) * config->frame_count);
if (anim->delays != NULL) {
memcpy(anim->delays, config->delays, sizeof(uint32_t) * config->frame_count);
} else {
LISA_UI_LOGW("Failed to allocate delays array, using default delay");
}
}
update_frame(anim);
int delay = get_frame_delay(anim, 0);
if (anim->timer != NULL) {
lv_timer_set_period(anim->timer, delay);
}
LISA_UI_LOGI("Animation configured: %u frames, default_delay=%ums, next_delay=%ums, loop=%d", config->frame_count,
anim->default_delay, delay, config->loop);
return 0;
}
int lisa_ui_anim_set_frames(lv_obj_t *obj, const lv_img_dsc_t *frames, uint16_t frame_count, uint32_t delay_ms, int loop)
{
lisa_ui_anim_config_t config = {
.frames = frames, .delays = NULL, .frame_count = frame_count, .default_delay = delay_ms, .loop = loop};
return lisa_ui_anim_set_config(obj, &config);
}
void lisa_ui_anim_start(lv_obj_t *obj)
{
LV_ASSERT_OBJ(obj, MY_CLASS);
lisa_ui_anim_t *anim = (lisa_ui_anim_t *)obj;
if (anim->timer == NULL || anim->frames == NULL || anim->frame_count == 0) {
LISA_UI_LOGE("Cannot start: animation not configured");
return;
}
if (!anim->playing) {
anim->playing = 1;
lv_timer_resume(anim->timer);
LISA_UI_LOGI("Animation started");
}
if (anim->loop > 0) {
anim->loop--;
}
}
void lisa_ui_anim_pause(lv_obj_t *obj)
{
LV_ASSERT_OBJ(obj, MY_CLASS);
lisa_ui_anim_t *anim = (lisa_ui_anim_t *)obj;
if (anim->timer == NULL) {
return;
}
if (anim->playing) {
anim->playing = 0;
lv_timer_pause(anim->timer);
LISA_UI_LOGI("Animation paused at frame %u", anim->current_frame);
}
}
void lisa_ui_anim_stop(lv_obj_t *obj)
{
LV_ASSERT_OBJ(obj, MY_CLASS);
lisa_ui_anim_t *anim = (lisa_ui_anim_t *)obj;
if (anim->timer == NULL) {
return;
}
anim->playing = 0;
anim->current_frame = 0;
lv_timer_pause(anim->timer);
lv_timer_set_period(anim->timer, get_frame_delay(anim, 0));
if (anim->frames != NULL && anim->frame_count > 0) {
update_frame(anim);
}
LISA_UI_LOGI("Animation stopped and reset");
}
void lisa_ui_anim_set_frame(lv_obj_t *obj, uint16_t frame_idx)
{
LV_ASSERT_OBJ(obj, MY_CLASS);
lisa_ui_anim_t *anim = (lisa_ui_anim_t *)obj;
if (anim->frames == NULL) {
return;
}
if (frame_idx < anim->frame_count) {
anim->current_frame = frame_idx;
update_frame(anim);
if (anim->timer != NULL) {
lv_timer_set_period(anim->timer, get_frame_delay(anim, frame_idx));
}
LISA_UI_LOGD("Frame set to %u", frame_idx);
} else {
LISA_UI_LOGW("Invalid frame index: %u (max: %u)", frame_idx, anim->frame_count - 1);
}
}
uint16_t lisa_ui_anim_get_frame(lv_obj_t *obj)
{
LV_ASSERT_OBJ(obj, MY_CLASS);
lisa_ui_anim_t *anim = (lisa_ui_anim_t *)obj;
return anim->current_frame;
}
bool lisa_ui_anim_is_playing(lv_obj_t *obj)
{
LV_ASSERT_OBJ(obj, MY_CLASS);
lisa_ui_anim_t *anim = (lisa_ui_anim_t *)obj;
return anim->playing != 0;
}
void lisa_ui_anim_stop_cb_set(lv_obj_t *obj, lisa_ui_anim_stop_cb_t cb)
{
LV_ASSERT_OBJ(obj, MY_CLASS);
lisa_ui_anim_t *anim = (lisa_ui_anim_t *)obj;
anim->stop_cb = cb;
}
void lisa_ui_anim_loop_cb_set(lv_obj_t *obj, lisa_ui_anim_loop_cb_t cb)
{
LV_ASSERT_OBJ(obj, MY_CLASS);
lisa_ui_anim_t *anim = (lisa_ui_anim_t *)obj;
anim->loop_cb = cb;
}

View File

@@ -0,0 +1,56 @@
#ifndef LISA_UI_ANIM_H
#define LISA_UI_ANIM_H
#ifdef __cplusplus
extern "C" {
#endif
#include "lvgl.h"
typedef void (*lisa_ui_anim_stop_cb_t)(lv_obj_t *obj);
typedef void (*lisa_ui_anim_loop_cb_t)(lv_obj_t *obj);
typedef struct {
lv_img_t img;
lisa_ui_anim_stop_cb_t stop_cb;
lisa_ui_anim_loop_cb_t loop_cb;
const lv_img_dsc_t *frames;
uint32_t *delays;
uint16_t frame_count;
uint16_t current_frame;
uint32_t default_delay;
lv_timer_t *timer;
int32_t loop;
uint8_t playing : 1;
} lisa_ui_anim_t;
typedef struct {
const lv_img_dsc_t *frames;
const uint32_t *delays;
uint16_t frame_count;
uint32_t default_delay;
int loop;
int pause_frame_idx;
} lisa_ui_anim_config_t;
extern const lv_obj_class_t lisa_ui_anim_class;
lv_obj_t *lisa_ui_anim_create(lv_obj_t *parent);
int lisa_ui_anim_set_config(lv_obj_t *obj, const lisa_ui_anim_config_t *config);
int lisa_ui_anim_set_frames(lv_obj_t *obj, const lv_img_dsc_t *frames, uint16_t frame_count,
uint32_t delay_ms, int loop);
void lisa_ui_anim_start(lv_obj_t *obj);
void lisa_ui_anim_pause(lv_obj_t *obj);
void lisa_ui_anim_stop(lv_obj_t *obj);
void lisa_ui_anim_set_frame(lv_obj_t *obj, uint16_t frame_idx);
uint16_t lisa_ui_anim_get_frame(lv_obj_t *obj);
bool lisa_ui_anim_is_playing(lv_obj_t *obj);
void lisa_ui_anim_stop_cb_set(lv_obj_t *obj, lisa_ui_anim_stop_cb_t cb);
void lisa_ui_anim_loop_cb_set(lv_obj_t *obj, lisa_ui_anim_loop_cb_t cb);
#ifdef __cplusplus
}
#endif
#endif

View File

@@ -0,0 +1,161 @@
#include "lisa_ui_anim_ext.h"
static void lisa_ui_anim_ext_class_constructor(const lv_obj_class_t *class_p, lv_obj_t *obj);
static void lisa_ui_anim_ext_class_destructor(const lv_obj_class_t *class_p, lv_obj_t *obj);
const lv_obj_class_t lisa_ui_anim_ext_class = {
.constructor_cb = lisa_ui_anim_ext_class_constructor,
.destructor_cb = lisa_ui_anim_ext_class_destructor,
.event_cb = NULL,
.width_def = LV_PCT(100),
.height_def = LV_PCT(100),
.instance_size = sizeof(lisa_ui_anim_ext_t),
.base_class = &lisa_ui_anim_class,
};
#define MY_CLASS &lisa_ui_anim_ext_class
static void lisa_ui_anim_stop_cb_handle(lv_obj_t *obj)
{
lisa_ui_anim_ext_t *anim_ext = (lisa_ui_anim_ext_t *)obj;
if (anim_ext->state == LISA_UI_ANIM_STATE_ENTER) {
anim_ext->state = LISA_UI_ANIM_STATE_LOOP;
lisa_ui_anim_set_config(obj, &anim_ext->curr.loop);
lisa_ui_anim_start(obj);
} else if (anim_ext->state == LISA_UI_ANIM_STATE_LOOP) {
anim_ext->state = LISA_UI_ANIM_STATE_STOPING;
if (anim_ext->curr.exit.frame_count != 0) {
lisa_ui_anim_set_config(obj, &anim_ext->curr.exit);
lisa_ui_anim_start(obj);
}
} else if (anim_ext->state == LISA_UI_ANIM_STATE_NEXT_REQ) {
lisa_ui_anim_ext_set_config(obj, &anim_ext->next);
lisa_ui_anim_ext_start(obj);
} else {
anim_ext->state = LISA_UI_ANIM_STATE_STOPED;
}
}
static void lisa_ui_anim_loop_cb_handle(lv_obj_t *obj)
{
lisa_ui_anim_ext_t *anim_ext = (lisa_ui_anim_ext_t *)obj;
if (anim_ext->state == LISA_UI_ANIM_STATE_STOP_REQ) {
if (anim_ext->curr.exit.frame_count != 0) {
anim_ext->state = LISA_UI_ANIM_STATE_STOPING;
lisa_ui_anim_set_config(obj, &anim_ext->curr.exit);
lisa_ui_anim_start(obj);
} else {
anim_ext->state = LISA_UI_ANIM_STATE_STOPED;
lisa_ui_anim_stop(obj);
}
} else if (anim_ext->state == LISA_UI_ANIM_STATE_NEXT_REQ) {
lisa_ui_anim_ext_set_config(obj, &anim_ext->next);
lisa_ui_anim_ext_start(obj);
}
}
static void lisa_ui_anim_ext_class_constructor(const lv_obj_class_t *class_p, lv_obj_t *obj)
{
LV_UNUSED(class_p);
LV_TRACE_OBJ_CREATE("begin");
lisa_ui_anim_ext_t *anim_ext = (lisa_ui_anim_ext_t *)obj;
memset(&anim_ext->curr, 0, sizeof(lisa_ui_anim_config_t));
memset(&anim_ext->next, 0, sizeof(lisa_ui_anim_config_t));
anim_ext->state = LISA_UI_ANIM_STATE_NONE;
lisa_ui_anim_stop_cb_set(obj, lisa_ui_anim_stop_cb_handle);
lisa_ui_anim_loop_cb_set(obj, lisa_ui_anim_loop_cb_handle);
LV_TRACE_OBJ_CREATE("finished");
}
static void lisa_ui_anim_ext_class_destructor(const lv_obj_class_t *class_p, lv_obj_t *obj)
{
LV_UNUSED(class_p);
LV_TRACE_OBJ_CREATE("begin");
LV_TRACE_OBJ_CREATE("finished");
}
lv_obj_t *lisa_ui_anim_ext_create(lv_obj_t *parent)
{
lv_obj_t *obj = lv_obj_class_create_obj(MY_CLASS, parent);
lv_obj_class_init_obj(obj);
return obj;
}
void lisa_ui_anim_ext_set_config(lv_obj_t *obj, const lisa_ui_anim_ext_config_t *config)
{
LV_ASSERT_OBJ(obj, MY_CLASS);
LV_ASSERT(config != NULL);
lisa_ui_anim_ext_t *anim_ext = (lisa_ui_anim_ext_t *)obj;
memcpy(&anim_ext->curr.enter, &config->enter, sizeof(lisa_ui_anim_config_t));
memcpy(&anim_ext->curr.loop, &config->loop, sizeof(lisa_ui_anim_config_t));
memcpy(&anim_ext->curr.exit, &config->exit, sizeof(lisa_ui_anim_config_t));
}
void lisa_ui_anim_ext_start(lv_obj_t *obj)
{
lisa_ui_anim_ext_t *anim_ext = (lisa_ui_anim_ext_t *)obj;
lisa_ui_anim_config_t *cfg = &anim_ext->curr.enter;
if (cfg->frame_count == 0) {
cfg = &anim_ext->curr.loop;
anim_ext->state = LISA_UI_ANIM_STATE_LOOP;
} else {
anim_ext->state = LISA_UI_ANIM_STATE_ENTER;
}
lisa_ui_anim_set_config(obj, cfg);
lisa_ui_anim_start(obj);
}
void lisa_ui_anim_ext_stop(lv_obj_t *obj)
{
lisa_ui_anim_ext_t *anim_ext = (lisa_ui_anim_ext_t *)obj;
if (anim_ext->state == LISA_UI_ANIM_STATE_NONE || anim_ext->state == LISA_UI_ANIM_STATE_STOPED) {
return;
}
if (anim_ext->curr.exit.frame_count == 0) {
anim_ext->state = LISA_UI_ANIM_STATE_STOPED;
lisa_ui_anim_stop(obj);
return;
}
anim_ext->state = LISA_UI_ANIM_STATE_STOP_REQ;
}
void lisa_ui_anim_ext_next(lv_obj_t *obj, const lisa_ui_anim_ext_config_t *next)
{
lisa_ui_anim_ext_t *anim_ext = (lisa_ui_anim_ext_t *)obj;
if ((anim_ext->state != LISA_UI_ANIM_STATE_STOPED) && (anim_ext->state != LISA_UI_ANIM_STATE_NONE)) {
memcpy(&anim_ext->next, next, sizeof(lisa_ui_anim_ext_config_t));
anim_ext->state = LISA_UI_ANIM_STATE_NEXT_REQ;
} else {
lisa_ui_anim_ext_set_config(obj, next);
lisa_ui_anim_ext_start(obj);
}
}
void lisa_ui_anim_ext_next_imm(lv_obj_t *obj, const lisa_ui_anim_ext_config_t *next)
{
lisa_ui_anim_ext_t *anim_ext = (lisa_ui_anim_ext_t *)obj;
lisa_ui_anim_ext_exit(obj);
lisa_ui_anim_ext_next(obj, next);
}
void lisa_ui_anim_ext_exit(lv_obj_t *obj)
{
lisa_ui_anim_ext_t *anim_ext = (lisa_ui_anim_ext_t *)obj;
anim_ext->state = LISA_UI_ANIM_STATE_STOPED;
lisa_ui_anim_stop(obj);
}

View File

@@ -0,0 +1,40 @@
#ifndef __LISA_UI_ANIM_EXT_H__
#define __LISA_UI_ANIM_EXT_H__
#include "lisa_ui_anim.h"
enum {
LISA_UI_ANIM_STATE_NONE,
LISA_UI_ANIM_STATE_ENTER,
LISA_UI_ANIM_STATE_LOOP,
LISA_UI_ANIM_STATE_STOP_REQ,
LISA_UI_ANIM_STATE_NEXT_REQ,
LISA_UI_ANIM_STATE_STOPING,
LISA_UI_ANIM_STATE_STOPED,
LISA_UI_ANIM_STATE_MAX,
};
typedef struct {
lisa_ui_anim_config_t enter;
lisa_ui_anim_config_t loop;
lisa_ui_anim_config_t exit;
} lisa_ui_anim_ext_config_t;
typedef struct {
lisa_ui_anim_t anim;
lisa_ui_anim_ext_config_t curr;
lisa_ui_anim_ext_config_t next;
uint8_t state;
} lisa_ui_anim_ext_t;
extern const lv_obj_class_t lisa_ui_anim_ext_class;
lv_obj_t *lisa_ui_anim_ext_create(lv_obj_t *parent);
void lisa_ui_anim_ext_exit(lv_obj_t *obj);
void lisa_ui_anim_ext_stop(lv_obj_t *obj);
void lisa_ui_anim_ext_start(lv_obj_t *obj);
void lisa_ui_anim_ext_next(lv_obj_t *obj, const lisa_ui_anim_ext_config_t *next);
void lisa_ui_anim_ext_next_imm(lv_obj_t *obj, const lisa_ui_anim_ext_config_t *next);
void lisa_ui_anim_ext_set_config(lv_obj_t *obj, const lisa_ui_anim_ext_config_t *config);
#endif /* __LISA_UI_ANIM_EXT_H__ */

View File

@@ -0,0 +1,48 @@
#include "lvgl.h"
#include "lisa_ui.h"
#include "lisa_ui_assets.h"
#include "lisa_ui_fonts.h"
static lv_obj_t *toast_obj = NULL;
static lv_timer_t *toast_timer = NULL;
static void toast_hide_cb(lv_timer_t *timer)
{
if (toast_obj) {
lv_obj_del(toast_obj);
toast_obj = NULL;
}
if (toast_timer) {
lv_timer_del(toast_timer);
toast_timer = NULL;
}
}
void lisa_ui_toast_show(const char *txt)
{
if (toast_obj) {
toast_hide_cb(NULL);
}
toast_obj = lv_obj_create(lv_layer_sys());
lv_obj_add_flag(toast_obj, LV_OBJ_FLAG_FLOATING);
lv_obj_set_size(toast_obj, lv_pct(80), LV_SIZE_CONTENT);
lv_obj_set_style_radius(toast_obj, 8, LV_PART_MAIN);
lv_obj_set_style_bg_color(toast_obj, lv_color_hex(0x333333), LV_PART_MAIN);
lv_obj_set_style_bg_opa(toast_obj, LV_OPA_80, LV_PART_MAIN);
lv_obj_set_style_border_width(toast_obj, 0, LV_PART_MAIN);
lv_obj_set_style_pad_all(toast_obj, 12, LV_PART_MAIN);
lv_obj_set_style_pad_gap(toast_obj, 12, LV_PART_MAIN);
lv_obj_t *label = lv_label_create(toast_obj);
lv_label_set_text(label, txt);
lv_obj_set_style_text_color(label, lv_color_hex(0xFFFFFF), LV_PART_MAIN);
lv_obj_set_style_text_font(label, &lv_font_chinese_16, LV_PART_MAIN);
lv_obj_set_style_text_align(label, LV_TEXT_ALIGN_CENTER, LV_PART_MAIN);
lv_label_set_long_mode(label, LV_LABEL_LONG_SCROLL_CIRCULAR);
lv_obj_align(label, LV_ALIGN_CENTER, 0, 0);
lv_obj_align(toast_obj, LV_ALIGN_BOTTOM_MID, 0, -40);
toast_timer = lv_timer_create(toast_hide_cb, 2000, NULL);
}

View File

@@ -0,0 +1,6 @@
#ifndef __LISA_UI_TOAST_H__
#define __LISA_UI_TOAST_H__
void lisa_ui_toast_show(const char *txt);
#endif /* __LISA_UI_TOAST_H__ */