From: Caisking Date: Thu, 23 Oct 2025 10:28:36 +0000 (+0800) Subject: 增加女性健康的按键交互功能 X-Git-Tag: V1.0.9_20251128_194606_LN~180^2~1^2~1 X-Git-Url: http://git.snicker.com.cn/?a=commitdiff_plain;h=b623dc96a674dcea26a62104a189f6a7ea9f4060;p=mirrors%2FFD230.git 增加女性健康的按键交互功能 --- diff --git a/code/sdk/application/wearable/fd272/nativeapp/nativelauncher/include/UiConfig.h b/code/sdk/application/wearable/fd272/nativeapp/nativelauncher/include/UiConfig.h index 6c9ae69b5..2766c4a95 100644 --- a/code/sdk/application/wearable/fd272/nativeapp/nativelauncher/include/UiConfig.h +++ b/code/sdk/application/wearable/fd272/nativeapp/nativelauncher/include/UiConfig.h @@ -635,7 +635,7 @@ namespace OHOS static constexpr const char *PNG_APPLIST_SMART_IMAGE = APP_LIST_PATH "/smart.bin"; //语音助手 static constexpr const char *PNG_APPLIST_CALENDAR_IMAGE = APP_LIST_PATH "/calendar.bin"; //日历 static constexpr const char *PNG_APPLIST_HRV_IMAGE = APP_LIST_PATH "/hrv.bin"; //hrv - static constexpr const char *PNG_APPLIST_WOMANHEALTH_IMAGE = APP_LIST_PATH "/womanhealth.bin"; //女性健康 + static constexpr const char *PNG_APPLIST_WOMANHEALTH_IMAGE = APP_LIST_PATH "/woman_health.bin"; //女性健康 //big Icon static constexpr const char *PNG_APPLIST_THEME_IMAGE_BIG = APP_LIST_PATH "/applist_big_icon/theme.bin"; // 显示方式 static constexpr const char *PNG_APPLIST_CARDSET_IMAGE_BIG = APP_LIST_PATH "/applist_big_icon/cardsetting.bin"; // 卡片管理 @@ -679,7 +679,7 @@ namespace OHOS static constexpr const char *PNG_APPLIST_SMART_IMAGE_BIG = APP_LIST_PATH "/applist_big_icon/smart.bin"; //语音助手 static constexpr const char *PNG_APPLIST_CALENDAR_IMAGE_BIG = APP_LIST_PATH "/applist_big_icon/calendar.bin"; //日历 static constexpr const char *PNG_APPLIST_HRV_IMAGE_BIG = APP_LIST_PATH "/applist_big_icon/hrv.bin"; //hrv - static constexpr const char *PNG_APPLIST_WOMANHEALTH_IMAGE_BIG = APP_LIST_PATH "/applist_big_icon/womanhealth.bin"; //女性健康 + static constexpr const char *PNG_APPLIST_WOMANHEALTH_IMAGE_BIG = APP_LIST_PATH "/applist_big_icon/woman_health.bin"; //女性健康 // AppList hexagon icon static constexpr const char *PNG_APPLIST_DEFAULT_IMG = APP_RES_PATH "/regular_hexagon.bin"; diff --git a/code/sdk/application/wearable/fd272/nativeapp/nativeui/womenhealth/include/WomenHealthModel.h b/code/sdk/application/wearable/fd272/nativeapp/nativeui/womenhealth/include/WomenHealthModel.h index b0d0e07ad..250bca865 100644 --- a/code/sdk/application/wearable/fd272/nativeapp/nativeui/womenhealth/include/WomenHealthModel.h +++ b/code/sdk/application/wearable/fd272/nativeapp/nativeui/womenhealth/include/WomenHealthModel.h @@ -11,17 +11,67 @@ namespace OHOS { + //时间结构体,年月日 + typedef struct { + int year; + int month; + int day; + } TimeObj; - - class WomenHealthModel { - public: - static WomenHealthModel *GetInstance(void); - - private: - WomenHealthModel(); - WomenHealthModel(const WomenHealthModel &); - virtual ~WomenHealthModel(); +class WomenHealthModel { +public: + static WomenHealthModel *GetInstance(void); + void SetMenstrualStartDate(const TimeObj &startDate); + void SetMenstrualEndDate(const TimeObj &endDate); + TimeObj GetMenstrualStartDate(); + TimeObj GetMenstrualEndDate(); + TimeObj GetOvulationStartDate(); + TimeObj GetOvulationEndDate(); + int TimeDiffInMinutes(const TimeObj &startDate, const TimeObj &endDate); - }; + // 判断某天是否在月经期内(已记录的) + bool IsInMenstrualPeriod(int year, int month, int day); + // 判断某天是否在排卵期内(已记录的) + bool IsInOvulationPeriod(int year, int month, int day); + // 判断某天是否在预测经期内 + bool IsInPredictPeriod(int year, int month, int day); + // 判断某天是否在预测排卵期内(下次和下下次的排卵期) + bool IsInPredictOvulationPeriod(int year, int month, int day); + + // 辅助方法:获取经期开始的年月 + void GetMenstrualStartYearMonth(int &year, int &month); + // 辅助方法:获取预测的最后一个月(第3次预测经期的月份) + void GetLastPredictYearMonth(int &year, int &month); + // 辅助方法:检查是否已设置经期数据 + bool HasMenstrualData(); + +private: + WomenHealthModel(); + WomenHealthModel(const WomenHealthModel &); + virtual ~WomenHealthModel(); + + // 比较两个日期,返回: <0表示date1在date2前,0表示相等,>0表示date1在date2后 + int CompareDate(int year1, int month1, int day1, int year2, int month2, int day2); + // 给日期加上天数,返回新日期 + TimeObj AddDays(const TimeObj &date, int days); + + //3个月的经期日期开始时间 + TimeObj menstrualStartDate[3]; + //3个月的经期日期结束时间 + TimeObj menstrualEndDate[3]; + //3个月的排卵期开始时间 + TimeObj ovulationStartDate[3]; + //3个月的排卵期结束时间 + TimeObj ovulationEndDate[3]; + //经期周期(天数) + int menstrualCycle = 5; + //排卵期周期(天数) + int ovulationCycle = 10; + //经期与排卵期之间的天数 + int menstrualOvulationDays = 5; + //月经周期总长度(默认28天) + int totalCycle = 28; + +}; } #endif \ No newline at end of file diff --git a/code/sdk/application/wearable/fd272/nativeapp/nativeui/womenhealth/include/WomenHealthPresenter.h b/code/sdk/application/wearable/fd272/nativeapp/nativeui/womenhealth/include/WomenHealthPresenter.h index 3bfe17760..ea7c5e3ab 100644 --- a/code/sdk/application/wearable/fd272/nativeapp/nativeui/womenhealth/include/WomenHealthPresenter.h +++ b/code/sdk/application/wearable/fd272/nativeapp/nativeui/womenhealth/include/WomenHealthPresenter.h @@ -18,6 +18,14 @@ #include "NativeAbility.h" namespace OHOS { + +//下个月 +#define NEXT_MONTH "nextmonth" +//上个月 +#define PREV_MONTH "prevmonth" +//编辑 +#define EDIT "edit" + class WomenHealthView; class WomenHealthPresenter : public Presenter, public UIView::OnClickListener, diff --git a/code/sdk/application/wearable/fd272/nativeapp/nativeui/womenhealth/include/WomenHealthView.h b/code/sdk/application/wearable/fd272/nativeapp/nativeui/womenhealth/include/WomenHealthView.h index fe6e16dee..4b2a4a734 100644 --- a/code/sdk/application/wearable/fd272/nativeapp/nativeui/womenhealth/include/WomenHealthView.h +++ b/code/sdk/application/wearable/fd272/nativeapp/nativeui/womenhealth/include/WomenHealthView.h @@ -21,8 +21,17 @@ #include "components/ui_image_view.h" #include "womenhealth/include/WomenHealthPresenter.h" + + namespace OHOS { -namespace OHOS { +// 日期状态枚举 +enum DayStatus { + DAY_STATUS_NORMAL = 0, // 普通日期 + DAY_STATUS_TODAY, // 今天(灰色) + DAY_STATUS_MENSTRUAL, // 月经期(红色) + DAY_STATUS_OVULATION, // 排卵期(蓝色) + DAY_STATUS_PREDICT // 预测经期(浅红色) +}; class WomenHealthView : public View { @@ -33,25 +42,64 @@ public: void OnStart() override; static WomenHealthView *GetInstance(); bool updata_label(int8_t type); + + // 公共方法:判断当前是否显示今天所在月份 + bool IsShowingCurrentMonth(); + + void BackToToday(); // 回到今天所在月份 private: bool Label_init(); + + // 拆分后的初始化函数(降低耦合度) + void InitDayMarkers(); // 初始化颜色标记圆圈 + void InitTitleLabel(); // 初始化顶部标题"生理周期" + void InitMonthLabel(int year, int month, int day, int weekday); // 初始化日期标签(X月X日 星期X) + void InitArrowButtons(); // 初始化左右箭头按钮 + void InitEditButton(); // 初始化底部"编辑"按钮 + void InitWeekLabels(); // 初始化星期标签 + void InitDayLabels(); // 初始化日期标签UI对象 + void UpdateDayDisplay(int year, int month, int maxDays, int weekday); // 更新日期显示 + + // 辅助函数 + int CalculateMonthDays(int year, int month); // 计算某月有多少天 + int CalculateWeekday(int year, int month, int day); // 使用蔡勒公式计算星期几 + void UpdateArrowButtonsVisibility(); // 更新左右箭头按钮的显示状态 + void UpdateEditButtonText(); // 更新底部按钮文本(编辑/回今天) + + + // 判断某一天的状态(月经期、排卵期、预测经期等) + DayStatus GetDayStatus(uint32_t year, uint32_t month, uint32_t day); + + // 为指定日期绘制颜色标记圆圈 + void DrawDayMarker(int dayIndex, int x, int y, DayStatus status); + UIViewGroup *group{nullptr}; UIView:: OnDragListener* dragListener{nullptr}; - UILabel *mon_label{nullptr}; + + // 顶部UI组件 + UILabel *title_label{nullptr}; // 标题"生理周期" + UILabel *mon_label{nullptr}; // 日期标签"X月X日 星期X" + UILabelButton *left_arrow{nullptr}; // 左箭头按钮 + UILabelButton *right_arrow{nullptr}; // 右箭头按钮 + + // 星期和日期标签 UILabel *week_label[7]{nullptr}; UILabel *day_label[31]{nullptr}; - UIImageView *imageview{nullptr}; - - UIImageAnimatorView *PullUpAnimatorView{nullptr}; // 上拉动画 - - ImageAnimatorInfo PullUpimageAnimatorInfo[70]{0}; - - const ImageInfo** PullUpimageAnimator_imageInfo{nullptr}; - uint32_t show_y=0; - uint32_t show_m=0; - uint32_t show_d=0; + // 底部按钮 + UILabelButton *edit_button{nullptr}; // 编辑按钮 + + // 不同状态的标记圆圈(最多显示31天) + UIImageView *dayMarker[31]{nullptr}; // 颜色标记圆圈数组 + + uint32_t show_y=0; // 当前显示的年 + uint32_t show_m=0; // 当前显示的月 + uint32_t show_d=0; // 当前显示的日 + + uint32_t today_y=0; // 今天的年 + uint32_t today_m=0; // 今天的月 + uint32_t today_d=0; // 今天的日 }; } diff --git a/code/sdk/application/wearable/fd272/nativeapp/nativeui/womenhealth/src/WomenHealthModel.cpp b/code/sdk/application/wearable/fd272/nativeapp/nativeui/womenhealth/src/WomenHealthModel.cpp index c24dee697..69900c8e8 100644 --- a/code/sdk/application/wearable/fd272/nativeapp/nativeui/womenhealth/src/WomenHealthModel.cpp +++ b/code/sdk/application/wearable/fd272/nativeapp/nativeui/womenhealth/src/WomenHealthModel.cpp @@ -7,29 +7,308 @@ #include "womenhealth/include/WomenHealthModel.h" + #include "wearable_log.h" + - #ifdef __cplusplus - extern "C" { - #endif - - using namespace OHOS; - - WomenHealthModel::WomenHealthModel() - { - } - - WomenHealthModel::~WomenHealthModel() - { - } + /* +- 经期默认时长:5-6 天(用户可在该范围选择具体天数) +- 排卵期默认计算:基于 28 天标准周期,自动推算为 10 天(排卵日及前后各 4-5 天) +- 周期默认值:28 天(示例:9 月 3 日开始经期,下次预测经期为 10 月 1 日,即 9 月 3 日加 28 天) - WomenHealthModel *WomenHealthModel::GetInstance(void) + */ + + namespace OHOS { - static WomenHealthModel callLogModel; - return &callLogModel; - } - - #ifdef __cplusplus - } - #endif + + WomenHealthModel::WomenHealthModel() + { + // 初始化所有日期为0 + for (int i = 0; i < 3; i++) { + menstrualStartDate[i] = {0, 0, 0}; + menstrualEndDate[i] = {0, 0, 0}; + ovulationStartDate[i] = {0, 0, 0}; + ovulationEndDate[i] = {0, 0, 0}; + } + } + + WomenHealthModel::~WomenHealthModel(){} + + WomenHealthModel *WomenHealthModel::GetInstance(void) + { + static WomenHealthModel instance; + return &instance; + } + + //获取第一个月的经期开始时间 + TimeObj WomenHealthModel::GetMenstrualStartDate() + { + return menstrualStartDate[0]; + } + //获取第一个月的经期结束时间 + TimeObj WomenHealthModel::GetMenstrualEndDate() + { + return menstrualEndDate[0]; + } + //获取第一个月的排卵期开始时间 + TimeObj WomenHealthModel::GetOvulationStartDate() + { + return ovulationStartDate[0]; + } + //获取第一个月的排卵期结束时间 + TimeObj WomenHealthModel::GetOvulationEndDate() + { + return ovulationEndDate[0]; + } + + /** + * @brief 比较两个日期 + * @return <0表示date1在date2前,0表示相等,>0表示date1在date2后 + */ + int WomenHealthModel::CompareDate(int year1, int month1, int day1, int year2, int month2, int day2) + { + if (year1 != year2) return year1 - year2; + if (month1 != month2) return month1 - month2; + return day1 - day2; + } + + /** + * @brief 给日期加上指定天数 + * @param date 原始日期 + * @param days 要加的天数 + * @return 新日期 + */ + TimeObj WomenHealthModel::AddDays(const TimeObj &date, int days) + { + TimeObj result = date; + result.day += days; + + // 简单的日期计算(可以优化) + while (result.day > 31) { + result.day -= 30; // 简化:每月按30天计算 + result.month++; + if (result.month > 12) { + result.month = 1; + result.year++; + } + } + + while (result.day < 1) { + result.month--; + if (result.month < 1) { + result.month = 12; + result.year--; + } + result.day += 30; // 简化:每月按30天计算 + } + + return result; + } + + /** + * @brief 用户设置经期开始日期 + * @param startDate 经期开始日期 + * + * 根据开始日期自动计算: + * 1. 经期结束时间 = 开始时间 + menstrualCycle天 + * 2. 排卵期开始时间 = 经期结束时间 + menstrualOvulationDays天 + * 3. 排卵期结束时间 = 排卵期开始时间 + ovulationCycle天 + * 4. 预测下次经期 = 本次开始 + totalCycle天(28天) + */ + void WomenHealthModel::SetMenstrualStartDate(const TimeObj &startDate) + { + //设置这个月的经期开始时间 + menstrualStartDate[0] = startDate; + + //经期结束时间 = 开始时间 + menstrualCycle天(默认5天) + menstrualEndDate[0] = AddDays(menstrualStartDate[0], menstrualCycle); + + //排卵期开始时间 = 经期结束时间 + menstrualOvulationDays天(默认5天) + ovulationStartDate[0] = AddDays(menstrualEndDate[0], menstrualOvulationDays); + + //排卵期结束时间 = 排卵期开始时间 + ovulationCycle天(默认10天) + ovulationEndDate[0] = AddDays(ovulationStartDate[0], ovulationCycle); + + //预测下次经期 = 本次开始 + totalCycle天(默认28天) + menstrualStartDate[1] = AddDays(menstrualStartDate[0], totalCycle); + menstrualEndDate[1] = AddDays(menstrualStartDate[1], menstrualCycle); + ovulationStartDate[1] = AddDays(menstrualEndDate[1], menstrualOvulationDays); + ovulationEndDate[1] = AddDays(ovulationStartDate[1], ovulationCycle); + + //预测下下次经期 = 下次开始 + totalCycle天 + menstrualStartDate[2] = AddDays(menstrualStartDate[1], totalCycle); + menstrualEndDate[2] = AddDays(menstrualStartDate[2], menstrualCycle); + ovulationStartDate[2] = AddDays(menstrualEndDate[2], menstrualOvulationDays); + ovulationEndDate[2] = AddDays(ovulationStartDate[2], ovulationCycle); + + //打印所有时间 + WEARABLE_LOGD(WEARABLE_LOG_MODULE_APP, "=== 本次月经 ==="); + WEARABLE_LOGD(WEARABLE_LOG_MODULE_APP, "经期开始: %d-%d-%d", menstrualStartDate[0].year, menstrualStartDate[0].month, menstrualStartDate[0].day); + WEARABLE_LOGD(WEARABLE_LOG_MODULE_APP, "经期结束: %d-%d-%d", menstrualEndDate[0].year, menstrualEndDate[0].month, menstrualEndDate[0].day); + WEARABLE_LOGD(WEARABLE_LOG_MODULE_APP, "排卵期开始: %d-%d-%d", ovulationStartDate[0].year, ovulationStartDate[0].month, ovulationStartDate[0].day); + WEARABLE_LOGD(WEARABLE_LOG_MODULE_APP, "排卵期结束: %d-%d-%d", ovulationEndDate[0].year, ovulationEndDate[0].month, ovulationEndDate[0].day); + WEARABLE_LOGD(WEARABLE_LOG_MODULE_APP, "=== 预测下次周期 ==="); + WEARABLE_LOGD(WEARABLE_LOG_MODULE_APP, "预测经期: %d-%d-%d 至 %d-%d-%d", menstrualStartDate[1].year, menstrualStartDate[1].month, menstrualStartDate[1].day, menstrualEndDate[1].year, menstrualEndDate[1].month, menstrualEndDate[1].day); + WEARABLE_LOGD(WEARABLE_LOG_MODULE_APP, "预测排卵期: %d-%d-%d 至 %d-%d-%d", ovulationStartDate[1].year, ovulationStartDate[1].month, ovulationStartDate[1].day, ovulationEndDate[1].year, ovulationEndDate[1].month, ovulationEndDate[1].day); + WEARABLE_LOGD(WEARABLE_LOG_MODULE_APP, "=== 预测下下次周期 ==="); + WEARABLE_LOGD(WEARABLE_LOG_MODULE_APP, "预测经期: %d-%d-%d 至 %d-%d-%d", menstrualStartDate[2].year, menstrualStartDate[2].month, menstrualStartDate[2].day, menstrualEndDate[2].year, menstrualEndDate[2].month, menstrualEndDate[2].day); + WEARABLE_LOGD(WEARABLE_LOG_MODULE_APP, "预测排卵期: %d-%d-%d 至 %d-%d-%d", ovulationStartDate[2].year, ovulationStartDate[2].month, ovulationStartDate[2].day, ovulationEndDate[2].year, ovulationEndDate[2].month, ovulationEndDate[2].day); + } + + int WomenHealthModel::TimeDiffInMinutes(const TimeObj &startDate, const TimeObj &endDate) + { + //计算差值天数 + int diffDays = (endDate.year - startDate.year) * 365 + (endDate.month - startDate.month) * 30 + (endDate.day - startDate.day); + return diffDays; + } + + /** + * @brief 用户设置经期结束日期 + * @param endDate 经期结束日期 + * + * 根据开始和结束日期重新计算周期长度 + */ + void WomenHealthModel::SetMenstrualEndDate(const TimeObj &endDate) + { + menstrualEndDate[0] = endDate; + //计算经期持续天数 + int diffDays = TimeDiffInMinutes(menstrualStartDate[0], menstrualEndDate[0]); + if (diffDays > 0) { + menstrualCycle = diffDays; + } + //重新计算排卵期和预测经期 + SetMenstrualStartDate(menstrualStartDate[0]); + } + + /** + * @brief 判断某天是否在月经期内(已记录的) + * @return true表示在月经期内 + */ + bool WomenHealthModel::IsInMenstrualPeriod(int year, int month, int day) + { + // 检查第一个月经期(已记录的) + if (menstrualStartDate[0].year == 0) { + return false; // 还没有设置数据 + } + + int cmpStart = CompareDate(year, month, day, + menstrualStartDate[0].year, menstrualStartDate[0].month, menstrualStartDate[0].day); + int cmpEnd = CompareDate(year, month, day, + menstrualEndDate[0].year, menstrualEndDate[0].month, menstrualEndDate[0].day); + + // 在开始和结束之间 + return (cmpStart >= 0 && cmpEnd <= 0); + } + + /** + * @brief 判断某天是否在排卵期内 + * @return true表示在排卵期内 + */ + bool WomenHealthModel::IsInOvulationPeriod(int year, int month, int day) + { + // 检查排卵期 + if (ovulationStartDate[0].year == 0) { + return false; // 还没有设置数据 + } + + int cmpStart = CompareDate(year, month, day, + ovulationStartDate[0].year, ovulationStartDate[0].month, ovulationStartDate[0].day); + int cmpEnd = CompareDate(year, month, day, + ovulationEndDate[0].year, ovulationEndDate[0].month, ovulationEndDate[0].day); + + // 在开始和结束之间 + return (cmpStart >= 0 && cmpEnd <= 0); + } + + /** + * @brief 判断某天是否在预测经期内 + * @return true表示在预测经期内 + */ + bool WomenHealthModel::IsInPredictPeriod(int year, int month, int day) + { + // 检查预测的月经期(索引1和2) + for (int i = 1; i < 3; i++) { + if (menstrualStartDate[i].year == 0) { + continue; + } + + int cmpStart = CompareDate(year, month, day, + menstrualStartDate[i].year, menstrualStartDate[i].month, menstrualStartDate[i].day); + int cmpEnd = CompareDate(year, month, day, + menstrualEndDate[i].year, menstrualEndDate[i].month, menstrualEndDate[i].day); + + // 在预测的开始和结束之间 + if (cmpStart >= 0 && cmpEnd <= 0) { + return true; + } + } + + return false; + } + + /** + * @brief 判断某天是否在预测排卵期内(下次和下下次的排卵期) + * @return true表示在预测排卵期内 + */ + bool WomenHealthModel::IsInPredictOvulationPeriod(int year, int month, int day) + { + // 检查预测的排卵期(索引1和2) + for (int i = 1; i < 3; i++) { + if (ovulationStartDate[i].year == 0) { + continue; + } + + int cmpStart = CompareDate(year, month, day, + ovulationStartDate[i].year, ovulationStartDate[i].month, ovulationStartDate[i].day); + int cmpEnd = CompareDate(year, month, day, + ovulationEndDate[i].year, ovulationEndDate[i].month, ovulationEndDate[i].day); + + // 在预测的排卵期开始和结束之间 + if (cmpStart >= 0 && cmpEnd <= 0) { + return true; + } + } + + return false; + } + + /** + * @brief 获取经期开始的年月 + * @param year 输出参数:年份 + * @param month 输出参数:月份 + */ + void WomenHealthModel::GetMenstrualStartYearMonth(int &year, int &month) + { + year = menstrualStartDate[0].year; + month = menstrualStartDate[0].month; + } + + /** + * @brief 获取预测的最后一个月(第3次预测经期的月份) + * @param year 输出参数:年份 + * @param month 输出参数:月份 + */ + void WomenHealthModel::GetLastPredictYearMonth(int &year, int &month) + { + // 第3次预测(索引2)的经期结束月份 + if (menstrualEndDate[2].year != 0) { + year = menstrualEndDate[2].year; + month = menstrualEndDate[2].month; + } else { + year = 0; + month = 0; + } + } + + /** + * @brief 检查是否已设置经期数据 + * @return true表示已设置,false表示未设置 + */ + bool WomenHealthModel::HasMenstrualData() + { + return (menstrualStartDate[0].year != 0); + } + + +} \ No newline at end of file diff --git a/code/sdk/application/wearable/fd272/nativeapp/nativeui/womenhealth/src/WomenHealthPresenter.cpp b/code/sdk/application/wearable/fd272/nativeapp/nativeui/womenhealth/src/WomenHealthPresenter.cpp index 314407917..4ffa656e8 100644 --- a/code/sdk/application/wearable/fd272/nativeapp/nativeui/womenhealth/src/WomenHealthPresenter.cpp +++ b/code/sdk/application/wearable/fd272/nativeapp/nativeui/womenhealth/src/WomenHealthPresenter.cpp @@ -17,7 +17,7 @@ { REGIST_MENU_PRO(VIEW_WOMEN_HEALTH, WomenHealthView, WomenHealthPresenter, - PNG_APPLIST_CALENDAR_IMAGE_BIG, PNG_APPLIST_CALENDAR_IMAGE, PNG_APPLIST_CALENDAR_IMAGE, "日历", STR_CALENDAR); + PNG_APPLIST_WOMANHEALTH_IMAGE_BIG, PNG_APPLIST_WOMANHEALTH_IMAGE, PNG_APPLIST_WOMANHEALTH_IMAGE, "女性健康", STR_WOMANHEALTH); static WomenHealthPresenter *g_pMainView = nullptr; WomenHealthPresenter::WomenHealthPresenter() @@ -50,13 +50,44 @@ RootView::GetInstance()->SetOnKeyActListener(this); } - bool WomenHealthPresenter::OnClick(UIView &view, const ClickEvent &event) - { - UNUSED(event); - WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "OnClick"); - - return false; - } + bool WomenHealthPresenter::OnClick(UIView &view, const ClickEvent &event) + { + UNUSED(event); + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "WomenHealthPresenter::OnClick"); + + if(strcmp(view.GetViewId(), NEXT_MONTH) == 0) + { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "点击右箭头:切换到下个月"); + WomenHealthView::GetInstance()->updata_label(2); + } + else if(strcmp(view.GetViewId(), PREV_MONTH) == 0) + { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "点击左箭头:切换到上个月"); + WomenHealthView::GetInstance()->updata_label(1); + } + else if(strcmp(view.GetViewId(), EDIT) == 0) + { + // 判断当前是否显示今天所在月份 + if (WomenHealthView::GetInstance()->IsShowingCurrentMonth()) + { + // 当前显示今天所在月份 -> 执行"编辑"功能 + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "点击编辑按钮:跳转到编辑页面"); + // TODO: 跳转到编辑页面,让用户设置经期开始日期 + // NativeAbility::GetInstance().Navigate(VIEW_WOMEN_HEALTH_EDIT); + } + else + { + // 当前显示其他月份 -> 执行"回今天"功能 + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "点击回今天按钮:返回今天所在月份"); + //需要先判断界面是否存在 + if(WomenHealthView::GetInstance() != nullptr) + { + WomenHealthView::GetInstance()->BackToToday(); + } + } + } + return true; + } bool WomenHealthPresenter::OnDragEnd(OHOS::UIView &view, const OHOS::DragEvent &event) { @@ -66,16 +97,16 @@ if (event.GetPreLastPoint().x - event.GetStartPoint().x > 100) NativeAbility::GetInstance().Back(); } - if (event.GetDragDirection() == DragEvent::DIRECTION_BOTTOM_TO_TOP) - { - // if(event.GetPreLastPoint().y-event.GetStartPoint().y>100) - WomenHealthView::GetInstance()->updata_label(2); - } - if (event.GetDragDirection() == DragEvent::DIRECTION_TOP_TO_BOTTOM) - { - // if(event.GetPreLastPoint().y-event.GetStartPoint().y>100) - WomenHealthView::GetInstance()->updata_label(1); - } + // if (event.GetDragDirection() == DragEvent::DIRECTION_BOTTOM_TO_TOP) + // { + // // if(event.GetPreLastPoint().y-event.GetStartPoint().y>100) + // WomenHealthView::GetInstance()->updata_label(2); + // } + // if (event.GetDragDirection() == DragEvent::DIRECTION_TOP_TO_BOTTOM) + // { + // // if(event.GetPreLastPoint().y-event.GetStartPoint().y>100) + // WomenHealthView::GetInstance()->updata_label(1); + // } return true; } diff --git a/code/sdk/application/wearable/fd272/nativeapp/nativeui/womenhealth/src/WomenHealthView.cpp b/code/sdk/application/wearable/fd272/nativeapp/nativeui/womenhealth/src/WomenHealthView.cpp index a1099833c..96c78d4ac 100644 --- a/code/sdk/application/wearable/fd272/nativeapp/nativeui/womenhealth/src/WomenHealthView.cpp +++ b/code/sdk/application/wearable/fd272/nativeapp/nativeui/womenhealth/src/WomenHealthView.cpp @@ -14,381 +14,895 @@ #include "NativeAbility.h" #include "ConfigModel.h" #include "languagecode/LanguageInit.h" - namespace OHOS - { - - static WomenHealthView *g_pMainView_WomenHealthView = nullptr; - - static char *dial_id_str[] = {"7", "8", "9", "C", "%", "4", "5", "6", "÷", "x", "1", "2", "3", "-", "+", "", "0", ".", "=", ""}; - - static constexpr int16_t PULLUP_ANIMATOR_VIEW_X = 213; - static constexpr int16_t PULLUP_ANIMATOR_VIEW_Y = 380; - static constexpr int16_t PULLUP_ANIMATOR_VIEW_W = 40; - static constexpr int16_t PULLUP_ANIMATOR_VIEW_H = 40; - static constexpr int16_t PULLUP_ANIMATOR_VIEW_NUM_FRAMES = 70; - static constexpr int16_t PULLUP_ANIMATOR_VIEW_TIME = 100; - - static constexpr const char *PullUpPaths = "/user/res/weather/PullUp/PullUp_"; - - static constexpr const char *stepPaths = "/user/res/step/"; - - WomenHealthView::WomenHealthView() - { - g_pMainView_WomenHealthView = this; - } - - WomenHealthView::~WomenHealthView() - { - g_pMainView_WomenHealthView = nullptr; - if (group) - { - group->RemoveAll(); - - if (mon_label) - { - delete mon_label; - mon_label = nullptr; - } - - if (imageview) - { - delete imageview; - imageview = nullptr; - } - - for (int i = 0; i < 7; i++) - { - if (week_label[i]) - { - delete week_label[i]; - week_label[i] = nullptr; - } - } - - for (int i = 0; i < 31; i++) - { - if (day_label[i]) - { - delete day_label[i]; - day_label[i] = nullptr; - } - } - - if (PullUpAnimatorView != nullptr) - { - PullUpAnimatorView->Stop(); - delete PullUpAnimatorView; - PullUpAnimatorView = nullptr; - } - - FreeImageInfoArray(PullUpimageAnimator_imageInfo, PullUpPaths, PULLUP_ANIMATOR_VIEW_NUM_FRAMES); - - delete group; - group = nullptr; - } - - g_pMainView_WomenHealthView = nullptr; - } - - WomenHealthView *WomenHealthView::GetInstance() - { - return g_pMainView_WomenHealthView; - } - static char *week_Z_str[] = {"一", "二", "三", "四", "五", "六", "日"}; - static char *week_E_str[] = {"MON", "THE", "WED", "THU", "FRI", "SAT", "SUN"}; - - bool WomenHealthView::updata_label(int8_t type) - { - for (int i = 0; i < 31; i++) - { - day_label[i]->SetVisible(false); - } - imageview->SetVisible(false); - - if (type == 1) - { - show_m--; - if (show_m == 0) - { - show_m = 12; - show_y--; - } - } - else - { - show_m++; - if (show_m == 13) - { - show_m = 1; - show_y++; - } - } - uint8_t mon_max_day = 0; - uint8_t is_run = 0; - if (show_y % 4 == 0 && show_y % 100 != 0 || show_y % 400 == 0) - { - is_run = 1; - } - - if (show_m == 2) - { - mon_max_day = 28 + is_run; - } - else if (show_m == 1 || show_m == 3 || show_m == 5 || show_m == 7 || show_m == 8 || show_m == 10 || show_m == 12) - { - mon_max_day = 31; - } - else - { - mon_max_day = 30; - } - - int y = show_y; - int m = show_m; - int week = 0; - { - if (m == 1 || m == 2) - { - m += 12; - y--; - } - week = (1 + 2 * m + 3 * (m + 1) / 5 + y + y / 4 - y / 100 + y / 400) % 7; - } - - char mon[20] = {0}; - sprintf(mon, "%d.%d", show_y, show_m); - mon_label->SetText(mon); - - for (int i = 0; i < mon_max_day; i++) - { - int x = ((i + week) % 7); - int y = ((i + week) / 7); - day_label[i]->SetPosition(58 + 50 * x, 140 + 44 * y); // widget's left position and top position - char day[5]; - sprintf(day, "%d", i + 1); - ; - day_label[i]->SetText(day); - time_t currtime; - time(&currtime); - struct tm *timeinfo; - timeinfo = localtime(&currtime); - int y_ = timeinfo->tm_year + 1900; - int m_ = timeinfo->tm_mon + 1; - int d_ = timeinfo->tm_mday; - if (i + 1 == d_ && show_y == y_ && show_m == m_) - { - imageview->SetPosition(58 + 50 * x, 140 + 44 * y); - imageview->SetVisible(true); - } - day_label[i]->SetVisible(true); - } - RootView::GetInstance()->Invalidate(); - return true; - } - bool WomenHealthView::Label_init() - { - // Add Label to App - uint8_t mon_max_day = 0; - - uint8_t is_run = 0; - time_t currtime; - time(&currtime); - struct tm *timeinfo; - timeinfo = localtime(&currtime); - int y = timeinfo->tm_year + 1900; - int m = timeinfo->tm_mon + 1; - int d = timeinfo->tm_mday; - int h = timeinfo->tm_hour; - int min = timeinfo->tm_min; - int week = timeinfo->tm_wday; - WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "%d,%d,%d,%d", y, m, d, week); - if (y % 4 == 0 && y % 100 != 0 || y % 400 == 0) // - { - is_run = 1; - } - - if (m == 2) - { - mon_max_day = 28 + is_run; - } - else if (m == 1 || - m == 3 || - m == 5 || - m == 7 || - m == 8 || - m == 10 || - m == 12) - { - mon_max_day = 31; - } - else - { - mon_max_day = 30; - } - - show_y = y; - show_m = m; - show_d = d; - - if (mon_label == nullptr) - { - mon_label = new UILabel(); - } - mon_label->SetPosition(83, 50); // widget's left position and top position - mon_label->Resize(300, 35); // widget's width and height - mon_label->SetViewId("label1"); // Set style - mon_label->SetStyle(STYLE_TEXT_FONT, 30); - - char mon[20] = {0}; - sprintf(mon, "%d.%d", y, m); - - if (m == 1 || m == 2) - { - m += 12; - y--; - } - week = (1 + 2 * m + 3 * (m + 1) / 5 + y + y / 4 - y / 100 + y / 400) % 7; - - mon_label->SetText(mon); - mon_label->SetLineBreakMode(UILabel::LINE_BREAK_WRAP); - mon_label->SetAlign(TEXT_ALIGNMENT_CENTER, TEXT_ALIGNMENT_CENTER); - mon_label->SetRollSpeed(0); - mon_label->SetFont(DEFAULT_VECTOR_FONT_FILENAME, 30); - - // Add to rootView - group->Add(mon_label); - - for (int i = 0; i < 7; i++) - { - if (week_label[i] == nullptr) - { - week_label[i] = new UILabel(); - } - week_label[i]->SetPosition(58 + 50 * (i), 110); // widget's left position and top position - week_label[i]->Resize(50, 30); // widget's width and height - week_label[i]->SetViewId("label3"); // Set style - week_label[i]->SetStyle(STYLE_TEXT_FONT, 20); - - if (ConfigModel::GetInstance()->GetLanguage() == ZH_CN) - week_label[i]->SetText(week_Z_str[i]); - else - week_label[i]->SetText(week_E_str[i]); - - week_label[i]->SetLineBreakMode(UILabel::LINE_BREAK_WRAP); - week_label[i]->SetTextColor(Color::GetColorFromRGBA(0x66, 0x66, 0x66, 0xff)); - week_label[i]->SetAlign(TEXT_ALIGNMENT_CENTER, TEXT_ALIGNMENT_CENTER); - week_label[i]->SetRollSpeed(0); - week_label[i]->SetFont(DEFAULT_VECTOR_FONT_FILENAME, 20); - - group->Add(week_label[i]); - week_label[i]->SetVisible(true); - } - // Add Label to App - if (imageview == nullptr) - { - imageview = new UIImageView(); - } - - imageview->SetPosition(58, 140); - imageview->Resize(50, 50); - imageview->SetViewId("image1"); - imageview->SetStyle(STYLE_BACKGROUND_COLOR, 0xffff5a36); - imageview->SetStyle(STYLE_BACKGROUND_OPA, 0xff); - imageview->SetStyle(STYLE_BORDER_RADIUS, 100); - group->Add(imageview); - imageview->SetVisible(false); - - for (int i = 0; i < 31; i++) - { - if (day_label[i] == nullptr) - { - day_label[i] = new UILabel(); - } - - day_label[i]->SetPosition(58, 140); // widget's left position and top position - day_label[i]->Resize(50, 44); // widget's width and height - day_label[i]->SetViewId("label3"); // Set style - day_label[i]->SetStyle(STYLE_TEXT_FONT, 20); - - day_label[i]->SetStyle(STYLE_BORDER_RADIUS, 100); - - day_label[i]->SetLineBreakMode(UILabel::LINE_BREAK_ELLIPSIS); - day_label[i]->SetAlign(TEXT_ALIGNMENT_CENTER, TEXT_ALIGNMENT_CENTER); - day_label[i]->SetRollSpeed(0); - day_label[i]->SetFont(DEFAULT_VECTOR_FONT_FILENAME, 30); - - group->Add(day_label[i]); - day_label[i]->SetVisible(false); - } - for (int i = 0; i < mon_max_day; i++) - { - int x = ((i + week) % 7); - int y = ((i + week) / 7); - day_label[i]->SetPosition(58 + 50 * x, 140 + 44 * y); // widget's left position and top position - char day[5]; - sprintf(day, "%d", i + 1); - ; - day_label[i]->SetText(day); - if (i + 1 == d) - { - imageview->SetPosition(58 + 50 * x, 140 + 44 * y); - imageview->SetVisible(true); - } - day_label[i]->SetVisible(true); - } - - PullUpimageAnimator_imageInfo = CreateImageInfoArray(PullUpPaths, PULLUP_ANIMATOR_VIEW_NUM_FRAMES); - if (PullUpAnimatorView == nullptr) - { - PullUpAnimatorView = new UIImageAnimatorView(); - if (PullUpAnimatorView == nullptr) - { - WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "PullUpAnimatorView new fail"); - return false; - } - for (uint8_t i = 0; i < PULLUP_ANIMATOR_VIEW_NUM_FRAMES; i++) - { - // PullUpimageAnimatorInfo[i].imageInfo = ImageCacheManager::GetInstance().LoadSingleRes(PullUpPathsArray[i]); - // PullUpimageAnimatorInfo[i].imageType = IMG_SRC_IMAGE_INFO; - PullUpimageAnimatorInfo[i].imageInfo = PullUpimageAnimator_imageInfo[i]; - // PullUpPathsArray[i]; - PullUpimageAnimatorInfo[i].imageType = IMG_SRC_IMAGE_INFO; - PullUpimageAnimatorInfo[i].pos.x = PULLUP_ANIMATOR_VIEW_X; - PullUpimageAnimatorInfo[i].pos.y = PULLUP_ANIMATOR_VIEW_Y; - PullUpimageAnimatorInfo[i].width = PULLUP_ANIMATOR_VIEW_W; - PullUpimageAnimatorInfo[i].height = PULLUP_ANIMATOR_VIEW_H; - } - PullUpAnimatorView->SetPosition(PULLUP_ANIMATOR_VIEW_X, PULLUP_ANIMATOR_VIEW_Y, PULLUP_ANIMATOR_VIEW_W, PULLUP_ANIMATOR_VIEW_H); - PullUpAnimatorView->SetStyle(STYLE_BACKGROUND_COLOR, 0xffffffff); - PullUpAnimatorView->SetStyle(STYLE_BACKGROUND_OPA, 0xff); - PullUpAnimatorView->SetImageAnimatorSrc(PullUpimageAnimatorInfo, PULLUP_ANIMATOR_VIEW_NUM_FRAMES, PULLUP_ANIMATOR_VIEW_TIME); - PullUpAnimatorView->SetRepeat(true); - PullUpAnimatorView->Start(); - } - group->Add(PullUpAnimatorView); - - return true; - } - - void WomenHealthView::OnStart() - { - group = new UIViewGroup(); - if (group == nullptr) - { - WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "CallingView::UIViewGroup::new group error"); - return; - } - group->SetPosition(0, 0); - group->Resize(VERTICAL_RESOLUTION, HORIZONTAL_RESOLUTION); // 454 : value - group->SetTouchable(true); - group->SetDraggable(true); - group->SetOnDragListener(presenter_); - AddViewToRootContainer(group); - - if (!Label_init()) - { - WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "CallingView::UIViewGroup::new group error"); - return; - } - } - } \ No newline at end of file +namespace OHOS +{ + // 全局静态指针,用于获取当前View实例 + static WomenHealthView *g_pMainView_WomenHealthView = nullptr; + + /** + * @brief 构造函数 - 创建女性健康界面对象 + * 作用:初始化全局实例指针,让其他地方可以通过GetInstance()获取这个对象 + */ + WomenHealthView::WomenHealthView() + { + g_pMainView_WomenHealthView = this; // 保存当前对象指针到全局变量 + } + + /** + * @brief 析构函数 - 销毁女性健康界面对象 + * 作用:清理所有分配的内存,防止内存泄漏 + * 包括:主容器、月份标签、星期标签、日期标签、今日标记、动画等 + */ + WomenHealthView::~WomenHealthView() + { + g_pMainView_WomenHealthView = nullptr; + if (group) // 如果主容器存在 + { + group->RemoveAll(); // 移除所有子控件 + + // 删除月份标签(如:"2024.10") + if (mon_label) + { + delete mon_label; + mon_label = nullptr; + } + + // 删除31个日期颜色标记圆圈 + for (int i = 0; i < 31; i++) + { + if (dayMarker[i]) + { + delete dayMarker[i]; + dayMarker[i] = nullptr; + } + } + + // 删除7个星期标签(一、二、三、四、五、六、日) + for (int i = 0; i < 7; i++) + { + if (week_label[i]) + { + delete week_label[i]; + week_label[i] = nullptr; + } + } + + // 删除31个日期标签(1-31号) + for (int i = 0; i < 31; i++) + { + if (day_label[i]) + { + delete day_label[i]; + day_label[i] = nullptr; + } + } + + // 删除顶部标题"生理周期" + if (title_label) + { + delete title_label; + title_label = nullptr; + } + + // 删除日期标签"X月X日 星期X" + if (mon_label) + { + delete mon_label; + mon_label = nullptr; + } + + // 删除左右箭头按钮 + if (left_arrow) + { + delete left_arrow; + left_arrow = nullptr; + } + if (right_arrow) + { + delete right_arrow; + right_arrow = nullptr; + } + + // 删除底部"编辑"按钮 + if (edit_button) + { + delete edit_button; + edit_button = nullptr; + } + + // 删除主容器 + delete group; + group = nullptr; + } + + g_pMainView_WomenHealthView = nullptr; + } + + /** + * @brief 获取女性健康界面的单例对象 + * @return 返回当前女性健康界面的指针 + * 作用:其他地方可以通过这个函数获取界面对象,进行界面更新等操作 + */ + WomenHealthView *WomenHealthView::GetInstance() + { + return g_pMainView_WomenHealthView; + } + + /** + * @brief 判断当前是否显示今天所在月份 + * @return true=当前显示今天所在月份, false=显示其他月份 + * + * 作用:供Presenter调用,判断应该执行"编辑"还是"回今天"功能 + */ + bool WomenHealthView::IsShowingCurrentMonth() + { + return (show_y == today_y && show_m == today_m); + } + + // 中文星期标签数组 + static char *week_Z_str[] = {"一", "二", "三", "四", "五", "六", "日"}; + // 英文星期标签数组 + static char *week_E_str[] = {"MON", "THE", "WED", "THU", "FRI", "SAT", "SUN"}; + + /** + * @brief 计算某月有多少天 + * @param year 年份 + * @param month 月份(1-12) + * @return 该月的天数 + */ + int WomenHealthView::CalculateMonthDays(int year, int month) + { + // 判断是否闰年 + bool isLeapYear = (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0); + + if (month == 2) { + return isLeapYear ? 29 : 28; // 2月 + } else if (month == 1 || month == 3 || month == 5 || month == 7 || + month == 8 || month == 10 || month == 12) { + return 31; // 大月 + } else { + return 30; // 小月 + } + } + + /** + * @brief 使用系统函数计算星期几(更准确) + * @param year 年份 + * @param month 月份(1-12) + * @param day 日期 + * @return 星期几(0=星期日, 1=星期一, ..., 6=星期六) + * + * 使用系统的 mktime 和 localtime 函数来计算星期,避免手动实现蔡勒公式可能的错误 + */ + int WomenHealthView::CalculateWeekday(int year, int month, int day) + { + struct tm timeStruct = {0}; + timeStruct.tm_year = year - 1900; // tm_year是从1900年开始 + timeStruct.tm_mon = month - 1; // tm_mon是0-11 + timeStruct.tm_mday = day; + + // 使用mktime来规范化时间结构,并自动计算星期 + time_t t = mktime(&timeStruct); + if (t == -1) { + // 如果转换失败,使用蔡勒公式作为备用 + int y = year; + int m = month; + if (m == 1 || m == 2) { + m += 12; + y--; + } + int w = (day + 2 * m + 3 * (m + 1) / 5 + y + y / 4 - y / 100 + y / 400 + 1) % 7; + return (w + 6) % 7; + } + + // 获取规范化后的时间结构 + struct tm *result = localtime(&t); + + // tm_wday: 0=星期日, 1=星期一, ..., 6=星期六(正好符合我们的需求) + return result->tm_wday; + } + + /** + * @brief 初始化31个颜色标记圆圈 + * + * 作用:创建用于标记月经期、排卵期等状态的圆圈 + * 注意:必须先添加到容器,这样才会显示在日期标签下方 + */ + void WomenHealthView::InitDayMarkers() + { + for (int i = 0; i < 31; i++) { + if (dayMarker[i] == nullptr) { + dayMarker[i] = new UIImageView(); + if (dayMarker[i] != nullptr) { + dayMarker[i]->Resize(46, 46); + dayMarker[i]->SetStyle(STYLE_BACKGROUND_OPA, 0xff); + dayMarker[i]->SetStyle(STYLE_BORDER_RADIUS, 23); + group->Add(dayMarker[i]); + dayMarker[i]->SetVisible(false); // 默认隐藏 + } + } + } + } + + /** + * @brief 初始化顶部标题"生理周期" + */ + void WomenHealthView::InitTitleLabel() + { + if (title_label == nullptr) { + title_label = new UILabel(); + } + + title_label->SetPosition(0, 20); // 顶部中央 + title_label->Resize(454, 40); // 全屏宽度 + title_label->SetText("生理周期"); + title_label->SetStyle(STYLE_TEXT_FONT, 28); + title_label->SetAlign(TEXT_ALIGNMENT_CENTER, TEXT_ALIGNMENT_CENTER); + title_label->SetLineBreakMode(UILabel::LINE_BREAK_WRAP); + title_label->SetRollSpeed(0); + title_label->SetFont(DEFAULT_VECTOR_FONT_FILENAME, 28); + title_label->SetTextColor(Color::White()); + + group->Add(title_label); + } + + /** + * @brief 初始化日期标签(显示"X月X日 星期X") + * @param year 年份 + * @param month 月份 + * @param day 日期(当前日期) + * @param weekday 星期几 + */ + void WomenHealthView::InitMonthLabel(int year, int month, int day, int weekday) + { + if (mon_label == nullptr) { + mon_label = new UILabel(); + } + + mon_label->SetPosition(100, 75); // 标题下方,中央位置(左右留出箭头空间) + mon_label->Resize(254, 35); // 宽度留给左右箭头 + mon_label->SetViewId("label1"); + mon_label->SetStyle(STYLE_TEXT_FONT, 24); + + // 格式化日期文本:"9月5日 星期五" + static const char* weekday_str[] = {"星期日", "星期一", "星期二", "星期三", "星期四", "星期五", "星期六"}; + char date_text[50] = {0}; + int wd = CalculateWeekday(year, month, day); // 计算星期几 + sprintf(date_text, "%d月%d日 %s", month, day, weekday_str[wd]); + mon_label->SetText(date_text); + + mon_label->SetLineBreakMode(UILabel::LINE_BREAK_WRAP); + mon_label->SetAlign(TEXT_ALIGNMENT_CENTER, TEXT_ALIGNMENT_CENTER); + mon_label->SetRollSpeed(0); + mon_label->SetFont(DEFAULT_VECTOR_FONT_FILENAME, 24); + mon_label->SetTextColor(Color::White()); + + group->Add(mon_label); + } + + /** + * @brief 初始化左右箭头按钮 + */ + void WomenHealthView::InitArrowButtons() + { + // 左箭头按钮 + if (left_arrow == nullptr) { + left_arrow = new UILabelButton(); + } + left_arrow->SetPosition(40, 50); // 日期左侧 + left_arrow->Resize(80, 80); + left_arrow->SetText("<"); + left_arrow->SetStyle(STYLE_TEXT_FONT, 30); + left_arrow->SetStyle(STYLE_LINE_HEIGHT, 35); + left_arrow->SetFont(DEFAULT_VECTOR_FONT_FILENAME, 30); + + left_arrow->SetViewId(PREV_MONTH); + // 设置按钮背景样式 + left_arrow->SetStyle(STYLE_BACKGROUND_COLOR, OPA_OPAQUE); + left_arrow->SetStyle(STYLE_BACKGROUND_OPA, OPA_OPAQUE); + left_arrow->SetStyleForState(STYLE_BACKGROUND_COLOR, OPA_OPAQUE, UIButton::PRESSED); + left_arrow->SetStyleForState(STYLE_BACKGROUND_OPA, OPA_OPAQUE, UIButton::PRESSED); + left_arrow->SetStyle(STYLE_BORDER_RADIUS, 100); + UIView::OnClickListener *clickListener = (UIView::OnClickListener *)OHOS::WomenHealthPresenter::GetInstance(); + left_arrow->SetOnClickListener(clickListener); + + group->Add(left_arrow); + + // 右箭头按钮 + if (right_arrow == nullptr) { + right_arrow = new UILabelButton(); + } + right_arrow->SetPosition(320, 50); // 日期右侧 + right_arrow->Resize(80, 80); + right_arrow->SetText(">"); + right_arrow->SetStyle(STYLE_TEXT_FONT, 30); + right_arrow->SetStyle(STYLE_LINE_HEIGHT, 35); + right_arrow->SetFont(DEFAULT_VECTOR_FONT_FILENAME, 30); + + right_arrow->SetViewId(NEXT_MONTH); + // 设置按钮背景样式 + right_arrow->SetStyle(STYLE_BACKGROUND_COLOR, OPA_OPAQUE); + right_arrow->SetStyle(STYLE_BACKGROUND_OPA, OPA_OPAQUE); + right_arrow->SetStyleForState(STYLE_BACKGROUND_COLOR, OPA_OPAQUE, UIButton::PRESSED); + right_arrow->SetStyleForState(STYLE_BACKGROUND_OPA, OPA_OPAQUE, UIButton::PRESSED); + right_arrow->SetStyle(STYLE_BORDER_RADIUS, 100); + UIView::OnClickListener *clickListener2 = (UIView::OnClickListener *)OHOS::WomenHealthPresenter::GetInstance(); + right_arrow->SetOnClickListener(clickListener2); + + group->Add(right_arrow); + } + + /** + * @brief 初始化底部"编辑"按钮 + */ + void WomenHealthView::InitEditButton() + { + if (edit_button == nullptr) { + edit_button = new UILabelButton(); + } + + edit_button->SetPosition(118, 350); // 底部中央 + edit_button->Resize(230, 80); + edit_button->SetText("编辑"); + edit_button->SetStyle(STYLE_TEXT_FONT, 30); + edit_button->SetStyle(STYLE_LINE_HEIGHT, 35); + edit_button->SetFont(DEFAULT_VECTOR_FONT_FILENAME, 30); + edit_button->SetViewId(EDIT); + // 设置按钮背景样式 + edit_button->SetStyle(STYLE_BACKGROUND_COLOR, OPA_OPAQUE); + edit_button->SetStyle(STYLE_BACKGROUND_OPA, OPA_OPAQUE); + edit_button->SetStyleForState(STYLE_BACKGROUND_COLOR, OPA_OPAQUE, UIButton::PRESSED); + edit_button->SetStyleForState(STYLE_BACKGROUND_OPA, OPA_OPAQUE, UIButton::PRESSED); + edit_button->SetStyle(STYLE_BORDER_RADIUS, 100); + UIView::OnClickListener *clickListener = (UIView::OnClickListener *)OHOS::WomenHealthPresenter::GetInstance(); + edit_button->SetOnClickListener(clickListener); + + group->Add(edit_button); + } + + /** + * @brief 初始化7个星期标签(一、二、三、四、五、六、日) + * + * 作用:显示日历的星期头部 + * 注意:根据设计图要求,暂时不显示星期行 + */ + void WomenHealthView::InitWeekLabels() + { + for (int i = 0; i < 7; i++) { + if (week_label[i] == nullptr) { + week_label[i] = new UILabel(); + } + + // 设置位置:横向排列,起始X=58,每个间隔50像素 + week_label[i]->SetPosition(58 + 50 * i, 110); + week_label[i]->Resize(50, 30); + week_label[i]->SetViewId("label3"); + week_label[i]->SetStyle(STYLE_TEXT_FONT, 20); + + // 根据系统语言设置中文或英文 + if (ConfigModel::GetInstance()->GetLanguage() == ZH_CN) { + week_label[i]->SetText(week_Z_str[i]); // 中文:"一"、"二"等 + } else { + week_label[i]->SetText(week_E_str[i]); // 英文:"MON"、"TUE"等 + } + + week_label[i]->SetLineBreakMode(UILabel::LINE_BREAK_WRAP); + week_label[i]->SetTextColor(Color::GetColorFromRGBA(0x66, 0x66, 0x66, 0xff)); // 灰色 + week_label[i]->SetAlign(TEXT_ALIGNMENT_CENTER, TEXT_ALIGNMENT_CENTER); + week_label[i]->SetRollSpeed(0); + week_label[i]->SetFont(DEFAULT_VECTOR_FONT_FILENAME, 20); + + group->Add(week_label[i]); + week_label[i]->SetVisible(false); // 暂时隐藏星期行(按设计图要求) + } + } + + /** + * @brief 初始化31个日期标签UI对象(只创建,不设置内容) + * + * 作用:预先创建所有可能用到的日期标签对象 + * 注意:因为去掉了星期行,日期起始Y坐标调整为120(原来是140) + */ + void WomenHealthView::InitDayLabels() + { + for (int i = 0; i < 31; i++) { + if (day_label[i] == nullptr) { + day_label[i] = new UILabel(); + } + + day_label[i]->SetPosition(58, 120); // 初始位置(往上移了20像素) + day_label[i]->Resize(50, 44); + day_label[i]->SetViewId("label3"); + day_label[i]->SetStyle(STYLE_TEXT_FONT, 20); + day_label[i]->SetStyle(STYLE_BORDER_RADIUS, 100); + + day_label[i]->SetLineBreakMode(UILabel::LINE_BREAK_ELLIPSIS); + day_label[i]->SetAlign(TEXT_ALIGNMENT_CENTER, TEXT_ALIGNMENT_CENTER); + day_label[i]->SetRollSpeed(0); + day_label[i]->SetFont(DEFAULT_VECTOR_FONT_FILENAME, 30); + + group->Add(day_label[i]); + day_label[i]->SetVisible(false); // 默认隐藏 + } + } + + /** + * @brief 更新底部按钮文本 + * + * 作用:根据当前显示的月份,动态修改按钮文本 + * 规则: + * - 如果当前显示的是今天所在月份 → 显示"编辑" + * - 如果当前显示的不是今天所在月份 → 显示"回今天" + */ + void WomenHealthView::UpdateEditButtonText() + { + if (edit_button == nullptr) { + return; + } + + // 判断当前显示的是否是今天所在月份 + if (show_y == today_y && show_m == today_m) { + edit_button->SetText("编辑"); + } else { + edit_button->SetText("回今天"); + } + } + + /** + * @brief 回到今天所在的月份 + * + * 作用:点击"回今天"按钮时调用,将界面切换回今天所在的月份 + */ + void WomenHealthView::BackToToday() + { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "回到今天所在月份: %då¹´%d月", today_y, today_m); + + // 第一步:隐藏所有日期标签和颜色标记 + for (int i = 0; i < 31; i++) { + day_label[i]->SetVisible(false); + if (dayMarker[i]) { + dayMarker[i]->SetVisible(false); + } + } + + // 第二步:恢复到今天的年月 + show_y = today_y; + show_m = today_m; + show_d = today_d; + + // 第三步:计算天数和星期 + int mon_max_day = CalculateMonthDays(show_y, show_m); + int weekday = CalculateWeekday(show_y, show_m, 1); + + // 第四步:更新日期标签 + static const char* weekday_str[] = {"星期日", "星期一", "星期二", "星期三", "星期四", "星期五", "星期六"}; + char date_text[50] = {0}; + int wd = CalculateWeekday(show_y, show_m, show_d); + sprintf(date_text, "%d月%d日 %s", show_m, show_d, weekday_str[wd]); + mon_label->SetText(date_text); + + // 第五步:更新日期显示 + UpdateDayDisplay(show_y, show_m, mon_max_day, weekday); + + // 第六步:更新箭头和按钮状态 + UpdateArrowButtonsVisibility(); + UpdateEditButtonText(); + + // 第七步:刷新界面 + RootView::GetInstance()->Invalidate(); + } + + /** + * @brief 更新左右箭头按钮的显示状态 + * + * 作用:根据当前显示的月份,判断是否显示左右箭头按钮 + * 规则: + * 1. 如果当前月 = 经期开始月,隐藏左箭头(不能查看更早的月份) + * 2. 如果当前月 = 预测的最后一个月,隐藏右箭头(不能查看更晚的月份) + */ + void WomenHealthView::UpdateArrowButtonsVisibility() + { + WomenHealthModel* model = WomenHealthModel::GetInstance(); + + // 如果还没有设置经期数据,显示所有箭头 + if (!model->HasMenstrualData()) { + if (left_arrow) left_arrow->SetVisible(true); + if (right_arrow) right_arrow->SetVisible(true); + return; + } + + // 获取经期开始的年月 + int startYear = 0, startMonth = 0; + model->GetMenstrualStartYearMonth(startYear, startMonth); + + // 获取预测的最后一个月 + int lastYear = 0, lastMonth = 0; + model->GetLastPredictYearMonth(lastYear, lastMonth); + + // 判断是否显示左箭头:当前月 == 经期开始月 时隐藏 + if (left_arrow) { + if (show_y == startYear && show_m == startMonth) { + left_arrow->SetVisible(false); // 已经是最早的月份,隐藏左箭头 + } else { + left_arrow->SetVisible(true); + } + } + + // 判断是否显示右箭头:当前月 == 预测最后一个月 时隐藏 + if (right_arrow) { + if (lastYear != 0 && show_y == lastYear && show_m == lastMonth) { + right_arrow->SetVisible(false); // 已经是最后的月份,隐藏右箭头 + } else { + right_arrow->SetVisible(true); + } + } + } + + /** + * @brief 更新日期显示(显示当月所有日期并绘制状态标记) + * @param year 年份 + * @param month 月份 + * @param maxDays 该月最大天数 + * @param weekday 1号是星期几 + */ + void WomenHealthView::UpdateDayDisplay(int year, int month, int maxDays, int weekday) + { + // 显示当月的所有日期 + for (int i = 0; i < maxDays; i++) { + // 计算该日期在日历表格中的位置 + int x = (i + weekday) % 7; // 列位置(0-6) + int y = (i + weekday) / 7; // 行位置(0-5) + + // 计算屏幕坐标(Y坐标从120开始,因为去掉了星期行) + int posX = 58 + 50 * x; + int posY = 120 + 44 * y; + + // 设置日期标签位置和文本 + day_label[i]->SetPosition(posX, posY); + char day[5]; + sprintf(day, "%d", i + 1); + day_label[i]->SetText(day); + + // 判断该日期的状态并绘制颜色标记 + DayStatus status = GetDayStatus(year, month, i + 1); + DrawDayMarker(i, posX, posY, status); + + day_label[i]->SetVisible(true); + } + + // 隐藏多余的日期标签(如果从大月切换到小月) + for (int i = maxDays; i < 31; i++) { + day_label[i]->SetVisible(false); + dayMarker[i]->SetVisible(false); + } + } + + /** + * @brief 判断某一天的状态 + * @param year 年份 + * @param month 月份 + * @param day 日期 + * @return DayStatus 返回该日期的状态 + * + * 作用:根据女性健康数据模型,判断某天是月经期、排卵期、预测经期还是普通日 + * + * 判断优先级(从高到低): + * 1. 今天(灰色) + * 2. 已记录月经期(红色) + * 3. 当前排卵期(蓝色) + * 4. 预测排卵期(蓝色) ← 新增 + * 5. 预测月经期(粉色) + * 6. 普通日期(白色) + */ + DayStatus WomenHealthView::GetDayStatus(uint32_t year, uint32_t month, uint32_t day) + { + // 获取当前系统时间 + time_t now; + time(&now); + struct tm *nowInfo = localtime(&now); + uint32_t todayYear = nowInfo->tm_year + 1900; + uint32_t todayMonth = nowInfo->tm_mon + 1; + uint32_t todayDay = nowInfo->tm_mday; + + // 如果是今天,返回今天状态(灰色圆圈) + if (year == todayYear && month == todayMonth && day == todayDay) { + return DAY_STATUS_TODAY; + } + + // 从WomenHealthModel获取数据,判断该天所处的时期 + WomenHealthModel* model = WomenHealthModel::GetInstance(); + + // 判断优先级: + // 1. 优先判断是否在已记录的月经期内(最高优先级) + if (model->IsInMenstrualPeriod(year, month, day)) { + return DAY_STATUS_MENSTRUAL; // 月经期(红色标记) + } + + // 2. 判断是否在排卵期内(包括当前排卵期) + if (model->IsInOvulationPeriod(year, month, day)) { + return DAY_STATUS_OVULATION; // 排卵期(蓝色标记) + } + + // 3. 判断是否在预测排卵期内(下次和下下次的排卵期,也是蓝色) + if (model->IsInPredictOvulationPeriod(year, month, day)) { + return DAY_STATUS_OVULATION; // 预测排卵期(蓝色标记) + } + + // 4. 判断是否在预测经期内 + if (model->IsInPredictPeriod(year, month, day)) { + return DAY_STATUS_PREDICT; // 预测经期(粉色标记) + } + + // 5. 普通日期 + return DAY_STATUS_NORMAL; // 普通日期(白色) + } + + /** + * @brief 为指定日期绘制颜色标记圆圈 + * @param dayIndex 日期索引(0-30,对应1-31号) + * @param x 圆圈的X坐标 + * @param y 圆圈的Y坐标 + * @param status 日期状态(决定圆圈颜色) + * + * 作用:根据日期状态绘制不同颜色的圆圈背景 + * - 月经期:红色 (0xFFFF5A5A) + * - 排卵期:蓝色 (0xFF5AB0FF) + * - 预测经期:浅红色 (0xFFFFB5B5) + * - 今天:灰色 (0xFF808080) + * - 普通日:不显示圆圈 + */ + void WomenHealthView::DrawDayMarker(int dayIndex, int x, int y, DayStatus status) + { + // 如果是普通日期,隐藏标记 + if (status == DAY_STATUS_NORMAL) { + if (dayMarker[dayIndex]) { + dayMarker[dayIndex]->SetVisible(false); + } + return; + } + + // 如果标记对象不存在,创建它 + if (dayMarker[dayIndex] == nullptr) { + dayMarker[dayIndex] = new UIImageView(); + if (dayMarker[dayIndex] == nullptr) { + return; + } + dayMarker[dayIndex]->Resize(46,46); // 圆圈大小46*46 + dayMarker[dayIndex]->SetStyle(STYLE_BACKGROUND_OPA, 0xff); // 不透明 + dayMarker[dayIndex]->SetStyle(STYLE_BORDER_RADIUS, 23); // 圆角23,形成圆形 + group->Add(dayMarker[dayIndex]); + } + + // 根据状态设置颜色 + uint32_t color = 0xFFFFFFFF; // 默认白色 + switch (status) { + case DAY_STATUS_MENSTRUAL: // 月经期:红色 + color = 0xFFFF5A5A; + break; + case DAY_STATUS_OVULATION: // 排卵期:蓝色 + color = 0xFF5AB0FF; + break; + case DAY_STATUS_PREDICT: // 预测经期:浅红色 + color = 0xFFFFB5B5; + break; + case DAY_STATUS_TODAY: // 今天:灰色 + color = 0xFF808080; + break; + default: + break; + } + + // 设置位置和颜色 + dayMarker[dayIndex]->SetPosition(x, y); + dayMarker[dayIndex]->SetStyle(STYLE_BACKGROUND_COLOR, color); + dayMarker[dayIndex]->SetVisible(true); + } + + /** + * @brief 更新日历标签显示(优化后的版本) + * @param type 切换类型:1=切换到上个月, 2=切换到下个月, 其他=刷新当前月 + * @return 成功返回true,失败返回false + * + * 作用:当用户上下滑动或点击箭头时,切换月份并重新显示日历 + * + * 工作流程: + * 1. 隐藏所有日期标签和标记 + * 2. 获取边界月份(经期开始月和预测最后月) + * 3. 检查是否可以切换月份(防止超出边界) + * 4. 根据type参数计算新的年月 + * 5. 更新日期标签"X月X日 星期X" + * 6. 调用UpdateDayDisplay更新显示 + * 7. 更新箭头按钮显示状态 + * 8. 刷新界面 + */ + bool WomenHealthView::updata_label(int8_t type) + { + // 第一步:隐藏所有日期标签和颜色标记 + for (int i = 0; i < 31; i++) { + day_label[i]->SetVisible(false); + if (dayMarker[i]) { + dayMarker[i]->SetVisible(false); + } + } + + // 第二步:获取Model数据,判断边界 + WomenHealthModel* model = WomenHealthModel::GetInstance(); + + // 如果还没有设置经期数据,允许自由切换月份 + if (model->HasMenstrualData()) { + // 获取经期开始的年月 + int startYear = 0, startMonth = 0; + model->GetMenstrualStartYearMonth(startYear, startMonth); + + // 获取预测的最后一个月 + int lastYear = 0, lastMonth = 0; + model->GetLastPredictYearMonth(lastYear, lastMonth); + + // 第三步:检查边界,防止超出范围 + if (type == 1) { // 想要查看上个月 + // 如果当前已经是经期开始的月份,不允许再往前 + if (show_y == startYear && show_m == startMonth) { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "已经是最早的月份,无法查看上个月"); + return false; + } + } else if (type == 2) { // 想要查看下个月 + // 如果当前已经是预测的最后一个月,不允许再往后 + if (lastYear != 0 && show_y == lastYear && show_m == lastMonth) { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "已经是最后的月份,无法查看下个月"); + return false; + } + } + } + + // 第四步:根据滑动方向更新年月 + if (type == 1) { // 向上滑动或点击左箭头,显示上个月 + show_m--; + if (show_m == 0) { // 跨年到去年12月 + show_m = 12; + show_y--; + } + } else { // 向下滑动或点击右箭头,显示下个月 + show_m++; + if (show_m == 13) { // 跨年到明年1月 + show_m = 1; + show_y++; + } + } + + // 第五步:使用辅助函数计算该月天数和1号星期几 + int mon_max_day = CalculateMonthDays(show_y, show_m); + int weekday = CalculateWeekday(show_y, show_m, 1); + + // 第六步:更新日期标签("X月X日 星期X"格式) + static const char* weekday_str[] = {"星期日", "星期一", "星期二", "星期三", "星期四", "星期五", "星期六"}; + char date_text[50] = {0}; + int wd = CalculateWeekday(show_y, show_m, show_d); // 使用当前保存的日期 + sprintf(date_text, "%d月%d日 %s", show_m, show_d, weekday_str[wd]); + mon_label->SetText(date_text); + + // 第七步:调用UpdateDayDisplay更新日期显示 + UpdateDayDisplay(show_y, show_m, mon_max_day, weekday); + + // 第八步:更新箭头按钮的显示状态 + UpdateArrowButtonsVisibility(); + + // 第九步:更新底部按钮文本(编辑/回今天) + UpdateEditButtonText(); + + // 第十步:刷新屏幕显示 + RootView::GetInstance()->Invalidate(); + return true; + } + /** + * @brief 初始化所有标签和UI组件 + * @return 成功返回true,失败返回false + * + * 作用:第一次进入界面时,创建所有需要的UI组件 + * + * 工作流程: + * 1. 获取当前系统时间 + * 2. 计算当月天数和1号星期几 + * 3. 创建并设置月份标签 + * 4. 创建并设置7个星期标签 + * 5. 创建今日标记圆圈 + * 6. 创建31个日期标签 + * 7. 显示当月所有日期 + * 8. 创建上拉动画 + */ + /** + * @brief 初始化所有UI组件(重构后的简洁版本) + * + * 作用:调用各个独立的初始化函数,降低耦合度 + * + * 工作流程: + * 1. 获取当前系统时间 + * 2. 计算当月天数和1号星期几 + * 3. 依次调用各个初始化函数 + * 4. 更新日期显示 + */ + bool WomenHealthView::Label_init() + { + // 第一步:获取当前系统时间 + time_t currtime; + time(&currtime); + struct tm *timeinfo = localtime(&currtime); + + int year = timeinfo->tm_year + 1900; // 年份 + int month = timeinfo->tm_mon + 1; // 月份 + int day = timeinfo->tm_mday; // 日期 + + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "女性健康界面初始化: %då¹´%d月%d日", year, month, day); + + // 第二步:计算当月天数和1号星期几 + int mon_max_day = CalculateMonthDays(year, month); + int weekday = CalculateWeekday(year, month, 1); // 计算1号是星期几 + + // 第三步:保存今天的日期(用于"回今天"功能) + today_y = year; + today_m = month; + today_d = day; + + // 第四步:保存当前显示的年月日(用于后续切换月份) + show_y = year; + show_m = month; + show_d = day; + + // 第五步:依次调用各个独立的初始化函数(降低耦合度) + InitDayMarkers(); // 初始化颜色标记圆圈 + InitTitleLabel(); // 初始化顶部标题"生理周期" + InitMonthLabel(year, month, day, weekday); // 初始化日期标签"X月X日 星期X" + InitArrowButtons(); // 初始化左右箭头按钮 + InitWeekLabels(); // 初始化星期标签(已隐藏) + InitDayLabels(); // 初始化日期标签 + UpdateDayDisplay(year, month, mon_max_day, weekday); // 更新日期显示 + InitEditButton(); // 初始化底部"编辑"按钮 + UpdateArrowButtonsVisibility(); // 更新箭头按钮的显示状态 + UpdateEditButtonText(); // 更新底部按钮文本(编辑/回今天) + + return true; + } + + /** + * @brief 界面启动函数 - 进入女性健康界面时调用 + * + * 作用:创建主容器并初始化所有UI组件 + * + * 工作流程: + * 1. 创建主容器(UIViewGroup) + * 2. 设置容器大小为全屏 + * 3. 启用触摸和拖动功能(支持上下滑动切换月份) + * 4. 设置拖动监听器(由Presenter处理) + * 5. 调用Label_init()初始化所有标签 + */ + void WomenHealthView::OnStart() + { + // 创建主容器 + group = new UIViewGroup(); + if (group == nullptr) + { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "WomenHealthView::创建主容器失败"); + return; + } + + // 设置容器属性 + group->SetPosition(0, 0); // 位置:屏幕左上角 + group->Resize(VERTICAL_RESOLUTION, HORIZONTAL_RESOLUTION); // 大小:全屏 + group->SetTouchable(true); // 允许触摸 + group->SetDraggable(true); // 允许拖动 + group->SetOnDragListener(presenter_); // 设置拖动监听器,由Presenter处理滑动事件 + + // 将容器添加到根容器 + AddViewToRootContainer(group); + + //测试一下效果 设置今天为月经开始日期 + TimeObj startDate = {2025, 10, 23}; + WomenHealthModel::GetInstance()->SetMenstrualStartDate(startDate); + + // 初始化所有标签和UI组件 + if (!Label_init()) + { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "WomenHealthView::初始化标签失败"); + return; + } + } +} \ No newline at end of file