增加血氧与ECG
authorxue <2144439972@qq.com>
Mon, 8 Jun 2026 06:22:25 +0000 (14:22 +0800)
committerxue <2144439972@qq.com>
Mon, 8 Jun 2026 06:22:25 +0000 (14:22 +0800)
72 files changed:
code/sdk/application/brandy/brandy_standard/service_init.c
code/sdk/application/wearable/n9/nativeapp/nativelauncher/include/AppViewIDs.h
code/sdk/application/wearable/n9/nativeapp/nativeui/bloodpressure/include/BPAbnormalView.h [new file with mode: 0644]
code/sdk/application/wearable/n9/nativeapp/nativeui/bloodpressure/include/BPAtCmd.h [new file with mode: 0644]
code/sdk/application/wearable/n9/nativeapp/nativeui/bloodpressure/include/BPCalibFirstDoneView.h [new file with mode: 0644]
code/sdk/application/wearable/n9/nativeapp/nativeui/bloodpressure/include/BPCalibSecondDoneView.h [new file with mode: 0644]
code/sdk/application/wearable/n9/nativeapp/nativeui/bloodpressure/include/BPCalibStopView.h [new file with mode: 0644]
code/sdk/application/wearable/n9/nativeapp/nativeui/bloodpressure/include/BPCalibratingView.h [new file with mode: 0644]
code/sdk/application/wearable/n9/nativeapp/nativeui/bloodpressure/include/BPEntryView.h [new file with mode: 0644]
code/sdk/application/wearable/n9/nativeapp/nativeui/bloodpressure/include/BPHistoryView.h [new file with mode: 0644]
code/sdk/application/wearable/n9/nativeapp/nativeui/bloodpressure/include/BPInstructionView.h [new file with mode: 0644]
code/sdk/application/wearable/n9/nativeapp/nativeui/bloodpressure/include/BPMainPresenter.h [new file with mode: 0644]
code/sdk/application/wearable/n9/nativeapp/nativeui/bloodpressure/include/BPMainView.h [new file with mode: 0644]
code/sdk/application/wearable/n9/nativeapp/nativeui/bloodpressure/include/BPMeasureResultView.h [new file with mode: 0644]
code/sdk/application/wearable/n9/nativeapp/nativeui/bloodpressure/include/BPMeasuringView.h [new file with mode: 0644]
code/sdk/application/wearable/n9/nativeapp/nativeui/bloodpressure/include/BPModel.h [new file with mode: 0644]
code/sdk/application/wearable/n9/nativeapp/nativeui/bloodpressure/include/BPValueConfirmView.h [new file with mode: 0644]
code/sdk/application/wearable/n9/nativeapp/nativeui/bloodpressure/include/BPValueInputView.h [new file with mode: 0644]
code/sdk/application/wearable/n9/nativeapp/nativeui/bloodpressure/include/BPViewClickIds.h [new file with mode: 0644]
code/sdk/application/wearable/n9/nativeapp/nativeui/bloodpressure/src/BPAbnormalView.cpp [new file with mode: 0644]
code/sdk/application/wearable/n9/nativeapp/nativeui/bloodpressure/src/BPAtCmd.cpp [new file with mode: 0644]
code/sdk/application/wearable/n9/nativeapp/nativeui/bloodpressure/src/BPCalibFirstDoneView.cpp [new file with mode: 0644]
code/sdk/application/wearable/n9/nativeapp/nativeui/bloodpressure/src/BPCalibSecondDoneView.cpp [new file with mode: 0644]
code/sdk/application/wearable/n9/nativeapp/nativeui/bloodpressure/src/BPCalibStopView.cpp [new file with mode: 0644]
code/sdk/application/wearable/n9/nativeapp/nativeui/bloodpressure/src/BPCalibratingView.cpp [new file with mode: 0644]
code/sdk/application/wearable/n9/nativeapp/nativeui/bloodpressure/src/BPEntryView.cpp [new file with mode: 0644]
code/sdk/application/wearable/n9/nativeapp/nativeui/bloodpressure/src/BPHistoryView.cpp [new file with mode: 0644]
code/sdk/application/wearable/n9/nativeapp/nativeui/bloodpressure/src/BPInstructionView.cpp [new file with mode: 0644]
code/sdk/application/wearable/n9/nativeapp/nativeui/bloodpressure/src/BPMainPresenter.cpp [new file with mode: 0644]
code/sdk/application/wearable/n9/nativeapp/nativeui/bloodpressure/src/BPMainView.cpp [new file with mode: 0644]
code/sdk/application/wearable/n9/nativeapp/nativeui/bloodpressure/src/BPMeasureResultView.cpp [new file with mode: 0644]
code/sdk/application/wearable/n9/nativeapp/nativeui/bloodpressure/src/BPMeasuringView.cpp [new file with mode: 0644]
code/sdk/application/wearable/n9/nativeapp/nativeui/bloodpressure/src/BPModel.cpp [new file with mode: 0644]
code/sdk/application/wearable/n9/nativeapp/nativeui/bloodpressure/src/BPValueConfirmView.cpp [new file with mode: 0644]
code/sdk/application/wearable/n9/nativeapp/nativeui/bloodpressure/src/BPValueInputView.cpp [new file with mode: 0644]
code/sdk/application/wearable/n9/nativeapp/nativeui/ecg/include/ECGAbnormalView.h [new file with mode: 0644]
code/sdk/application/wearable/n9/nativeapp/nativeui/ecg/include/ECGEntryView.h [new file with mode: 0644]
code/sdk/application/wearable/n9/nativeapp/nativeui/ecg/include/ECGHistoryView.h [new file with mode: 0644]
code/sdk/application/wearable/n9/nativeapp/nativeui/ecg/include/ECGInstructionView.h [new file with mode: 0644]
code/sdk/application/wearable/n9/nativeapp/nativeui/ecg/include/ECGMainPresenter.h [new file with mode: 0644]
code/sdk/application/wearable/n9/nativeapp/nativeui/ecg/include/ECGMainView.h [new file with mode: 0644]
code/sdk/application/wearable/n9/nativeapp/nativeui/ecg/include/ECGMeasureCompleteView.h [new file with mode: 0644]
code/sdk/application/wearable/n9/nativeapp/nativeui/ecg/include/ECGMeasureResultView.h [new file with mode: 0644]
code/sdk/application/wearable/n9/nativeapp/nativeui/ecg/include/ECGMeasuringView.h [new file with mode: 0644]
code/sdk/application/wearable/n9/nativeapp/nativeui/ecg/include/ECGModel.h [new file with mode: 0644]
code/sdk/application/wearable/n9/nativeapp/nativeui/ecg/include/ECGViewClickIds.h [new file with mode: 0644]
code/sdk/application/wearable/n9/nativeapp/nativeui/ecg/src/ECGAbnormalView.cpp [new file with mode: 0644]
code/sdk/application/wearable/n9/nativeapp/nativeui/ecg/src/ECGEntryView.cpp [new file with mode: 0644]
code/sdk/application/wearable/n9/nativeapp/nativeui/ecg/src/ECGHistoryView.cpp [new file with mode: 0644]
code/sdk/application/wearable/n9/nativeapp/nativeui/ecg/src/ECGInstructionView.cpp [new file with mode: 0644]
code/sdk/application/wearable/n9/nativeapp/nativeui/ecg/src/ECGMainPresenter.cpp [new file with mode: 0644]
code/sdk/application/wearable/n9/nativeapp/nativeui/ecg/src/ECGMainView.cpp [new file with mode: 0644]
code/sdk/application/wearable/n9/nativeapp/nativeui/ecg/src/ECGMeasureCompleteView.cpp [new file with mode: 0644]
code/sdk/application/wearable/n9/nativeapp/nativeui/ecg/src/ECGMeasureResultView.cpp [new file with mode: 0644]
code/sdk/application/wearable/n9/nativeapp/nativeui/ecg/src/ECGMeasuringView.cpp [new file with mode: 0644]
code/sdk/application/wearable/n9/nativeapp/nativeui/ecg/src/ECGModel.cpp [new file with mode: 0644]
code/sdk/files/n9/fs/user/res/bloodpressure/belt_loose.bin [new file with mode: 0644]
code/sdk/files/n9/fs/user/res/bloodpressure/first.bin [new file with mode: 0644]
code/sdk/files/n9/fs/user/res/bloodpressure/nonet.bin [new file with mode: 0644]
code/sdk/files/n9/fs/user/res/bloodpressure/seat.bin [new file with mode: 0644]
code/sdk/files/n9/fs/user/res/bloodpressure/second.bin [new file with mode: 0644]
code/sdk/files/n9/fs/user/res/bloodpressure/vectorgrreen_s.bin [new file with mode: 0644]
code/sdk/files/n9/fs/user/res/bloodpressure/warning_s.bin [new file with mode: 0644]
code/sdk/files/n9/fs/user/res/bloodpressure/wear.bin [new file with mode: 0644]
code/sdk/files/n9/fs/user/res/ecg/background.bin [new file with mode: 0644]
code/sdk/files/n9/fs/user/res/ecg/contact_good.bin [new file with mode: 0644]
code/sdk/files/n9/fs/user/res/ecg/disclaimer.bin [new file with mode: 0644]
code/sdk/files/n9/fs/user/res/ecg/heart_gray.bin [new file with mode: 0644]
code/sdk/files/n9/fs/user/res/ecg/heart_green_s.bin [new file with mode: 0644]
code/sdk/files/n9/fs/user/res/ecg/heart_red.bin [new file with mode: 0644]
code/sdk/files/n9/fs/user/res/ecg/heart_red_s.bin [new file with mode: 0644]
code/sdk/files/n9/fs/user/res/ecg/wait_contact.bin [new file with mode: 0644]

index 6ed3503..5fe4fcf 100644 (file)
@@ -25,6 +25,7 @@
 #ifdef SUPPORT_AT_JSINSTALL\r
 #include "ohosfwk_at_process.h"\r
 #endif\r
+#include "bloodpressure/include/BPAtCmd.h"\r
 #ifdef SUPPORT_AT_APP\r
 #include "app_cmd_manager.h"\r
 #endif\r
@@ -133,6 +134,9 @@ static void register_at_service(void)
     at_cmd_table_t *ohosfwk_table = get_ohosfwk_cmd_table(&ohosfwk_cmd_count);\r
     at_cmd_add_moudle(ohosfwk_table, ohosfwk_cmd_count);\r
 #endif\r
+    uint32_t bp_ui_cmd_count;\r
+    at_cmd_table_t *bp_ui_table = get_bp_ui_at_cmd_table(&bp_ui_cmd_count);\r
+    at_cmd_add_moudle(bp_ui_table, bp_ui_cmd_count);\r
 #ifdef SUPPORT_AT_APP\r
     uint32_t app_cmd_count;\r
     at_cmd_table_t *app_table = get_app_cmd_table(&app_cmd_count);\r
index b92743c..07dc743 100644 (file)
@@ -168,6 +168,8 @@ typedef enum : uint16_t
   VIEW_MAIN_ALIPAY, // 支付宝
 #endif
   VIEW_HEART_CHART, // 心率曲线图检测
+  VIEW_BLOOD_PRESSURE, // 血压(主入口/历史/说明)
+  VIEW_ECG,            // 心电(主入口/历史/测量)
   VIEW_ADD_NEW_ID = 250,
   /* -------------------------------------------------------------------------- */
   VIEW_RECENT_APP,
diff --git a/code/sdk/application/wearable/n9/nativeapp/nativeui/bloodpressure/include/BPAbnormalView.h b/code/sdk/application/wearable/n9/nativeapp/nativeui/bloodpressure/include/BPAbnormalView.h
new file mode 100644 (file)
index 0000000..97eb4f5
--- /dev/null
@@ -0,0 +1,40 @@
+#ifndef BP_ABNORMAL_VIEW_H
+#define BP_ABNORMAL_VIEW_H
+
+#include "components/ui_label_button.h"
+#include "components/ui_label_ext.h"
+#include "components/ui_image_view.h"
+#include "components/ui_view_group.h"
+
+namespace OHOS {
+
+typedef enum : uint8_t {
+  BP_ABNORMAL_TYPE_NOT_CALIBRATED = 0,//未校准类型
+  BP_ABNORMAL_TYPE_ARM_MOVE,//手臂移动类型
+  BP_ABNORMAL_TYPE_MEASURE_TIMEOUT,//测量超时类型
+  BP_ABNORMAL_TYPE_CALIBRATION_DIFFERENCE_TOO_LARGE,//校准差异过大类型
+  BP_ABNORMAL_TYPE_DISCLAIMER,//免责提醒类型
+  MAX_BP_ABNORMAL_TYPE,//最大界面类型
+} BPAbnormalType;
+
+class BPAbnormalView {
+public:
+  BPAbnormalView();
+  ~BPAbnormalView();
+  static BPAbnormalView *GetInstance();
+  UIViewGroup *InitView(BPAbnormalType type);
+  void UpdateForType(BPAbnormalType type);
+
+private:
+  void ApplyAbnormalType(BPAbnormalType type);
+  UIViewGroup *group_{nullptr};
+  UILabelExt *titleLabel_{nullptr};
+  UILabelExt *noteLabel_{nullptr};
+  UIImageView *imageView_{nullptr};
+  UILabelButton *CancelButton_{nullptr};
+  UILabelButton *RetryButton_{nullptr};
+};
+
+} // namespace OHOS
+
+#endif
diff --git a/code/sdk/application/wearable/n9/nativeapp/nativeui/bloodpressure/include/BPAtCmd.h b/code/sdk/application/wearable/n9/nativeapp/nativeui/bloodpressure/include/BPAtCmd.h
new file mode 100644 (file)
index 0000000..dc74291
--- /dev/null
@@ -0,0 +1,21 @@
+/*
+ * Copyright (c) CompanyNameMagicTag. All rights reserved.
+ * Description: 血压 UI 子界面 AT 调试命令
+ */
+#ifndef BP_AT_CMD_H
+#define BP_AT_CMD_H
+
+#include <stdint.h>
+#include "at_cmd_api.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+at_cmd_table_t *get_bp_ui_at_cmd_table(uint32_t *cmd_count);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/code/sdk/application/wearable/n9/nativeapp/nativeui/bloodpressure/include/BPCalibFirstDoneView.h b/code/sdk/application/wearable/n9/nativeapp/nativeui/bloodpressure/include/BPCalibFirstDoneView.h
new file mode 100644 (file)
index 0000000..92d5a4e
--- /dev/null
@@ -0,0 +1,34 @@
+#ifndef BP_CALIB_FIRST_DONE_VIEW_H
+#define BP_CALIB_FIRST_DONE_VIEW_H
+
+#include "components/ui_box_progress.h"
+#include "components/ui_image_view.h"
+#include "components/ui_label_button.h"
+#include "components/ui_label_ext.h"
+#include "components/ui_view_group.h"
+
+namespace OHOS {
+
+#define BLOOD_PATH_FIRST_DONE "/user/res/bloodpressure/vectorgrreen_s.bin"
+
+class BPCalibFirstDoneView {
+public:
+  BPCalibFirstDoneView();
+  ~BPCalibFirstDoneView();
+  static BPCalibFirstDoneView *GetInstance();
+  UIViewGroup *InitView();
+
+private:
+  void InitProgressBar();
+
+  UIViewGroup *group_{nullptr};
+  UILabelExt *titleLabel_{nullptr};
+  UILabelExt *noteLabel_{nullptr};
+  UIImageView *imageView_{nullptr};
+  UIBoxProgress *progressBar_{nullptr};
+  UILabelButton *RetryButton_{nullptr};
+};
+
+} // namespace OHOS
+
+#endif
diff --git a/code/sdk/application/wearable/n9/nativeapp/nativeui/bloodpressure/include/BPCalibSecondDoneView.h b/code/sdk/application/wearable/n9/nativeapp/nativeui/bloodpressure/include/BPCalibSecondDoneView.h
new file mode 100644 (file)
index 0000000..85f1c3a
--- /dev/null
@@ -0,0 +1,29 @@
+#ifndef BP_CALIB_SECOND_DONE_VIEW_H
+#define BP_CALIB_SECOND_DONE_VIEW_H
+
+#include "components/ui_image_view.h"
+#include "components/ui_label_button.h"
+#include "components/ui_label_ext.h"
+#include "components/ui_view_group.h"
+
+namespace OHOS {
+
+class BPCalibSecondDoneView {
+public:
+  BPCalibSecondDoneView();
+  ~BPCalibSecondDoneView();
+  static BPCalibSecondDoneView *GetInstance();
+  UIViewGroup *InitView();
+
+private:
+  UIViewGroup *group_{nullptr};
+  UILabelExt *titleLabel_{nullptr};
+  UILabelExt *timeLabel_{nullptr};
+  UILabelExt *noteLabel_{nullptr};
+  UILabelButton *confirmButton_{nullptr};
+  UIImageView *imageView_{nullptr};
+};
+
+} // namespace OHOS
+
+#endif
diff --git a/code/sdk/application/wearable/n9/nativeapp/nativeui/bloodpressure/include/BPCalibStopView.h b/code/sdk/application/wearable/n9/nativeapp/nativeui/bloodpressure/include/BPCalibStopView.h
new file mode 100644 (file)
index 0000000..d2a2369
--- /dev/null
@@ -0,0 +1,32 @@
+#ifndef BP_CALIB_STOP_VIEW_H
+#define BP_CALIB_STOP_VIEW_H
+
+#include "components/ui_label.h"
+#include "components/ui_label_button_ext.h"
+#include "components/ui_label_ext.h"
+#include "components/ui_view_group.h"
+
+namespace OHOS {
+
+class BPCalibStopView {
+public:
+  BPCalibStopView();
+  ~BPCalibStopView();
+  static BPCalibStopView *GetInstance();
+  UIViewGroup *InitView();
+  void UpdateForMode(bool isMeasure);
+
+private:
+  void InitRoundIconButton(UILabelButtonExt *&button, int16_t x, int16_t y, const char *viewId,
+                           const char *imagePath);
+
+  UIViewGroup *group_{nullptr};
+  UILabelExt *titleLabel_{nullptr};
+  UILabelExt *noteLabel_{nullptr};
+  UILabelButtonExt *cancelButton_{nullptr};
+  UILabelButtonExt *confirmButton_{nullptr};
+};
+
+} // namespace OHOS
+
+#endif
diff --git a/code/sdk/application/wearable/n9/nativeapp/nativeui/bloodpressure/include/BPCalibratingView.h b/code/sdk/application/wearable/n9/nativeapp/nativeui/bloodpressure/include/BPCalibratingView.h
new file mode 100644 (file)
index 0000000..234271b
--- /dev/null
@@ -0,0 +1,37 @@
+#ifndef BP_CALIBRATING_VIEW_H
+#define BP_CALIBRATING_VIEW_H
+
+#include "components/ui_circle_progress.h"
+#include "components/ui_image_view.h"
+#include "components/ui_label.h"
+#include "components/ui_label_ext.h"
+#include "components/ui_view_group.h"
+
+namespace OHOS {
+
+class BPCalibratingView {
+public:
+  BPCalibratingView();
+  ~BPCalibratingView();
+  static BPCalibratingView *GetInstance();
+  UIViewGroup *InitView();
+  /** @param seconds 兼容旧接口,按 90s 映射为百分比 */
+  void UpdateTimeText(int seconds);
+  void UpdateProgress(int percent);
+  void UpdateRound(uint8_t round);
+
+private:
+  void InitCircleProgress();
+
+  UIViewGroup *group_{nullptr};
+  UIImageView *progressBgImage_{nullptr};
+  UICircleProgress *circleProgress_{nullptr};
+  UILabelExt *statusLabel_{nullptr};
+  UILabel *percentValue_{nullptr};
+  UILabel *percentSign_{nullptr};
+  UILabelExt *hintLabel_{nullptr};
+};
+
+} // namespace OHOS
+
+#endif
diff --git a/code/sdk/application/wearable/n9/nativeapp/nativeui/bloodpressure/include/BPEntryView.h b/code/sdk/application/wearable/n9/nativeapp/nativeui/bloodpressure/include/BPEntryView.h
new file mode 100644 (file)
index 0000000..6858450
--- /dev/null
@@ -0,0 +1,54 @@
+#ifndef BP_ENTRY_VIEW_H
+#define BP_ENTRY_VIEW_H
+
+#include "components/ui_image_view.h"
+#include "components/ui_label.h"
+#include "components/ui_label_ext.h"
+#include "components/ui_label_button.h"
+#include "components/ui_view.h"
+#include "components/ui_view_group.h"
+#include "UiConfig.h"
+
+namespace OHOS {
+
+#include "bloodpressure/include/BPViewClickIds.h"
+
+#define BLOOD_PATH_SUCC PNG_SUCC_IMAGE
+#define BLOOD_PATH_FAIL PNG_ATTENTION_IMAGE
+#define BLOOD_PATH_CALIBRATION_EXPIRED BLOOD_PATH_FAIL
+#define BLOOD_PATH_NOTE BLOOD_PATH_FAIL
+#define BLOOD_PATH_MEASURE_TIMEOUT "user/res/bloodpressure/nonet.bin"
+#define BLOOD_PATH_WARNING_S "/user/res/bloodpressure/warning_s.bin"
+
+class BPEntryView {
+public:
+  BPEntryView();
+  ~BPEntryView();
+  static BPEntryView *GetInstance();
+  UIViewGroup *InitView();
+  void InitCalibratedView();
+  void InitCalibrationExpiredView();
+  void InitNotCalibratedView();
+  void InitPageIndicator();
+
+private:
+  void InitCalibExpiringSoonBar();
+  void InitNoCalibratedBar();
+
+  UIViewGroup *group_{nullptr};
+  UILabel *titleLabel_{nullptr};
+  UILabel *instructionLabel_{nullptr};
+  UIImageView *imageView_{nullptr};
+  UILabelButton *instructionButton_{nullptr};
+  UILabelButton *measureButton_{nullptr};
+  UIView *calibWarnBg_{nullptr};
+  UIImageView *calibWarnIcon_{nullptr};
+  UILabelExt *calibWarnLabel_{nullptr};
+  UIView *dotEntry_{nullptr};
+  UIView *dotHistory_{nullptr};
+  int8_t currentState_{0};
+};
+
+} // namespace OHOS
+
+#endif
diff --git a/code/sdk/application/wearable/n9/nativeapp/nativeui/bloodpressure/include/BPHistoryView.h b/code/sdk/application/wearable/n9/nativeapp/nativeui/bloodpressure/include/BPHistoryView.h
new file mode 100644 (file)
index 0000000..163165f
--- /dev/null
@@ -0,0 +1,63 @@
+#ifndef BP_HISTORY_VIEW_H
+#define BP_HISTORY_VIEW_H
+
+#include "components/ui_image_animator.h"
+#include "components/ui_image_view.h"
+#include "components/ui_label.h"
+#include "components/ui_label_button.h"
+#include "components/ui_label_ext.h"
+#include "components/ui_swipe_view.h"
+#include "components/ui_view_group.h"
+#include <vector>
+
+namespace OHOS {
+
+#include "bloodpressure/include/BPViewClickIds.h"
+
+class BPHistoryView {
+public:
+  BPHistoryView();
+  ~BPHistoryView();
+  static BPHistoryView *GetInstance();
+  UIViewGroup *InitView();
+  void RefreshView();
+  void OnRecordPageChanged(uint16_t pageIndex);
+  void HideArrowWhileDragging();
+  void StartArrowAnimation();
+  void StopArrowAnimation();
+
+private:
+  UIViewGroup *CreateRecordPage(const struct BPRecord &record, size_t index);
+  void ClearRecordPages();
+  void RebuildRecordPages();
+  void InitEmptyPage();
+  void InitPageIndicator();
+  void InitPullDownAnimator();
+  void UnloadPullDownAnimator();
+  void UpdatePageVisibility();
+  void UpdateArrowVisibility();
+  static bool IsHighBloodPressure(uint8_t sbp, uint8_t dbp);
+  static std::string FormatRecordTime(uint32_t timestamp);
+
+  UIViewGroup *group_{nullptr};
+  UISwipeView *recordSwipeView_{nullptr};
+  UIViewGroup *emptyPage_{nullptr};
+  std::vector<UIViewGroup *> recordPages_;
+  UIImageAnimatorView *pullDownAnimator_{nullptr};
+  ImageAnimatorInfo pullDownFrameInfo_[70]{};
+  const ImageInfo **pullUpImageInfo_{nullptr};
+  bool pullDownInited_{false};
+
+  UILabel *emptyTitleLabel_{nullptr};
+  UIImageView *emptyImage_{nullptr};
+  UILabel *emptyHintLabel_{nullptr};
+  UILabelButton *startMeasureButton_{nullptr};
+  UIView *dotEntry_{nullptr};
+  UIView *dotHistory_{nullptr};
+
+  size_t currentRecordIndex_{0};
+};
+
+} // namespace OHOS
+
+#endif
diff --git a/code/sdk/application/wearable/n9/nativeapp/nativeui/bloodpressure/include/BPInstructionView.h b/code/sdk/application/wearable/n9/nativeapp/nativeui/bloodpressure/include/BPInstructionView.h
new file mode 100644 (file)
index 0000000..1170de7
--- /dev/null
@@ -0,0 +1,46 @@
+#ifndef BP_INSTRUCTION_VIEW_H
+#define BP_INSTRUCTION_VIEW_H
+
+#include "components/ui_image_view.h"
+#include "components/ui_label.h"
+#include "components/ui_label_button.h"
+#include "components/ui_view_group.h"
+
+namespace OHOS {
+
+#include "bloodpressure/include/BPViewClickIds.h"
+
+#define BLOOD_PATH_SEAT "/user/res/bloodpressure/seat.bin"
+#define BLOOD_PATH_WEAR "/user/res/bloodpressure/wear.bin"
+#define BLOOD_PATH_FIRST "/user/res/bloodpressure/first.bin"
+#define BLOOD_PATH_SECOND "/user/res/bloodpressure/second.bin"
+#define BLOOD_PATH_BELT_LOOSE "/user/res/bloodpressure/belt_loose.bin"
+typedef enum : uint8_t {
+  BP_INSTRUCTION_TYPE_SEAT = 0,//坐姿引导界面类型
+  BP_INSTRUCTION_TYPE_WEAR,//佩戴引导界面类型
+  BP_INSTRUCTION_TYPE_BELT_LOOSE,//佩戴过松引导界面类型
+  BP_INSTRUCTION_TYPE_FIRST,//校准第一轮说明界面类型
+  BP_INSTRUCTION_TYPE_SECOND,//校准第二轮说明界面类型
+  MAX_BP_INSTRUCTION_TYPE,//最大界面类型
+} BPInstructionType;
+
+class BPInstructionView {
+public:
+  BPInstructionView();
+  ~BPInstructionView();
+  static BPInstructionView *GetInstance();
+  UIViewGroup *InitView(BPInstructionType type);
+  void UpdateForType(BPInstructionType type);
+
+private:
+  void ApplyInstructionType(BPInstructionType type);
+  UIViewGroup *group_{nullptr};
+  UIImageView *imageView_{nullptr};
+  UILabel *instructionLabel_{nullptr};
+  UILabelButton *backButton_{nullptr};
+  UILabelButton *measureButton_{nullptr};
+};
+
+} // namespace OHOS
+
+#endif
diff --git a/code/sdk/application/wearable/n9/nativeapp/nativeui/bloodpressure/include/BPMainPresenter.h b/code/sdk/application/wearable/n9/nativeapp/nativeui/bloodpressure/include/BPMainPresenter.h
new file mode 100644 (file)
index 0000000..04ae824
--- /dev/null
@@ -0,0 +1,76 @@
+#ifndef BP_MAIN_PRESENTER_H
+#define BP_MAIN_PRESENTER_H
+
+#include "Presenter.h"
+#include "bloodpressure/include/BPMainView.h"
+#include "bloodpressure/include/BPInstructionView.h"
+#include "components/ui_swipe_view.h"
+#include "common/key_code.h"
+#include "key/include/KeyInputListener.h"
+
+namespace OHOS {
+
+class GraphicTimer;
+
+class BPMainPresenter : public Presenter<BPMainView>,
+                        public UIView::OnClickListener,
+                        public UIView::OnDragListener,
+                        public UISwipeView::OnSwipeListener,
+                        public RootView::OnKeyActListener {
+public:
+  enum class BPProgressMode : uint8_t { None = 0, Measure, Calibrate };
+
+  BPMainPresenter();
+  virtual ~BPMainPresenter();
+  static BPMainPresenter *GetInstance();
+
+  void OnStart() override;
+  void OnPause() override;
+  void OnResume() override;
+  void OnStop() override;
+
+  bool OnDragStart(UIView &view, const DragEvent &event) override;
+  bool OnDrag(UIView &view, const DragEvent &event) override;
+  bool OnDragEnd(UIView &view, const DragEvent &event) override;
+  void OnSwipe(UISwipeView &view) override;
+  bool OnClick(UIView &view, const ClickEvent &event) override;
+  bool OnKeyAct(UIView &view, const KeyEvent &event) override;
+
+  void NotifyCalibrationComplete(uint8_t sbp, uint8_t dbp);
+  void NotifyMeasureComplete(uint8_t sbp, uint8_t dbp);
+
+private:
+  void NavigateBack();
+  bool HandleSwipeBack();
+  bool IsFlowSubPage(int16_t viewId) const;
+  bool NeedsStopConfirm(int16_t viewId) const;
+  void RequestStopConfirm(int16_t fromView);
+  void StartMeasureFlow(BPMainView *mainView);
+  void StartCalibrationFlow(BPMainView *mainView, uint8_t round);
+  void StartProgressSession(BPProgressMode mode);
+  void PauseProgressSession();
+  void ResumeProgressSession();
+  void StopProgressSession();
+  void OnProgressTick();
+  void OnProgressTimeout();
+  void UpdateProgressUi(int percent);
+  void CompleteCalibrationFlow(uint8_t sbp, uint8_t dbp);
+  void CompleteMeasureFlow(uint8_t sbp, uint8_t dbp);
+  static void ProgressTimerThunk(void *arg);
+
+  static constexpr uint32_t kProgressTickMs = 150U;
+  static constexpr int kProgressMaxPercent = 100;
+
+  GraphicTimer *progressTimer_{nullptr};
+  BPProgressMode progressMode_{BPProgressMode::None};
+  int progressPercent_{0};
+  bool progressDataReceived_{false};
+  bool progressPaused_{false};
+  int16_t stopReturnView_{MAX_BPVIEW};
+  bool stopIsMeasure_{false};
+  BPProgressMode lastTimeoutMode_{BPProgressMode::None};
+};
+
+} // namespace OHOS
+
+#endif
diff --git a/code/sdk/application/wearable/n9/nativeapp/nativeui/bloodpressure/include/BPMainView.h b/code/sdk/application/wearable/n9/nativeapp/nativeui/bloodpressure/include/BPMainView.h
new file mode 100644 (file)
index 0000000..705456c
--- /dev/null
@@ -0,0 +1,81 @@
+#ifndef BP_MAIN_VIEW_H
+#define BP_MAIN_VIEW_H
+
+#include "View.h"
+#include "bloodpressure/include/BPInstructionView.h"
+#include "components/ui_view_group.h"
+
+namespace OHOS {
+
+
+typedef enum : uint8_t {
+  BP_ENTRY_VIEW = 0,//主界面
+  BP_HISTORY_VIEW,//历史记录界面
+  BP_INSTRUCTION_VIEW,//指导界面
+  BP_ABNORMAL_VIEW,//异常界面
+  BP_MEASURING_VIEW,//测量界面
+  BP_MEASURE_STOP_VIEW,//测量停止界面
+  BP_MEASURE_RESULT_VIEW,//测量结果界面
+  BP_CALIBRATING_VIEW,//校准界面
+  BP_CALIB_STOP_VIEW,//校准停止界面
+  BP_VALUE_INPUT_VIEW,//值输入界面
+  BP_VALUE_CONFIRM_VIEW,//值确认界面
+  BP_CALIB_FIRST_DONE_VIEW,//校准第一轮完成界面
+  BP_CALIB_SECOND_DONE_VIEW,//校准第二轮完成界面
+  MAX_BPVIEW,//最大界面 
+} BPVIEWID;
+
+class BPMainPresenter;
+class BPMainView : public View<BPMainPresenter> {
+public:
+  BPMainView();
+  virtual ~BPMainView();
+  static BPMainView *GetInstance();
+  void OnStart() override;
+  void OnStop() override;
+
+  bool ChangeBPEntryView(int16_t thisView, int16_t type);
+  bool ChangeBPHistoryView(int16_t thisView, int16_t type);
+  bool ChangeBPInstructionView(int16_t thisView, int16_t type);
+  bool ChangeBPAbnormalView(int16_t thisView, int16_t type);
+  bool ChangeBPMeasuringView(int16_t thisView, int16_t type);
+  bool ChangeBPMeasureResultView(int16_t thisView, int16_t type);
+  bool ChangeBPCalibratingView(int16_t thisView, int16_t type);
+  bool ChangeBPCalibStopView(int16_t thisView, int16_t type);
+  bool ChangeBPValueInputView(int16_t thisView, int16_t type);
+  bool ChangeBPValueConfirmView(int16_t thisView, int16_t type);
+  bool ChangeBPCalibFirstDoneView(int16_t thisView, int16_t type);
+  bool ChangeBPCalibSecondDoneView(int16_t thisView, int16_t type);
+
+  bool ClickFunc(int16_t interface, int16_t type);
+  void ChangeView(int16_t hideView, int16_t showView, int16_t type = 0);
+  int16_t GetCurrentViewId() const;
+  int16_t GetInstructionSubType() const { return instructionSubType_; }
+  void RefreshCurrentView();
+  /** AT^BPUIVIEW 调试用:切换到指定子界面,subType 用于说明页/异常页子类型 */
+  void ShowAtTestPage(int16_t viewId, int16_t subType = 0);
+
+private:
+  void DeInit();
+
+  class BPEntryView *entryView_{nullptr};
+  class BPHistoryView *historyView_{nullptr};
+  class BPInstructionView *instructionView_{nullptr};
+  class BPAbnormalView *abnormalView_{nullptr};
+  class BPMeasuringView *measuringView_{nullptr};
+  class BPMeasureResultView *measureResultView_{nullptr};
+  class BPCalibratingView *calibratingView_{nullptr};
+  class BPCalibStopView *calibStopView_{nullptr};
+  class BPValueInputView *valueInputView_{nullptr};
+  class BPValueConfirmView *valueConfirmView_{nullptr};
+  class BPCalibFirstDoneView *calibFirstDoneView_{nullptr};
+  class BPCalibSecondDoneView *calibSecondDoneView_{nullptr};
+  UIViewGroup *container_{nullptr};
+  UIViewGroup *group_[MAX_BPVIEW]{nullptr};
+  int16_t currentViewId_{BP_ENTRY_VIEW};
+  int16_t instructionSubType_{0};
+};
+
+} // namespace OHOS
+
+#endif
diff --git a/code/sdk/application/wearable/n9/nativeapp/nativeui/bloodpressure/include/BPMeasureResultView.h b/code/sdk/application/wearable/n9/nativeapp/nativeui/bloodpressure/include/BPMeasureResultView.h
new file mode 100644 (file)
index 0000000..0e4d9f2
--- /dev/null
@@ -0,0 +1,46 @@
+#ifndef BP_MEASURE_RESULT_VIEW_H
+#define BP_MEASURE_RESULT_VIEW_H
+
+#include "components/ui_image_view.h"
+#include "components/ui_label.h"
+#include "components/ui_label_button.h"
+#include "components/ui_label_ext.h"
+#include "components/ui_view_group.h"
+
+namespace OHOS {
+
+class BPMeasureResultView {
+public:
+  BPMeasureResultView();
+  ~BPMeasureResultView();
+  static BPMeasureResultView *GetInstance();
+  UIViewGroup *InitView();
+  void UpdateText();
+
+private:
+  void InitDividerLines();
+  void ApplyNormalLayout();
+  void ApplyHighLayout();
+  void ApplyCalibExpiredLayout();
+  static bool IsHighBloodPressure(uint8_t sbp, uint8_t dbp);
+  static std::string FormatMeasureTime();
+
+  UIViewGroup *group_{nullptr};
+  UILabel *titleLabel_{nullptr};
+  UILabel *timeLabel_{nullptr};
+  UILabelExt *statusLabel_{nullptr};
+  UIImageView *hlineTop_{nullptr};
+  UIImageView *hlineBottom_{nullptr};
+  UIImageView *vlineMid_{nullptr};
+  UILabelExt *sbpTitle_{nullptr};
+  UILabelExt *dbpTitle_{nullptr};
+  UILabel *sbpValue_{nullptr};
+  UILabel *dbpValue_{nullptr};
+  UILabel *sbpUnit_{nullptr};
+  UILabel *dbpUnit_{nullptr};
+  UILabelButton *confirmButton_{nullptr};
+};
+
+} // namespace OHOS
+
+#endif
diff --git a/code/sdk/application/wearable/n9/nativeapp/nativeui/bloodpressure/include/BPMeasuringView.h b/code/sdk/application/wearable/n9/nativeapp/nativeui/bloodpressure/include/BPMeasuringView.h
new file mode 100644 (file)
index 0000000..13d2465
--- /dev/null
@@ -0,0 +1,33 @@
+#ifndef BP_MEASURING_VIEW_H
+#define BP_MEASURING_VIEW_H
+
+#include "components/ui_image_animator.h"
+#include "components/ui_label.h"
+#include "components/ui_view_group.h"
+
+namespace OHOS {
+
+class BPMeasuringView {
+public:
+  BPMeasuringView();
+  ~BPMeasuringView();
+  static BPMeasuringView *GetInstance();
+  UIViewGroup *InitView();
+  void StartMeasuringAnimation();
+  void StopMeasuringAnimation();
+  void UpdateProgressPercent(int percent);
+
+private:
+  bool InitMeasuringAnimator();
+  void UnloadAnimatorFrames();
+
+  UIViewGroup *group_{nullptr};
+  UILabel *noteLabel_{nullptr};
+  UIImageAnimatorView *animatorView_{nullptr};
+  ImageAnimatorInfo frameInfo_[40]{};
+  bool animatorInited_{false};
+};
+
+} // namespace OHOS
+
+#endif
diff --git a/code/sdk/application/wearable/n9/nativeapp/nativeui/bloodpressure/include/BPModel.h b/code/sdk/application/wearable/n9/nativeapp/nativeui/bloodpressure/include/BPModel.h
new file mode 100644 (file)
index 0000000..554db62
--- /dev/null
@@ -0,0 +1,98 @@
+#ifndef BP_MODEL_H
+#define BP_MODEL_H
+
+#include <cstdint>
+#include <deque>
+#include <string>
+
+namespace OHOS {
+
+struct BPRecord {
+  uint32_t timestamp;
+  uint8_t sbp;
+  uint8_t dbp;
+};
+
+/** 校准状态:GetCurrentstate() 返回值 */
+static constexpr int8_t BP_CALIB_STATE_VALID = 0;        // 已校准且未过期
+static constexpr int8_t BP_CALIB_STATE_EXPIRED = 1;      // 校准已过期
+static constexpr int8_t BP_CALIB_STATE_NOT_CALIBRATED = -1; // 从未校准
+
+/** 校准差异阈值,超过该值认为校准差异过大 */
+static constexpr uint8_t BP_CALIB_DIFF_THRESHOLD = 15;
+
+class BPModel {
+public:
+  BPModel();
+  ~BPModel();
+  static BPModel *GetInstance();
+
+  void LoadData();
+  BPRecord GetLastRecord() const;
+  const std::deque<BPRecord> &GetHistoryRecords() const;
+  std::string FormatTimestamp(uint32_t timestamp) const;
+  /** MM-DD HH:mm,历史记录卡片用 */
+  std::string FormatShortTimestamp(uint32_t timestamp) const;
+
+  /** 0:已校准  1:校准已过期  -1:未校准 */
+  int8_t GetCurrentstate();
+  /** 校准完成后写入当前时间戳与校准值 */
+  bool SaveCalibrationResult();
+  int32_t GetCalibrationTimestamp() const;
+
+  /** 校准有效期(秒),默认 30 天,与 fd230 needMeasure 一致 */
+  void SetCalibrationTimeoutSec(uint32_t seconds);
+  uint32_t GetCalibrationTimeoutSec() const;
+  /** 校准剩余天数,-1 表示未校准或已过期 */
+  int32_t GetCalibrationRemainingDays() const;
+  /** 校准即将过期(剩余天数 <= warnDays) */
+  bool IsCalibrationExpiringSoon(uint32_t warnDays = 3) const;
+
+  /** 首次使用免责声明 */
+  bool IsFirstUse() const;
+  bool MarkDisclaimerAccepted();
+
+  enum class BPValueType : uint8_t { SBP = 0, DBP };
+  void ResetInputValues();
+  void AdjustInputValue(BPValueType type, bool increase);
+  void SetInputSbp(uint8_t sbp);
+  void SetInputDbp(uint8_t dbp);
+  uint8_t GetInputSbp() const;
+  uint8_t GetInputDbp() const;
+  void SetMeasureResult(uint8_t sbp, uint8_t dbp);
+  uint8_t GetResultSbp() const;
+  uint8_t GetResultDbp() const;
+  void SetCalibrationRound(uint8_t round);
+  uint8_t GetCalibrationRound() const;
+
+  void SaveFirstRoundInput();
+  void SetSecondRoundInput(uint8_t sbp, uint8_t dbp);
+  bool IsCalibrationDifferenceTooLarge() const;
+  bool SaveMeasureRecord(uint8_t sbp, uint8_t dbp);
+
+private:
+  bool CreateBPDataDirectory();
+  bool LoadCalibrationFromStorage();
+  bool SaveCalibrationToStorage();
+  bool LoadHistoryFromStorage();
+  bool SaveHistoryToStorage();
+
+  BPRecord lastRecord_{0, 0, 0};
+  std::deque<BPRecord> historyRecords_;
+  int32_t calibrationTimestamp_{-1};
+  uint32_t calibrationTimeoutSec_{30U * 24U * 3600U};
+  uint8_t inputSbp_{120};
+  uint8_t inputDbp_{80};
+  uint8_t resultSbp_{0};
+  uint8_t resultDbp_{0};
+  uint8_t calibrationRound_{1};
+  uint8_t firstRoundSbp_{120};
+  uint8_t firstRoundDbp_{80};
+  uint8_t secondRoundSbp_{120};
+  uint8_t secondRoundDbp_{80};
+  bool firstRoundSaved_{false};
+};
+
+} // namespace OHOS
+
+#endif
diff --git a/code/sdk/application/wearable/n9/nativeapp/nativeui/bloodpressure/include/BPValueConfirmView.h b/code/sdk/application/wearable/n9/nativeapp/nativeui/bloodpressure/include/BPValueConfirmView.h
new file mode 100644 (file)
index 0000000..2d2f20d
--- /dev/null
@@ -0,0 +1,45 @@
+#ifndef BP_VALUE_CONFIRM_VIEW_H
+#define BP_VALUE_CONFIRM_VIEW_H
+
+#include "components/ui_image_view.h"
+#include "components/ui_label.h"
+#include "components/ui_label_button.h"
+#include "components/ui_label_ext.h"
+#include "components/ui_view_group.h"
+
+namespace OHOS {
+
+class BPValueConfirmView {
+public:
+  BPValueConfirmView();
+  ~BPValueConfirmView();
+  static BPValueConfirmView *GetInstance();
+  UIViewGroup *InitView();
+  void UpdateView();
+
+private:
+  void InitDividerLines();
+  void InitActionButtons();
+  void ApplyValidLayout();
+  void ApplyInvalidLayout(uint8_t sbp, uint8_t dbp);
+
+  UIViewGroup *group_{nullptr};
+  UILabelExt *titleLabel_{nullptr};
+  UILabelExt *errorLabel_{nullptr};
+  UIImageView *hlineTop_{nullptr};
+  UIImageView *vlineMid_{nullptr};
+  UILabelExt *sbpTitle_{nullptr};
+  UILabelExt *dbpTitle_{nullptr};
+  UILabel *sbpValue_{nullptr};
+  UILabel *dbpValue_{nullptr};
+  UILabel *sbpUnit_{nullptr};
+  UILabel *dbpUnit_{nullptr};
+  UILabelButton *modifyButton_{nullptr};
+  UILabelButton *startCalibButton_{nullptr};
+  UILabelButton *errorOkButton_{nullptr};
+  bool isValid_{true};
+};
+
+} // namespace OHOS
+
+#endif
diff --git a/code/sdk/application/wearable/n9/nativeapp/nativeui/bloodpressure/include/BPValueInputView.h b/code/sdk/application/wearable/n9/nativeapp/nativeui/bloodpressure/include/BPValueInputView.h
new file mode 100644 (file)
index 0000000..2d040e5
--- /dev/null
@@ -0,0 +1,41 @@
+#ifndef BP_VALUE_INPUT_VIEW_H
+#define BP_VALUE_INPUT_VIEW_H
+
+#include "components/ui_label_ext.h"
+#include "components/ui_label_button_ext.h"
+#include "components/ui_picker.h"
+#include "components/ui_view_group.h"
+
+namespace OHOS {
+
+class BPValueInputView {
+public:
+  static constexpr int16_t SBP_MIN = 60;
+  static constexpr int16_t SBP_MAX = 250;
+  static constexpr int16_t DBP_MIN = 40;
+  static constexpr int16_t DBP_MAX = 150;
+
+  BPValueInputView();
+  ~BPValueInputView();
+  static BPValueInputView *GetInstance();
+  UIViewGroup *InitView();
+  void SyncPickerFromModel();
+  UIPicker *GetSbpPicker() const { return sbpPicker_; }
+  UIPicker *GetDbpPicker() const { return dbpPicker_; }
+
+private:
+  void InitBpPicker(UIPicker *&picker, int16_t x, int16_t y, int16_t width, int16_t height,
+                    const char *viewId, int16_t start, int16_t end);
+
+  UIViewGroup *group_{nullptr};
+  UILabelExt *titleLabel_{nullptr};
+  UILabelExt *sbpLabel_{nullptr};
+  UILabelExt *dbpLabel_{nullptr};
+  UIPicker *sbpPicker_{nullptr};
+  UIPicker *dbpPicker_{nullptr};
+  UILabelButtonExt *okButton_{nullptr};
+};
+
+} // namespace OHOS
+
+#endif
diff --git a/code/sdk/application/wearable/n9/nativeapp/nativeui/bloodpressure/include/BPViewClickIds.h b/code/sdk/application/wearable/n9/nativeapp/nativeui/bloodpressure/include/BPViewClickIds.h
new file mode 100644 (file)
index 0000000..dc3f1be
--- /dev/null
@@ -0,0 +1,30 @@
+#ifndef BP_VIEW_CLICK_IDS_H
+#define BP_VIEW_CLICK_IDS_H
+
+#define BP_CLICK_HISTORY "bp_click_history"
+#define BP_CLICK_INSTRUCTION "bp_click_instruction"
+#define BP_CLICK_MEASURE "bp_click_measure"
+#define BP_CLICK_RECALIBRATE "bp_click_recalibrate"
+#define BP_CLICK_HISTORY_BACK "bp_click_history_back"
+#define BP_CLICK_INSTRUCTION_BACK "bp_click_instruction_back"
+
+#define BP_CLICK_STOP "bp_click_stop"
+#define BP_CLICK_RETRY "bp_click_retry"
+#define BP_CLICK_EXIT "bp_click_exit"
+#define BP_CLICK_CONFIRM "bp_click_confirm"
+#define BP_CLICK_VALUE_NEXT "bp_value_next"
+#define BP_CLICK_VALUE_CONFIRM "bp_value_confirm"
+#define BP_CLICK_VALUE_ERROR_OK "bp_value_error_ok"
+#define BP_CLICK_SBP_LEFT "SBPLeft"
+#define BP_CLICK_SBP_RIGHT "SBPRight"
+#define BP_CLICK_DBP_LEFT "DBPLeft"
+#define BP_CLICK_DBP_RIGHT "DBPRight"
+#define BP_CLICK_MEASURE_STOP "bp_measure_stop"
+#define BP_CLICK_CALIB_STOP "bp_calib_stop"
+#define BP_CLICK_CALIB_STOP_CANCEL "bp_calib_stop_cancel"
+#define BP_CLICK_CALIB_STOP_CONFIRM "bp_calib_stop_confirm"
+#define BP_CLICK_FIRST_CALIB_DONE "bp_first_calib_done"
+#define BP_CLICK_SECOND_CALIB_DONE "bp_second_calib_done"
+#define BP_CLICK_DISCLAIMER_ACCEPT "bp_disclaimer_accept"
+
+#endif
diff --git a/code/sdk/application/wearable/n9/nativeapp/nativeui/bloodpressure/src/BPAbnormalView.cpp b/code/sdk/application/wearable/n9/nativeapp/nativeui/bloodpressure/src/BPAbnormalView.cpp
new file mode 100644 (file)
index 0000000..feab500
--- /dev/null
@@ -0,0 +1,125 @@
+#include "bloodpressure/include/BPAbnormalView.h"
+#include "bloodpressure/include/BPMainPresenter.h"
+#include "bloodpressure/include/BPViewClickIds.h"
+#include "bloodpressure/include/BPEntryView.h"
+#include "ui_resource_string.h"
+#include "uicomponents/UIComponents.h"
+#include "wearable_log.h"
+
+namespace OHOS {
+
+static BPAbnormalView *g_instance = nullptr;
+
+BPAbnormalView::BPAbnormalView() { g_instance = this; }
+
+BPAbnormalView::~BPAbnormalView() {
+  if (group_) {
+    group_->RemoveAll();
+    delete titleLabel_;
+    delete noteLabel_;
+    delete imageView_;
+    delete CancelButton_;
+    delete RetryButton_;
+    delete group_;
+    titleLabel_ = nullptr;
+    noteLabel_ = nullptr;
+    imageView_ = nullptr;
+    CancelButton_ = nullptr;
+    RetryButton_ = nullptr;
+    group_ = nullptr;
+  }
+  g_instance = nullptr;
+}
+
+BPAbnormalView *BPAbnormalView::GetInstance() { return g_instance; }
+
+void BPAbnormalView::ApplyAbnormalType(BPAbnormalType type) {
+  if (titleLabel_ == nullptr || noteLabel_ == nullptr || CancelButton_ == nullptr ||
+      RetryButton_ == nullptr) {
+    return;
+  }
+  titleLabel_->SetText("请根据提示进行操作");
+  noteLabel_->SetText("请根据提示进行操作");
+  if (imageView_ != nullptr) {
+    imageView_->SetSrc(BLOOD_PATH_NOTE);
+    imageView_->SetVisible(true);
+  }
+  CancelButton_->SetVisible(true);
+  RetryButton_->SetVisible(true);
+  CancelButton_->Resize(170, 90);
+  CancelButton_->SetPosition(58, 320);
+  RetryButton_->SetPosition(238, 320);
+  CancelButton_->SetText("取消");
+  RetryButton_->SetText("重新校准");
+  CancelButton_->SetStyle(STYLE_BACKGROUND_COLOR, 0xff191919);
+  RetryButton_->SetStyle(STYLE_BACKGROUND_COLOR, 0xffff5a36);
+
+  switch (type) {
+    case BP_ABNORMAL_TYPE_NOT_CALIBRATED:
+      titleLabel_->SetText("需先完成校准");
+      noteLabel_->SetText("请准备传统血压计\n约需5分钟");
+      CancelButton_->SetText("稍后");
+      RetryButton_->SetText("开始校准");
+      break;
+    case BP_ABNORMAL_TYPE_ARM_MOVE:
+      titleLabel_->SetText("提示");
+      noteLabel_->SetText("检测到手臂移动\n请保持静止后重试");
+      RetryButton_->SetViewId(BP_CLICK_RETRY);
+      RetryButton_->SetText("重试");
+      break;
+    case BP_ABNORMAL_TYPE_MEASURE_TIMEOUT:
+      titleLabel_->SetText("提示");
+      if (imageView_ != nullptr) {
+        imageView_->SetSrc(BLOOD_PATH_MEASURE_TIMEOUT);
+      }
+      noteLabel_->SetText("未能获取有效信号\n请调整佩戴后重试");
+      RetryButton_->SetViewId(BP_CLICK_RETRY);
+      RetryButton_->SetText("重试");
+      break;
+    case BP_ABNORMAL_TYPE_CALIBRATION_DIFFERENCE_TOO_LARGE:
+      titleLabel_->SetText("两次偏差过大");
+      noteLabel_->SetText("检测到两次偏差较大\n建议重新校准");
+      break;
+    case BP_ABNORMAL_TYPE_DISCLAIMER:
+      titleLabel_->SetText("测量结果仅供参考");
+      noteLabel_->SetText("不可替代专业医疗诊断\n如有健康疑虑,请咨询医生");
+      RetryButton_->SetVisible(false);
+      CancelButton_->SetViewId(BP_CLICK_DISCLAIMER_ACCEPT);
+      CancelButton_->SetText("同意并继续");
+      CancelButton_->Resize(280, 90);
+      CancelButton_->SetPosition(93, 320);
+      CancelButton_->SetStyle(STYLE_BACKGROUND_COLOR, 0xffff5a36);
+      break;
+    default:
+      break;
+  }
+}
+
+void BPAbnormalView::UpdateForType(BPAbnormalType type) { ApplyAbnormalType(type); }
+
+UIViewGroup *BPAbnormalView::InitView(BPAbnormalType type) {
+  group_ = new UIViewGroup();
+  group_->SetPosition(0, 0, Screen::GetInstance().GetWidth(), Screen::GetInstance().GetHeight());
+  InitLabel(titleLabel_, 83, 50, 300, 35, "bp_abnormal_title", "请根据提示进行操作",
+            DEFAULT_VECTOR_FONT_FILENAME, 30);
+  InitImageView(imageView_, 193, 120, 80, 80, "bp_abnormal_image", BLOOD_PATH_NOTE);
+  InitLabel(noteLabel_, 53, 200, 360, 100, "bp_abnormal_note", "请根据提示进行操作",
+            DEFAULT_VECTOR_FONT_FILENAME, 24);
+  InitLabelButton(CancelButton_, 58, 320, 170, 90, BP_CLICK_EXIT, "取消", 26);
+  InitLabelButton(RetryButton_, 238, 320, 170, 90, BP_CLICK_RECALIBRATE, "重新校准", 26);
+  RetryButton_->SetStyle(STYLE_BACKGROUND_COLOR, 0xffff5a36);
+
+  ApplyAbnormalType(type);
+
+  auto *listener = static_cast<UIView::OnClickListener *>(BPMainPresenter::GetInstance());
+  CancelButton_->SetOnClickListener(listener);
+  RetryButton_->SetOnClickListener(listener);
+  group_->Add(titleLabel_);
+  group_->Add(imageView_);
+  group_->Add(noteLabel_);
+  group_->Add(CancelButton_);
+  group_->Add(RetryButton_);
+  return group_;
+}
+
+} // namespace OHOS
diff --git a/code/sdk/application/wearable/n9/nativeapp/nativeui/bloodpressure/src/BPAtCmd.cpp b/code/sdk/application/wearable/n9/nativeapp/nativeui/bloodpressure/src/BPAtCmd.cpp
new file mode 100644 (file)
index 0000000..c30c300
--- /dev/null
@@ -0,0 +1,186 @@
+/*
+ * Copyright (c) CompanyNameMagicTag. All rights reserved.
+ * Description: 血压模块 AT 调试命令
+ *
+ * AT^BPUIVIEW=viewId[,subType][,enterSlice]  — 切换子界面
+ * AT^BPCALIBVAL=sbp,dbp                        — 模拟校准完成(15s 内收到即成功)
+ * AT^BPMEASVAL=sbp,dbp                         — 模拟测量完成(15s 内收到即成功)
+ */
+#include "bloodpressure/include/BPAtCmd.h"
+#include "bloodpressure/include/BPMainPresenter.h"
+#include "bloodpressure/include/BPMainView.h"
+#include "AppViewIDs.h"
+#include "NativeAbility.h"
+#include "at_cmd_api.h"
+#include "custom_utils.h"
+#include "debug_print.h"
+#include <cstdlib>
+#include <cstring>
+
+namespace OHOS {
+} // namespace OHOS
+
+#define BP_AT_MAX_ARGC 4
+#define BP_AT_MAX_ARGV_LEN 16
+#define BP_AT_ARRAY_COUNT(a) (sizeof(a) / sizeof((a)[0]))
+
+static int BpAtConvertParam(uint8_t *para, uint32_t paraLen,
+                            uint8_t argv[BP_AT_MAX_ARGC][BP_AT_MAX_ARGV_LEN], uint32_t *argc) {
+  uint32_t pos = 0;
+  *argc = 0;
+  if (para == nullptr || argc == nullptr) {
+    return -1;
+  }
+  for (uint32_t i = 0; i < paraLen; i++) {
+    if ((para[i] == ' ' || para[i] == '\n' || para[i] == ',') && pos != 0) {
+      argv[*argc][pos] = '\0';
+      (*argc)++;
+      pos = 0;
+      if (*argc >= BP_AT_MAX_ARGC) {
+        return -1;
+      }
+    } else if (para[i] != ' ' && para[i] != '\n' && para[i] != ',') {
+      argv[*argc][pos] = para[i];
+      pos++;
+      if (pos >= BP_AT_MAX_ARGV_LEN) {
+        return -1;
+      }
+    }
+  }
+  if (pos != 0) {
+    (*argc)++;
+  }
+  return 0;
+}
+
+static void BpAtPrintHelp(void) {
+  PRINT("AT^BPUIVIEW=viewId[,subType][,enterSlice]\r\n");
+  PRINT("AT^BPCALIBVAL=sbp,dbp  — calibration done\r\n");
+  PRINT("AT^BPMEASVAL=sbp,dbp   — measure done\r\n");
+  PRINT("  viewId: 0入口 1历史 2说明 3异常 4测量中 5测量停 6测量结果\r\n");
+  PRINT("          7校准中 8校准停 9值输入 10值确认 11首轮完成 12次轮完成\r\n");
+  PRINT("  subType: instruction 0-4 / abnormal 0-4, else 0\r\n");
+  PRINT("  enterSlice: 1=Enter VIEW_BLOOD_PRESSURE first (default 1)\r\n");
+}
+
+static uint32_t bp_ui_view_at(uint8_t *para, uint32_t paraLen, char *strCommonCmd) {
+  (void)strCommonCmd;
+  if (paraLen == 0 || (para != nullptr && paraLen >= 1 && para[0] == '?')) {
+    BpAtPrintHelp();
+    return ERRCODE_SUCC;
+  }
+
+  uint8_t argv[BP_AT_MAX_ARGC][BP_AT_MAX_ARGV_LEN] = {{0}};
+  uint32_t argc = 0;
+  if (BpAtConvertParam(para, paraLen, argv, &argc) != 0 || argc < 1) {
+    BpAtPrintHelp();
+    return ERRCODE_FAIL;
+  }
+
+  int viewId = atoi(reinterpret_cast<char *>(argv[0]));
+  int subType = (argc >= 2) ? atoi(reinterpret_cast<char *>(argv[1])) : 0;
+  int enterSlice = (argc >= 3) ? atoi(reinterpret_cast<char *>(argv[2])) : 1;
+
+  if (viewId < 0 || viewId >= static_cast<int>(OHOS::MAX_BPVIEW)) {
+    PRINT("BPUIVIEW invalid viewId=%d (0-%d)\r\n", viewId, OHOS::MAX_BPVIEW - 1);
+    BpAtPrintHelp();
+    return ERRCODE_FAIL;
+  }
+
+  uapi_set_screen_on_api();
+
+  if (enterSlice != 0) {
+    OHOS::NativeAbility &ability = OHOS::NativeAbility::GetInstance();
+    if (ability.GetCurSliceId() != VIEW_BLOOD_PRESSURE) {
+      ability.EnterSlice(VIEW_BLOOD_PRESSURE);
+    }
+  }
+
+  OHOS::BPMainView *mainView = OHOS::BPMainView::GetInstance();
+  if (mainView == nullptr) {
+    PRINT("BPUIVIEW fail: BPMainView not ready, try enterSlice=1\r\n");
+    return ERRCODE_FAIL;
+  }
+
+  mainView->ShowAtTestPage(static_cast<int16_t>(viewId), static_cast<int16_t>(subType));
+  PRINT("BPUIVIEW ok: view=%d subType=%d cur=%d\r\n", viewId, subType,
+        mainView->GetCurrentViewId());
+  return ERRCODE_SUCC;
+}
+
+static uint32_t bp_calib_val_at(uint8_t *para, uint32_t paraLen, char *strCommonCmd) {
+  (void)strCommonCmd;
+  if (paraLen == 0 || (para != nullptr && paraLen >= 1 && para[0] == '?')) {
+    PRINT("AT^BPCALIBVAL=sbp,dbp\r\n");
+    return ERRCODE_SUCC;
+  }
+
+  uint8_t argv[BP_AT_MAX_ARGC][BP_AT_MAX_ARGV_LEN] = {{0}};
+  uint32_t argc = 0;
+  if (BpAtConvertParam(para, paraLen, argv, &argc) != 0 || argc < 2) {
+    PRINT("BPCALIBVAL need sbp,dbp\r\n");
+    return ERRCODE_FAIL;
+  }
+
+  int sbp = atoi(reinterpret_cast<char *>(argv[0]));
+  int dbp = atoi(reinterpret_cast<char *>(argv[1]));
+  if (sbp < 0 || sbp > 255 || dbp < 0 || dbp > 255) {
+    PRINT("BPCALIBVAL invalid sbp/dbp\r\n");
+    return ERRCODE_FAIL;
+  }
+
+  uapi_set_screen_on_api();
+  OHOS::BPMainPresenter *presenter = OHOS::BPMainPresenter::GetInstance();
+  if (presenter == nullptr) {
+    PRINT("BPCALIBVAL fail: presenter not ready\r\n");
+    return ERRCODE_FAIL;
+  }
+  presenter->NotifyCalibrationComplete(static_cast<uint8_t>(sbp), static_cast<uint8_t>(dbp));
+  PRINT("BPCALIBVAL ok: sbp=%d dbp=%d\r\n", sbp, dbp);
+  return ERRCODE_SUCC;
+}
+
+static uint32_t bp_meas_val_at(uint8_t *para, uint32_t paraLen, char *strCommonCmd) {
+  (void)strCommonCmd;
+  if (paraLen == 0 || (para != nullptr && paraLen >= 1 && para[0] == '?')) {
+    PRINT("AT^BPMEASVAL=sbp,dbp\r\n");
+    return ERRCODE_SUCC;
+  }
+
+  uint8_t argv[BP_AT_MAX_ARGC][BP_AT_MAX_ARGV_LEN] = {{0}};
+  uint32_t argc = 0;
+  if (BpAtConvertParam(para, paraLen, argv, &argc) != 0 || argc < 2) {
+    PRINT("BPMEASVAL need sbp,dbp\r\n");
+    return ERRCODE_FAIL;
+  }
+
+  int sbp = atoi(reinterpret_cast<char *>(argv[0]));
+  int dbp = atoi(reinterpret_cast<char *>(argv[1]));
+  if (sbp < 0 || sbp > 255 || dbp < 0 || dbp > 255) {
+    PRINT("BPMEASVAL invalid sbp/dbp\r\n");
+    return ERRCODE_FAIL;
+  }
+
+  uapi_set_screen_on_api();
+  OHOS::BPMainPresenter *presenter = OHOS::BPMainPresenter::GetInstance();
+  if (presenter == nullptr) {
+    PRINT("BPMEASVAL fail: presenter not ready\r\n");
+    return ERRCODE_FAIL;
+  }
+  presenter->NotifyMeasureComplete(static_cast<uint8_t>(sbp), static_cast<uint8_t>(dbp));
+  PRINT("BPMEASVAL ok: sbp=%d dbp=%d\r\n", sbp, dbp);
+  return ERRCODE_SUCC;
+}
+
+static const at_cmd_table_t g_bp_ui_at_cmd[] = {
+    {(char *)"AT^BPUIVIEW", bp_ui_view_at, (char *)"viewId[,subType][,enterSlice]"},
+    {(char *)"AT^BPCALIBVAL", bp_calib_val_at, (char *)"sbp,dbp"},
+    {(char *)"AT^BPMEASVAL", bp_meas_val_at, (char *)"sbp,dbp"},
+};
+
+extern "C" at_cmd_table_t *get_bp_ui_at_cmd_table(uint32_t *cmd_count) {
+  if (cmd_count != nullptr) {
+    *cmd_count = BP_AT_ARRAY_COUNT(g_bp_ui_at_cmd);
+  }
+  return const_cast<at_cmd_table_t *>(g_bp_ui_at_cmd);
+}
diff --git a/code/sdk/application/wearable/n9/nativeapp/nativeui/bloodpressure/src/BPCalibFirstDoneView.cpp b/code/sdk/application/wearable/n9/nativeapp/nativeui/bloodpressure/src/BPCalibFirstDoneView.cpp
new file mode 100644 (file)
index 0000000..8483465
--- /dev/null
@@ -0,0 +1,89 @@
+#include "bloodpressure/include/BPCalibFirstDoneView.h"
+#include "bloodpressure/include/BPMainPresenter.h"
+#include "bloodpressure/include/BPViewClickIds.h"
+#include "uicomponents/UIComponents.h"
+
+namespace OHOS {
+
+namespace {
+static constexpr int16_t BP_FIRST_DONE_PROGRESS_X = 119;
+static constexpr int16_t BP_FIRST_DONE_PROGRESS_Y = 144;
+static constexpr int16_t BP_FIRST_DONE_PROGRESS_W = 236;
+static constexpr int16_t BP_FIRST_DONE_PROGRESS_H = 10;
+static constexpr int16_t BP_FIRST_DONE_PROGRESS_VALUE = 50;
+static constexpr uint32_t BP_FIRST_DONE_PROGRESS_BG = 0xff333333;
+static constexpr uint32_t BP_FIRST_DONE_PROGRESS_FG = 0xff4fc34c;
+} // namespace
+
+static BPCalibFirstDoneView *g_instance = nullptr;
+
+BPCalibFirstDoneView::BPCalibFirstDoneView() { g_instance = this; }
+
+BPCalibFirstDoneView::~BPCalibFirstDoneView() {
+  if (group_ != nullptr) {
+    group_->RemoveAll();
+    delete titleLabel_;
+    delete noteLabel_;
+    delete imageView_;
+    delete progressBar_;
+    delete RetryButton_;
+    delete group_;
+    titleLabel_ = nullptr;
+    noteLabel_ = nullptr;
+    imageView_ = nullptr;
+    progressBar_ = nullptr;
+    RetryButton_ = nullptr;
+    group_ = nullptr;
+  }
+  g_instance = nullptr;
+}
+
+BPCalibFirstDoneView *BPCalibFirstDoneView::GetInstance() { return g_instance; }
+
+void BPCalibFirstDoneView::InitProgressBar() {
+  progressBar_ = new UIBoxProgress();
+  if (progressBar_ == nullptr) {
+    return;
+  }
+  progressBar_->SetPosition(BP_FIRST_DONE_PROGRESS_X, BP_FIRST_DONE_PROGRESS_Y,
+                            BP_FIRST_DONE_PROGRESS_W, BP_FIRST_DONE_PROGRESS_H);
+  progressBar_->SetValidWidth(BP_FIRST_DONE_PROGRESS_W);
+  progressBar_->SetValidHeight(BP_FIRST_DONE_PROGRESS_H);
+  progressBar_->SetRange(100, 0);
+  progressBar_->SetValue(BP_FIRST_DONE_PROGRESS_VALUE);
+  progressBar_->SetStyle(STYLE_BORDER_RADIUS, 4);
+  progressBar_->EnableBackground(true);
+  progressBar_->SetBackgroundStyle(STYLE_BACKGROUND_COLOR, BP_FIRST_DONE_PROGRESS_BG);
+  progressBar_->SetBackgroundStyle(STYLE_BACKGROUND_OPA, 0xff);
+  progressBar_->SetForegroundStyle(STYLE_BACKGROUND_COLOR, BP_FIRST_DONE_PROGRESS_FG);
+  progressBar_->SetForegroundStyle(STYLE_BACKGROUND_OPA, 0xff);
+}
+
+UIViewGroup *BPCalibFirstDoneView::InitView() {
+  group_ = new UIViewGroup();
+  group_->SetPosition(0, 0, Screen::GetInstance().GetWidth(), Screen::GetInstance().GetHeight());
+  InitImageView(imageView_, 114, 50, 32, 32, "bp_first_done_image", BLOOD_PATH_FIRST_DONE);
+  InitLabel(titleLabel_, 114 + 32 + 8, 50, 198, 35, "bp_first_done_title", "第1轮校准完成",
+            DEFAULT_VECTOR_FONT_FILENAME, 30);
+  InitProgressBar();
+  InitLabel(noteLabel_, 83, 178, 300, 105, "bp_first_done_note",
+            "请准备开始第2轮校准\n请再次使用传统血压计\n测量同一只手",
+            DEFAULT_VECTOR_FONT_FILENAME, 30);
+  noteLabel_->SetTextColor(Color::GetColorFromRGBA(0xff, 0xff, 0xff, 0xb2));
+  noteLabel_->SetLineBreakMode(UILabel::LINE_BREAK_WRAP);
+  noteLabel_->SetAlign(TEXT_ALIGNMENT_CENTER, TEXT_ALIGNMENT_CENTER);
+
+  InitLabelButton(RetryButton_, 93, 330, 280, 90, BP_CLICK_FIRST_CALIB_DONE, "确定", 30);
+  RetryButton_->SetStyle(STYLE_BACKGROUND_COLOR, 0xff0176ff);
+  RetryButton_->SetOnClickListener(static_cast<UIView::OnClickListener *>(BPMainPresenter::GetInstance()));
+  group_->Add(imageView_);
+  group_->Add(titleLabel_);
+  if (progressBar_ != nullptr) {
+    group_->Add(progressBar_);
+  }
+  group_->Add(noteLabel_);
+  group_->Add(RetryButton_);
+  return group_;
+}
+
+} // namespace OHOS
diff --git a/code/sdk/application/wearable/n9/nativeapp/nativeui/bloodpressure/src/BPCalibSecondDoneView.cpp b/code/sdk/application/wearable/n9/nativeapp/nativeui/bloodpressure/src/BPCalibSecondDoneView.cpp
new file mode 100644 (file)
index 0000000..f55999b
--- /dev/null
@@ -0,0 +1,60 @@
+#include "bloodpressure/include/BPCalibSecondDoneView.h"
+#include "bloodpressure/include/BPEntryView.h"
+#include "bloodpressure/include/BPMainPresenter.h"
+#include "bloodpressure/include/BPViewClickIds.h"
+#include "uicomponents/UIComponents.h"
+
+namespace OHOS {
+
+static BPCalibSecondDoneView *g_instance = nullptr;
+
+BPCalibSecondDoneView::BPCalibSecondDoneView() { g_instance = this; }
+
+BPCalibSecondDoneView::~BPCalibSecondDoneView() {
+  if (group_) {
+    group_->RemoveAll();
+    delete titleLabel_;
+    delete timeLabel_;
+    delete noteLabel_;
+    delete confirmButton_;
+    delete imageView_;
+    delete group_;
+    titleLabel_ = nullptr;
+    timeLabel_ = nullptr;
+    noteLabel_ = nullptr;
+    confirmButton_ = nullptr;
+    imageView_ = nullptr;
+    group_ = nullptr;
+  }
+  g_instance = nullptr;
+}
+
+BPCalibSecondDoneView *BPCalibSecondDoneView::GetInstance() { return g_instance; }
+
+UIViewGroup *BPCalibSecondDoneView::InitView() {
+  group_ = new UIViewGroup();
+  group_->SetPosition(0, 0, Screen::GetInstance().GetWidth(), Screen::GetInstance().GetHeight());
+  InitImageView(imageView_, 193, 141, 80, 80, "bp_second_done_image", BLOOD_PATH_SUCC);
+  InitLabel(titleLabel_, 0, 50, 466, 35, "bp_second_done_title", "校准完成",
+            DEFAULT_VECTOR_FONT_FILENAME, 30);
+  titleLabel_->SetTextColor(Color::GetColorFromRGBA(0x4f, 0xc3, 0x4c, 0xff));
+
+  InitLabel(timeLabel_, 0, 90, 466, 28, "bp_second_done_time", "有效期至:----",
+            DEFAULT_VECTOR_FONT_FILENAME, 24);
+  InitLabel(noteLabel_, 0, 141 + 80 + 24, 466, 105, "bp_second_done_note",
+            "校准结果已上传\n30天后可重新校准", DEFAULT_VECTOR_FONT_FILENAME, 30);
+  noteLabel_->SetLineBreakMode(UILabel::LINE_BREAK_WRAP);
+  noteLabel_->SetAlign(TEXT_ALIGNMENT_CENTER, TEXT_ALIGNMENT_CENTER);
+
+  InitLabelButton(confirmButton_, 93, 340, 280, 90, BP_CLICK_SECOND_CALIB_DONE, "确定", 30);
+  confirmButton_->SetStyle(STYLE_BACKGROUND_COLOR, 0xff0176ff);
+  confirmButton_->SetOnClickListener(static_cast<UIView::OnClickListener *>(BPMainPresenter::GetInstance()));
+  group_->Add(imageView_);
+  group_->Add(titleLabel_);
+  group_->Add(timeLabel_);
+  group_->Add(noteLabel_);
+  group_->Add(confirmButton_);
+  return group_;
+}
+
+} // namespace OHOS
diff --git a/code/sdk/application/wearable/n9/nativeapp/nativeui/bloodpressure/src/BPCalibStopView.cpp b/code/sdk/application/wearable/n9/nativeapp/nativeui/bloodpressure/src/BPCalibStopView.cpp
new file mode 100644 (file)
index 0000000..d1d8879
--- /dev/null
@@ -0,0 +1,108 @@
+#include "bloodpressure/include/BPCalibStopView.h"
+#include "bloodpressure/include/BPMainPresenter.h"
+#include "bloodpressure/include/BPViewClickIds.h"
+#include "UiConfig.h"
+#include "uicomponents/UIComponents.h"
+#include "wearable_log.h"
+
+namespace OHOS {
+
+namespace {
+static constexpr int16_t BP_CALIB_STOP_BTN_X = 111;
+static constexpr int16_t BP_CALIB_STOP_BTN_Y = 299;
+static constexpr int16_t BP_CALIB_STOP_BTN_SIZE = 100;
+static constexpr int16_t BP_CALIB_STOP_BTN_GAP = 45;
+} // namespace
+
+static BPCalibStopView *g_instance = nullptr;
+
+BPCalibStopView::BPCalibStopView() { g_instance = this; }
+
+BPCalibStopView::~BPCalibStopView() {
+  if (group_ != nullptr) {
+    group_->RemoveAll();
+    delete titleLabel_;
+    delete noteLabel_;
+    delete cancelButton_;
+    delete confirmButton_;
+    delete group_;
+    titleLabel_ = nullptr;
+    noteLabel_ = nullptr;
+    cancelButton_ = nullptr;
+    confirmButton_ = nullptr;
+    group_ = nullptr;
+  }
+  g_instance = nullptr;
+}
+
+BPCalibStopView *BPCalibStopView::GetInstance() { return g_instance; }
+
+void BPCalibStopView::UpdateForMode(bool isMeasure) {
+  if (titleLabel_ != nullptr) {
+    titleLabel_->SetText(isMeasure ? "中止测量" : "中止校准");
+  }
+  if (noteLabel_ != nullptr) {
+    noteLabel_->SetText(isMeasure ? "是否中止当前血压测量\n本次测量将取消"
+                                  : "是否中止当前血压校准\n本次校准数据将丢弃");
+  }
+}
+
+void BPCalibStopView::InitRoundIconButton(UILabelButtonExt *&button, int16_t x, int16_t y,
+                                          const char *viewId, const char *imagePath) {
+  button = new UILabelButtonExt();
+  if (button == nullptr) {
+    WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "BPCalibStopView InitRoundIconButton fail");
+    return;
+  }
+  button->SetPosition(x, y, BP_CALIB_STOP_BTN_SIZE, BP_CALIB_STOP_BTN_SIZE);
+  button->SetViewId(viewId);
+  button->SetStyle(STYLE_BACKGROUND_COLOR, 0xffffffff);
+  button->SetStyle(STYLE_BACKGROUND_OPA, 25);
+  button->SetStyleForState(STYLE_BACKGROUND_COLOR, 0xffffffff, UIButton::PRESSED);
+  button->SetStyleForState(STYLE_BACKGROUND_OPA, 25, UIButton::PRESSED);
+  button->SetStyle(STYLE_BORDER_RADIUS, 100);
+  button->SetImagePosition(0, 0);
+  button->SetImageSrc(imagePath, imagePath);
+}
+
+UIViewGroup *BPCalibStopView::InitView() {
+  group_ = new UIViewGroup();
+  if (group_ == nullptr) {
+    return nullptr;
+  }
+  group_->SetPosition(0, 0, Screen::GetInstance().GetWidth(), Screen::GetInstance().GetHeight());
+
+  InitLabel(titleLabel_, 83, 50, 300, 35, "bp_calib_stop_title", "中止校准",
+            DEFAULT_VECTOR_FONT_FILENAME, 30);
+  titleLabel_->SetStyle(STYLE_TEXT_COLOR, 0xffff5a36);
+  titleLabel_->SetAlign(TEXT_ALIGNMENT_CENTER, TEXT_ALIGNMENT_CENTER);
+
+  InitLabel(noteLabel_, 53, 167, 360, 70, "bp_calib_stop_note",
+            "是否中止当前血压校准\n本次校准数据将丢弃", DEFAULT_VECTOR_FONT_FILENAME, 30);
+  noteLabel_->SetLineBreakMode(UILabel::LINE_BREAK_WRAP);
+  noteLabel_->SetAlign(TEXT_ALIGNMENT_CENTER, TEXT_ALIGNMENT_CENTER);
+
+  auto *listener = static_cast<UIView::OnClickListener *>(BPMainPresenter::GetInstance());
+  InitRoundIconButton(cancelButton_, BP_CALIB_STOP_BTN_X, BP_CALIB_STOP_BTN_Y,
+                      BP_CLICK_CALIB_STOP_CANCEL, PNG_SYSTEM_CHECK_OFF_IMAGE);
+  InitRoundIconButton(confirmButton_, BP_CALIB_STOP_BTN_X + BP_CALIB_STOP_BTN_SIZE + BP_CALIB_STOP_BTN_GAP,
+                      BP_CALIB_STOP_BTN_Y, BP_CLICK_CALIB_STOP_CONFIRM, PNG_SYSTEM_CHECK_ON_IMAGE);
+  if (cancelButton_ != nullptr) {
+    cancelButton_->SetOnClickListener(listener);
+  }
+  if (confirmButton_ != nullptr) {
+    confirmButton_->SetOnClickListener(listener);
+  }
+
+  group_->Add(titleLabel_);
+  group_->Add(noteLabel_);
+  if (cancelButton_ != nullptr) {
+    group_->Add(cancelButton_);
+  }
+  if (confirmButton_ != nullptr) {
+    group_->Add(confirmButton_);
+  }
+  return group_;
+}
+
+} // namespace OHOS
diff --git a/code/sdk/application/wearable/n9/nativeapp/nativeui/bloodpressure/src/BPCalibratingView.cpp b/code/sdk/application/wearable/n9/nativeapp/nativeui/bloodpressure/src/BPCalibratingView.cpp
new file mode 100644 (file)
index 0000000..fd9f8b8
--- /dev/null
@@ -0,0 +1,161 @@
+#include "bloodpressure/include/BPCalibratingView.h"
+#include "bloodpressure/include/BPModel.h"
+#include "UiConfig.h"
+
+#include "ui_resource_string.h"
+#include "uicomponents/UIComponents.h"
+#include "wearable_log.h"
+#include <string>
+
+namespace OHOS {
+
+namespace {
+static constexpr const char *BP_PROGRESS_BG_IMAGE = "/user/res/step/step_b.bin";
+static constexpr int16_t BP_CALIB_PROGRESS_MAX_SEC = 90;
+static constexpr uint32_t BP_STATUS_COLOR = 0xff5BB8FF;
+static constexpr uint32_t BP_PROGRESS_FG_COLOR = 0xff2195F6;
+static constexpr uint32_t BP_PROGRESS_FG_COLOR_END = 0xff5320E2;
+} // namespace
+
+static BPCalibratingView *g_instance = nullptr;
+
+BPCalibratingView::BPCalibratingView() { g_instance = this; }
+
+BPCalibratingView::~BPCalibratingView() {
+  if (group_ != nullptr) {
+    group_->RemoveAll();
+    delete progressBgImage_;
+    delete circleProgress_;
+    delete statusLabel_;
+    delete percentValue_;
+    delete percentSign_;
+    delete hintLabel_;
+    delete group_;
+    progressBgImage_ = nullptr;
+    circleProgress_ = nullptr;
+    statusLabel_ = nullptr;
+    percentValue_ = nullptr;
+    percentSign_ = nullptr;
+    hintLabel_ = nullptr;
+    group_ = nullptr;
+  }
+  g_instance = nullptr;
+}
+
+BPCalibratingView *BPCalibratingView::GetInstance() { return g_instance; }
+
+void BPCalibratingView::InitCircleProgress() {
+  circleProgress_ = new UICircleProgress();
+  if (circleProgress_ == nullptr) {
+    WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "BPCalibratingView circleProgress new fail");
+    return;
+  }
+  circleProgress_->SetPosition(0, 0);
+  circleProgress_->Resize(HORIZONTAL_RESOLUTION, VERTICAL_RESOLUTION);
+  circleProgress_->SetViewId("bp_calib_circle_progress");
+  circleProgress_->SetStyle(STYLE_BACKGROUND_OPA, 0x00);
+  circleProgress_->SetBackgroundStyle(STYLE_BACKGROUND_COLOR, 0xff000000);
+  circleProgress_->SetBackgroundStyle(STYLE_BACKGROUND_OPA, 0x00);
+  circleProgress_->SetBackgroundStyle(STYLE_BORDER_OPA, 0x00);
+  circleProgress_->SetBackgroundStyle(STYLE_LINE_OPA, 0x00);
+  circleProgress_->SetForegroundStyle(STYLE_BACKGROUND_COLOR, BP_PROGRESS_FG_COLOR);
+  circleProgress_->SetForegroundStyle(STYLE_BORDER_COLOR, 0xff000000);
+  circleProgress_->SetForegroundStyle(STYLE_LINE_COLOR, BP_PROGRESS_FG_COLOR_END);
+  circleProgress_->SetForegroundStyle(STYLE_LINE_WIDTH, 20);
+  circleProgress_->SetCenterPosition(233, 229);
+  circleProgress_->SetRadius(205);
+  circleProgress_->SetStartAngle(320);
+  circleProgress_->SetEndAngle(40);
+  circleProgress_->SetCapType(CAP_ROUND);
+  circleProgress_->SetValue(0);
+}
+
+void BPCalibratingView::UpdateProgress(int percent) {
+  if (percent < 0) {
+    percent = 0;
+  } else if (percent > 100) {
+    percent = 100;
+  }
+  if (circleProgress_ != nullptr) {
+    circleProgress_->SetValue(percent);
+    circleProgress_->SetCapType(CAP_ROUND);
+  }
+  if (percentValue_ != nullptr) {
+    percentValue_->SetText(std::to_string(percent).c_str());
+  }
+}
+
+void BPCalibratingView::UpdateRound(uint8_t round) {
+  if (statusLabel_ == nullptr) {
+    return;
+  }
+  const char *text = (round == 2) ? "第2轮校准中" : "第1轮校准中";
+  statusLabel_->SetText(text);
+}
+
+void BPCalibratingView::UpdateTimeText(int seconds) {
+  int percent = 0;
+  if (BP_CALIB_PROGRESS_MAX_SEC > 0 && seconds > 0) {
+    percent = seconds * 100 / BP_CALIB_PROGRESS_MAX_SEC;
+    if (percent > 100) {
+      percent = 100;
+    }
+  }
+  UpdateProgress(percent);
+}
+
+UIViewGroup *BPCalibratingView::InitView() {
+  group_ = new UIViewGroup();
+  if (group_ == nullptr) {
+    return nullptr;
+  }
+  group_->SetPosition(0, 0, Screen::GetInstance().GetWidth(), Screen::GetInstance().GetHeight());
+
+  InitImageView(progressBgImage_, 28, 80, 100, 100, "bp_calib_progress_bg", BP_PROGRESS_BG_IMAGE);
+
+  InitCircleProgress();
+
+  InitLabelDefault(statusLabel_, 0, 50, 466, 40, "bp_calib_status", "第1轮校准中", 30);
+  statusLabel_->SetTextColor(Color::GetColorFromRGBA(
+      (BP_STATUS_COLOR >> 16) & 0xFF, (BP_STATUS_COLOR >> 8) & 0xFF, BP_STATUS_COLOR & 0xFF, 0xFF));
+  statusLabel_->SetAlign(TEXT_ALIGNMENT_CENTER, TEXT_ALIGNMENT_CENTER);
+
+  percentValue_ = new UILabel();
+  percentValue_->SetPosition(175-44, 179);
+  percentValue_->Resize(132, 56);
+  percentValue_->SetViewId("bp_calib_percent");
+  percentValue_->SetText("0");
+  percentValue_->SetLineBreakMode(UILabel::LINE_BREAK_ADAPT);
+  percentValue_->SetTextColor(Color::GetColorFromRGBA(0x09, 0x8b, 0xea, 0xff));
+  percentValue_->SetAlign(TEXT_ALIGNMENT_RIGHT, TEXT_ALIGNMENT_CENTER);
+  percentValue_->SetFont(GILROY_BOLD_FONT, 80);
+
+  percentSign_ = new UILabel();
+  percentSign_->SetPosition(175+88+4, 179+56-24);
+  percentSign_->Resize(25, 24);
+  percentSign_->SetViewId("bp_calib_percent_sign");
+  percentSign_->SetText("%");
+  percentSign_->SetLineBreakMode(UILabel::LINE_BREAK_ADAPT);
+  percentSign_->SetTextColor(Color::GetColorFromRGBA(0x09, 0x8b, 0xea, 0xff));
+  percentSign_->SetAlign(TEXT_ALIGNMENT_LEFT, TEXT_ALIGNMENT_CENTER);
+  percentSign_->SetFont(DEFAULT_VECTOR_FONT_FILENAME, 34);
+
+  InitLabel(hintLabel_, 83, 278, 300, 40, "bp_calib_hint", "请保持静止",
+            DEFAULT_VECTOR_FONT_FILENAME, 30);
+  hintLabel_->SetAlign(TEXT_ALIGNMENT_CENTER, TEXT_ALIGNMENT_CENTER);
+
+  group_->Add(progressBgImage_);
+  if (circleProgress_ != nullptr) {
+    group_->Add(circleProgress_);
+  }
+  group_->Add(statusLabel_);
+  group_->Add(percentValue_);
+  group_->Add(percentSign_);
+  group_->Add(hintLabel_);
+
+  UpdateRound(BPModel::GetInstance()->GetCalibrationRound());
+  UpdateProgress(0);
+  return group_;
+}
+
+} // namespace OHOS
diff --git a/code/sdk/application/wearable/n9/nativeapp/nativeui/bloodpressure/src/BPEntryView.cpp b/code/sdk/application/wearable/n9/nativeapp/nativeui/bloodpressure/src/BPEntryView.cpp
new file mode 100644 (file)
index 0000000..902f340
--- /dev/null
@@ -0,0 +1,234 @@
+#include "bloodpressure/include/BPEntryView.h"
+#include "bloodpressure/include/BPMainPresenter.h"
+#include "bloodpressure/include/BPModel.h"
+#include "UiConfig.h"
+#include "ui_resource_string.h"
+#include "uicomponents/UIComponents.h"
+#include "wearable_log.h"
+#include "securec.h"
+
+namespace OHOS {
+
+static BPEntryView *g_pBPEntryView = nullptr;
+
+BPEntryView::BPEntryView() {
+  g_pBPEntryView = this;
+}
+
+BPEntryView::~BPEntryView() {
+  if (group_ != nullptr) {
+    group_->RemoveAll();
+    delete titleLabel_;
+    delete instructionLabel_;
+    delete instructionButton_;
+    delete measureButton_;
+    delete imageView_;
+    delete calibWarnBg_;
+    delete calibWarnIcon_;
+    delete calibWarnLabel_;
+    delete dotEntry_;
+    delete dotHistory_;
+    delete group_;
+    titleLabel_ = nullptr;
+    instructionLabel_ = nullptr;
+    instructionButton_ = nullptr;
+    measureButton_ = nullptr;
+    imageView_ = nullptr;
+    calibWarnBg_ = nullptr;
+    calibWarnIcon_ = nullptr;
+    calibWarnLabel_ = nullptr;
+    dotEntry_ = nullptr;
+    dotHistory_ = nullptr;
+    group_ = nullptr;
+  }
+  g_pBPEntryView = nullptr;
+}
+
+BPEntryView *BPEntryView::GetInstance() { return g_pBPEntryView; }
+
+
+UIViewGroup *BPEntryView::InitView() {
+  group_ = new UIViewGroup();
+  if (group_ == nullptr) {
+    return nullptr;
+  }
+  group_->SetPosition(0, 0, Screen::GetInstance().GetWidth(),
+                      Screen::GetInstance().GetHeight());
+
+  //获取当前状态
+  currentState_ = BPModel::GetInstance()->GetCurrentstate();
+
+  //根据当前状态显示对应视图
+  if (currentState_ == BP_CALIB_STATE_VALID) {
+    InitCalibratedView();
+  } else if (currentState_ == BP_CALIB_STATE_EXPIRED) {
+    InitCalibrationExpiredView();
+  } else if (currentState_ == BP_CALIB_STATE_NOT_CALIBRATED) {
+    //未校准
+    InitNotCalibratedView();
+  }
+  InitPageIndicator();
+  return group_;
+}
+
+void BPEntryView::InitPageIndicator() {
+  dotEntry_ = new UIView();
+  dotHistory_ = new UIView();
+  if (dotEntry_ == nullptr || dotHistory_ == nullptr || group_ == nullptr) {
+    return;
+  }
+  dotEntry_->SetPosition(213, 430, 10, 10);
+  dotEntry_->SetStyle(STYLE_BACKGROUND_COLOR, 0xffffffff);
+  dotEntry_->SetStyle(STYLE_BACKGROUND_OPA, 0xff);
+  dotEntry_->SetStyle(STYLE_BORDER_RADIUS, 5);
+  dotHistory_->SetPosition(243, 430, 10, 10);
+  dotHistory_->SetStyle(STYLE_BACKGROUND_COLOR, 0xff666666);
+  dotHistory_->SetStyle(STYLE_BACKGROUND_OPA, 0xff);
+  dotHistory_->SetStyle(STYLE_BORDER_RADIUS, 5);
+  group_->Add(dotEntry_);
+  group_->Add(dotHistory_);
+}
+
+void BPEntryView::InitCalibExpiringSoonBar() {
+  BPModel *model = BPModel::GetInstance();
+  if (model == nullptr || !model->IsCalibrationExpiringSoon() || group_ == nullptr) {
+    return;
+  }
+
+  int32_t daysLeft = model->GetCalibrationRemainingDays();
+  if (daysLeft < 0) {
+    return;
+  }
+
+  char textBuf[48] = {0};
+  if (sprintf_s(textBuf, sizeof(textBuf), "校准即将过期·剩%d天", daysLeft) < 0) {
+    return;
+  }
+
+  calibWarnBg_ = new UIView();
+  calibWarnIcon_ = new UIImageView();
+  calibWarnLabel_ = new UILabelExt();
+  if (calibWarnBg_ == nullptr || calibWarnIcon_ == nullptr || calibWarnLabel_ == nullptr) {
+    delete calibWarnBg_;
+    delete calibWarnIcon_;
+    delete calibWarnLabel_;
+    calibWarnBg_ = nullptr;
+    calibWarnIcon_ = nullptr;
+    calibWarnLabel_ = nullptr;
+    return;
+  }
+
+  calibWarnBg_->SetPosition(67, 101, 331,51);
+  calibWarnBg_->SetStyle(STYLE_BACKGROUND_COLOR, 0xff2e241b);
+  calibWarnBg_->SetStyle(STYLE_BACKGROUND_OPA, 0xff);
+  calibWarnBg_->SetStyle(STYLE_BORDER_RADIUS, 20);
+
+  calibWarnIcon_->SetPosition(67 + 12, 101 + 12, 26, 26);
+  calibWarnIcon_->SetSrc(BLOOD_PATH_WARNING_S);
+
+  calibWarnLabel_->SetPosition(67 + 26 + 12 + 8, 101+8, 273,35);
+  calibWarnLabel_->SetViewId("bp_calib_warn_label");
+  calibWarnLabel_->SetText(textBuf);
+  calibWarnLabel_->SetFont(DEFAULT_VECTOR_FONT_FILENAME, 30);
+  calibWarnLabel_->SetTextColor(Color::GetColorFromRGBA(0xe7, 0x96, 0x44, 0xff));
+  calibWarnLabel_->SetAlign(TEXT_ALIGNMENT_LEFT, TEXT_ALIGNMENT_CENTER);
+  calibWarnLabel_->SetLineBreakMode(UILabel::LINE_BREAK_ELLIPSIS);
+
+  group_->Add(calibWarnBg_);
+  group_->Add(calibWarnIcon_);
+  group_->Add(calibWarnLabel_);
+}
+
+void BPEntryView::InitNoCalibratedBar() {
+  BPModel *model = BPModel::GetInstance();
+  if (model == nullptr || group_ == nullptr) {
+    return;
+  }
+
+  calibWarnBg_ = new UIView();
+  calibWarnIcon_ = new UIImageView();
+  calibWarnLabel_ = new UILabelExt();
+  if (calibWarnBg_ == nullptr || calibWarnIcon_ == nullptr || calibWarnLabel_ == nullptr) {
+    delete calibWarnBg_;
+    delete calibWarnIcon_;
+    delete calibWarnLabel_;
+    calibWarnBg_ = nullptr;
+    calibWarnIcon_ = nullptr;
+    calibWarnLabel_ = nullptr;
+    return;
+  }
+
+  calibWarnBg_->SetPosition(159, 101, 148,51);
+  calibWarnBg_->SetStyle(STYLE_BACKGROUND_COLOR, 0xff2e241b);
+  calibWarnBg_->SetStyle(STYLE_BACKGROUND_OPA, 0xff);
+  calibWarnBg_->SetStyle(STYLE_BORDER_RADIUS, 20);
+
+  calibWarnIcon_->SetPosition(159 + 12, 101 + 12, 26, 26);
+  calibWarnIcon_->SetSrc(BLOOD_PATH_WARNING_S);
+
+  calibWarnLabel_->SetPosition(159 + 26 + 12 + 8, 101+8, 90,35);
+  calibWarnLabel_->SetViewId("bp_calib_warn_label");
+  calibWarnLabel_->SetText("未校准");
+  calibWarnLabel_->SetFont(DEFAULT_VECTOR_FONT_FILENAME, 30);
+  calibWarnLabel_->SetTextColor(Color::GetColorFromRGBA(0xff, 0x5a, 0x36, 0xff));
+  calibWarnLabel_->SetAlign(TEXT_ALIGNMENT_LEFT, TEXT_ALIGNMENT_CENTER);
+  calibWarnLabel_->SetLineBreakMode(UILabel::LINE_BREAK_ELLIPSIS);
+
+  group_->Add(calibWarnBg_);
+  group_->Add(calibWarnIcon_);
+  group_->Add(calibWarnLabel_);
+}
+
+void BPEntryView::InitCalibratedView() {
+  //初始化已校准视图
+  InitUILabel(titleLabel_, 0, 50, 466, 35, "bp_entry_title", "血压测量",
+               DEFAULT_VECTOR_FONT_FILENAME, 30);
+  InitCalibExpiringSoonBar();
+  InitUILabel(instructionLabel_, 68, 198, 330, 70, "bp_instruction_label", "测量前30分钟\n请避免剧烈运动、咖啡和烟酒",
+              DEFAULT_VECTOR_FONT_FILENAME, 30);
+  InitLabelButton(instructionButton_, 58, 320, 170, 90, BP_CLICK_INSTRUCTION, "测量说明", 26);
+  InitLabelButton(measureButton_, 58+170+10, 320, 170, 90, BP_CLICK_MEASURE, "马上测量", 26);
+  group_->Add(titleLabel_);
+  group_->Add(instructionLabel_);
+  auto *listener = static_cast<UIView::OnClickListener *>(BPMainPresenter::GetInstance());
+  instructionButton_->SetOnClickListener(listener);
+  measureButton_->SetOnClickListener(listener);
+  group_->Add(instructionButton_);
+  group_->Add(measureButton_);
+}
+
+void BPEntryView::InitCalibrationExpiredView() {
+  //初始化校准已过期视图
+  InitUILabel(titleLabel_, 0, 50, 466, 35, "bp_entry_title", "校准已过期",
+    DEFAULT_VECTOR_FONT_FILENAME, 30);
+  titleLabel_->SetTextColor(Color::GetColorFromRGBA(0xff, 0x5a, 0x36, 0xff));
+  InitImageView(imageView_, 193, 120, 80, 80, "bp_calibration_expired_image", BLOOD_PATH_CALIBRATION_EXPIRED);
+  InitUILabel(instructionLabel_, 53, 230, 360, 70, "bp_instruction_label", "继续测量的数据仅供\n趋势参考",
+   DEFAULT_VECTOR_FONT_FILENAME, 30);
+  InitLabelButton(instructionButton_, 58, 320, 170, 90, BP_CLICK_MEASURE, "马上测量", 26);
+  InitLabelButton(measureButton_, 238, 320, 170, 90, BP_CLICK_RECALIBRATE, "重新校准", 26);
+  auto *listener = static_cast<UIView::OnClickListener *>(BPMainPresenter::GetInstance());
+  instructionButton_->SetOnClickListener(listener);
+  measureButton_->SetOnClickListener(listener);
+  group_->Add(titleLabel_);
+  group_->Add(imageView_);
+  group_->Add(instructionLabel_);
+  group_->Add(instructionButton_);
+  group_->Add(measureButton_);
+}
+
+void BPEntryView::InitNotCalibratedView() {
+  //初始化未校准视图
+  InitUILabel(titleLabel_, 0, 50, 466, 35, "bp_entry_title", "血压测量",
+    DEFAULT_VECTOR_FONT_FILENAME, 30);
+  InitNoCalibratedBar();
+  InitUILabel(instructionLabel_, 33, 152, 400, 162, "bp_instruction_label", "请先完成校准",
+    DEFAULT_VECTOR_FONT_FILENAME, 30);
+  InitLabelButton(measureButton_, 93, 320, 280, 90, BP_CLICK_MEASURE, "开始校准", 26);
+  measureButton_->SetOnClickListener(static_cast<UIView::OnClickListener *>(BPMainPresenter::GetInstance()));
+  group_->Add(titleLabel_);
+  group_->Add(instructionLabel_);
+  group_->Add(measureButton_);
+}
+
+} // namespace OHOS
diff --git a/code/sdk/application/wearable/n9/nativeapp/nativeui/bloodpressure/src/BPHistoryView.cpp b/code/sdk/application/wearable/n9/nativeapp/nativeui/bloodpressure/src/BPHistoryView.cpp
new file mode 100644 (file)
index 0000000..bf87fa0
--- /dev/null
@@ -0,0 +1,443 @@
+#include "bloodpressure/include/BPHistoryView.h"
+#include "bloodpressure/include/BPMainPresenter.h"
+#include "bloodpressure/include/BPModel.h"
+#include "components/ui_button.h"
+#include "common/image_cache_manager.h"
+#include "common/screen.h"
+#include "uicomponents/UIComponents.h"
+#include "wearable_log.h"
+
+namespace OHOS {
+
+namespace {
+static constexpr uint32_t BP_COLOR_LINE = 0xff333333;
+static constexpr int16_t BP_RESULT_LEFT_X = 56;
+static constexpr int16_t BP_RESULT_RIGHT_X = 56+233;
+static constexpr int16_t BP_HISTORY_PULL_X = 213;
+static constexpr int16_t BP_HISTORY_PULL_Y = 360;
+static constexpr int16_t BP_HISTORY_PULL_W = 40;
+static constexpr int16_t BP_HISTORY_PULL_H = 40;
+static constexpr int16_t BP_HISTORY_PULL_NUM = 70;
+static constexpr int16_t BP_HISTORY_PULL_DELAY = 100;
+static constexpr int16_t BP_HISTORY_MAX_ITEMS = 10;
+static constexpr const char *BP_PULLUP_PATH = "/user/res/weather/PullUp/PullUp_";
+static constexpr const char *BP_NO_RECORD_IMAGE = "/user/res/Training/modules/Norecord.bin";
+} // namespace
+
+static BPHistoryView *g_pBPHistoryView = nullptr;
+
+BPHistoryView::BPHistoryView() { g_pBPHistoryView = this; }
+
+BPHistoryView::~BPHistoryView() {
+  StopArrowAnimation();
+  UnloadPullDownAnimator();
+  ClearRecordPages();
+  if (group_ != nullptr) {
+    group_->RemoveAll();
+    delete recordSwipeView_;
+    delete emptyPage_;
+    delete dotEntry_;
+    delete dotHistory_;
+    delete group_;
+    recordSwipeView_ = nullptr;
+    emptyPage_ = nullptr;
+    dotEntry_ = nullptr;
+    dotHistory_ = nullptr;
+    group_ = nullptr;
+  }
+  emptyTitleLabel_ = nullptr;
+  emptyImage_ = nullptr;
+  emptyHintLabel_ = nullptr;
+  startMeasureButton_ = nullptr;
+  pullDownAnimator_ = nullptr;
+  g_pBPHistoryView = nullptr;
+}
+
+BPHistoryView *BPHistoryView::GetInstance() { return g_pBPHistoryView; }
+
+//判断血压是否偏高
+bool BPHistoryView::IsHighBloodPressure(uint8_t sbp, uint8_t dbp) {
+  return sbp >= 140 || dbp >= 90;
+}
+
+//格式化记录时间
+std::string BPHistoryView::FormatRecordTime(uint32_t timestamp) {
+  return BPModel::GetInstance()->FormatShortTimestamp(timestamp);
+}
+
+//清除记录页面
+void BPHistoryView::ClearRecordPages() {
+  if (recordSwipeView_ != nullptr) {
+    recordSwipeView_->RemoveAll();
+  }
+  for (auto *page : recordPages_) {
+    delete page;
+  }
+  recordPages_.clear();
+}
+
+//初始化页面指示器
+void BPHistoryView::InitPageIndicator() {
+  dotEntry_ = new UIView();
+  dotHistory_ = new UIView();
+  if (dotEntry_ == nullptr || dotHistory_ == nullptr) {
+    return;
+  }
+  dotEntry_->SetPosition(213, 430, 10, 10);
+  dotEntry_->SetStyle(STYLE_BACKGROUND_COLOR, 0xff666666);
+  dotEntry_->SetStyle(STYLE_BACKGROUND_OPA, 0xff);
+  dotEntry_->SetStyle(STYLE_BORDER_RADIUS, 5);
+  dotHistory_->SetPosition(243, 430, 10, 10);
+  dotHistory_->SetStyle(STYLE_BACKGROUND_COLOR, 0xffffffff);
+  dotHistory_->SetStyle(STYLE_BACKGROUND_OPA, 0xff);
+  dotHistory_->SetStyle(STYLE_BORDER_RADIUS, 5);
+}
+
+//初始化下拉动画
+void BPHistoryView::InitPullDownAnimator() {
+  if (pullDownInited_) {
+    return;
+  }
+  pullUpImageInfo_ = CreateImageInfoArray(BP_PULLUP_PATH, BP_HISTORY_PULL_NUM);
+  if (pullUpImageInfo_ == nullptr) {
+    WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "BPHistoryView CreateImageInfoArray fail");
+    return;
+  }
+  pullDownAnimator_ = new UIImageAnimatorView();
+  if (pullDownAnimator_ == nullptr) {
+    WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "BPHistoryView pullDownAnimator new fail");
+    return;
+  }
+  for (int16_t i = 0; i < BP_HISTORY_PULL_NUM; i++) {
+    pullDownFrameInfo_[i].imageInfo = pullUpImageInfo_[BP_HISTORY_PULL_NUM - 1 - i];
+    pullDownFrameInfo_[i].imageType = IMG_SRC_IMAGE_INFO;
+    pullDownFrameInfo_[i].pos.x = BP_HISTORY_PULL_X;
+    pullDownFrameInfo_[i].pos.y = BP_HISTORY_PULL_Y;
+    pullDownFrameInfo_[i].width = BP_HISTORY_PULL_W;
+    pullDownFrameInfo_[i].height = BP_HISTORY_PULL_H;
+  }
+  pullDownAnimator_->SetPosition(BP_HISTORY_PULL_X, BP_HISTORY_PULL_Y, BP_HISTORY_PULL_W,
+                                 BP_HISTORY_PULL_H);
+  pullDownAnimator_->SetImageAnimatorSrc(pullDownFrameInfo_, BP_HISTORY_PULL_NUM,
+                                         BP_HISTORY_PULL_DELAY);
+  pullDownAnimator_->SetRepeat(true);
+  pullDownAnimator_->SetAutoEnable(false);
+  pullDownAnimator_->SetResizeMode(UIImageView::FILL);
+  pullDownAnimator_->SetTouchable(false);
+  pullDownInited_ = true;
+}
+
+//卸载下拉动画
+void BPHistoryView::UnloadPullDownAnimator() {
+  if (!pullDownInited_) {
+    return;
+  }
+  if (pullDownAnimator_ != nullptr) {
+    delete pullDownAnimator_;
+    pullDownAnimator_ = nullptr;
+  }
+  if (pullUpImageInfo_ != nullptr) {
+    FreeImageInfoArray(pullUpImageInfo_, BP_PULLUP_PATH, BP_HISTORY_PULL_NUM);
+    pullUpImageInfo_ = nullptr;
+  }
+  pullDownInited_ = false;
+}
+
+//开始箭头动画
+void BPHistoryView::StartArrowAnimation() {
+  const auto &records = BPModel::GetInstance()->GetHistoryRecords();
+  if (records.empty() || currentRecordIndex_ + 1 >= records.size()) {
+    StopArrowAnimation();
+    return;
+  }
+  if (!pullDownInited_) {
+    InitPullDownAnimator();
+  }
+  if (pullDownAnimator_ != nullptr) {
+    pullDownAnimator_->SetVisible(true);
+    pullDownAnimator_->Start();
+  }
+}
+
+//停止箭头动画
+void BPHistoryView::StopArrowAnimation() {
+  if (pullDownAnimator_ != nullptr) {
+    pullDownAnimator_->Stop();
+  }
+}
+
+//隐藏箭头动画
+void BPHistoryView::HideArrowWhileDragging() {
+  if (pullDownAnimator_ != nullptr) {
+    pullDownAnimator_->SetVisible(false);
+  }
+}
+//创建记录页面
+UIViewGroup *BPHistoryView::CreateRecordPage(const BPRecord &record, size_t index) {
+  UIViewGroup *page = new UIViewGroup();
+  if (page == nullptr) {
+    return nullptr;
+  }
+  page->SetPosition(0, 0, Screen::GetInstance().GetWidth(), Screen::GetInstance().GetHeight());
+
+  UILabel *titleLabel = nullptr;
+  UILabel *timeLabel = nullptr;
+  UIImageView *hlineTop = nullptr;
+  UIImageView *hlineBottom = nullptr;
+  UIImageView *vlineMid = nullptr;
+  UILabelExt *sbpTitle = nullptr;
+  UILabelExt *dbpTitle = nullptr;
+  UILabel *sbpValue = nullptr;
+  UILabel *dbpValue = nullptr;
+  UILabel *sbpUnit = nullptr;
+  UILabel *dbpUnit = nullptr;
+
+  InitUILabel(titleLabel, 0, 50, 466, 35, "bp_history_record_title", index == 0 ? "最新记录" : "最近记录",
+              DEFAULT_VECTOR_FONT_FILENAME, 30);
+  if (index == 0) {
+    titleLabel->SetTextColor(Color::GetColorFromRGBA(0x4f, 0xc3, 0x4c, 0xff));
+  } else {
+    titleLabel->SetTextColor(Color::GetColorFromRGBA(0xff, 0xff, 0xff, 0xff));
+  }
+  titleLabel->SetAlign(TEXT_ALIGNMENT_CENTER, TEXT_ALIGNMENT_CENTER);
+
+  std::string timeStr = FormatRecordTime(record.timestamp);
+  InitUILabel(timeLabel, 0, 93, 466, 33, "bp_history_record_time", timeStr.c_str(),
+              DEFAULT_VECTOR_FONT_FILENAME, 28);
+  timeLabel->SetTextColor(Color::GetColorFromRGBA(0x99, 0x99, 0x99, 0xff));
+  timeLabel->SetAlign(TEXT_ALIGNMENT_CENTER, TEXT_ALIGNMENT_CENTER);
+
+  hlineTop = new UIImageView();
+  if (hlineTop != nullptr) {
+    hlineTop->SetPosition(0, 158, 466, 2);
+    hlineTop->SetStyle(STYLE_BACKGROUND_COLOR, BP_COLOR_LINE);
+    hlineTop->SetStyle(STYLE_BACKGROUND_OPA, 0xff);
+  }
+  hlineBottom = new UIImageView();
+  if (hlineBottom != nullptr) {
+    hlineBottom->SetPosition(0, 308, 466, 2);
+    hlineBottom->SetStyle(STYLE_BACKGROUND_COLOR, BP_COLOR_LINE);
+    hlineBottom->SetStyle(STYLE_BACKGROUND_OPA, 0xff);
+  }
+  vlineMid = new UIImageView();
+  if (vlineMid != nullptr) {
+    vlineMid->SetPosition(233, 188, 2, 90);
+    vlineMid->SetStyle(STYLE_BACKGROUND_COLOR, BP_COLOR_LINE);
+    vlineMid->SetStyle(STYLE_BACKGROUND_OPA, 0xff);
+  }
+
+  InitLabel(sbpTitle, 0, 158+30+5, 233, 35, "bp_history_sbp_title", "收缩压",
+            DEFAULT_VECTOR_FONT_FILENAME, 30);
+  InitLabel(dbpTitle, 233, 158+30+5, 233, 35, "bp_history_dbp_title", "舒张压",
+            DEFAULT_VECTOR_FONT_FILENAME, 30);
+  sbpTitle->SetAlign(TEXT_ALIGNMENT_CENTER, TEXT_ALIGNMENT_CENTER);
+  dbpTitle->SetAlign(TEXT_ALIGNMENT_CENTER, TEXT_ALIGNMENT_CENTER);
+  sbpTitle->SetTextColor(Color::GetColorFromRGBA(0xff, 0xff, 0xff, 0xff));
+  dbpTitle->SetTextColor(Color::GetColorFromRGBA(0xff, 0xff, 0xff, 0xff));
+
+  std::string sbpStr = std::to_string(record.sbp);
+  std::string dbpStr = std::to_string(record.dbp);
+  InitUILabel(sbpValue, BP_RESULT_LEFT_X, 250, 58, 28, "bp_history_sbp", sbpStr.c_str(),
+              DEFAULT_VECTOR_FONT_FILENAME, 40);
+  InitUILabel(dbpValue, BP_RESULT_RIGHT_X, 250, 58, 28, "bp_history_dbp", dbpStr.c_str(),
+              DEFAULT_VECTOR_FONT_FILENAME, 40);
+  sbpValue->SetAlign(TEXT_ALIGNMENT_CENTER, TEXT_ALIGNMENT_CENTER);
+  dbpValue->SetAlign(TEXT_ALIGNMENT_CENTER, TEXT_ALIGNMENT_CENTER);
+
+  if (IsHighBloodPressure(record.sbp, record.dbp)) {
+    sbpValue->SetTextColor(Color::GetColorFromRGBA(0xff, 0x5a, 0x36, 0xff));
+    dbpValue->SetTextColor(Color::GetColorFromRGBA(0xff, 0x5a, 0x36, 0xff));
+  }
+
+  InitUILabel(sbpUnit, BP_RESULT_LEFT_X+4, 264, 62, 14, "bp_history_sbp_unit", "mmHg",
+              DEFAULT_VECTOR_FONT_FILENAME, 20);
+  InitUILabel(dbpUnit, BP_RESULT_RIGHT_X+4, 264, 62, 14, "bp_history_dbp_unit", "mmHg",
+              DEFAULT_VECTOR_FONT_FILENAME, 20);
+  sbpUnit->SetAlign(TEXT_ALIGNMENT_CENTER, TEXT_ALIGNMENT_CENTER);
+  dbpUnit->SetAlign(TEXT_ALIGNMENT_CENTER, TEXT_ALIGNMENT_CENTER);
+  sbpUnit->SetTextColor(Color::GetColorFromRGBA(0x99, 0x99, 0x99, 0xff));
+  dbpUnit->SetTextColor(Color::GetColorFromRGBA(0x99, 0x99, 0x99, 0xff));
+
+  page->Add(titleLabel);
+  page->Add(timeLabel);
+  if (hlineTop != nullptr) {
+    page->Add(hlineTop);
+  }
+  if (vlineMid != nullptr) {
+    page->Add(vlineMid);
+  }
+  page->Add(sbpTitle);
+  page->Add(dbpTitle);
+  page->Add(sbpValue);
+  page->Add(dbpValue);
+  page->Add(sbpUnit);
+  page->Add(dbpUnit);
+  if (hlineBottom != nullptr) {
+    page->Add(hlineBottom);
+  }
+  return page;
+}
+
+//重建记录页面
+void BPHistoryView::RebuildRecordPages() {
+  if (recordSwipeView_ == nullptr) {
+    return;
+  }
+  ClearRecordPages();
+  const auto &records = BPModel::GetInstance()->GetHistoryRecords();
+  size_t count = records.size();
+  if (count > BP_HISTORY_MAX_ITEMS) {
+    count = BP_HISTORY_MAX_ITEMS;
+  }
+  for (size_t i = 0; i < count; i++) {
+    UIViewGroup *page = CreateRecordPage(records[i], i);
+    if (page == nullptr) {
+      continue;
+    }
+    page->SetViewIndex(static_cast<uint16_t>(i));
+    recordSwipeView_->Add(page);
+    recordPages_.push_back(page);
+  }
+  recordSwipeView_->SetCurrentPage(0, false);
+  currentRecordIndex_ = 0;
+}
+
+//初始化空页面
+void BPHistoryView::InitEmptyPage() {
+  emptyPage_ = new UIViewGroup();
+  if (emptyPage_ == nullptr) {
+    return;
+  }
+  emptyPage_->SetPosition(0, 0, Screen::GetInstance().GetWidth(),
+                          Screen::GetInstance().GetHeight());
+  emptyPage_->SetDraggable(true);
+  emptyPage_->SetTouchable(true);
+  emptyPage_->SetOnDragListener(
+      static_cast<UIView::OnDragListener *>(BPMainPresenter::GetInstance()));
+
+  InitUILabel(emptyTitleLabel_, 0, 50, 466, 35, "bp_history_empty_title", "暂无测量记录",
+              DEFAULT_VECTOR_FONT_FILENAME, 30);
+  emptyTitleLabel_->SetAlign(TEXT_ALIGNMENT_CENTER, TEXT_ALIGNMENT_CENTER);
+
+  InitImageView(emptyImage_, 174, 120, 118, 120, "bp_history_empty_image", BP_NO_RECORD_IMAGE);
+
+  InitUILabel(emptyHintLabel_, 53, 260, 360, 70, "bp_history_empty_hint",
+              "完成一次测量后可在这里查看记录", DEFAULT_VECTOR_FONT_FILENAME, 28);
+  emptyHintLabel_->SetAlign(TEXT_ALIGNMENT_CENTER, TEXT_ALIGNMENT_CENTER);
+  emptyHintLabel_->SetLineBreakMode(UILabel::LINE_BREAK_WRAP);
+
+  InitLabelButton(startMeasureButton_, 93, 330, 280, 90, BP_CLICK_MEASURE, "开始测量", 30);
+  startMeasureButton_->SetStyle(STYLE_BACKGROUND_COLOR, 0xff4fc34c);
+  startMeasureButton_->SetStyleForState(STYLE_BACKGROUND_COLOR, 0xff4fc34c, UIButton::PRESSED);
+  startMeasureButton_->SetStyle(STYLE_BORDER_RADIUS, 45);
+  startMeasureButton_->SetOnClickListener(
+      static_cast<UIView::OnClickListener *>(BPMainPresenter::GetInstance()));
+
+  emptyPage_->Add(emptyTitleLabel_);
+  emptyPage_->Add(emptyImage_);
+  emptyPage_->Add(emptyHintLabel_);
+  emptyPage_->Add(startMeasureButton_);
+}
+
+//更新箭头可见性
+void BPHistoryView::UpdateArrowVisibility() {
+  if (pullDownAnimator_ == nullptr) {
+    return;
+  }
+  const auto &records = BPModel::GetInstance()->GetHistoryRecords();
+  size_t count = records.size();
+  if (count > BP_HISTORY_MAX_ITEMS) {
+    count = BP_HISTORY_MAX_ITEMS;
+  }
+  bool hasNext = count > 0 && currentRecordIndex_ + 1 < count;
+  pullDownAnimator_->SetVisible(hasNext);
+  if (hasNext) {
+    pullDownAnimator_->Start();
+  } else {
+    pullDownAnimator_->Stop();
+  }
+}
+
+//更新页面可见性
+void BPHistoryView::UpdatePageVisibility() {
+  const auto &records = BPModel::GetInstance()->GetHistoryRecords();
+  bool hasRecords = !records.empty();
+  if (recordSwipeView_ != nullptr) {
+    recordSwipeView_->SetVisible(hasRecords);
+  }
+  if (pullDownAnimator_ != nullptr) {
+    pullDownAnimator_->SetVisible(hasRecords);
+  }
+  if (emptyPage_ != nullptr) {
+    emptyPage_->SetVisible(!hasRecords);
+  }
+  if (dotEntry_ != nullptr && dotHistory_ != nullptr) {
+    dotEntry_->SetVisible(!hasRecords);
+    dotHistory_->SetVisible(!hasRecords);
+  }
+}
+
+//记录页面改变
+void BPHistoryView::OnRecordPageChanged(uint16_t pageIndex) {
+  currentRecordIndex_ = pageIndex;
+  UpdateArrowVisibility();
+  if (group_ != nullptr) {
+    group_->Invalidate();
+  }
+}
+
+//刷新页面
+void BPHistoryView::RefreshView() {
+  RebuildRecordPages();
+  UpdatePageVisibility();
+  UpdateArrowVisibility();
+  if (group_ != nullptr) {
+    group_->Invalidate();
+  }
+}
+
+//初始化页面
+UIViewGroup *BPHistoryView::InitView() {
+  group_ = new UIViewGroup();
+  if (group_ == nullptr) {
+    return nullptr;
+  }
+  group_->SetPosition(0, 0, Screen::GetInstance().GetWidth(),
+                      Screen::GetInstance().GetHeight());
+
+  recordSwipeView_ = new UISwipeView(UISwipeView::VERTICAL);
+  if (recordSwipeView_ == nullptr) {
+    return group_;
+  }
+  recordSwipeView_->SetPosition(0, 0, Screen::GetInstance().GetWidth(),
+                                Screen::GetInstance().GetHeight());
+  recordSwipeView_->SetAnimatorTime(50);
+  recordSwipeView_->SetDraggable(true);
+  recordSwipeView_->SetTouchable(true);
+  auto *presenter = static_cast<BPMainPresenter *>(BPMainPresenter::GetInstance());
+  recordSwipeView_->SetOnDragListener(presenter);
+  recordSwipeView_->SetOnSwipeListener(presenter);
+
+  InitPullDownAnimator();
+  InitEmptyPage();
+  InitPageIndicator();
+
+  group_->Add(recordSwipeView_);
+  if (pullDownAnimator_ != nullptr) {
+    group_->Add(pullDownAnimator_);
+  }
+  if (emptyPage_ != nullptr) {
+    group_->Add(emptyPage_);
+  }
+  if (dotEntry_ != nullptr) {
+    group_->Add(dotEntry_);
+  }
+  if (dotHistory_ != nullptr) {
+    group_->Add(dotHistory_);
+  }
+
+  RefreshView();
+  return group_;
+}
+
+} // namespace OHOS
diff --git a/code/sdk/application/wearable/n9/nativeapp/nativeui/bloodpressure/src/BPInstructionView.cpp b/code/sdk/application/wearable/n9/nativeapp/nativeui/bloodpressure/src/BPInstructionView.cpp
new file mode 100644 (file)
index 0000000..2101df4
--- /dev/null
@@ -0,0 +1,106 @@
+#include "bloodpressure/include/BPInstructionView.h"
+#include "bloodpressure/include/BPMainPresenter.h"
+#include "bloodpressure/include/BPViewClickIds.h"
+#include "UiConfig.h"
+#include "uicomponents/UIComponents.h"
+#include "wearable_log.h"
+
+namespace OHOS {
+
+static BPInstructionView *g_pBPInstructionView = nullptr;
+
+BPInstructionView::BPInstructionView() { g_pBPInstructionView = this; }
+
+BPInstructionView::~BPInstructionView() {
+  if (group_ != nullptr) {
+    group_->RemoveAll();
+    delete imageView_;
+    delete instructionLabel_;
+    delete backButton_;
+    delete measureButton_;
+    delete group_;
+    imageView_ = nullptr;
+    instructionLabel_ = nullptr;
+    backButton_ = nullptr;
+    measureButton_ = nullptr;
+    group_ = nullptr;
+  }
+  g_pBPInstructionView = nullptr;
+}
+
+BPInstructionView *BPInstructionView::GetInstance() { return g_pBPInstructionView; }
+
+void BPInstructionView::ApplyInstructionType(BPInstructionType type) {
+  if (imageView_ == nullptr || instructionLabel_ == nullptr || measureButton_ == nullptr) {
+    return;
+  }
+  switch (type) {
+    case BP_INSTRUCTION_TYPE_SEAT:
+      imageView_->SetSrc(BLOOD_PATH_SEAT);
+      instructionLabel_->SetText("双脚着地,端坐在椅子上\n双手手背朝上,手臂平放在桌子上");
+      instructionLabel_->SetPosition(8, 230, 450, 70);
+      measureButton_->SetText("开始测量");
+      break;
+    case BP_INSTRUCTION_TYPE_WEAR:
+      imageView_->SetSrc(BLOOD_PATH_WEAR);
+      instructionLabel_->SetText("手表和手腕之间仅限一指距离\n偏紧佩戴手表");
+      instructionLabel_->SetPosition(38, 230, 370, 70);
+      measureButton_->SetText("开始测量");
+      break;
+    case BP_INSTRUCTION_TYPE_BELT_LOOSE:
+      imageView_->SetSrc(BLOOD_PATH_BELT_LOOSE);
+      instructionLabel_->SetText("表带过松\n请调整松紧后重试");
+      instructionLabel_->SetPosition(113, 230, 240, 70);
+      measureButton_->SetText("重新测量");
+      break;
+    case BP_INSTRUCTION_TYPE_FIRST:
+      imageView_->SetSrc(BLOOD_PATH_FIRST);
+      instructionLabel_->SetText("坐姿端正,手臂平放桌面\n手表与心脏同高");
+      instructionLabel_->SetPosition(68, 230, 330, 70);
+      measureButton_->SetText("开始校准");
+      break;
+    case BP_INSTRUCTION_TYPE_SECOND:
+      imageView_->SetSrc(BLOOD_PATH_SECOND);
+      instructionLabel_->SetText("测量同一只手,保持相同姿势\n准备完成后开始校准");
+      instructionLabel_->SetPosition(38, 230, 390, 70);
+      measureButton_->SetText("开始校准");
+      break;
+    default:
+      break;
+  }
+}
+
+void BPInstructionView::UpdateForType(BPInstructionType type) { ApplyInstructionType(type); }
+
+UIViewGroup *BPInstructionView::InitView(BPInstructionType type) {
+  group_ = new UIViewGroup();
+  if (group_ == nullptr) {
+    return nullptr;
+  }
+  group_->SetPosition(0, 0, Screen::GetInstance().GetWidth(),
+                      Screen::GetInstance().GetHeight());
+
+  InitImageView(imageView_, 141, 36, 184, 184, "bp_instruction_image", BLOOD_PATH_SEAT);
+  InitUILabel(instructionLabel_, 53, 230, 360, 70, "bp_instruction_label", "",
+              DEFAULT_VECTOR_FONT_FILENAME, 30);
+  instructionLabel_->SetLineBreakMode(UILabel::LINE_BREAK_WRAP);
+  instructionLabel_->SetAlign(TEXT_ALIGNMENT_CENTER, TEXT_ALIGNMENT_CENTER);
+  InitLabelButton(backButton_, 58, 320, 170, 90, BP_CLICK_INSTRUCTION_BACK, "取消", 26);
+  InitLabelButton(measureButton_, 238, 320, 170, 90, BP_CLICK_MEASURE, "开始", 26);
+
+  ApplyInstructionType(type);
+
+  auto *clickListener = static_cast<UIView::OnClickListener *>(BPMainPresenter::GetInstance());
+  auto *dragListener = static_cast<UIView::OnDragListener *>(BPMainPresenter::GetInstance());
+  backButton_->SetOnClickListener(clickListener);
+  measureButton_->SetOnClickListener(clickListener);
+  group_->SetOnDragListener(dragListener);
+
+  group_->Add(imageView_);
+  group_->Add(instructionLabel_);
+  group_->Add(backButton_);
+  group_->Add(measureButton_);
+  return group_;
+}
+
+} // namespace OHOS
diff --git a/code/sdk/application/wearable/n9/nativeapp/nativeui/bloodpressure/src/BPMainPresenter.cpp b/code/sdk/application/wearable/n9/nativeapp/nativeui/bloodpressure/src/BPMainPresenter.cpp
new file mode 100644 (file)
index 0000000..1f8a45a
--- /dev/null
@@ -0,0 +1,508 @@
+#include "bloodpressure/include/BPMainPresenter.h"
+#include "bloodpressure/include/BPEntryView.h"
+#include "bloodpressure/include/BPInstructionView.h"
+#include "bloodpressure/include/BPHistoryView.h"
+#include "bloodpressure/include/BPValueInputView.h"
+#include "bloodpressure/include/BPModel.h"
+#include "bloodpressure/include/BPViewClickIds.h"
+#include "bloodpressure/include/BPCalibStopView.h"
+#include "bloodpressure/include/BPMeasuringView.h"
+#include "bloodpressure/include/BPCalibratingView.h"
+#include "bloodpressure/include/BPAbnormalView.h"
+#include "NativeRegisterManager.h"
+#include "NativeAbility.h"
+#include "UiConfig.h"
+#include "power_display_service.h"
+#include "graphic_timer.h"
+#include "ohos_timer.h"
+#include "service/graphic_service.h"
+#include "wearable_log.h"
+#include <custom_utils.h>
+#include <map>
+#include <string>
+
+namespace OHOS {
+
+static BPMainPresenter *g_pBPMainPresenter = nullptr;
+
+REGIST_MENU_PRO(VIEW_BLOOD_PRESSURE, BPMainView, BPMainPresenter,
+                PNG_APPLIST_BP_MESSURE_IMAGE_BIG, PNG_APPLIST_BP_MESSURE_IMAGE,
+                PNG_APPLIST_BP_MESSURE_IMAGE, "血压", STR_BP_CHECK);
+
+BPMainPresenter::BPMainPresenter() { g_pBPMainPresenter = this; }
+
+BPMainPresenter::~BPMainPresenter() {
+  StopProgressSession();
+  g_pBPMainPresenter = nullptr;
+}
+
+BPMainPresenter *BPMainPresenter::GetInstance() { return g_pBPMainPresenter; }
+
+void BPMainPresenter::OnStart() { Presenter<BPMainView>::OnStart(); }
+
+void BPMainPresenter::OnPause(void) { PauseProgressSession(); }
+
+void BPMainPresenter::OnResume(void) {
+  RootView::GetInstance()->SetOnKeyActListener(this);
+  BPMainView *mainView = BPMainView::GetInstance();
+  if (mainView != nullptr && !progressDataReceived_ && progressMode_ != BPProgressMode::None) {
+    int16_t current = mainView->GetCurrentViewId();
+    if (current == BP_MEASURING_VIEW || current == BP_CALIBRATING_VIEW) {
+      ResumeProgressSession();
+    }
+  }
+}
+
+void BPMainPresenter::OnStop(void) { StopProgressSession(); }
+
+void BPMainPresenter::NavigateBack() {
+  StopProgressSession();
+  NativeAbility::GetInstance().Back(TransitionType::TRANSITION_BACK_TO_HEXAGONS);
+}
+
+bool BPMainPresenter::IsFlowSubPage(int16_t viewId) const {
+  return viewId != BP_ENTRY_VIEW && viewId != BP_HISTORY_VIEW;
+}
+
+bool BPMainPresenter::NeedsStopConfirm(int16_t viewId) const {
+  return viewId == BP_MEASURING_VIEW || viewId == BP_CALIBRATING_VIEW;
+}
+
+void BPMainPresenter::RequestStopConfirm(int16_t fromView) {
+  BPMainView *mainView = BPMainView::GetInstance();
+  if (mainView == nullptr) {
+    return;
+  }
+  stopReturnView_ = fromView;
+  stopIsMeasure_ = (fromView == BP_MEASURING_VIEW);
+  PauseProgressSession();
+  BPCalibStopView *stopView = BPCalibStopView::GetInstance();
+  if (stopView != nullptr) {
+    stopView->UpdateForMode(stopIsMeasure_);
+  }
+  mainView->ChangeView(fromView, BP_CALIB_STOP_VIEW);
+}
+
+bool BPMainPresenter::HandleSwipeBack() {
+  BPMainView *mainView = BPMainView::GetInstance();
+  if (mainView == nullptr) {
+    NavigateBack();
+    return true;
+  }
+  int16_t current = mainView->GetCurrentViewId();
+  if (NeedsStopConfirm(current)) {
+    RequestStopConfirm(current);
+    return true;
+  }
+  if (current == BP_CALIB_STOP_VIEW) {
+    return true;
+  }
+  if (current == BP_ENTRY_VIEW) {
+    NavigateBack();
+  } else if (IsFlowSubPage(current)) {
+    mainView->ChangeView(current, BP_ENTRY_VIEW);
+  } else {
+    mainView->ChangeView(current, BP_ENTRY_VIEW);
+  }
+  return true;
+}
+
+void BPMainPresenter::ProgressTimerThunk(void *arg) {
+  auto *self = static_cast<BPMainPresenter *>(arg);
+  if (self == nullptr) {
+    return;
+  }
+  GraphicService::GetInstance()->PostGraphicEvent([self]() { self->OnProgressTick(); });
+}
+
+void BPMainPresenter::StartProgressSession(BPProgressMode mode) {
+  StopProgressSession();
+  progressMode_ = mode;
+  progressPercent_ = 0;
+  progressDataReceived_ = false;
+  progressPaused_ = false;
+  UpdateProgressUi(0);
+
+  const uint32_t tick = GetOSTick(static_cast<int32_t>(kProgressTickMs));
+  progressTimer_ = new GraphicTimer(static_cast<int32_t>(tick), ProgressTimerThunk, this, true);
+  if (progressTimer_ == nullptr || !progressTimer_->Start()) {
+    delete progressTimer_;
+    progressTimer_ = nullptr;
+  }
+}
+
+void BPMainPresenter::PauseProgressSession() { progressPaused_ = true; }
+
+void BPMainPresenter::ResumeProgressSession() { progressPaused_ = false; }
+
+void BPMainPresenter::StopProgressSession() {
+  if (progressTimer_ != nullptr) {
+    progressTimer_->Stop();
+    delete progressTimer_;
+    progressTimer_ = nullptr;
+  }
+  progressMode_ = BPProgressMode::None;
+  progressPercent_ = 0;
+  progressDataReceived_ = false;
+  progressPaused_ = false;
+}
+
+void BPMainPresenter::UpdateProgressUi(int percent) {
+  if (progressMode_ == BPProgressMode::Measure) {
+    BPMeasuringView *view = BPMeasuringView::GetInstance();
+    if (view != nullptr) {
+      view->UpdateProgressPercent(percent);
+    }
+  } else if (progressMode_ == BPProgressMode::Calibrate) {
+    BPCalibratingView *view = BPCalibratingView::GetInstance();
+    if (view != nullptr) {
+      view->UpdateProgress(percent);
+    }
+  }
+}
+
+void BPMainPresenter::OnProgressTick() {
+  if (progressPaused_ || progressDataReceived_ || progressMode_ == BPProgressMode::None) {
+    return;
+  }
+
+  progressPercent_ += 1;
+  if (progressPercent_ > kProgressMaxPercent) {
+    progressPercent_ = kProgressMaxPercent;
+  }
+  UpdateProgressUi(progressPercent_);
+
+  if (progressPercent_ >= kProgressMaxPercent && !progressDataReceived_) {
+    lastTimeoutMode_ = progressMode_;
+    OnProgressTimeout();
+  }
+}
+
+void BPMainPresenter::OnProgressTimeout() {
+  StopProgressSession();
+  BPMainView *mainView = BPMainView::GetInstance();
+  if (mainView == nullptr) {
+    return;
+  }
+  int16_t current = mainView->GetCurrentViewId();
+  if (current == BP_CALIB_STOP_VIEW && stopReturnView_ < MAX_BPVIEW) {
+    current = stopReturnView_;
+  }
+  if (current == BP_MEASURING_VIEW || current == BP_CALIBRATING_VIEW) {
+    mainView->ChangeView(current, BP_ABNORMAL_VIEW, BP_ABNORMAL_TYPE_MEASURE_TIMEOUT);
+  }
+}
+
+void BPMainPresenter::CompleteCalibrationFlow(uint8_t sbp, uint8_t dbp) {
+  UNUSED(sbp);
+  UNUSED(dbp);
+  BPModel *model = BPModel::GetInstance();
+  BPMainView *mainView = BPMainView::GetInstance();
+  if (mainView == nullptr) {
+    return;
+  }
+
+  if (model->GetCalibrationRound() == 1) {
+    model->SaveFirstRoundInput();
+    mainView->ChangeView(BP_CALIBRATING_VIEW, BP_CALIB_FIRST_DONE_VIEW);
+    return;
+  }
+
+  model->SetSecondRoundInput(model->GetInputSbp(), model->GetInputDbp());
+  if (model->IsCalibrationDifferenceTooLarge()) {
+    mainView->ChangeView(BP_CALIBRATING_VIEW, BP_ABNORMAL_VIEW,
+                         BP_ABNORMAL_TYPE_CALIBRATION_DIFFERENCE_TOO_LARGE);
+    return;
+  }
+
+  model->SaveCalibrationResult();
+  mainView->ChangeView(BP_CALIBRATING_VIEW, BP_CALIB_SECOND_DONE_VIEW);
+}
+
+void BPMainPresenter::CompleteMeasureFlow(uint8_t sbp, uint8_t dbp) {
+  BPModel *model = BPModel::GetInstance();
+  model->SetMeasureResult(sbp, dbp);
+  model->SaveMeasureRecord(sbp, dbp);
+  BPMainView *mainView = BPMainView::GetInstance();
+  if (mainView != nullptr) {
+    mainView->ChangeView(BP_MEASURING_VIEW, BP_MEASURE_RESULT_VIEW);
+  }
+}
+
+void BPMainPresenter::NotifyCalibrationComplete(uint8_t sbp, uint8_t dbp) {
+  if (progressDataReceived_) {
+    return;
+  }
+  progressDataReceived_ = true;
+  StopProgressSession();
+  BPModel::GetInstance()->SetMeasureResult(sbp, dbp);
+  CompleteCalibrationFlow(sbp, dbp);
+}
+
+void BPMainPresenter::NotifyMeasureComplete(uint8_t sbp, uint8_t dbp) {
+  if (progressDataReceived_) {
+    return;
+  }
+  progressDataReceived_ = true;
+  StopProgressSession();
+  CompleteMeasureFlow(sbp, dbp);
+}
+
+bool BPMainPresenter::OnDragStart(UIView &view, const DragEvent &event) {
+  UNUSED(view);
+  UNUSED(event);
+  BPMainView *mainView = BPMainView::GetInstance();
+  if (mainView == nullptr || mainView->GetCurrentViewId() != BP_HISTORY_VIEW) {
+    return false;
+  }
+  BPHistoryView *historyView = BPHistoryView::GetInstance();
+  if (historyView != nullptr) {
+    historyView->HideArrowWhileDragging();
+  }
+  return false;
+}
+
+bool BPMainPresenter::OnDrag(UIView &view, const DragEvent &event) {
+  UNUSED(view);
+  UNUSED(event);
+  return false;
+}
+
+bool BPMainPresenter::OnDragEnd(UIView &view, const DragEvent &event) {
+  BPMainView *mainView = BPMainView::GetInstance();
+  if (mainView == nullptr) {
+    return false;
+  }
+  int16_t current = mainView->GetCurrentViewId();
+  uint8_t direction = event.GetDragDirection();
+
+  if (direction == DragEvent::DIRECTION_LEFT_TO_RIGHT) {
+    HandleSwipeBack();
+    return true;
+  }
+  if (current == BP_ENTRY_VIEW && direction == DragEvent::DIRECTION_RIGHT_TO_LEFT) {
+    mainView->ChangeView(BP_ENTRY_VIEW, BP_HISTORY_VIEW);
+    return true;
+  }
+  return false;
+}
+
+void BPMainPresenter::OnSwipe(UISwipeView &view) {
+  UNUSED(view);
+  BPHistoryView *historyView = BPHistoryView::GetInstance();
+  if (historyView != nullptr) {
+    historyView->OnRecordPageChanged(view.GetCurrentPage());
+  }
+}
+
+void BPMainPresenter::StartMeasureFlow(BPMainView *mainView) {
+  int16_t hide = mainView->GetCurrentViewId();
+  mainView->ChangeView(hide, BP_MEASURING_VIEW);
+  StartProgressSession(BPProgressMode::Measure);
+}
+
+void BPMainPresenter::StartCalibrationFlow(BPMainView *mainView, uint8_t round) {
+  BPModel *model = BPModel::GetInstance();
+  model->SetCalibrationRound(round);
+  model->ResetInputValues();
+  int16_t hide = mainView->GetCurrentViewId();
+  mainView->ChangeView(hide, BP_VALUE_INPUT_VIEW);
+}
+
+bool BPMainPresenter::OnClick(UIView &view, const ClickEvent &event) {
+  UNUSED(event);
+  std::string viewId = view.GetViewId();
+  BPMainView *mainView = BPMainView::GetInstance();
+  if (mainView == nullptr) {
+    return true;
+  }
+  int16_t current = mainView->GetCurrentViewId();
+  BPModel *model = BPModel::GetInstance();
+
+  if (viewId == BP_CLICK_DISCLAIMER_ACCEPT) {
+    model->MarkDisclaimerAccepted();
+    mainView->ChangeView(BP_ABNORMAL_VIEW, BP_ENTRY_VIEW);
+    return true;
+  }
+  if (viewId == BP_CLICK_HISTORY) {
+    mainView->ChangeView(BP_ENTRY_VIEW, BP_HISTORY_VIEW);
+    return true;
+  }
+  if (viewId == BP_CLICK_HISTORY_BACK) {
+    mainView->ChangeView(BP_HISTORY_VIEW, BP_ENTRY_VIEW);
+    return true;
+  }
+  if (viewId == BP_CLICK_INSTRUCTION_BACK) {
+    mainView->ChangeView(BP_INSTRUCTION_VIEW, BP_ENTRY_VIEW);
+    return true;
+  }
+  if (viewId == BP_CLICK_INSTRUCTION) {
+    mainView->ChangeView(BP_ENTRY_VIEW, BP_INSTRUCTION_VIEW, BP_INSTRUCTION_TYPE_SEAT);
+    return true;
+  }
+  if (viewId == BP_CLICK_VALUE_NEXT) {
+    BPValueInputView *inputView = BPValueInputView::GetInstance();
+    if (inputView != nullptr && inputView->GetSbpPicker() != nullptr &&
+        inputView->GetDbpPicker() != nullptr) {
+      model->SetInputSbp(static_cast<uint8_t>(
+          BPValueInputView::SBP_MIN + inputView->GetSbpPicker()->GetSelected()));
+      model->SetInputDbp(static_cast<uint8_t>(
+          BPValueInputView::DBP_MIN + inputView->GetDbpPicker()->GetSelected()));
+    }
+    mainView->ChangeView(BP_VALUE_INPUT_VIEW, BP_VALUE_CONFIRM_VIEW);
+    return true;
+  }
+  if (viewId == BP_CLICK_VALUE_CONFIRM) {
+    if (model->GetInputSbp() > model->GetInputDbp()) {
+      mainView->ChangeView(BP_VALUE_CONFIRM_VIEW, BP_CALIBRATING_VIEW);
+      StartProgressSession(BPProgressMode::Calibrate);
+    }
+    return true;
+  }
+  if (viewId == BP_CLICK_VALUE_ERROR_OK) {
+    mainView->ChangeView(BP_VALUE_CONFIRM_VIEW, BP_VALUE_INPUT_VIEW);
+    return true;
+  }
+  if (viewId == BP_CLICK_STOP && current == BP_VALUE_CONFIRM_VIEW) {
+    mainView->ChangeView(BP_VALUE_CONFIRM_VIEW, BP_VALUE_INPUT_VIEW);
+    return true;
+  }
+  if (viewId == BP_CLICK_MEASURE_STOP) {
+    RequestStopConfirm(BP_MEASURING_VIEW);
+    return true;
+  }
+  if (viewId == BP_CLICK_CALIB_STOP) {
+    RequestStopConfirm(BP_CALIBRATING_VIEW);
+    return true;
+  }
+  if (viewId == BP_CLICK_CALIB_STOP_CANCEL) {
+    int16_t backView = (stopReturnView_ < MAX_BPVIEW) ? stopReturnView_ : BP_CALIBRATING_VIEW;
+    mainView->ChangeView(BP_CALIB_STOP_VIEW, backView);
+    ResumeProgressSession();
+    return true;
+  }
+  if (viewId == BP_CLICK_CALIB_STOP_CONFIRM) {
+    StopProgressSession();
+    mainView->ChangeView(BP_CALIB_STOP_VIEW, BP_ENTRY_VIEW);
+    stopReturnView_ = MAX_BPVIEW;
+    return true;
+  }
+  if (viewId == BP_CLICK_STOP) {
+    if (NeedsStopConfirm(current)) {
+      RequestStopConfirm(current);
+      return true;
+    }
+    StopProgressSession();
+    mainView->ChangeView(current, BP_ENTRY_VIEW);
+    return true;
+  }
+  if (viewId == BP_CLICK_EXIT) {
+    StopProgressSession();
+    mainView->ChangeView(current, BP_ENTRY_VIEW);
+    return true;
+  }
+  if (viewId == BP_CLICK_RETRY) {
+    if (current == BP_ABNORMAL_VIEW) {
+      if (lastTimeoutMode_ == BPProgressMode::Calibrate) {
+        mainView->ChangeView(BP_ABNORMAL_VIEW, BP_CALIBRATING_VIEW);
+        StartProgressSession(BPProgressMode::Calibrate);
+      } else {
+        StartMeasureFlow(mainView);
+      }
+    } else if (current == BP_CALIB_STOP_VIEW) {
+      int16_t backView = (stopReturnView_ < MAX_BPVIEW) ? stopReturnView_ : BP_CALIBRATING_VIEW;
+      mainView->ChangeView(BP_CALIB_STOP_VIEW, backView);
+      ResumeProgressSession();
+    }
+    return true;
+  }
+  if (viewId == BP_CLICK_CONFIRM) {
+    if (current == BP_MEASURE_RESULT_VIEW) {
+      mainView->ChangeView(BP_MEASURE_RESULT_VIEW, BP_ENTRY_VIEW);
+    }
+    return true;
+  }
+  if (viewId == BP_CLICK_FIRST_CALIB_DONE) {
+    model->SetCalibrationRound(2);
+    mainView->ChangeView(BP_CALIB_FIRST_DONE_VIEW, BP_INSTRUCTION_VIEW, BP_INSTRUCTION_TYPE_SECOND);
+    return true;
+  }
+  if (viewId == BP_CLICK_SECOND_CALIB_DONE) {
+    mainView->ChangeView(BP_CALIB_SECOND_DONE_VIEW, BP_ENTRY_VIEW);
+    return true;
+  }
+
+  if (viewId == BP_CLICK_RECALIBRATE) {
+    if (current == BP_ABNORMAL_VIEW) {
+      model->SetCalibrationRound(1);
+      mainView->ChangeView(BP_ABNORMAL_VIEW, BP_INSTRUCTION_VIEW, BP_INSTRUCTION_TYPE_FIRST);
+      return true;
+    }
+    model->SetCalibrationRound(1);
+    mainView->ChangeView(current, BP_INSTRUCTION_VIEW, BP_INSTRUCTION_TYPE_FIRST);
+    return true;
+  }
+
+  if (viewId == BP_CLICK_MEASURE) {
+    if (current == BP_INSTRUCTION_VIEW) {
+      auto instructionType =
+          static_cast<BPInstructionType>(mainView->GetInstructionSubType());
+      if (instructionType == BP_INSTRUCTION_TYPE_FIRST ||
+          instructionType == BP_INSTRUCTION_TYPE_SECOND) {
+        StartCalibrationFlow(mainView, model->GetCalibrationRound());
+        return true;
+      }
+      int8_t state = model->GetCurrentstate();
+      if (state == BP_CALIB_STATE_NOT_CALIBRATED) {
+        StartCalibrationFlow(mainView, model->GetCalibrationRound());
+      } else {
+        StartMeasureFlow(mainView);
+      }
+      return true;
+    }
+    if (current == BP_HISTORY_VIEW) {
+      int8_t state = model->GetCurrentstate();
+      if (state == BP_CALIB_STATE_NOT_CALIBRATED) {
+        model->SetCalibrationRound(1);
+        mainView->ChangeView(BP_HISTORY_VIEW, BP_INSTRUCTION_VIEW, BP_INSTRUCTION_TYPE_FIRST);
+      } else if (state == BP_CALIB_STATE_EXPIRED) {
+        StartMeasureFlow(mainView);
+      } else {
+        mainView->ChangeView(BP_HISTORY_VIEW, BP_INSTRUCTION_VIEW, BP_INSTRUCTION_TYPE_SEAT);
+      }
+      return true;
+    }
+    if (current == BP_ENTRY_VIEW) {
+      int8_t state = model->GetCurrentstate();
+      if (state == BP_CALIB_STATE_NOT_CALIBRATED) {
+        model->SetCalibrationRound(1);
+        mainView->ChangeView(BP_ENTRY_VIEW, BP_INSTRUCTION_VIEW, BP_INSTRUCTION_TYPE_FIRST);
+      } else if (state == BP_CALIB_STATE_EXPIRED) {
+        StartMeasureFlow(mainView);
+      } else {
+        mainView->ChangeView(BP_ENTRY_VIEW, BP_INSTRUCTION_VIEW, BP_INSTRUCTION_TYPE_SEAT);
+      }
+      return true;
+    }
+  }
+
+  return true;
+}
+
+bool BPMainPresenter::OnKeyAct(UIView &view, const KeyEvent &event) {
+  UNUSED(view);
+  unsigned short keyID = event.GetKeyId();
+  unsigned short keyState = event.GetState();
+  if (keyID == static_cast<uint16_t>(ZliteKeyCode::ZLITE_KEY_POWER) && keyState == 1 &&
+      uapi_get_screen_state() == 0) {
+    BPMainView *mainView = BPMainView::GetInstance();
+    if (mainView != nullptr && NeedsStopConfirm(mainView->GetCurrentViewId())) {
+      RequestStopConfirm(mainView->GetCurrentViewId());
+      return true;
+    }
+    HandleSwipeBack();
+  }
+  return true;
+}
+
+} // namespace OHOS
diff --git a/code/sdk/application/wearable/n9/nativeapp/nativeui/bloodpressure/src/BPMainView.cpp b/code/sdk/application/wearable/n9/nativeapp/nativeui/bloodpressure/src/BPMainView.cpp
new file mode 100644 (file)
index 0000000..208ba4b
--- /dev/null
@@ -0,0 +1,427 @@
+#include "bloodpressure/include/BPMainView.h"
+#include "bloodpressure/include/BPMainPresenter.h"
+#include "bloodpressure/include/BPModel.h"
+#include "bloodpressure/include/BPEntryView.h"
+#include "bloodpressure/include/BPHistoryView.h"
+#include "bloodpressure/include/BPInstructionView.h"
+#include "bloodpressure/include/BPAbnormalView.h"
+#include "bloodpressure/include/BPMeasuringView.h"
+#include "bloodpressure/include/BPMeasureResultView.h"
+#include "bloodpressure/include/BPCalibratingView.h"
+#include "bloodpressure/include/BPCalibStopView.h"
+#include "bloodpressure/include/BPValueInputView.h"
+#include "bloodpressure/include/BPValueConfirmView.h"
+#include "bloodpressure/include/BPCalibFirstDoneView.h"
+#include "bloodpressure/include/BPCalibSecondDoneView.h"
+#include "wearable_log.h"
+#include "common/screen.h"
+
+namespace OHOS {
+
+static BPMainView *g_pBPMainView = nullptr;
+
+BPMainView::BPMainView() {
+  g_pBPMainView = this;
+}
+
+BPMainView::~BPMainView() {
+  DeInit();
+  g_pBPMainView = nullptr;
+}
+
+BPMainView *BPMainView::GetInstance() { return g_pBPMainView; }
+
+void BPMainView::DeInit() {
+  delete entryView_;
+  delete historyView_;
+  delete instructionView_;
+  delete abnormalView_;
+  delete measuringView_;
+  delete measureResultView_;
+  delete calibratingView_;
+  delete calibStopView_;
+  delete valueInputView_;
+  delete valueConfirmView_;
+  delete calibFirstDoneView_;
+  delete calibSecondDoneView_;
+  entryView_ = nullptr;
+  historyView_ = nullptr;
+  instructionView_ = nullptr;
+  abnormalView_ = nullptr;
+  measuringView_ = nullptr;
+  measureResultView_ = nullptr;
+  calibratingView_ = nullptr;
+  calibStopView_ = nullptr;
+  valueInputView_ = nullptr;
+  valueConfirmView_ = nullptr;
+  calibFirstDoneView_ = nullptr;
+  calibSecondDoneView_ = nullptr;
+}
+
+void BPMainView::OnStart() {
+  View<BPMainPresenter>::OnStart();
+  if (container_ == nullptr) {
+    container_ = new UIViewGroup();
+    if (container_ == nullptr) {
+      return;
+    }
+    container_->SetPosition(0, 0, Screen::GetInstance().GetWidth(), Screen::GetInstance().GetHeight());
+    container_->SetDraggable(true);
+    container_->SetTouchable(true);
+    container_->SetOnDragListener(presenter_);
+    AddViewToRootContainer(container_);
+  }
+  BPModel::GetInstance()->LoadData();
+  if (BPModel::GetInstance()->IsFirstUse()) {
+    ChangeView(MAX_BPVIEW, BP_ABNORMAL_VIEW, BP_ABNORMAL_TYPE_DISCLAIMER);
+  } else {
+    ChangeView(MAX_BPVIEW, BP_ENTRY_VIEW);
+  }
+  RootView::GetInstance()->SetOnKeyActListener(
+      static_cast<RootView::OnKeyActListener *>(BPMainPresenter::GetInstance()));
+  Draw();
+}
+
+void BPMainView::OnStop() {
+  View<BPMainPresenter>::OnStop();
+  if (container_ != nullptr) {
+    container_->RemoveAll();
+    delete container_;
+    container_ = nullptr;
+  }
+}
+
+bool BPMainView::ChangeBPEntryView(int16_t thisView, int16_t type) {
+  UNUSED(type);
+  if (group_[thisView] != nullptr) {
+    return true;
+  }
+  entryView_ = new BPEntryView();
+  if (entryView_ == nullptr) {
+    return false;
+  }
+  group_[thisView] = entryView_->InitView();
+  if (group_[thisView] == nullptr) {
+    return false;
+  }
+  container_->Add(group_[thisView]);
+  return true;
+}
+
+bool BPMainView::ChangeBPHistoryView(int16_t thisView, int16_t type) {
+  UNUSED(type);
+  if (group_[thisView] != nullptr) {
+    if (historyView_ != nullptr) {
+      historyView_->RefreshView();
+    }
+    return true;
+  }
+  historyView_ = new BPHistoryView();
+  if (historyView_ == nullptr) {
+    return false;
+  }
+  group_[thisView] = historyView_->InitView();
+  if (group_[thisView] == nullptr) {
+    return false;
+  }
+  container_->Add(group_[thisView]);
+  return true;
+}
+
+bool BPMainView::ChangeBPInstructionView(int16_t thisView, int16_t type) {
+  instructionSubType_ = type;
+  if (group_[thisView] != nullptr) {
+    if (instructionView_ != nullptr) {
+      instructionView_->UpdateForType(static_cast<BPInstructionType>(type));
+    }
+    return true;
+  }
+  instructionView_ = new BPInstructionView();
+  if (instructionView_ == nullptr) {
+    return false;
+  }
+  group_[thisView] = instructionView_->InitView(static_cast<BPInstructionType>(type));
+  if (group_[thisView] == nullptr) {
+    return false;
+  }
+  container_->Add(group_[thisView]);
+  return true;
+}
+
+bool BPMainView::ChangeBPAbnormalView(int16_t thisView, int16_t type) {
+  if (group_[thisView] != nullptr) {
+    if (abnormalView_ != nullptr) {
+      abnormalView_->UpdateForType(static_cast<BPAbnormalType>(type));
+    }
+    return true;
+  }
+  abnormalView_ = new BPAbnormalView();
+  if (abnormalView_ == nullptr) {
+    return false;
+  }
+  group_[thisView] = abnormalView_->InitView(static_cast<BPAbnormalType>(type));
+  if (group_[thisView] == nullptr) {
+    return false;
+  }
+  container_->Add(group_[thisView]);
+  return true;
+}
+
+bool BPMainView::ChangeBPMeasuringView(int16_t thisView, int16_t type) {
+  UNUSED(type);
+  if (group_[thisView] != nullptr) {
+    return true;
+  }
+  measuringView_ = new BPMeasuringView();
+  if (measuringView_ == nullptr) {
+    return false;
+  }
+  group_[thisView] = measuringView_->InitView();
+  if (group_[thisView] == nullptr) {
+    return false;
+  }
+  container_->Add(group_[thisView]);
+  return true;
+}
+
+bool BPMainView::ChangeBPMeasureResultView(int16_t thisView, int16_t type) {
+  UNUSED(type);
+  if (group_[thisView] != nullptr) {
+    if (measureResultView_ != nullptr) {
+      measureResultView_->UpdateText();
+    }
+    return true;
+  }
+  measureResultView_ = new BPMeasureResultView();
+  if (measureResultView_ == nullptr) {
+    return false;
+  }
+  group_[thisView] = measureResultView_->InitView();
+  if (group_[thisView] == nullptr) {
+    return false;
+  }
+  container_->Add(group_[thisView]);
+  return true;
+}
+
+bool BPMainView::ChangeBPCalibratingView(int16_t thisView, int16_t type) {
+  UNUSED(type);
+  if (group_[thisView] != nullptr) {
+    if (calibratingView_ != nullptr) {
+      calibratingView_->UpdateRound(BPModel::GetInstance()->GetCalibrationRound());
+      calibratingView_->UpdateProgress(0);
+    }
+    return true;
+  }
+  calibratingView_ = new BPCalibratingView();
+  if (calibratingView_ == nullptr) {
+    return false;
+  }
+  group_[thisView] = calibratingView_->InitView();
+  if (group_[thisView] == nullptr) {
+    return false;
+  }
+  container_->Add(group_[thisView]);
+  return true;
+}
+
+bool BPMainView::ChangeBPCalibStopView(int16_t thisView, int16_t type) {
+  UNUSED(type);
+  if (group_[thisView] != nullptr) {
+    return true;
+  }
+  calibStopView_ = new BPCalibStopView();
+  if (calibStopView_ == nullptr) {
+    return false;
+  }
+  group_[thisView] = calibStopView_->InitView();
+  if (group_[thisView] == nullptr) {
+    return false;
+  }
+  container_->Add(group_[thisView]);
+  return true;
+}
+
+bool BPMainView::ChangeBPValueInputView(int16_t thisView, int16_t type) {
+  UNUSED(type);
+  if (group_[thisView] != nullptr) {
+    if (valueInputView_ != nullptr) {
+      valueInputView_->SyncPickerFromModel();
+    }
+    return true;
+  }
+  valueInputView_ = new BPValueInputView();
+  if (valueInputView_ == nullptr) {
+    return false;
+  }
+  group_[thisView] = valueInputView_->InitView();
+  if (group_[thisView] == nullptr) {
+    return false;
+  }
+  container_->Add(group_[thisView]);
+  return true;
+}
+
+bool BPMainView::ChangeBPValueConfirmView(int16_t thisView, int16_t type) {
+  UNUSED(type);
+  if (group_[thisView] != nullptr) {
+    if (valueConfirmView_ != nullptr) {
+      valueConfirmView_->UpdateView();
+    }
+    return true;
+  }
+  valueConfirmView_ = new BPValueConfirmView();
+  if (valueConfirmView_ == nullptr) {
+    return false;
+  }
+  group_[thisView] = valueConfirmView_->InitView();
+  if (group_[thisView] == nullptr) {
+    return false;
+  }
+  container_->Add(group_[thisView]);
+  return true;
+}
+
+bool BPMainView::ChangeBPCalibFirstDoneView(int16_t thisView, int16_t type) {
+  UNUSED(type);
+  if (group_[thisView] != nullptr) {
+    return true;
+  }
+  calibFirstDoneView_ = new BPCalibFirstDoneView();
+  if (calibFirstDoneView_ == nullptr) {
+    return false;
+  }
+  group_[thisView] = calibFirstDoneView_->InitView();
+  if (group_[thisView] == nullptr) {
+    return false;
+  }
+  container_->Add(group_[thisView]);
+  return true;
+}
+
+bool BPMainView::ChangeBPCalibSecondDoneView(int16_t thisView, int16_t type) {
+  UNUSED(type);
+  if (group_[thisView] != nullptr) {
+    return true;
+  }
+  calibSecondDoneView_ = new BPCalibSecondDoneView();
+  if (calibSecondDoneView_ == nullptr) {
+    return false;
+  }
+  group_[thisView] = calibSecondDoneView_->InitView();
+  if (group_[thisView] == nullptr) {
+    return false;
+  }
+  container_->Add(group_[thisView]);
+  return true;
+}
+
+struct BPViewMapper {
+  const int16_t viewId;
+  bool (BPMainView::*func)(int16_t, int16_t);
+};
+
+static const BPViewMapper ViewMapper[] = {
+    {BP_ENTRY_VIEW, &BPMainView::ChangeBPEntryView},
+    {BP_HISTORY_VIEW, &BPMainView::ChangeBPHistoryView},
+    {BP_INSTRUCTION_VIEW, &BPMainView::ChangeBPInstructionView},
+    {BP_ABNORMAL_VIEW, &BPMainView::ChangeBPAbnormalView},
+    {BP_MEASURING_VIEW, &BPMainView::ChangeBPMeasuringView},
+    {BP_MEASURE_RESULT_VIEW, &BPMainView::ChangeBPMeasureResultView},
+    {BP_CALIBRATING_VIEW, &BPMainView::ChangeBPCalibratingView},
+    {BP_CALIB_STOP_VIEW, &BPMainView::ChangeBPCalibStopView},
+    {BP_VALUE_INPUT_VIEW, &BPMainView::ChangeBPValueInputView},
+    {BP_VALUE_CONFIRM_VIEW, &BPMainView::ChangeBPValueConfirmView},
+    {BP_CALIB_FIRST_DONE_VIEW, &BPMainView::ChangeBPCalibFirstDoneView},
+    {BP_CALIB_SECOND_DONE_VIEW, &BPMainView::ChangeBPCalibSecondDoneView},
+};
+
+bool BPMainView::ClickFunc(int16_t interface, int16_t type) {
+  int funcSize = sizeof(ViewMapper) / sizeof(ViewMapper[0]);
+  for (int i = 0; i < funcSize; i++) {
+    if (interface == ViewMapper[i].viewId) {
+      return (this->*(ViewMapper[i].func))(interface, type);
+    }
+  }
+  return false;
+}
+
+void BPMainView::ChangeView(int16_t hideView, int16_t showView, int16_t type) {
+  if (showView >= MAX_BPVIEW) {
+    return;
+  }
+  if (hideView != MAX_BPVIEW) {
+    if (hideView >= MAX_BPVIEW || group_[hideView] == nullptr) {
+      return;
+    }
+    if (hideView == BP_MEASURING_VIEW && measuringView_ != nullptr) {
+      measuringView_->StopMeasuringAnimation();
+    }
+    if (hideView == BP_HISTORY_VIEW && historyView_ != nullptr) {
+      historyView_->StopArrowAnimation();
+    }
+    group_[hideView]->SetVisible(false);
+  }
+  if (!ClickFunc(showView, type)) {
+    return;
+  }
+  group_[showView]->SetVisible(true);
+  currentViewId_ = showView;
+  if (showView == BP_MEASURING_VIEW && measuringView_ != nullptr) {
+    measuringView_->StartMeasuringAnimation();
+    measuringView_->UpdateProgressPercent(0);
+  }
+  if (showView == BP_HISTORY_VIEW && historyView_ != nullptr) {
+    historyView_->RefreshView();
+    historyView_->StartArrowAnimation();
+  }
+  RefreshCurrentView();
+  container_->Invalidate();
+}
+
+void BPMainView::RefreshCurrentView() {
+  switch (currentViewId_) {
+    case BP_MEASURE_RESULT_VIEW:
+      if (measureResultView_ != nullptr) {
+        measureResultView_->UpdateText();
+      }
+      break;
+    case BP_VALUE_INPUT_VIEW:
+      if (valueInputView_ != nullptr) {
+        valueInputView_->SyncPickerFromModel();
+      }
+      break;
+    case BP_VALUE_CONFIRM_VIEW:
+      if (valueConfirmView_ != nullptr) {
+        valueConfirmView_->UpdateView();
+      }
+      break;
+    default:
+      break;
+  }
+}
+
+int16_t BPMainView::GetCurrentViewId() const { return currentViewId_; }
+
+void BPMainView::ShowAtTestPage(int16_t viewId, int16_t subType) {
+  if (viewId < 0 || viewId >= MAX_BPVIEW) {
+    return;
+  }
+  instructionSubType_ = subType;
+  if (currentViewId_ == viewId) {
+    if (viewId == BP_INSTRUCTION_VIEW && instructionView_ != nullptr) {
+      instructionView_->UpdateForType(static_cast<BPInstructionType>(subType));
+    } else if (viewId == BP_ABNORMAL_VIEW && abnormalView_ != nullptr) {
+      abnormalView_->UpdateForType(static_cast<BPAbnormalType>(subType));
+    } else {
+      RefreshCurrentView();
+    }
+    if (container_ != nullptr) {
+      container_->Invalidate();
+    }
+    return;
+  }
+  ChangeView(currentViewId_, viewId, subType);
+}
+
+} // namespace OHOS
diff --git a/code/sdk/application/wearable/n9/nativeapp/nativeui/bloodpressure/src/BPMeasureResultView.cpp b/code/sdk/application/wearable/n9/nativeapp/nativeui/bloodpressure/src/BPMeasureResultView.cpp
new file mode 100644 (file)
index 0000000..93a8253
--- /dev/null
@@ -0,0 +1,248 @@
+#include "bloodpressure/include/BPMeasureResultView.h"
+#include "bloodpressure/include/BPMainPresenter.h"
+#include "bloodpressure/include/BPModel.h"
+#include "bloodpressure/include/BPViewClickIds.h"
+#include "securec.h"
+#include "uicomponents/UIComponents.h"
+#include <ctime>
+
+namespace OHOS {
+
+namespace {
+static constexpr uint32_t BP_COLOR_LINE = 0xff333333;
+static constexpr int16_t BP_RESULT_COL_W = 170;
+static constexpr int16_t BP_RESULT_LEFT_X = 58;
+static constexpr int16_t BP_RESULT_RIGHT_X = 238;
+} // namespace
+
+static BPMeasureResultView *g_instance = nullptr;
+
+BPMeasureResultView::BPMeasureResultView() { g_instance = this; }
+
+BPMeasureResultView::~BPMeasureResultView() {
+  if (group_ != nullptr) {
+    group_->RemoveAll();
+    delete titleLabel_;
+    delete timeLabel_;
+    delete statusLabel_;
+    delete hlineTop_;
+    delete hlineBottom_;
+    delete vlineMid_;
+    delete sbpTitle_;
+    delete dbpTitle_;
+    delete sbpValue_;
+    delete dbpValue_;
+    delete sbpUnit_;
+    delete dbpUnit_;
+    delete confirmButton_;
+    delete group_;
+    titleLabel_ = nullptr;
+    timeLabel_ = nullptr;
+    statusLabel_ = nullptr;
+    hlineTop_ = nullptr;
+    hlineBottom_ = nullptr;
+    vlineMid_ = nullptr;
+    sbpTitle_ = nullptr;
+    dbpTitle_ = nullptr;
+    sbpValue_ = nullptr;
+    dbpValue_ = nullptr;
+    sbpUnit_ = nullptr;
+    dbpUnit_ = nullptr;
+    confirmButton_ = nullptr;
+    group_ = nullptr;
+  }
+  g_instance = nullptr;
+}
+
+BPMeasureResultView *BPMeasureResultView::GetInstance() { return g_instance; }
+
+//判断血压是否偏高
+bool BPMeasureResultView::IsHighBloodPressure(uint8_t sbp, uint8_t dbp) {
+  return sbp >= 140 || dbp >= 90;
+}
+//格式化测量时间
+std::string BPMeasureResultView::FormatMeasureTime() {
+  time_t now = time(nullptr);
+  if (now <= 0) {
+    return "--";
+  }
+  tm *timeInfo = localtime(&now);
+  if (timeInfo == nullptr) {
+    return "--";
+  }
+  char buf[32] = {0};
+  if (sprintf_s(buf, sizeof(buf), "%02d-%02d %02d:%02d", timeInfo->tm_mon + 1,
+                timeInfo->tm_mday, timeInfo->tm_hour, timeInfo->tm_min) < 0) {
+    return "--";
+  }
+  return std::string(buf);
+}
+
+//初始化分割线
+void BPMeasureResultView::InitDividerLines() {
+  hlineTop_ = new UIImageView();
+  if (hlineTop_ != nullptr) {
+    hlineTop_->SetPosition(0, 158, 466, 2);
+    hlineTop_->SetStyle(STYLE_BACKGROUND_COLOR, BP_COLOR_LINE);
+    hlineTop_->SetStyle(STYLE_BACKGROUND_OPA, 0xff);
+  }
+  hlineBottom_ = new UIImageView();
+  if (hlineBottom_ != nullptr) {
+    hlineBottom_->SetPosition(0, 308, 466, 2);
+    hlineBottom_->SetStyle(STYLE_BACKGROUND_COLOR, BP_COLOR_LINE);
+    hlineBottom_->SetStyle(STYLE_BACKGROUND_OPA, 0xff);
+  }
+  vlineMid_ = new UIImageView();
+  if (vlineMid_ != nullptr) {
+    vlineMid_->SetPosition(233, 188, 2, 90);
+    vlineMid_->SetStyle(STYLE_BACKGROUND_COLOR, BP_COLOR_LINE);
+    vlineMid_->SetStyle(STYLE_BACKGROUND_OPA, 0xff);
+  }
+}
+
+//应用正常布局
+void BPMeasureResultView::ApplyNormalLayout() {
+  if (titleLabel_ != nullptr) {
+    titleLabel_->SetText("测量成功");
+    titleLabel_->SetTextColor(Color::GetColorFromRGBA(0x4f, 0xc3, 0x4c, 0xff));
+  }
+  if (statusLabel_ != nullptr) {
+    statusLabel_->SetText("血压处于正常范围");
+    statusLabel_->SetTextColor(Color::GetColorFromRGBA(0x4f, 0xc3, 0x4c, 0xff));
+  }
+  if (sbpValue_ != nullptr) {
+    sbpValue_->SetTextColor(Color::GetColorFromRGBA(0xff, 0xff, 0xff, 0xff));
+  }
+  if (dbpValue_ != nullptr) {
+    dbpValue_->SetTextColor(Color::GetColorFromRGBA(0xff, 0xff, 0xff, 0xff));
+  }
+}
+
+//应用偏高布局
+void BPMeasureResultView::ApplyHighLayout() {
+  if (titleLabel_ != nullptr) {
+    titleLabel_->SetText("测量成功");
+    titleLabel_->SetTextColor(Color::GetColorFromRGBA(0xff, 0x5a, 0x36, 0xff));
+  }
+  if (statusLabel_ != nullptr) {
+    statusLabel_->SetText("血压偏高,请关注");
+    statusLabel_->SetTextColor(Color::GetColorFromRGBA(0xff, 0x5a, 0x36, 0xff));
+  }
+  if (sbpValue_ != nullptr) {
+    sbpValue_->SetTextColor(Color::GetColorFromRGBA(0xff, 0x5a, 0x36, 0xff));
+  }
+  if (dbpValue_ != nullptr) {
+    dbpValue_->SetTextColor(Color::GetColorFromRGBA(0xff, 0x5a, 0x36, 0xff));
+  }
+}
+
+//应用校准过期布局
+void BPMeasureResultView::ApplyCalibExpiredLayout() {
+  if (titleLabel_ != nullptr) {
+    titleLabel_->SetText("测量成功");
+  }
+  if (statusLabel_ != nullptr) {
+    statusLabel_->SetText("校准已过期");
+    statusLabel_->SetTextColor(Color::GetColorFromRGBA(0xff, 0x5a, 0x36, 0xff));
+  }
+
+}
+
+//更新文本
+void BPMeasureResultView::UpdateText() {
+  BPModel *model = BPModel::GetInstance();
+  uint8_t sbp = model->GetResultSbp();
+  uint8_t dbp = model->GetResultDbp();
+
+  if (timeLabel_ != nullptr) {
+    timeLabel_->SetText(FormatMeasureTime().c_str());
+  }
+  if (sbpValue_ != nullptr) {
+    sbpValue_->SetText(std::to_string(sbp).c_str());
+  }
+  if (dbpValue_ != nullptr) {
+    dbpValue_->SetText(std::to_string(dbp).c_str());
+  }
+
+  if (IsHighBloodPressure(sbp, dbp)) {
+    ApplyHighLayout();
+  } else {
+    ApplyNormalLayout();
+  }
+}
+
+UIViewGroup *BPMeasureResultView::InitView() {
+  group_ = new UIViewGroup();
+  group_->SetPosition(0, 0, Screen::GetInstance().GetWidth(), Screen::GetInstance().GetHeight());
+
+  InitUILabel(titleLabel_, 0, 50, 466, 35, "bp_result_title", "测量成功",
+               DEFAULT_VECTOR_FONT_FILENAME, 30);
+  titleLabel_->SetAlign(TEXT_ALIGNMENT_CENTER, TEXT_ALIGNMENT_CENTER);
+
+  InitUILabel(timeLabel_, 0, 93, 466, 33, "bp_result_time", "--",
+              DEFAULT_VECTOR_FONT_FILENAME, 28);
+  timeLabel_->SetTextColor(Color::GetColorFromRGBA(0x99, 0x99, 0x99, 0xff));
+  timeLabel_->SetAlign(TEXT_ALIGNMENT_CENTER, TEXT_ALIGNMENT_CENTER);
+
+  InitDividerLines();
+
+  InitLabel(sbpTitle_, BP_RESULT_LEFT_X, 140, BP_RESULT_COL_W, 30, "bp_result_sbp_title", "收缩压",
+            DEFAULT_VECTOR_FONT_FILENAME, 22);
+  InitLabel(dbpTitle_, BP_RESULT_RIGHT_X, 140, BP_RESULT_COL_W, 30, "bp_result_dbp_title", "舒张压",
+            DEFAULT_VECTOR_FONT_FILENAME, 22);
+  sbpTitle_->SetAlign(TEXT_ALIGNMENT_CENTER, TEXT_ALIGNMENT_CENTER);
+  dbpTitle_->SetAlign(TEXT_ALIGNMENT_CENTER, TEXT_ALIGNMENT_CENTER);
+  sbpTitle_->SetTextColor(Color::GetColorFromRGBA(0xff, 0xff, 0xff, 0xff));
+  dbpTitle_->SetTextColor(Color::GetColorFromRGBA(0xff, 0xff, 0xff, 0xff));
+
+  InitUILabel(sbpValue_, BP_RESULT_LEFT_X, 178, BP_RESULT_COL_W, 64, "bp_result_sbp", "--",
+              DEFAULT_VECTOR_FONT_FILENAME, 56);
+  InitUILabel(dbpValue_, BP_RESULT_RIGHT_X, 178, BP_RESULT_COL_W, 64, "bp_result_dbp", "--",
+              DEFAULT_VECTOR_FONT_FILENAME, 56);
+  sbpValue_->SetAlign(TEXT_ALIGNMENT_CENTER, TEXT_ALIGNMENT_CENTER);
+  dbpValue_->SetAlign(TEXT_ALIGNMENT_CENTER, TEXT_ALIGNMENT_CENTER);
+
+  InitUILabel(sbpUnit_, BP_RESULT_LEFT_X, 248, BP_RESULT_COL_W, 28, "bp_result_sbp_unit", "mmHg",
+              DEFAULT_VECTOR_FONT_FILENAME, 22);
+  InitUILabel(dbpUnit_, BP_RESULT_RIGHT_X, 248, BP_RESULT_COL_W, 28, "bp_result_dbp_unit", "mmHg",
+              DEFAULT_VECTOR_FONT_FILENAME, 22);
+  sbpUnit_->SetAlign(TEXT_ALIGNMENT_CENTER, TEXT_ALIGNMENT_CENTER);
+  dbpUnit_->SetAlign(TEXT_ALIGNMENT_CENTER, TEXT_ALIGNMENT_CENTER);
+  sbpUnit_->SetTextColor(Color::GetColorFromRGBA(0x99, 0x99, 0x99, 0xff));
+  dbpUnit_->SetTextColor(Color::GetColorFromRGBA(0x99, 0x99, 0x99, 0xff));
+
+  InitLabel(statusLabel_, 0, 292, 466, 35, "bp_result_status", "血压处于正常范围",
+            DEFAULT_VECTOR_FONT_FILENAME, 26);
+  statusLabel_->SetAlign(TEXT_ALIGNMENT_CENTER, TEXT_ALIGNMENT_CENTER);
+
+  InitLabelButton(confirmButton_, 93, 330, 280, 90, BP_CLICK_CONFIRM, "确定", 30);
+  confirmButton_->SetStyle(STYLE_BACKGROUND_COLOR, 0xff191919);
+  confirmButton_->SetStyleForState(STYLE_BACKGROUND_COLOR, 0xff191919, UIButton::PRESSED);
+  confirmButton_->SetStyle(STYLE_BORDER_RADIUS, 45);
+  confirmButton_->SetOnClickListener(static_cast<UIView::OnClickListener *>(BPMainPresenter::GetInstance()));
+
+  group_->Add(titleLabel_);
+  group_->Add(timeLabel_);
+  if (hlineTop_ != nullptr) {
+    group_->Add(hlineTop_);
+  }
+  if (vlineMid_ != nullptr) {
+    group_->Add(vlineMid_);
+  }
+  group_->Add(sbpTitle_);
+  group_->Add(dbpTitle_);
+  group_->Add(sbpValue_);
+  group_->Add(dbpValue_);
+  group_->Add(sbpUnit_);
+  group_->Add(dbpUnit_);
+  if (hlineBottom_ != nullptr) {
+    group_->Add(hlineBottom_);
+  }
+  group_->Add(statusLabel_);
+  group_->Add(confirmButton_);
+
+  UpdateText();
+  return group_;
+}
+
+} // namespace OHOS
diff --git a/code/sdk/application/wearable/n9/nativeapp/nativeui/bloodpressure/src/BPMeasuringView.cpp b/code/sdk/application/wearable/n9/nativeapp/nativeui/bloodpressure/src/BPMeasuringView.cpp
new file mode 100644 (file)
index 0000000..5c6cbd5
--- /dev/null
@@ -0,0 +1,165 @@
+#include "bloodpressure/include/BPMeasuringView.h"
+#include "common/image_cache_manager.h"
+#include "securec.h"
+#include "uicomponents/UIComponents.h"
+#include "wearable_log.h"
+
+namespace OHOS {
+
+static constexpr int16_t BP_MEASURING_ANIM_X = 133;
+static constexpr int16_t BP_MEASURING_ANIM_Y = 105;
+static constexpr int16_t BP_MEASURING_ANIM_W = 200;
+static constexpr int16_t BP_MEASURING_ANIM_H = 200;
+static constexpr int16_t BP_MEASURING_ANIM_FRAME_COUNT = 40;
+static constexpr int16_t BP_MEASURING_ANIM_DELAY = 70;
+
+static const char *kMeasuringAnimPaths[BP_MEASURING_ANIM_FRAME_COUNT] = {
+    "/user/res/AIVoice/AIVoiceThinking/1.bin",
+    "/user/res/AIVoice/AIVoiceThinking/2.bin",
+    "/user/res/AIVoice/AIVoiceThinking/3.bin",
+    "/user/res/AIVoice/AIVoiceThinking/4.bin",
+    "/user/res/AIVoice/AIVoiceThinking/5.bin",
+    "/user/res/AIVoice/AIVoiceThinking/6.bin",
+    "/user/res/AIVoice/AIVoiceThinking/7.bin",
+    "/user/res/AIVoice/AIVoiceThinking/8.bin",
+    "/user/res/AIVoice/AIVoiceThinking/9.bin",
+    "/user/res/AIVoice/AIVoiceThinking/10.bin",
+    "/user/res/AIVoice/AIVoiceThinking/11.bin",
+    "/user/res/AIVoice/AIVoiceThinking/12.bin",
+    "/user/res/AIVoice/AIVoiceThinking/13.bin",
+    "/user/res/AIVoice/AIVoiceThinking/14.bin",
+    "/user/res/AIVoice/AIVoiceThinking/15.bin",
+    "/user/res/AIVoice/AIVoiceThinking/16.bin",
+    "/user/res/AIVoice/AIVoiceThinking/17.bin",
+    "/user/res/AIVoice/AIVoiceThinking/18.bin",
+    "/user/res/AIVoice/AIVoiceThinking/19.bin",
+    "/user/res/AIVoice/AIVoiceThinking/20.bin",
+    "/user/res/AIVoice/AIVoiceThinking/21.bin",
+    "/user/res/AIVoice/AIVoiceThinking/22.bin",
+    "/user/res/AIVoice/AIVoiceThinking/23.bin",
+    "/user/res/AIVoice/AIVoiceThinking/24.bin",
+    "/user/res/AIVoice/AIVoiceThinking/25.bin",
+    "/user/res/AIVoice/AIVoiceThinking/26.bin",
+    "/user/res/AIVoice/AIVoiceThinking/27.bin",
+    "/user/res/AIVoice/AIVoiceThinking/28.bin",
+    "/user/res/AIVoice/AIVoiceThinking/29.bin",
+    "/user/res/AIVoice/AIVoiceThinking/30.bin",
+    "/user/res/AIVoice/AIVoiceThinking/31.bin",
+    "/user/res/AIVoice/AIVoiceThinking/32.bin",
+    "/user/res/AIVoice/AIVoiceThinking/33.bin",
+    "/user/res/AIVoice/AIVoiceThinking/34.bin",
+    "/user/res/AIVoice/AIVoiceThinking/35.bin",
+    "/user/res/AIVoice/AIVoiceThinking/36.bin",
+    "/user/res/AIVoice/AIVoiceThinking/37.bin",
+    "/user/res/AIVoice/AIVoiceThinking/38.bin",
+    "/user/res/AIVoice/AIVoiceThinking/39.bin",
+    "/user/res/AIVoice/AIVoiceThinking/40.bin",
+};
+
+static BPMeasuringView *g_instance = nullptr;
+
+BPMeasuringView::BPMeasuringView() { g_instance = this; }
+
+BPMeasuringView::~BPMeasuringView() {
+  StopMeasuringAnimation();
+  if (group_ != nullptr) {
+    group_->RemoveAll();
+    delete noteLabel_;
+    delete animatorView_;
+    delete group_;
+    noteLabel_ = nullptr;
+    animatorView_ = nullptr;
+    group_ = nullptr;
+  }
+  UnloadAnimatorFrames();
+  g_instance = nullptr;
+}
+
+BPMeasuringView *BPMeasuringView::GetInstance() { return g_instance; }
+
+bool BPMeasuringView::InitMeasuringAnimator() {
+  if (animatorInited_) {
+    return animatorView_ != nullptr;
+  }
+  animatorView_ = new UIImageAnimatorView();
+  if (animatorView_ == nullptr) {
+    WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "BPMeasuringView animator new fail");
+    return false;
+  }
+  for (int16_t i = 0; i < BP_MEASURING_ANIM_FRAME_COUNT; i++) {
+    frameInfo_[i].imageInfo =
+        ImageCacheManager::GetInstance().LoadSingleRes(kMeasuringAnimPaths[i]);
+    frameInfo_[i].imageType = IMG_SRC_IMAGE_INFO;
+    frameInfo_[i].pos.x = BP_MEASURING_ANIM_X;
+    frameInfo_[i].pos.y = BP_MEASURING_ANIM_Y;
+    frameInfo_[i].width = BP_MEASURING_ANIM_W;
+    frameInfo_[i].height = BP_MEASURING_ANIM_H;
+  }
+  animatorView_->SetPosition(BP_MEASURING_ANIM_X, BP_MEASURING_ANIM_Y,
+                             BP_MEASURING_ANIM_W, BP_MEASURING_ANIM_H);
+  animatorView_->SetImageAnimatorSrc(frameInfo_, BP_MEASURING_ANIM_FRAME_COUNT,
+                                     BP_MEASURING_ANIM_DELAY);
+  animatorView_->SetRepeat(true);
+  animatorView_->SetAutoEnable(false);
+  animatorView_->SetResizeMode(UIImageView::FILL);
+  animatorInited_ = true;
+  return true;
+}
+
+void BPMeasuringView::UnloadAnimatorFrames() {
+  if (!animatorInited_) {
+    return;
+  }
+  for (int16_t i = 0; i < BP_MEASURING_ANIM_FRAME_COUNT; i++) {
+    ImageCacheManager::GetInstance().UnloadSingleRes(kMeasuringAnimPaths[i]);
+    frameInfo_[i].imageInfo = nullptr;
+  }
+  animatorInited_ = false;
+}
+
+void BPMeasuringView::StartMeasuringAnimation() {
+  if (!InitMeasuringAnimator() || animatorView_ == nullptr) {
+    return;
+  }
+  animatorView_->Start();
+}
+
+void BPMeasuringView::StopMeasuringAnimation() {
+  if (animatorView_ != nullptr) {
+    animatorView_->Stop();
+  }
+}
+
+UIViewGroup *BPMeasuringView::InitView() {
+  group_ = new UIViewGroup();
+  group_->SetPosition(0, 0, Screen::GetInstance().GetWidth(),
+                      Screen::GetInstance().GetHeight());
+  if (!InitMeasuringAnimator()) {
+    WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "BPMeasuringView InitMeasuringAnimator fail");
+  } else if (animatorView_ != nullptr) {
+    group_->Add(animatorView_);
+  }
+  InitUILabel(noteLabel_, 0, BP_MEASURING_ANIM_Y + BP_MEASURING_ANIM_H + 20, 466, 35,
+              "bp_measuring_note", "请保持静止", DEFAULT_VECTOR_FONT_FILENAME, 30);
+  group_->Add(noteLabel_);
+  return group_;
+}
+
+void BPMeasuringView::UpdateProgressPercent(int percent) {
+  if (noteLabel_ == nullptr) {
+    return;
+  }
+  if (percent < 0) {
+    percent = 0;
+  } else if (percent > 100) {
+    percent = 100;
+  }
+  char buf[32] = {0};
+  if (sprintf_s(buf, sizeof(buf), "请保持静止 %d%%", percent) < 0) {
+    noteLabel_->SetText("请保持静止");
+    return;
+  }
+  noteLabel_->SetText(buf);
+}
+
+} // namespace OHOS
diff --git a/code/sdk/application/wearable/n9/nativeapp/nativeui/bloodpressure/src/BPModel.cpp b/code/sdk/application/wearable/n9/nativeapp/nativeui/bloodpressure/src/BPModel.cpp
new file mode 100644 (file)
index 0000000..1fcb6b1
--- /dev/null
@@ -0,0 +1,457 @@
+#include "bloodpressure/include/BPModel.h"
+#include "custom_utils.h"
+#include "kv_store.h"
+#include "securec.h"
+#include "wearable_log.h"
+#include <cstdio>
+#include <ctime>
+#include <sys/stat.h>
+#include <unistd.h>
+
+namespace OHOS {
+
+namespace {
+static constexpr const char *BP_DATA_DIR = "/user/userdata/health/bp";
+static constexpr const char *BP_CALIBRATION_FILE = "/user/userdata/health/bp/BPCalibration.bin";
+static constexpr const char *BP_HISTORY_FILE = "/user/userdata/health/bp/BPHistory.bin";
+static constexpr const char *BP_KV_DISCLAIMER = "bp.disclaimer";
+/** 校准时间戳无效值 */
+static constexpr int32_t BP_CALIBRATION_INVALID_TS = -1;
+static constexpr uint32_t BP_CALIBRATION_MAGIC_V1 = 0x42504301U;
+static constexpr uint32_t BP_CALIBRATION_MAGIC_V2 = 0x42504302U;
+static constexpr uint32_t BP_HISTORY_MAGIC = 0x42504801U;
+static constexpr uint32_t BP_HISTORY_MAX_COUNT = 50U;
+
+struct BPCalibrationStorageV1 {
+  uint32_t magic;
+  int32_t timestamp;
+};
+
+struct BPCalibrationStorageV2 {
+  uint32_t magic;
+  int32_t timestamp;
+  uint8_t sbp1;
+  uint8_t dbp1;
+  uint8_t sbp2;
+  uint8_t dbp2;
+  uint8_t reserved[3];
+};
+
+struct BPHistoryStorage {
+  uint32_t magic;
+  uint32_t count;
+  BPRecord records[50];
+};
+} // namespace
+
+BPModel::BPModel() {
+  LoadCalibrationFromStorage();
+  LoadData();
+}
+
+BPModel::~BPModel() {}
+
+BPModel *BPModel::GetInstance() {
+  static BPModel instance;
+  return &instance;
+}
+
+bool BPModel::IsFirstUse() const {
+  char buf[8] = {0};
+  int len = UtilsGetValue(BP_KV_DISCLAIMER, buf, sizeof(buf));
+  return len <= 0 || buf[0] != '1';
+}
+
+bool BPModel::MarkDisclaimerAccepted() {
+  return UtilsSetValue(BP_KV_DISCLAIMER, "1") == 0;
+}
+
+bool BPModel::CreateBPDataDirectory() {
+  if (access(BP_DATA_DIR, F_OK) == 0) {
+    return true;
+  }
+  if (mkdir(BP_DATA_DIR, S_IRWXU) != 0) {
+    WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "BPModel::CreateBPDataDirectory mkdir fail");
+    return false;
+  }
+  return true;
+}
+
+/** 从存储中加载校准数据 */
+bool BPModel::LoadCalibrationFromStorage() {
+  calibrationTimestamp_ = BP_CALIBRATION_INVALID_TS;
+  firstRoundSaved_ = false;
+
+  FILE *fp = fopen(BP_CALIBRATION_FILE, "rb");
+  if (fp == nullptr) {
+    WEARABLE_LOGD(WEARABLE_LOG_MODULE_APP, "BPModel no calibration file, state=-1");
+    return false;
+  }
+
+  BPCalibrationStorageV2 storage = {0};
+  size_t readSize = fread(&storage, sizeof(BPCalibrationStorageV2), 1, fp);
+  fclose(fp);
+
+  if (readSize != 1) {
+    calibrationTimestamp_ = BP_CALIBRATION_INVALID_TS;
+    return false;
+  }
+
+  if (storage.magic == BP_CALIBRATION_MAGIC_V2) {
+    calibrationTimestamp_ = storage.timestamp;
+    firstRoundSbp_ = storage.sbp1;
+    firstRoundDbp_ = storage.dbp1;
+    secondRoundSbp_ = storage.sbp2;
+    secondRoundDbp_ = storage.dbp2;
+    firstRoundSaved_ = true;
+    return true;
+  }
+
+  if (storage.magic == BP_CALIBRATION_MAGIC_V1) {
+    BPCalibrationStorageV1 *legacy = reinterpret_cast<BPCalibrationStorageV1 *>(&storage);
+    if (legacy->timestamp != BP_CALIBRATION_INVALID_TS) {
+      calibrationTimestamp_ = legacy->timestamp;
+      return true;
+    }
+  }
+
+  calibrationTimestamp_ = BP_CALIBRATION_INVALID_TS;
+  return false;
+}
+
+/** 校准完成后写入当前时间戳与校准值 */
+bool BPModel::SaveCalibrationToStorage() {
+  if (!CreateBPDataDirectory()) {
+    return false;
+  }
+
+  BPCalibrationStorageV2 storage = {0};
+  storage.magic = BP_CALIBRATION_MAGIC_V2;
+  storage.timestamp = calibrationTimestamp_;
+  storage.sbp1 = firstRoundSbp_;
+  storage.dbp1 = firstRoundDbp_;
+  storage.sbp2 = secondRoundSbp_;
+  storage.dbp2 = secondRoundDbp_;
+
+  FILE *fp = fopen(BP_CALIBRATION_FILE, "wb");
+  if (fp == nullptr) {
+    WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "BPModel SaveCalibration fopen fail");
+    return false;
+  }
+
+  size_t writeSize = fwrite(&storage, sizeof(BPCalibrationStorageV2), 1, fp);
+  fclose(fp);
+  return writeSize == 1;
+}
+
+/** 校准完成后写入当前时间戳与校准值 */
+bool BPModel::SaveCalibrationResult() {
+  uint32_t now = 0;
+  if (uapi_get_timestampe_ss(&now) != ERRCODE_SUCC) {
+    WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "BPModel::SaveCalibrationResult get timestamp fail");
+    return false;
+  }
+
+  calibrationTimestamp_ = static_cast<int32_t>(now);
+  if (!SaveCalibrationToStorage()) {
+    return false;
+  }
+
+  WEARABLE_LOGD(WEARABLE_LOG_MODULE_APP,
+                "BPModel calibration saved ts=%d sbp1=%u dbp1=%u sbp2=%u dbp2=%u",
+                calibrationTimestamp_, firstRoundSbp_, firstRoundDbp_, secondRoundSbp_, secondRoundDbp_);
+  return true;
+}
+
+int32_t BPModel::GetCalibrationTimestamp() const { return calibrationTimestamp_; }
+
+/** 设置校准有效期(秒),默认 30 天,与 fd230 needMeasure 一致 */
+void BPModel::SetCalibrationTimeoutSec(uint32_t seconds) {
+  if (seconds > 0) {
+    calibrationTimeoutSec_ = seconds;
+  }
+}
+
+/** 获取校准有效期(秒) */
+uint32_t BPModel::GetCalibrationTimeoutSec() const { return calibrationTimeoutSec_; }
+
+/** 获取当前校准状态 */
+int8_t BPModel::GetCurrentstate() {
+  if (calibrationTimestamp_ == BP_CALIBRATION_INVALID_TS) {
+    LoadCalibrationFromStorage();
+  }
+
+  if (calibrationTimestamp_ == BP_CALIBRATION_INVALID_TS) {
+    return BP_CALIB_STATE_NOT_CALIBRATED;
+  }
+
+  uint32_t now = 0;
+  if (uapi_get_timestampe_ss(&now) != ERRCODE_SUCC) {
+    WEARABLE_LOGW(WEARABLE_LOG_MODULE_APP, "BPModel::GetCurrentstate get timestamp fail");
+    return BP_CALIB_STATE_NOT_CALIBRATED;
+  }
+
+  uint32_t calibTime = static_cast<uint32_t>(calibrationTimestamp_);
+  if (now < calibTime) {
+    return BP_CALIB_STATE_VALID;
+  }
+
+  uint32_t elapsed = now - calibTime;
+  if (elapsed > calibrationTimeoutSec_) {
+    return BP_CALIB_STATE_EXPIRED;
+  }
+
+  return BP_CALIB_STATE_VALID;
+}
+
+/** 获取校准剩余天数,-1 表示未校准或已过期 */
+int32_t BPModel::GetCalibrationRemainingDays() const {
+  if (calibrationTimestamp_ == BP_CALIBRATION_INVALID_TS) {
+    return -1;
+  }
+
+  uint32_t now = 0;
+  if (uapi_get_timestampe_ss(&now) != ERRCODE_SUCC) {
+    return -1;
+  }
+
+  uint32_t calibTime = static_cast<uint32_t>(calibrationTimestamp_);
+  if (now < calibTime) {
+    return static_cast<int32_t>((calibrationTimeoutSec_ + 86399U) / 86400U);
+  }
+
+  uint32_t elapsed = now - calibTime;
+  if (elapsed >= calibrationTimeoutSec_) {
+    return -1;
+  }
+
+  uint32_t remaining = calibrationTimeoutSec_ - elapsed;
+  return static_cast<int32_t>((remaining + 86399U) / 86400U);
+}
+
+/** 校准即将过期(剩余天数 <= warnDays) */
+bool BPModel::IsCalibrationExpiringSoon(uint32_t warnDays) const {
+  int32_t days = GetCalibrationRemainingDays();
+  return days >= 0 && static_cast<uint32_t>(days) <= warnDays;
+}
+
+/** 从存储中加载历史记录 */
+bool BPModel::LoadHistoryFromStorage() {
+  historyRecords_.clear();
+  lastRecord_ = {0, 0, 0};
+
+  FILE *fp = fopen(BP_HISTORY_FILE, "rb");
+  if (fp == nullptr) {
+    return false;
+  }
+
+  BPHistoryStorage storage = {0};
+  size_t readSize = fread(&storage, sizeof(BPHistoryStorage), 1, fp);
+  fclose(fp);
+
+  if (readSize != 1 || storage.magic != BP_HISTORY_MAGIC || storage.count == 0 ||
+      storage.count > BP_HISTORY_MAX_COUNT) {
+    return false;
+  }
+
+  for (uint32_t i = 0; i < storage.count; ++i) {
+    historyRecords_.push_back(storage.records[i]);
+  }
+  if (!historyRecords_.empty()) {
+    lastRecord_ = historyRecords_.front();
+  }
+  return true;
+}
+
+/** 将历史记录保存到存储 */
+bool BPModel::SaveHistoryToStorage() {
+  if (!CreateBPDataDirectory()) {
+    return false;
+  }
+
+  BPHistoryStorage storage = {0};
+  storage.magic = BP_HISTORY_MAGIC;
+  storage.count = static_cast<uint32_t>(historyRecords_.size());
+  if (storage.count > BP_HISTORY_MAX_COUNT) {
+    storage.count = BP_HISTORY_MAX_COUNT;
+  }
+  for (uint32_t i = 0; i < storage.count; ++i) {
+    storage.records[i] = historyRecords_[i];
+  }
+
+  FILE *fp = fopen(BP_HISTORY_FILE, "wb");
+  if (fp == nullptr) {
+    return false;
+  }
+  size_t writeSize = fwrite(&storage, sizeof(BPHistoryStorage), 1, fp);
+  fclose(fp);
+  return writeSize == 1;
+}
+
+/** 加载数据 */
+void BPModel::LoadData() {
+  if (LoadHistoryFromStorage()) {
+    return;
+  }
+
+  historyRecords_.clear();
+  lastRecord_ = {0, 0, 0};
+}
+
+/** 获取最后一次测量记录 */
+BPRecord BPModel::GetLastRecord() const { return lastRecord_; }
+
+/** 获取历史记录 */
+const std::deque<BPRecord> &BPModel::GetHistoryRecords() const { return historyRecords_; }
+
+/** 格式化时间戳 */
+std::string BPModel::FormatTimestamp(uint32_t timestamp) const {
+  if (timestamp == 0) {
+    return "--";
+  }
+  struct tm timeInfo = {0};
+  time_t ts = static_cast<time_t>(timestamp);
+  if (localtime_r(&ts, &timeInfo) == nullptr) {
+    return "--";
+  }
+  char buf[32] = {0};
+  if (sprintf_s(buf, sizeof(buf), "%04d/%02d/%02d %02d:%02d", timeInfo.tm_year + 1900,
+                timeInfo.tm_mon + 1, timeInfo.tm_mday, timeInfo.tm_hour,
+                timeInfo.tm_min) < 0) {
+    return "--";
+  }
+  return std::string(buf);
+}
+
+/** 格式化短时间戳 */
+std::string BPModel::FormatShortTimestamp(uint32_t timestamp) const {
+  if (timestamp == 0) {
+    return "--";
+  }
+  struct tm timeInfo = {0};
+  time_t ts = static_cast<time_t>(timestamp);
+  if (localtime_r(&ts, &timeInfo) == nullptr) {
+    return "--";
+  }
+  char buf[32] = {0};
+  if (sprintf_s(buf, sizeof(buf), "%02d-%02d %02d:%02d", timeInfo.tm_mon + 1,
+                timeInfo.tm_mday, timeInfo.tm_hour, timeInfo.tm_min) < 0) {
+    return "--";
+  }
+  return std::string(buf);
+}
+
+/** 记忆第一轮输入值 */
+void BPModel::ResetInputValues() {
+  if (calibrationRound_ == 2 && firstRoundSaved_) {
+    inputSbp_ = firstRoundSbp_;
+    inputDbp_ = firstRoundDbp_;
+  } else {
+    inputSbp_ = 120;
+    inputDbp_ = 80;
+  }
+}
+
+/** 调整输入值 */
+void BPModel::AdjustInputValue(BPValueType type, bool increase) {
+  uint8_t minVal = (type == BPValueType::SBP) ? 60 : 40;
+  uint8_t maxVal = (type == BPValueType::SBP) ? 250 : 150;
+  uint8_t *value = (type == BPValueType::SBP) ? &inputSbp_ : &inputDbp_;
+  if (increase) {
+    if (*value < maxVal) {
+      (*value)++;
+    }
+  } else if (*value > minVal) {
+    (*value)--;
+  }
+}
+
+/** 设置收缩压输入值 */
+void BPModel::SetInputSbp(uint8_t sbp) {
+  if (sbp < 60) {
+    inputSbp_ = 60;
+  } else if (sbp > 250) {
+    inputSbp_ = 250;
+  } else {
+    inputSbp_ = sbp;
+  }
+}
+
+/** 设置舒张压输入值 */
+void BPModel::SetInputDbp(uint8_t dbp) {
+  if (dbp < 40) {
+    inputDbp_ = 40;
+  } else if (dbp > 150) {
+    inputDbp_ = 150;
+  } else {
+    inputDbp_ = dbp;
+  }
+}
+
+/** 获取收缩压输入值 */
+uint8_t BPModel::GetInputSbp() const { return inputSbp_; }
+
+/** 获取舒张压输入值 */
+uint8_t BPModel::GetInputDbp() const { return inputDbp_; }
+
+/** 设置测量结果 */
+void BPModel::SetMeasureResult(uint8_t sbp, uint8_t dbp) {
+  resultSbp_ = sbp;
+  resultDbp_ = dbp;
+}
+
+/** 获取收缩压测量结果 */
+uint8_t BPModel::GetResultSbp() const { return resultSbp_; }
+
+/** 获取舒张压测量结果 */
+uint8_t BPModel::GetResultDbp() const { return resultDbp_; }
+
+/** 设置校准轮次 */
+void BPModel::SetCalibrationRound(uint8_t round) { calibrationRound_ = round; }
+
+/** 获取校准轮次 */
+uint8_t BPModel::GetCalibrationRound() const { return calibrationRound_; }
+
+/** 保存第一轮输入值 */
+void BPModel::SaveFirstRoundInput() {
+  firstRoundSbp_ = inputSbp_;
+  firstRoundDbp_ = inputDbp_;
+  firstRoundSaved_ = true;
+}
+
+/** 设置第二轮输入值 */
+void BPModel::SetSecondRoundInput(uint8_t sbp, uint8_t dbp) {
+  secondRoundSbp_ = sbp;
+  secondRoundDbp_ = dbp;
+}
+
+/** 判断校准差异是否过大 */
+  bool BPModel::IsCalibrationDifferenceTooLarge() const {
+  int sbpDiff = static_cast<int>(firstRoundSbp_) - static_cast<int>(secondRoundSbp_);
+  int dbpDiff = static_cast<int>(firstRoundDbp_) - static_cast<int>(secondRoundDbp_);
+  if (sbpDiff < 0) {
+    sbpDiff = -sbpDiff;
+  }
+  if (dbpDiff < 0) {
+    dbpDiff = -dbpDiff;
+  }
+  return sbpDiff > BP_CALIB_DIFF_THRESHOLD || dbpDiff > BP_CALIB_DIFF_THRESHOLD;
+}
+
+/** 保存测量记录 */
+bool BPModel::SaveMeasureRecord(uint8_t sbp, uint8_t dbp) {
+  uint32_t now = 0;
+  if (uapi_get_timestampe_ss(&now) != ERRCODE_SUCC) {
+    return false;
+  }
+
+  BPRecord record = {now, sbp, dbp};
+  historyRecords_.push_front(record);
+  while (historyRecords_.size() > BP_HISTORY_MAX_COUNT) {
+    historyRecords_.pop_back();
+  }
+  lastRecord_ = record;
+  return SaveHistoryToStorage();
+}
+
+} // namespace OHOS
diff --git a/code/sdk/application/wearable/n9/nativeapp/nativeui/bloodpressure/src/BPValueConfirmView.cpp b/code/sdk/application/wearable/n9/nativeapp/nativeui/bloodpressure/src/BPValueConfirmView.cpp
new file mode 100644 (file)
index 0000000..15e2f82
--- /dev/null
@@ -0,0 +1,253 @@
+#include "bloodpressure/include/BPValueConfirmView.h"
+#include "bloodpressure/include/BPMainPresenter.h"
+#include "bloodpressure/include/BPModel.h"
+#include "bloodpressure/include/BPViewClickIds.h"
+#include "common/screen.h"
+#include "ui_resource_string.h"
+#include "uicomponents/UIComponents.h"
+#include "wearable_log.h"
+
+namespace OHOS {
+
+namespace {
+static constexpr uint32_t BP_COLOR_WHITE = 0xffffffff;
+static constexpr uint32_t BP_COLOR_GREY_UNIT = 0xff666666;
+static constexpr uint32_t BP_COLOR_ERROR_TEXT = 0xffff4444;
+static constexpr uint32_t BP_COLOR_SBP_ERROR = 0xffff4444;
+static constexpr uint32_t BP_COLOR_BTN_GREY = 0xff333333;
+static constexpr uint32_t BP_COLOR_BTN_BLUE = 0xff0176ff;
+static constexpr uint32_t BP_COLOR_BTN_ORANGE = 0xffff5a36;
+static constexpr uint32_t BP_COLOR_LINE = 0xff333333;
+static constexpr int16_t BP_CONFIRM_BTN_Y = 320;
+static constexpr int16_t BP_CONFIRM_BTN_H = 90;
+static constexpr int16_t BP_CONFIRM_BTN_W = 170;
+static constexpr int16_t BP_CONFIRM_BTN_LEFT_X = 58;
+static constexpr int16_t BP_CONFIRM_BTN_RIGHT_X = 238;
+static constexpr int16_t BP_CONFIRM_ERROR_OK_X = 93;
+static constexpr int16_t BP_CONFIRM_ERROR_OK_W = 280;
+} // namespace
+
+static BPValueConfirmView *g_instance = nullptr;
+
+BPValueConfirmView::BPValueConfirmView() { g_instance = this; }
+
+BPValueConfirmView::~BPValueConfirmView() {
+  if (group_ != nullptr) {
+    group_->RemoveAll();
+    delete titleLabel_;
+    delete errorLabel_;
+    delete hlineTop_;
+    delete vlineMid_;
+    delete sbpTitle_;
+    delete dbpTitle_;
+    delete sbpValue_;
+    delete dbpValue_;
+    delete sbpUnit_;
+    delete dbpUnit_;
+    delete modifyButton_;
+    delete startCalibButton_;
+    delete errorOkButton_;
+    delete group_;
+    titleLabel_ = nullptr;
+    errorLabel_ = nullptr;
+    hlineTop_ = vlineMid_ = nullptr;
+    sbpTitle_ = dbpTitle_ = nullptr;
+    sbpValue_ = dbpValue_ = nullptr;
+    sbpUnit_ = dbpUnit_ = nullptr;
+    modifyButton_ = startCalibButton_ = errorOkButton_ = nullptr;
+    group_ = nullptr;
+  }
+  g_instance = nullptr;
+}
+
+BPValueConfirmView *BPValueConfirmView::GetInstance() { return g_instance; }
+
+
+void BPValueConfirmView::InitDividerLines() {
+  hlineTop_ = new UIImageView();
+  if (hlineTop_ != nullptr) {
+    hlineTop_->SetPosition(83, 128, 300, 2);
+    hlineTop_->SetStyle(STYLE_BACKGROUND_COLOR, BP_COLOR_LINE);
+    hlineTop_->SetStyle(STYLE_BACKGROUND_OPA, 0xff);
+    hlineTop_->SetVisible(true);
+  }
+  vlineMid_ = new UIImageView();
+  if (vlineMid_ != nullptr) {
+    vlineMid_->SetPosition(233, 140, 2, 130);
+    vlineMid_->SetStyle(STYLE_BACKGROUND_COLOR, BP_COLOR_LINE);
+    vlineMid_->SetStyle(STYLE_BACKGROUND_OPA, 0xff);
+    vlineMid_->SetVisible(true);
+  }
+}
+
+void BPValueConfirmView::InitActionButtons() {
+  auto *listener = static_cast<UIView::OnClickListener *>(BPMainPresenter::GetInstance());
+
+  InitLabelButton(modifyButton_, BP_CONFIRM_BTN_LEFT_X, BP_CONFIRM_BTN_Y, BP_CONFIRM_BTN_W,
+                  BP_CONFIRM_BTN_H, BP_CLICK_STOP, "修改", 26);
+  modifyButton_->SetStyle(STYLE_BACKGROUND_COLOR, BP_COLOR_BTN_GREY);
+  modifyButton_->SetStyleForState(STYLE_BACKGROUND_COLOR, BP_COLOR_BTN_GREY, UIButton::PRESSED);
+  modifyButton_->SetStyle(STYLE_BORDER_RADIUS, 45);
+  modifyButton_->SetOnClickListener(listener);
+
+  InitLabelButton(startCalibButton_, BP_CONFIRM_BTN_RIGHT_X, BP_CONFIRM_BTN_Y, BP_CONFIRM_BTN_W,
+                  BP_CONFIRM_BTN_H, BP_CLICK_VALUE_CONFIRM, "开始校准", 26);
+  startCalibButton_->SetStyle(STYLE_BACKGROUND_COLOR, BP_COLOR_BTN_BLUE);
+  startCalibButton_->SetStyleForState(STYLE_BACKGROUND_COLOR, BP_COLOR_BTN_BLUE, UIButton::PRESSED);
+  startCalibButton_->SetStyle(STYLE_BORDER_RADIUS, 45);
+  startCalibButton_->SetOnClickListener(listener);
+
+  InitLabelButton(errorOkButton_, BP_CONFIRM_ERROR_OK_X, 330, BP_CONFIRM_ERROR_OK_W,
+                  BP_CONFIRM_BTN_H, BP_CLICK_VALUE_ERROR_OK, "确定", 30);
+  errorOkButton_->SetStyle(STYLE_BACKGROUND_COLOR, BP_COLOR_BTN_ORANGE);
+  errorOkButton_->SetStyleForState(STYLE_BACKGROUND_COLOR, BP_COLOR_BTN_ORANGE, UIButton::PRESSED);
+  errorOkButton_->SetStyle(STYLE_BORDER_RADIUS, 45);
+  errorOkButton_->SetOnClickListener(listener);
+}
+
+/**
+ * @description: 有效血压值布局
+ */
+void BPValueConfirmView::ApplyValidLayout() {
+  isValid_ = true;
+  if (errorLabel_ != nullptr) {
+    errorLabel_->SetVisible(false);
+  }
+  if (sbpValue_ != nullptr) {
+    sbpValue_->SetTextColor(Color::GetColorFromRGBA(0xff, 0xff, 0xff, 0xff));
+  }
+  if (dbpValue_ != nullptr) {
+    dbpValue_->SetTextColor(Color::GetColorFromRGBA(0xff, 0xff, 0xff, 0xff));
+  }
+  if (modifyButton_ != nullptr) {
+    modifyButton_->SetVisible(true);
+  }
+  if (startCalibButton_ != nullptr) {
+    startCalibButton_->SetVisible(true);
+  }
+  if (errorOkButton_ != nullptr) {
+    errorOkButton_->SetVisible(false);
+  }
+}
+
+/**
+ * @description: 无效血压值布局
+ */
+void BPValueConfirmView::ApplyInvalidLayout(uint8_t sbp, uint8_t dbp) {
+  isValid_ = false;
+  if (errorLabel_ != nullptr) {
+    errorLabel_->SetVisible(true);
+  }
+  if (sbpValue_ != nullptr) {
+    sbpValue_->SetText(std::to_string(sbp).c_str());
+    sbpValue_->SetTextColor(Color::GetColorFromRGBA(0xff, 0x5a, 0x36, 0xff));
+  }
+  if (dbpValue_ != nullptr) {
+    dbpValue_->SetText(std::to_string(dbp).c_str());
+    dbpValue_->SetTextColor(Color::GetColorFromRGBA(0xff, 0xff, 0xff, 0xff));
+  }
+  if (modifyButton_ != nullptr) {
+    modifyButton_->SetVisible(false);
+  }
+  if (startCalibButton_ != nullptr) {
+    startCalibButton_->SetVisible(false);
+  }
+  if (errorOkButton_ != nullptr) {
+    errorOkButton_->SetVisible(true);
+  }
+}
+
+/**
+ * @description: 更新视图
+ */
+void BPValueConfirmView::UpdateView() {
+  BPModel *model = BPModel::GetInstance();
+  uint8_t sbp = model->GetInputSbp();
+  uint8_t dbp = model->GetInputDbp();
+
+  if (sbpValue_ != nullptr) {
+    sbpValue_->SetText(std::to_string(sbp).c_str());
+  }
+  if (dbpValue_ != nullptr) {
+    dbpValue_->SetText(std::to_string(dbp).c_str());
+  }
+
+  if (sbp > dbp) {
+    ApplyValidLayout();
+  } else {
+    ApplyInvalidLayout(sbp, dbp);
+  }
+}
+
+UIViewGroup *BPValueConfirmView::InitView() {
+  group_ = new UIViewGroup();
+  if (group_ == nullptr) {
+    WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "BPValueConfirmView InitView group fail");
+    return nullptr;
+  }
+  group_->SetPosition(0, 0, Screen::GetInstance().GetWidth(), Screen::GetInstance().GetHeight());
+
+  InitLabelExt(titleLabel_, 83, 50, 300, 35, "bp_value_confirm_title", STR_CONFIRM,
+               DEFAULT_VECTOR_FONT_FILENAME, 28);
+  titleLabel_->SetText("确认输入值");
+  titleLabel_->SetAlign(TEXT_ALIGNMENT_CENTER, TEXT_ALIGNMENT_CENTER);
+
+  InitLabelExt(errorLabel_, 53, 88, 360, 32, "bp_value_confirm_error", STR_CONFIRM,
+               DEFAULT_VECTOR_FONT_FILENAME, 22);
+  errorLabel_->SetText("收缩压应大于舒张压");
+  errorLabel_->SetTextColor(Color::GetColorFromRGBA(0xff, 0x44, 0x44, 0xff));
+  errorLabel_->SetAlign(TEXT_ALIGNMENT_CENTER, TEXT_ALIGNMENT_CENTER);
+  errorLabel_->SetVisible(false);
+
+  InitDividerLines();
+
+  InitLabel(sbpTitle_, BP_CONFIRM_BTN_LEFT_X, 140, BP_CONFIRM_BTN_W, 30, "bp_confirm_sbp_title", "收缩压",
+            DEFAULT_VECTOR_FONT_FILENAME, 22);
+  InitLabel(dbpTitle_, BP_CONFIRM_BTN_RIGHT_X, 140, BP_CONFIRM_BTN_W, 30, "bp_confirm_dbp_title", "舒张压",
+            DEFAULT_VECTOR_FONT_FILENAME, 22);
+  sbpTitle_->SetAlign(TEXT_ALIGNMENT_CENTER, TEXT_ALIGNMENT_CENTER);
+  dbpTitle_->SetAlign(TEXT_ALIGNMENT_CENTER, TEXT_ALIGNMENT_CENTER);
+
+  InitUILabel(sbpValue_, BP_CONFIRM_BTN_LEFT_X, 178, BP_CONFIRM_BTN_W, 64, "bp_confirm_sbp", "--",
+              DEFAULT_VECTOR_FONT_FILENAME, 56);
+  InitUILabel(dbpValue_, BP_CONFIRM_BTN_RIGHT_X, 178, BP_CONFIRM_BTN_W, 64, "bp_confirm_dbp", "--",
+              DEFAULT_VECTOR_FONT_FILENAME, 56);
+  sbpValue_->SetAlign(TEXT_ALIGNMENT_CENTER, TEXT_ALIGNMENT_CENTER);
+  dbpValue_->SetAlign(TEXT_ALIGNMENT_CENTER, TEXT_ALIGNMENT_CENTER);
+  sbpValue_->SetTextColor(Color::GetColorFromRGBA(0xff, 0xff, 0xff, 0xff));
+  dbpValue_->SetTextColor(Color::GetColorFromRGBA(0xff, 0xff, 0xff, 0xff));
+
+  InitUILabel(sbpUnit_, BP_CONFIRM_BTN_LEFT_X, 248, BP_CONFIRM_BTN_W, 28, "bp_confirm_sbp_unit",
+              "mmHg", DEFAULT_VECTOR_FONT_FILENAME, 22);
+  InitUILabel(dbpUnit_, BP_CONFIRM_BTN_RIGHT_X, 248, BP_CONFIRM_BTN_W, 28, "bp_confirm_dbp_unit",
+              "mmHg", DEFAULT_VECTOR_FONT_FILENAME, 22);
+  sbpUnit_->SetAlign(TEXT_ALIGNMENT_CENTER, TEXT_ALIGNMENT_CENTER);
+  dbpUnit_->SetAlign(TEXT_ALIGNMENT_CENTER, TEXT_ALIGNMENT_CENTER);
+  sbpUnit_->SetTextColor(Color::GetColorFromRGBA(0x99, 0x99, 0x99, 0xff));
+  dbpUnit_->SetTextColor(Color::GetColorFromRGBA(0x99, 0x99, 0x99, 0xff));
+
+  InitActionButtons();
+
+  group_->Add(titleLabel_);
+  group_->Add(errorLabel_);
+  if (hlineTop_ != nullptr) {
+    group_->Add(hlineTop_);
+  }
+  if (vlineMid_ != nullptr) {
+    group_->Add(vlineMid_);
+  }
+  group_->Add(sbpTitle_);
+  group_->Add(dbpTitle_);
+  group_->Add(sbpValue_);
+  group_->Add(dbpValue_);
+  group_->Add(sbpUnit_);
+  group_->Add(dbpUnit_);
+  group_->Add(modifyButton_);
+  group_->Add(startCalibButton_);
+  group_->Add(errorOkButton_);
+
+  UpdateView();
+  return group_;
+}
+
+} // namespace OHOS
diff --git a/code/sdk/application/wearable/n9/nativeapp/nativeui/bloodpressure/src/BPValueInputView.cpp b/code/sdk/application/wearable/n9/nativeapp/nativeui/bloodpressure/src/BPValueInputView.cpp
new file mode 100644 (file)
index 0000000..fa755f4
--- /dev/null
@@ -0,0 +1,131 @@
+#include "bloodpressure/include/BPValueInputView.h"
+#include "bloodpressure/include/BPMainPresenter.h"
+#include "bloodpressure/include/BPModel.h"
+#include "bloodpressure/include/BPViewClickIds.h"
+#include "common/screen.h"
+#include "graphic_config.h"
+#include "traing/Training_UiConfig.h"
+#include "ui_resource_string.h"
+#include "uicomponents/UIComponents.h"
+#include "wearable_log.h"
+
+namespace OHOS {
+
+namespace {
+static constexpr int16_t BP_PICKER_ITEM_HEIGHT = 70;
+static constexpr uint8_t BP_PICKER_BG_FONT = 40;
+static constexpr uint8_t BP_PICKER_HIGHLIGHT_FONT = 60;
+static constexpr int16_t BP_PICKER_Y = 126;
+static constexpr int16_t BP_PICKER_H = 181;
+static constexpr int16_t BP_SBP_PICKER_X = 79;
+static constexpr int16_t BP_SBP_PICKER_W = 120;
+static constexpr int16_t BP_DBP_PICKER_X = 267;
+static constexpr int16_t BP_DBP_PICKER_W = 120;
+static constexpr int16_t BP_UNIT_LABEL_Y = 324;
+} // namespace
+
+static BPValueInputView *g_instance = nullptr;
+
+BPValueInputView::BPValueInputView() { g_instance = this; }
+
+BPValueInputView::~BPValueInputView() {
+  if (group_ != nullptr) {
+    group_->RemoveAll();
+    delete titleLabel_;
+    delete sbpLabel_;
+    delete dbpLabel_;
+    delete sbpPicker_;
+    delete dbpPicker_;
+    delete okButton_;
+    delete group_;
+    titleLabel_ = nullptr;
+    sbpLabel_ = nullptr;
+    dbpLabel_ = nullptr;
+    sbpPicker_ = nullptr;
+    dbpPicker_ = nullptr;
+    okButton_ = nullptr;
+    group_ = nullptr;
+  }
+  g_instance = nullptr;
+}
+
+BPValueInputView *BPValueInputView::GetInstance() { return g_instance; }
+
+void BPValueInputView::InitBpPicker(UIPicker *&picker, int16_t x, int16_t y, int16_t width,
+                                    int16_t height, const char *viewId, int16_t start,
+                                    int16_t end) {
+  if (picker == nullptr) {
+    picker = new UIPicker();
+  }
+  if (picker == nullptr) {
+    WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "BPValueInputView InitBpPicker fail");
+    return;
+  }
+  picker->SetPosition(x, y);
+  picker->Resize(width, height);
+  picker->SetViewId(viewId);
+  picker->SetItemHeight(BP_PICKER_ITEM_HEIGHT);
+  picker->SetLoopState(true);
+  picker->SetTextColor(Color::GetColorFromRGBA(0x66, 0x66, 0x66, 0xff),
+                       Color::GetColorFromRGBA(0xff, 0xff, 0xff, 0xff));
+  picker->SetBackgroundFont(DEFAULT_VECTOR_FONT_FILENAME, BP_PICKER_BG_FONT);
+  picker->SetHighlightFont(DEFAULT_VECTOR_FONT_FILENAME, BP_PICKER_HIGHLIGHT_FONT);
+  picker->SetValues(start, end);
+  picker->SetVisible(true);
+}
+
+
+void BPValueInputView::SyncPickerFromModel() {
+  BPModel *model = BPModel::GetInstance();
+  if (sbpPicker_ != nullptr) {
+    int16_t sbp = static_cast<int16_t>(model->GetInputSbp());
+    if (sbp < SBP_MIN) {
+      sbp = SBP_MIN;
+    } else if (sbp > SBP_MAX) {
+      sbp = SBP_MAX;
+    }
+    sbpPicker_->SetSelected(static_cast<uint16_t>(sbp - SBP_MIN));
+  }
+  if (dbpPicker_ != nullptr) {
+    int16_t dbp = static_cast<int16_t>(model->GetInputDbp());
+    if (dbp < DBP_MIN) {
+      dbp = DBP_MIN;
+    } else if (dbp > DBP_MAX) {
+      dbp = DBP_MAX;
+    }
+    dbpPicker_->SetSelected(static_cast<uint16_t>(dbp - DBP_MIN));
+  }
+}
+
+UIViewGroup *BPValueInputView::InitView() {
+  group_ = new UIViewGroup();
+  group_->SetPosition(0, 0, Screen::GetInstance().GetWidth(), Screen::GetInstance().GetHeight());
+
+  InitLabel(titleLabel_, 128, 50, 210, 35, "bp_value_input_title", "输入血压计值",
+            DEFAULT_VECTOR_FONT_FILENAME, 30);
+  InitBpPicker(sbpPicker_, BP_SBP_PICKER_X, BP_PICKER_Y, BP_SBP_PICKER_W, BP_PICKER_H,
+               "bp_sbp_picker", SBP_MIN, SBP_MAX);
+  InitBpPicker(dbpPicker_, BP_DBP_PICKER_X, BP_PICKER_Y, BP_DBP_PICKER_W, BP_PICKER_H,
+               "bp_dbp_picker", DBP_MIN, DBP_MAX);
+  InitLabel(sbpLabel_, 114, BP_UNIT_LABEL_Y, 60, 23, "bp_sbp_unit", "收缩压",
+            DEFAULT_VECTOR_FONT_FILENAME, 20);
+  InitLabel(dbpLabel_, 292, BP_UNIT_LABEL_Y, 60, 23, "bp_dbp_unit", "舒张压",
+            DEFAULT_VECTOR_FONT_FILENAME, 20);
+  sbpLabel_->SetAlign(TEXT_ALIGNMENT_CENTER, TEXT_ALIGNMENT_CENTER);
+  dbpLabel_->SetAlign(TEXT_ALIGNMENT_CENTER, TEXT_ALIGNMENT_CENTER);
+
+  InitButtonExt(okButton_, 0, 366, VERTICAL_RESOLUTION, 100, BP_CLICK_VALUE_NEXT, STR_OK, 30);
+  okButton_->SetImageSrc(BUTTONOK_IMAGE, BUTTONOK_IMAGE);
+  okButton_->SetOnClickListener(static_cast<UIView::OnClickListener *>(BPMainPresenter::GetInstance()));
+
+  group_->Add(titleLabel_);
+  group_->Add(sbpPicker_);
+  group_->Add(dbpPicker_);
+  group_->Add(sbpLabel_);
+  group_->Add(dbpLabel_);
+  group_->Add(okButton_);
+  SyncPickerFromModel();
+  return group_;
+}
+
+} // namespace OHOS
diff --git a/code/sdk/application/wearable/n9/nativeapp/nativeui/ecg/include/ECGAbnormalView.h b/code/sdk/application/wearable/n9/nativeapp/nativeui/ecg/include/ECGAbnormalView.h
new file mode 100644 (file)
index 0000000..9623afc
--- /dev/null
@@ -0,0 +1,33 @@
+#ifndef ECG_ABNORMAL_VIEW_H
+#define ECG_ABNORMAL_VIEW_H
+
+#include "components/ui_image_view.h"
+#include "components/ui_label.h"
+#include "components/ui_label_button.h"
+#include "components/ui_view_group.h"
+
+namespace OHOS {
+
+#include "ecg/include/ECGViewClickIds.h"
+
+#define ECG_PATH_ABNORMAL "/user/res/bloodpressure/nonet.bin"
+
+class ECGAbnormalView {
+public:
+  ECGAbnormalView();
+  ~ECGAbnormalView();
+  static ECGAbnormalView *GetInstance();
+  UIViewGroup *InitView();
+
+private:
+  UIViewGroup *group_{nullptr};
+  UILabel *titleLabel_{nullptr};
+  UIImageView *iconView_{nullptr};
+  UILabel *noteLabel_{nullptr};
+  UILabelButton *exitButton_{nullptr};
+  UILabelButton *retryButton_{nullptr};
+};
+
+} // namespace OHOS
+
+#endif
diff --git a/code/sdk/application/wearable/n9/nativeapp/nativeui/ecg/include/ECGEntryView.h b/code/sdk/application/wearable/n9/nativeapp/nativeui/ecg/include/ECGEntryView.h
new file mode 100644 (file)
index 0000000..f9a1ef8
--- /dev/null
@@ -0,0 +1,37 @@
+#ifndef ECG_ENTRY_VIEW_H
+#define ECG_ENTRY_VIEW_H
+
+#include "components/ui_image_view.h"
+#include "components/ui_label.h"
+#include "components/ui_label_button.h"
+#include "components/ui_view.h"
+#include "components/ui_view_group.h"
+
+namespace OHOS {
+
+#include "ecg/include/ECGViewClickIds.h"
+
+class ECGEntryView {
+public:
+  ECGEntryView();
+  ~ECGEntryView();
+  static ECGEntryView *GetInstance();
+  UIViewGroup *InitView();
+  void InitPageIndicator();
+  void InitEntryView();
+
+private:
+  void ClearContent();
+
+
+  UIViewGroup *group_{nullptr};
+  UILabel *titleLabel_{nullptr};
+  UILabel *noteLabel_{nullptr};
+  UILabelButton *measureButton_{nullptr};
+  UIView *dotEntry_{nullptr};
+  UIView *dotHistory_{nullptr};
+};
+
+} // namespace OHOS
+
+#endif
diff --git a/code/sdk/application/wearable/n9/nativeapp/nativeui/ecg/include/ECGHistoryView.h b/code/sdk/application/wearable/n9/nativeapp/nativeui/ecg/include/ECGHistoryView.h
new file mode 100644 (file)
index 0000000..3c67914
--- /dev/null
@@ -0,0 +1,71 @@
+#ifndef ECG_HISTORY_VIEW_H
+#define ECG_HISTORY_VIEW_H
+
+#include "components/ui_image_view.h"
+#include "components/ui_label.h"
+#include "components/ui_label_button.h"
+#include "components/ui_scroll_view.h"
+#include "components/ui_view.h"
+#include "components/ui_view_group.h"
+#include <vector>
+
+namespace OHOS {
+
+#include "ecg/include/ECGViewClickIds.h"
+
+#define ECG_PATH_HEART_RED_SMALL_ICON "/user/res/ecg/heart_red_s.bin"
+#define ECG_PATH_HEART_RED_ICON "/user/res/ecg/heart_red.bin"
+#define ECG_PATH_HEART_GREEN_SMALL_ICON "/user/res/ecg/heart_green_s.bin"
+#define ECG_NO_RECORD_IMAGE "/user/res/Training/modules/Norecord.bin"
+
+class ECGHistoryView {
+public:
+  ECGHistoryView();
+  ~ECGHistoryView();
+  static ECGHistoryView *GetInstance();
+  UIViewGroup *InitView();
+  void RefreshView();
+  void ShowRecentPage();
+  void ShowListPage();
+  bool IsShowingListPage() const;
+ 
+private:
+  void ClearListItems();
+  void RebuildList();
+  void UpdateRecentPage();
+  void InitRecentPage();
+  void InitListPage();
+  void InitEmptyPage();
+  void InitPageIndicator();
+  void UpdatePageVisibility();
+
+  UIViewGroup *group_{nullptr};
+  UIViewGroup *recentPage_{nullptr};
+  UIViewGroup *listPage_{nullptr};
+  UIViewGroup *emptyPage_{nullptr};
+
+  UILabel *recentTitle_{nullptr};
+  UILabel *recentTime_{nullptr};
+  UILabel *recentResult_{nullptr};
+  UIImageView *recentHeart_{nullptr};
+  UILabel *recentBpm_{nullptr};
+  UILabel *recentBpmUnit_{nullptr};
+  UILabelButton *viewAllButton_{nullptr};
+
+  UILabel *listTitle_{nullptr};
+  UIScrollView *scrollView_{nullptr};
+  UIViewGroup *listContainer_{nullptr};
+
+  UILabel *emptyTitleLabel_{nullptr};
+  UIImageView *emptyImage_{nullptr};
+  UILabel *emptyHintLabel_{nullptr};
+  UILabelButton *startMeasureButton_{nullptr};
+  UIView *dotEntry_{nullptr};
+  UIView *dotHistory_{nullptr};
+  std::vector<UIView *> listItems_;
+  bool showingListPage_{false};
+};
+
+} // namespace OHOS
+
+#endif
diff --git a/code/sdk/application/wearable/n9/nativeapp/nativeui/ecg/include/ECGInstructionView.h b/code/sdk/application/wearable/n9/nativeapp/nativeui/ecg/include/ECGInstructionView.h
new file mode 100644 (file)
index 0000000..0b7f389
--- /dev/null
@@ -0,0 +1,51 @@
+#ifndef ECG_INSTRUCTION_VIEW_H
+#define ECG_INSTRUCTION_VIEW_H
+
+#include "components/ui_image_view.h"
+#include "components/ui_label.h"
+#include "components/ui_label_button.h"
+#include "components/ui_view_group.h"
+
+namespace OHOS {
+
+#include "ecg/include/ECGViewClickIds.h"
+
+#define ECG_PATH_WAIT_CONTACT "/user/res/ecg/wait_contact.bin"
+#define ECG_PATH_CONTACT_GOOD "/user/res/ecg/contact_good.bin"
+#define ECG_PATH_DISCLAIMER "/user/res/ecg/disclaimer.bin"
+
+typedef enum : uint8_t {
+  ECG_INSTRUCTION_TYPE_DISCLAIMER = 0,
+  ECG_INSTRUCTION_TYPE_WAIT_CONTACT,
+  ECG_INSTRUCTION_TYPE_CONTACT_GOOD,
+  MAX_ECG_INSTRUCTION_TYPE,
+} ECGInstructionType;
+
+class ECGInstructionView {
+public:
+  ECGInstructionView();
+  ~ECGInstructionView();
+  static ECGInstructionView *GetInstance();
+
+  UIViewGroup *InitView(ECGInstructionType type);
+  void UpdateForType(ECGInstructionType type);
+  ECGInstructionType GetCurrentType() const { return currentType_; }
+
+private:
+  void ClearContent();
+  void InitDisclaimer();
+  void InitWaitContact();
+  void InitContactGood();
+
+  UIViewGroup *group_{nullptr};
+  UILabel *titleLabel_{nullptr};
+  UILabel *contentLabel_{nullptr};
+  UILabel *noteLabel_{nullptr};
+  UIImageView *imageView_{nullptr};
+  UILabelButton *agreeButton_{nullptr};
+  ECGInstructionType currentType_{ECG_INSTRUCTION_TYPE_DISCLAIMER};
+};
+
+} // namespace OHOS
+
+#endif
diff --git a/code/sdk/application/wearable/n9/nativeapp/nativeui/ecg/include/ECGMainPresenter.h b/code/sdk/application/wearable/n9/nativeapp/nativeui/ecg/include/ECGMainPresenter.h
new file mode 100644 (file)
index 0000000..e839dd8
--- /dev/null
@@ -0,0 +1,52 @@
+#ifndef ECG_MAIN_PRESENTER_H
+#define ECG_MAIN_PRESENTER_H
+
+#include "Presenter.h"
+#include "ecg/include/ECGMainView.h"
+#include "components/ui_swipe_view.h"
+#include "common/key_code.h"
+#include "key/include/KeyInputListener.h"
+
+namespace OHOS {
+
+class ECGMainPresenter : public Presenter<ECGMainView>,
+                         public UIView::OnClickListener,
+                         public UIView::OnDragListener,
+                         public UISwipeView::OnSwipeListener,
+                         public RootView::OnKeyActListener {
+public:
+  ECGMainPresenter();
+  virtual ~ECGMainPresenter();
+  static ECGMainPresenter *GetInstance();
+
+  void OnStart() override;
+  void OnPause() override;
+  void OnResume() override;
+  void OnStop() override;
+
+  bool OnDragStart(UIView &view, const DragEvent &event) override;
+  bool OnDrag(UIView &view, const DragEvent &event) override;
+  bool OnDragEnd(UIView &view, const DragEvent &event) override;
+  void OnSwipe(UISwipeView &view) override;
+  bool OnClick(UIView &view, const ClickEvent &event) override;
+  bool OnKeyAct(UIView &view, const KeyEvent &event) override;
+
+  void StartMeasureTimer();
+  void StopMeasureTimer();
+  void StopContactGoodTimer();
+  void FinishMeasuringFlow(ECGMainView *mainView);
+  void OnBottomButtonPressed(ECGMainView *mainView);
+
+private:
+  void NavigateBack();
+  bool HandleSwipeBack();
+  bool IsFlowSubPage(int16_t viewId) const;
+  void StartMeasureFlow(ECGMainView *mainView);
+  void ShowWaitContact(ECGMainView *mainView);
+  void ShowContactGood(ECGMainView *mainView);
+  bool IsWaitingContact(ECGMainView *mainView) const;
+};
+
+} // namespace OHOS
+
+#endif
diff --git a/code/sdk/application/wearable/n9/nativeapp/nativeui/ecg/include/ECGMainView.h b/code/sdk/application/wearable/n9/nativeapp/nativeui/ecg/include/ECGMainView.h
new file mode 100644 (file)
index 0000000..83c7644
--- /dev/null
@@ -0,0 +1,62 @@
+#ifndef ECG_MAIN_VIEW_H
+#define ECG_MAIN_VIEW_H
+
+#include "View.h"
+#include "ecg/include/ECGInstructionView.h"
+#include "components/ui_view_group.h"
+
+namespace OHOS {
+
+typedef enum : uint8_t {
+  ECG_ENTRY_VIEW = 0,
+  ECG_HISTORY_VIEW,
+  ECG_INSTRUCTION_VIEW,
+  ECG_ABNORMAL_VIEW,
+  ECG_MEASURING_VIEW,
+  ECG_MEASURE_COMPLETE_VIEW,
+  ECG_MEASURE_RESULT_VIEW,
+  MAX_ECGVIEW,
+} ECGVIEWID;
+
+class ECGMainPresenter;
+class ECGMainView : public View<ECGMainPresenter> {
+public:
+  ECGMainView();
+  virtual ~ECGMainView();
+  static ECGMainView *GetInstance();
+  void OnStart() override;
+  void OnStop() override;
+
+  bool ChangeECGEntryView(int16_t thisView, int16_t type);
+  bool ChangeECGHistoryView(int16_t thisView, int16_t type);
+  bool ChangeECGInstructionView(int16_t thisView, int16_t type);
+  bool ChangeECGAbnormalView(int16_t thisView, int16_t type);
+  bool ChangeECGMeasuringView(int16_t thisView, int16_t type);
+  bool ChangeECGMeasureCompleteView(int16_t thisView, int16_t type);
+  bool ChangeECGMeasureResultView(int16_t thisView, int16_t type);
+
+  bool ClickFunc(int16_t interface, int16_t type);
+  void ChangeView(int16_t hideView, int16_t showView, int16_t type = 0);
+  int16_t GetCurrentViewId() const;
+  int16_t GetInstructionSubType() const { return instructionSubType_; }
+  void RefreshCurrentView();
+
+private:
+  void DeInit();
+
+  class ECGEntryView *entryView_{nullptr};
+  class ECGHistoryView *historyView_{nullptr};
+  class ECGInstructionView *instructionView_{nullptr};
+  class ECGAbnormalView *abnormalView_{nullptr};
+  class ECGMeasuringView *measuringView_{nullptr};
+  class ECGMeasureCompleteView *measureCompleteView_{nullptr};
+  class ECGMeasureResultView *measureResultView_{nullptr};
+  UIViewGroup *container_{nullptr};
+  UIViewGroup *group_[MAX_ECGVIEW]{nullptr};
+  int16_t currentViewId_{ECG_ENTRY_VIEW};
+  int16_t instructionSubType_{ECG_INSTRUCTION_TYPE_DISCLAIMER};
+};
+
+} // namespace OHOS
+
+#endif
diff --git a/code/sdk/application/wearable/n9/nativeapp/nativeui/ecg/include/ECGMeasureCompleteView.h b/code/sdk/application/wearable/n9/nativeapp/nativeui/ecg/include/ECGMeasureCompleteView.h
new file mode 100644 (file)
index 0000000..766e93a
--- /dev/null
@@ -0,0 +1,32 @@
+#ifndef ECG_MEASURE_COMPLETE_VIEW_H
+#define ECG_MEASURE_COMPLETE_VIEW_H
+
+#include "components/ui_image_animator.h"
+#include "components/ui_label.h"
+#include "components/ui_view_group.h"
+
+namespace OHOS {
+
+class ECGMeasureCompleteView {
+public:
+  ECGMeasureCompleteView();
+  ~ECGMeasureCompleteView();
+  static ECGMeasureCompleteView *GetInstance();
+  UIViewGroup *InitView();
+  void StartAnimation();
+  void StopAnimation();
+
+private:
+  bool InitAnimator();
+  void UnloadAnimatorFrames();
+
+  UIViewGroup *group_{nullptr};
+  UILabel *noteLabel_{nullptr};
+  UIImageAnimatorView *animatorView_{nullptr};
+  ImageAnimatorInfo frameInfo_[40]{};
+  bool animatorInited_{false};
+};
+
+} // namespace OHOS
+
+#endif
diff --git a/code/sdk/application/wearable/n9/nativeapp/nativeui/ecg/include/ECGMeasureResultView.h b/code/sdk/application/wearable/n9/nativeapp/nativeui/ecg/include/ECGMeasureResultView.h
new file mode 100644 (file)
index 0000000..47639ba
--- /dev/null
@@ -0,0 +1,33 @@
+#ifndef ECG_MEASURE_RESULT_VIEW_H
+#define ECG_MEASURE_RESULT_VIEW_H
+
+#include "components/ui_image_view.h"
+#include "components/ui_label.h"
+#include "components/ui_label_button.h"
+#include "components/ui_view_group.h"
+
+#define ECG_PATH_HEART_ICON "/user/res/heart_chart/hearticon.bin"
+#define ECG_PATH_RESULT_BACKGROUND "/user/res/ecg/result_background.bin"
+namespace OHOS {
+
+class ECGMeasureResultView {
+public:
+  ECGMeasureResultView();
+  ~ECGMeasureResultView();
+  static ECGMeasureResultView *GetInstance();
+  UIViewGroup *InitView();
+  void UpdateText();
+
+private:
+  UIViewGroup *group_{nullptr};
+  UIImageView *heartIcon_{nullptr};
+  UIImageView *backgroundIcon_{nullptr};
+  UILabel *bpmValueLabel_{nullptr};
+  UILabel *bpmUnitLabel_{nullptr};
+  UILabel *resultLabel_{nullptr};
+  UILabelButton *confirmButton_{nullptr};
+};
+
+} // namespace OHOS
+
+#endif
diff --git a/code/sdk/application/wearable/n9/nativeapp/nativeui/ecg/include/ECGMeasuringView.h b/code/sdk/application/wearable/n9/nativeapp/nativeui/ecg/include/ECGMeasuringView.h
new file mode 100644 (file)
index 0000000..339cf41
--- /dev/null
@@ -0,0 +1,40 @@
+#ifndef ECG_MEASURING_VIEW_H
+#define ECG_MEASURING_VIEW_H
+
+#include "components/ui_chart.h"
+#include "components/ui_label.h"
+#include "components/ui_view_group.h"
+
+namespace OHOS {
+
+class ECGMeasuringView {
+public:
+  ECGMeasuringView();
+  ~ECGMeasuringView();
+  static ECGMeasuringView *GetInstance();
+  UIViewGroup *InitView();
+  void ResetMeasurement();
+  /** @return true when countdown finished */
+  bool OnTimerTick();
+  void StopMeasurement();
+
+private:
+  void InitChart();
+  void UpdateWaveform();
+  void UpdateCountdownLabel();
+
+  UIViewGroup *group_{nullptr};
+  UILabel *titleLabel_{nullptr};
+  UIChartPolyline *chart_{nullptr};
+  UIChartDataSerial *dataSerial_{nullptr};
+  UILabel *countdownLabel_{nullptr};
+  UILabel *hintLabel_{nullptr};
+  Point wavePoints_[48]{};
+  float wavePhase_{0.0f};
+  int16_t countdownSec_{30};
+  static constexpr int16_t kWavePointCount = 48;
+};
+
+} // namespace OHOS
+
+#endif
diff --git a/code/sdk/application/wearable/n9/nativeapp/nativeui/ecg/include/ECGModel.h b/code/sdk/application/wearable/n9/nativeapp/nativeui/ecg/include/ECGModel.h
new file mode 100644 (file)
index 0000000..5c9bf2d
--- /dev/null
@@ -0,0 +1,51 @@
+#ifndef ECG_MODEL_H
+#define ECG_MODEL_H
+
+#include "gfx_utils/color.h"
+#include <cstdint>
+#include <deque>
+#include <string>
+
+namespace OHOS {
+
+enum class ECGResultType : uint8_t {
+  SINUS_RHYTHM = 0,
+  ATRIAL_FIBRILLATION = 1,
+  INCONCLUSIVE = 2,
+};
+
+struct ECGRecord {
+  uint32_t timestamp;
+  uint8_t bpm;
+  ECGResultType result;
+};
+
+class ECGModel {
+public:
+  ECGModel();
+  ~ECGModel();
+  static ECGModel *GetInstance();
+
+  void LoadData();
+  ECGRecord GetLastRecord() const;
+  bool HasHistory() const;
+  const std::deque<ECGRecord> &GetHistoryRecords() const;
+  std::string FormatShortTimestamp(uint32_t timestamp) const;
+  std::string GetResultText(ECGResultType type) const;
+  ColorType GetResultColor(ECGResultType type) const;
+
+  void SetMeasureResult(uint8_t bpm, ECGResultType result);
+  uint8_t GetResultBpm() const;
+  ECGResultType GetResultType() const;
+  void AddMeasureToHistory();
+
+private:
+  ECGRecord lastRecord_{0, 0, ECGResultType::INCONCLUSIVE};
+  std::deque<ECGRecord> historyRecords_;
+  uint8_t resultBpm_{0};
+  ECGResultType resultType_{ECGResultType::SINUS_RHYTHM};
+};
+
+} // namespace OHOS
+
+#endif
diff --git a/code/sdk/application/wearable/n9/nativeapp/nativeui/ecg/include/ECGViewClickIds.h b/code/sdk/application/wearable/n9/nativeapp/nativeui/ecg/include/ECGViewClickIds.h
new file mode 100644 (file)
index 0000000..b95d7a9
--- /dev/null
@@ -0,0 +1,15 @@
+#ifndef ECG_VIEW_CLICK_IDS_H
+#define ECG_VIEW_CLICK_IDS_H
+
+#define ECG_CLICK_HISTORY "ecg_click_history"
+#define ECG_CLICK_HISTORY_BACK "ecg_click_history_back"
+#define ECG_CLICK_VIEW_ALL "ecg_click_view_all"
+#define ECG_CLICK_INSTRUCTION "ecg_click_instruction"
+#define ECG_CLICK_INSTRUCTION_BACK "ecg_click_instruction_back"
+#define ECG_CLICK_MEASURE "ecg_click_measure"
+#define ECG_CLICK_STOP "ecg_click_stop"
+#define ECG_CLICK_RETRY "ecg_click_retry"
+#define ECG_CLICK_EXIT "ecg_click_exit"
+#define ECG_CLICK_CONFIRM "ecg_click_confirm"
+
+#endif
diff --git a/code/sdk/application/wearable/n9/nativeapp/nativeui/ecg/src/ECGAbnormalView.cpp b/code/sdk/application/wearable/n9/nativeapp/nativeui/ecg/src/ECGAbnormalView.cpp
new file mode 100644 (file)
index 0000000..dc2b818
--- /dev/null
@@ -0,0 +1,62 @@
+#include "ecg/include/ECGAbnormalView.h"
+#include "ecg/include/ECGMainPresenter.h"
+#include "uicomponents/UIComponents.h"
+
+namespace OHOS {
+
+static ECGAbnormalView *g_instance = nullptr;
+
+ECGAbnormalView::ECGAbnormalView() { g_instance = this; }
+
+ECGAbnormalView::~ECGAbnormalView() {
+  if (group_ != nullptr) {
+    group_->RemoveAll();
+    delete titleLabel_;
+    delete iconView_;
+    delete noteLabel_;
+    delete exitButton_;
+    delete retryButton_;
+    delete group_;
+    titleLabel_ = nullptr;
+    iconView_ = nullptr;
+    noteLabel_ = nullptr;
+    exitButton_ = nullptr;
+    retryButton_ = nullptr;
+    group_ = nullptr;
+  }
+  g_instance = nullptr;
+}
+
+ECGAbnormalView *ECGAbnormalView::GetInstance() { return g_instance; }
+
+UIViewGroup *ECGAbnormalView::InitView() {
+  group_ = new UIViewGroup();
+  group_->SetPosition(0, 0, Screen::GetInstance().GetWidth(), Screen::GetInstance().GetHeight());
+
+  InitUILabel(titleLabel_, 83, 50, 300, 35, "ecg_abnormal_title", "提示",
+            DEFAULT_VECTOR_FONT_FILENAME, 30);
+  InitImageView(iconView_, 193, 120, 80, 80, "ecg_abnormal_icon", ECG_PATH_ABNORMAL);
+  InitUILabel(noteLabel_, 53, 210, 360, 90, "ecg_abnormal_note",
+            "接触异常,请确保手指\n触摸电极后重试", DEFAULT_VECTOR_FONT_FILENAME, 26);
+  noteLabel_->SetLineBreakMode(UILabel::LINE_BREAK_WRAP);
+  noteLabel_->SetAlign(TEXT_ALIGNMENT_CENTER, TEXT_ALIGNMENT_CENTER);
+
+  InitLabelButton(exitButton_, 58, 320, 170, 90, ECG_CLICK_EXIT, "退出", 26);
+  InitLabelButton(retryButton_, 238, 320, 170, 90, ECG_CLICK_RETRY, "重试", 26);
+  exitButton_->SetStyle(STYLE_BACKGROUND_COLOR, 0xff191919);
+  retryButton_->SetStyle(STYLE_BACKGROUND_COLOR, 0xffff5a36);
+
+  auto *listener = static_cast<UIView::OnClickListener *>(ECGMainPresenter::GetInstance());
+  exitButton_->SetOnClickListener(listener);
+  retryButton_->SetOnClickListener(listener);
+  group_->SetOnDragListener(static_cast<UIView::OnDragListener *>(ECGMainPresenter::GetInstance()));
+
+  group_->Add(titleLabel_);
+  group_->Add(iconView_);
+  group_->Add(noteLabel_);
+  group_->Add(exitButton_);
+  group_->Add(retryButton_);
+  return group_;
+}
+
+} // namespace OHOS
diff --git a/code/sdk/application/wearable/n9/nativeapp/nativeui/ecg/src/ECGEntryView.cpp b/code/sdk/application/wearable/n9/nativeapp/nativeui/ecg/src/ECGEntryView.cpp
new file mode 100644 (file)
index 0000000..54fa503
--- /dev/null
@@ -0,0 +1,120 @@
+#include "ecg/include/ECGEntryView.h"
+#include "ecg/include/ECGMainPresenter.h"
+#include "ecg/include/ECGModel.h"
+#include "uicomponents/UIComponents.h"
+#include "wearable_log.h"
+
+namespace OHOS {
+
+static ECGEntryView *g_pECGEntryView = nullptr;
+
+ECGEntryView::ECGEntryView() { g_pECGEntryView = this; }
+
+ECGEntryView::~ECGEntryView() {
+  if (group_ != nullptr) {
+    group_->RemoveAll();
+    delete titleLabel_;
+    delete noteLabel_;
+    delete measureButton_;
+    delete dotEntry_;
+    delete dotHistory_;
+    delete group_;
+    titleLabel_ = nullptr;
+    noteLabel_ = nullptr;
+    measureButton_ = nullptr;
+    dotEntry_ = nullptr;
+    dotHistory_ = nullptr;
+    group_ = nullptr;
+  }
+  g_pECGEntryView = nullptr;
+}
+
+ECGEntryView *ECGEntryView::GetInstance() { return g_pECGEntryView; }
+
+void ECGEntryView::ClearContent() {
+  if (group_ == nullptr) {
+    return;
+  }
+  if (titleLabel_ != nullptr) {
+    group_->Remove(titleLabel_);
+    delete titleLabel_;
+    titleLabel_ = nullptr;
+  }
+  if (noteLabel_ != nullptr) {
+    group_->Remove(noteLabel_);
+    delete noteLabel_;
+    noteLabel_ = nullptr;
+  }
+  if (measureButton_ != nullptr) {
+    group_->Remove(measureButton_);
+    delete measureButton_;
+    measureButton_ = nullptr;
+  }
+  if (dotEntry_ != nullptr) {
+    group_->Remove(dotEntry_);
+    delete dotEntry_;
+    dotEntry_ = nullptr;
+  }
+  if (dotHistory_ != nullptr) {
+    group_->Remove(dotHistory_);
+    delete dotHistory_;
+    dotHistory_ = nullptr;
+  }
+}
+
+//分页指示器
+void ECGEntryView::InitPageIndicator() {
+  if (dotEntry_ != nullptr || dotHistory_ != nullptr) {
+    return;
+  }
+  dotEntry_ = new UIView();
+  dotHistory_ = new UIView();
+  if (dotEntry_ == nullptr || dotHistory_ == nullptr) {
+    return;
+  }
+  dotEntry_->SetPosition(213, 430, 10, 10);
+  dotEntry_->SetStyle(STYLE_BACKGROUND_COLOR, 0xffffffff);
+  dotEntry_->SetStyle(STYLE_BACKGROUND_OPA, 0xff);
+  dotEntry_->SetStyle(STYLE_BORDER_RADIUS, 5);
+  dotHistory_->SetPosition(243, 430, 10, 10);
+  dotHistory_->SetStyle(STYLE_BACKGROUND_COLOR, 0xff666666);
+  dotHistory_->SetStyle(STYLE_BACKGROUND_OPA, 0xff);
+  dotHistory_->SetStyle(STYLE_BORDER_RADIUS, 5);
+  group_->Add(dotEntry_);
+  group_->Add(dotHistory_);
+}
+
+
+void ECGEntryView::InitEntryView() {
+  InitUILabel(titleLabel_, 0, 50, 466, 35, "ecg_entry_title", "ECG测量",
+              DEFAULT_VECTOR_FONT_FILENAME, 30);
+  titleLabel_->SetAlign(TEXT_ALIGNMENT_CENTER, TEXT_ALIGNMENT_CENTER);
+
+  InitUILabel(noteLabel_, 0, 216, 466, 35, "ecg_entry_note", "保持指尖清洁湿润",
+              DEFAULT_VECTOR_FONT_FILENAME, 30);
+  noteLabel_->SetAlign(TEXT_ALIGNMENT_CENTER, TEXT_ALIGNMENT_CENTER);
+
+
+  InitLabelButton(measureButton_, 93, 320, 280, 90, ECG_CLICK_MEASURE, "开始测量", 30);
+  measureButton_->SetStyle(STYLE_BACKGROUND_COLOR, 0xff0176ff);
+  measureButton_->SetStyle(STYLE_BORDER_RADIUS, 100);
+  measureButton_->SetOnClickListener(static_cast<UIView::OnClickListener *>(ECGMainPresenter::GetInstance()));
+
+  group_->Add(titleLabel_);
+  group_->Add(noteLabel_);
+  group_->Add(measureButton_);
+}
+
+UIViewGroup *ECGEntryView::InitView() {
+  group_ = new UIViewGroup();
+  if (group_ == nullptr) {
+    return nullptr;
+  }
+  group_->SetPosition(0, 0, Screen::GetInstance().GetWidth(), Screen::GetInstance().GetHeight());
+  InitPageIndicator();
+  InitEntryView();
+  return group_;
+}
+
+
+} // namespace OHOS
diff --git a/code/sdk/application/wearable/n9/nativeapp/nativeui/ecg/src/ECGHistoryView.cpp b/code/sdk/application/wearable/n9/nativeapp/nativeui/ecg/src/ECGHistoryView.cpp
new file mode 100644 (file)
index 0000000..6692403
--- /dev/null
@@ -0,0 +1,351 @@
+#include "ecg/include/ECGHistoryView.h"
+#include "ecg/include/ECGMainPresenter.h"
+#include "ecg/include/ECGModel.h"
+#include "uicomponents/UIComponents.h"
+#include "wearable_log.h"
+
+namespace OHOS {
+
+namespace {
+static constexpr int16_t ECG_HISTORY_CARD_H = 160;
+static constexpr int16_t ECG_HISTORY_CARD_MARGIN = 40;
+static constexpr int16_t ECG_HISTORY_CARD_W = 380;
+static constexpr int16_t ECG_HISTORY_MAX_ITEMS = 10;
+static constexpr uint32_t ECG_CARD_BG_COLOR = 0xff1a1a1a;
+} // namespace
+
+static ECGHistoryView *g_pECGHistoryView = nullptr;
+
+ECGHistoryView::ECGHistoryView() { g_pECGHistoryView = this; }
+
+ECGHistoryView::~ECGHistoryView() {
+  ClearListItems();
+  if (group_ != nullptr) {
+    group_->RemoveAll();
+    delete recentPage_;
+    delete listPage_;
+    delete emptyPage_;
+    delete dotEntry_;
+    delete dotHistory_;
+    delete group_;
+    recentPage_ = nullptr;
+    listPage_ = nullptr;
+    emptyPage_ = nullptr;
+    dotEntry_ = nullptr;
+    dotHistory_ = nullptr;
+    group_ = nullptr;
+  }
+  recentTitle_ = nullptr;
+  recentTime_ = nullptr;
+  recentResult_ = nullptr;
+  recentHeart_ = nullptr;
+  recentBpm_ = nullptr;
+  recentBpmUnit_ = nullptr;
+  viewAllButton_ = nullptr;
+  listTitle_ = nullptr;
+  scrollView_ = nullptr;
+  listContainer_ = nullptr;
+  emptyTitleLabel_ = nullptr;
+  emptyImage_ = nullptr;
+  emptyHintLabel_ = nullptr;
+  startMeasureButton_ = nullptr;
+  g_pECGHistoryView = nullptr;
+}
+
+ECGHistoryView *ECGHistoryView::GetInstance() { return g_pECGHistoryView; }
+
+bool ECGHistoryView::IsShowingListPage() const { return showingListPage_; }
+
+//显示最近记录页面
+void ECGHistoryView::ShowRecentPage() {
+  showingListPage_ = false;
+  UpdatePageVisibility();
+  if (group_ != nullptr) {
+    group_->Invalidate();
+  }
+}
+
+//显示测量记录页面
+void ECGHistoryView::ShowListPage() {
+  showingListPage_ = true;
+  RebuildList();
+  UpdatePageVisibility();
+  if (group_ != nullptr) {
+    group_->Invalidate();
+  }
+}
+
+void ECGHistoryView::UpdatePageVisibility() {
+  bool hasRecords = ECGModel::GetInstance()->HasHistory();
+  if (emptyPage_ != nullptr) {
+    emptyPage_->SetVisible(!hasRecords);
+  }
+  if (recentPage_ != nullptr) {
+    recentPage_->SetVisible(hasRecords && !showingListPage_);
+  }
+  if (listPage_ != nullptr) {
+    listPage_->SetVisible(hasRecords && showingListPage_);
+  }
+  if (dotEntry_ != nullptr && dotHistory_ != nullptr) {
+    bool showDots = hasRecords && !showingListPage_;
+    dotEntry_->SetVisible(showDots);
+    dotHistory_->SetVisible(showDots);
+  }
+}
+
+void ECGHistoryView::ClearListItems() {
+  for (auto *item : listItems_) {
+    if (listContainer_ != nullptr) {
+      listContainer_->Remove(item);
+    }
+    delete item;
+  }
+  listItems_.clear(); 
+}
+
+void ECGHistoryView::InitPageIndicator() {
+  dotEntry_ = new UIView();
+  dotHistory_ = new UIView();
+  if (dotEntry_ == nullptr || dotHistory_ == nullptr) {
+    return;
+  }
+  dotEntry_->SetPosition(213, 430, 10, 10);
+  dotEntry_->SetStyle(STYLE_BACKGROUND_COLOR, 0xff666666);
+  dotEntry_->SetStyle(STYLE_BACKGROUND_OPA, 0xff);
+  dotEntry_->SetStyle(STYLE_BORDER_RADIUS, 5);
+  dotHistory_->SetPosition(243, 430, 10, 10);
+  dotHistory_->SetStyle(STYLE_BACKGROUND_COLOR, 0xffffffff);
+  dotHistory_->SetStyle(STYLE_BACKGROUND_OPA, 0xff);
+  dotHistory_->SetStyle(STYLE_BORDER_RADIUS, 5);
+}
+
+void ECGHistoryView::InitEmptyPage() {
+  emptyPage_ = new UIViewGroup();
+  if (emptyPage_ == nullptr) {
+    return;
+  }
+  emptyPage_->SetPosition(0, 0, Screen::GetInstance().GetWidth(), Screen::GetInstance().GetHeight());
+  emptyPage_->SetDraggable(true);
+  emptyPage_->SetTouchable(true);
+  emptyPage_->SetOnDragListener(static_cast<UIView::OnDragListener *>(ECGMainPresenter::GetInstance()));
+
+  InitUILabel(emptyTitleLabel_, 0, 50, 466, 35, "ecg_history_empty_title", "暂无测量记录",
+              DEFAULT_VECTOR_FONT_FILENAME, 30);
+  emptyTitleLabel_->SetAlign(TEXT_ALIGNMENT_CENTER, TEXT_ALIGNMENT_CENTER);
+  InitImageView(emptyImage_, 193, 120, 80, 80, "ecg_history_empty_image", ECG_NO_RECORD_IMAGE);
+  InitUILabel(emptyHintLabel_, 53, 260, 360, 70, "ecg_history_empty_hint",
+              "完成一次测量后\n可在这里查看记录", DEFAULT_VECTOR_FONT_FILENAME, 28);
+  emptyHintLabel_->SetAlign(TEXT_ALIGNMENT_CENTER, TEXT_ALIGNMENT_CENTER);
+  emptyHintLabel_->SetLineBreakMode(UILabel::LINE_BREAK_WRAP);
+  InitLabelButton(startMeasureButton_, 93, 320, 280, 90, ECG_CLICK_MEASURE, "开始测量", 30);
+  startMeasureButton_->SetStyle(STYLE_BACKGROUND_COLOR, 0xff0176ff);
+  startMeasureButton_->SetStyle(STYLE_BORDER_RADIUS, 100);
+  startMeasureButton_->SetOnClickListener(
+      static_cast<UIView::OnClickListener *>(ECGMainPresenter::GetInstance()));
+
+  emptyPage_->Add(emptyTitleLabel_);
+  emptyPage_->Add(emptyImage_);
+  emptyPage_->Add(emptyHintLabel_);
+  emptyPage_->Add(startMeasureButton_);
+}
+
+void ECGHistoryView::InitRecentPage() {
+  recentPage_ = new UIViewGroup();
+  if (recentPage_ == nullptr) {
+    return;
+  }
+  recentPage_->SetPosition(0, 0, Screen::GetInstance().GetWidth(), Screen::GetInstance().GetHeight());
+  recentPage_->SetDraggable(true);
+  recentPage_->SetTouchable(true);
+  recentPage_->SetOnDragListener(static_cast<UIView::OnDragListener *>(ECGMainPresenter::GetInstance()));
+
+  InitUILabel(recentTitle_, 0, 50, 466, 35, "ecg_recent_title", "最近记录",
+              DEFAULT_VECTOR_FONT_FILENAME, 30);
+  recentTitle_->SetAlign(TEXT_ALIGNMENT_CENTER, TEXT_ALIGNMENT_CENTER);
+
+  InitUILabel(recentTime_, 0, 93, 466, 33, "ecg_recent_time", "--",
+              DEFAULT_VECTOR_FONT_FILENAME, 28);
+  recentTime_->SetTextColor(Color::GetColorFromRGBA(0x99, 0x99, 0x99, 0xff));
+  recentTime_->SetAlign(TEXT_ALIGNMENT_CENTER, TEXT_ALIGNMENT_CENTER);
+
+  InitUILabel(recentResult_, 0, 182, 466, 35, "ecg_recent_result", "--",
+              DEFAULT_VECTOR_FONT_FILENAME, 30);
+  recentResult_->SetAlign(TEXT_ALIGNMENT_CENTER, TEXT_ALIGNMENT_CENTER);
+
+  InitImageView(recentHeart_, 129, 182+35+12+8, 54, 54, "ecg_recent_heart", ECG_PATH_HEART_RED_ICON);
+
+  InitUILabel(recentBpm_, 129+54+12, 182+35+12, 66, 66, "ecg_recent_bpm", "--",
+              DEFAULT_VECTOR_FONT_FILENAME, 65);
+  recentBpm_->SetAlign(TEXT_ALIGNMENT_CENTER, TEXT_ALIGNMENT_CENTER);
+
+  InitUILabel(recentBpmUnit_, 129+54+12+66+8, 258, 72, 30, "ecg_recent_bpm_unit", "次/分",
+              DEFAULT_VECTOR_FONT_FILENAME, 30);
+  recentBpmUnit_->SetTextColor(Color::GetColorFromRGBA(0x99, 0x99, 0x99, 0xff));
+  recentBpmUnit_->SetAlign(TEXT_ALIGNMENT_CENTER, TEXT_ALIGNMENT_CENTER);
+
+  InitLabelButton(viewAllButton_, 93, 320, 280, 90, ECG_CLICK_VIEW_ALL, "查看全部", 28);
+  viewAllButton_->SetStyle(STYLE_BACKGROUND_COLOR, 0xff191919);
+  viewAllButton_->SetStyle(STYLE_BORDER_RADIUS, 100);
+  viewAllButton_->SetOnClickListener(static_cast<UIView::OnClickListener *>(ECGMainPresenter::GetInstance()));
+
+  recentPage_->Add(recentTitle_);
+  recentPage_->Add(recentTime_);
+  recentPage_->Add(recentResult_);
+  recentPage_->Add(recentHeart_);
+  recentPage_->Add(recentBpm_);
+  recentPage_->Add(recentBpmUnit_);
+  recentPage_->Add(viewAllButton_);
+}
+
+void ECGHistoryView::InitListPage() {
+  listPage_ = new UIViewGroup();
+  if (listPage_ == nullptr) {
+    return;
+  }
+  listPage_->SetPosition(0, 0, Screen::GetInstance().GetWidth(), Screen::GetInstance().GetHeight());
+  listPage_->SetDraggable(true);
+  listPage_->SetTouchable(true);
+  listPage_->SetOnDragListener(static_cast<UIView::OnDragListener *>(ECGMainPresenter::GetInstance()));
+
+  InitUILabel(listTitle_, 0, 50, 466, 35, "ecg_list_title", "测量记录",
+              DEFAULT_VECTOR_FONT_FILENAME, 30);
+  listTitle_->SetAlign(TEXT_ALIGNMENT_CENTER, TEXT_ALIGNMENT_CENTER);
+
+  scrollView_ = new UIScrollView();
+  listContainer_ = new UIViewGroup();
+  if (scrollView_ != nullptr && listContainer_ != nullptr) {
+    scrollView_->SetPosition(0, 100, Screen::GetInstance().GetWidth(), 330);
+    scrollView_->SetHorizontalScrollState(false);
+    scrollView_->SetThrowDrag(true);
+    listContainer_->SetPosition(0, 0, Screen::GetInstance().GetWidth(), 330);
+    scrollView_->Add(listContainer_);
+    listPage_->Add(scrollView_);
+  }
+  listPage_->Add(listTitle_);
+}
+
+void ECGHistoryView::UpdateRecentPage() {
+  if (recentTitle_ == nullptr || recentTime_ == nullptr || recentResult_ == nullptr ||
+      recentBpm_ == nullptr || recentHeart_ == nullptr) {
+    return;
+  }
+  ECGModel *model = ECGModel::GetInstance();
+  if (!model->HasHistory()) {
+    return;
+  }
+  ECGRecord last = model->GetLastRecord();
+  recentTime_->SetText(model->FormatShortTimestamp(last.timestamp).c_str());
+  recentResult_->SetText(model->GetResultText(last.result).c_str());
+  recentResult_->SetTextColor(model->GetResultColor(last.result));
+  if (last.bpm > 0) {
+    recentBpm_->SetText(std::to_string(last.bpm).c_str());
+  } else {
+    recentBpm_->SetText("--");
+  }
+}
+
+void ECGHistoryView::RebuildList() {
+  if (listContainer_ == nullptr) {
+    return;
+  }
+  ClearListItems();
+  ECGModel *model = ECGModel::GetInstance();
+  const auto &records = model->GetHistoryRecords();
+  size_t count = records.size();
+  if (count > ECG_HISTORY_MAX_ITEMS) {
+    count = ECG_HISTORY_MAX_ITEMS;
+  }
+  int16_t y = 0;
+  for (size_t i = 0; i < count; i++) {
+    const ECGRecord &rec = records[i];
+
+    UIView *card = new UIView();
+    if (card == nullptr) {
+      continue;
+    }
+    card->SetPosition(ECG_HISTORY_CARD_MARGIN, y, ECG_HISTORY_CARD_W, ECG_HISTORY_CARD_H);
+    card->SetStyle(STYLE_BACKGROUND_COLOR, ECG_CARD_BG_COLOR);
+    card->SetStyle(STYLE_BACKGROUND_OPA, 0xff);
+    card->SetStyle(STYLE_BORDER_RADIUS, 30);
+    listContainer_->Add(card);
+    listItems_.push_back(card);
+
+    UIImageView *heartIcon = new UIImageView();
+    if (heartIcon != nullptr) {
+      heartIcon->SetPosition(ECG_HISTORY_CARD_MARGIN + 40, y + 36, 38, 38);
+      heartIcon->SetSrc(ECG_PATH_HEART_RED_SMALL_ICON);
+      listContainer_->Add(heartIcon);
+      listItems_.push_back(heartIcon);
+    }
+
+    UILabel *resultLabel = nullptr;
+    InitUILabel(resultLabel, ECG_HISTORY_CARD_MARGIN + 40 + 38 + 8 + 4, y + 36 + 5, 120, 35, "ecg_list_result", "",
+                DEFAULT_VECTOR_FONT_FILENAME, 30);
+    resultLabel->SetAlign(TEXT_ALIGNMENT_LEFT, TEXT_ALIGNMENT_CENTER);
+    resultLabel->SetText(model->GetResultText(rec.result).c_str());
+    resultLabel->SetTextColor(model->GetResultColor(rec.result));
+
+    UILabel *bpmLabel = nullptr;
+    std::string bpmStr = rec.bpm > 0 ? (std::to_string(rec.bpm) + " 次/分") : "-- 次/分";
+    InitUILabel(bpmLabel, ECG_HISTORY_CARD_MARGIN + 40 + 38 + 8 + 8 + 120, y + 36 + 5 + 4, 119, 35, "ecg_list_bpm",
+                bpmStr.c_str(), DEFAULT_VECTOR_FONT_FILENAME, 28);
+    bpmLabel->SetAlign(TEXT_ALIGNMENT_RIGHT, TEXT_ALIGNMENT_CENTER);
+
+    UILabel *timeLabel = nullptr;
+    InitUILabel(timeLabel, ECG_HISTORY_CARD_MARGIN + 40, y + 36 + 58, 172, 30, "ecg_list_time",
+                model->FormatShortTimestamp(rec.timestamp).c_str(), DEFAULT_VECTOR_FONT_FILENAME, 28);
+    timeLabel->SetTextColor(Color::GetColorFromRGBA(0x99, 0x99, 0x99, 0xff));
+
+    listContainer_->Add(resultLabel);
+    listContainer_->Add(bpmLabel);
+    listContainer_->Add(timeLabel);
+    listItems_.push_back(resultLabel);
+    listItems_.push_back(bpmLabel);
+    listItems_.push_back(timeLabel);
+    y += ECG_HISTORY_CARD_H + 20;
+  }
+  listContainer_->Resize(466, y > 0 ? y : 330);
+}
+
+void ECGHistoryView::RefreshView() {
+  showingListPage_ = false;
+  UpdateRecentPage();
+  RebuildList();
+  UpdatePageVisibility();
+  if (group_ != nullptr) {
+    group_->Invalidate();
+  }
+}
+
+UIViewGroup *ECGHistoryView::InitView() {
+  group_ = new UIViewGroup();
+  if (group_ == nullptr) {
+    return nullptr;
+  }
+  group_->SetPosition(0, 0, Screen::GetInstance().GetWidth(), Screen::GetInstance().GetHeight());
+
+  InitRecentPage();
+  InitListPage();
+  InitEmptyPage();
+  InitPageIndicator();
+
+  if (recentPage_ != nullptr) {
+    group_->Add(recentPage_);
+  }
+  if (listPage_ != nullptr) {
+    group_->Add(listPage_);
+  }
+  if (emptyPage_ != nullptr) {
+    group_->Add(emptyPage_);
+  }
+  if (dotEntry_ != nullptr) {
+    group_->Add(dotEntry_);
+  }
+  if (dotHistory_ != nullptr) {
+    group_->Add(dotHistory_);
+  }
+  RefreshView();
+  return group_;
+}
+
+} // namespace OHOS
diff --git a/code/sdk/application/wearable/n9/nativeapp/nativeui/ecg/src/ECGInstructionView.cpp b/code/sdk/application/wearable/n9/nativeapp/nativeui/ecg/src/ECGInstructionView.cpp
new file mode 100644 (file)
index 0000000..a3a4d01
--- /dev/null
@@ -0,0 +1,131 @@
+#include "ecg/include/ECGInstructionView.h"
+#include "ecg/include/ECGMainPresenter.h"
+#include "uicomponents/UIComponents.h"
+
+namespace OHOS {
+
+static ECGInstructionView *g_instance = nullptr;
+
+ECGInstructionView::ECGInstructionView() { g_instance = this; }
+
+ECGInstructionView::~ECGInstructionView() {
+  ClearContent();
+  if (group_ != nullptr) {
+    delete group_;
+    group_ = nullptr;
+  }
+  g_instance = nullptr;
+}
+
+ECGInstructionView *ECGInstructionView::GetInstance() { return g_instance; }
+
+void ECGInstructionView::ClearContent() {
+  if (group_ == nullptr) {
+    return;
+  }
+  group_->RemoveAll();
+  delete titleLabel_;
+  delete contentLabel_;
+  delete noteLabel_;
+  delete imageView_;
+  delete agreeButton_;
+  titleLabel_ = nullptr;
+  contentLabel_ = nullptr;
+  noteLabel_ = nullptr;
+  imageView_ = nullptr;
+  agreeButton_ = nullptr;
+}
+
+void ECGInstructionView::InitDisclaimer() {
+  InitImageView(imageView_, 193, 50, 80, 80, "ecg_disclaimer", ECG_PATH_DISCLAIMER);
+  InitUILabel(contentLabel_, 50, 147, 366, 70, "ecg_disclaimer_content",
+              "ECG 测量结果仅供健康参考\n不可替代专业医疗诊断", DEFAULT_VECTOR_FONT_FILENAME, 30);
+  contentLabel_->SetLineBreakMode(UILabel::LINE_BREAK_WRAP);
+  contentLabel_->SetAlign(TEXT_ALIGNMENT_CENTER, TEXT_ALIGNMENT_CENTER);
+  InitUILabel(noteLabel_, 0, 248, 466, 35, "ecg_disclaimer_note", "如有心脏相关疑虑,请咨询医生",
+              DEFAULT_VECTOR_FONT_FILENAME, 30);
+  noteLabel_->SetAlign(TEXT_ALIGNMENT_CENTER, TEXT_ALIGNMENT_CENTER);
+  InitLabelButton(agreeButton_, 93, 320, 280, 90, ECG_CLICK_MEASURE, "同意并继续", 26);
+  agreeButton_->SetStyle(STYLE_BACKGROUND_COLOR, 0xff0176ff);
+  agreeButton_->SetStyle(STYLE_BORDER_RADIUS, 100);
+
+  auto *clickListener = static_cast<UIView::OnClickListener *>(ECGMainPresenter::GetInstance());
+  auto *dragListener = static_cast<UIView::OnDragListener *>(ECGMainPresenter::GetInstance());
+  agreeButton_->SetOnClickListener(clickListener);
+  group_->SetOnDragListener(dragListener);
+
+  group_->Add(imageView_);
+  group_->Add(contentLabel_);
+  group_->Add(noteLabel_);
+  group_->Add(agreeButton_);
+}
+
+void ECGInstructionView::InitWaitContact() {
+  InitUILabel(titleLabel_, 0, 50, 466, 35, "ecg_wait_contact_title", "ECG测量",
+              DEFAULT_VECTOR_FONT_FILENAME, 30);
+  titleLabel_->SetAlign(TEXT_ALIGNMENT_CENTER, TEXT_ALIGNMENT_CENTER);
+  InitImageView(imageView_, 141, 36, 184, 184, "ecg_wait_contact", ECG_PATH_WAIT_CONTACT);
+  InitUILabel(contentLabel_, 0, 401, 466, 30, "ecg_wait_contact_content", "将手指放在电极上",
+              DEFAULT_VECTOR_FONT_FILENAME, 30);
+  contentLabel_->SetAlign(TEXT_ALIGNMENT_CENTER, TEXT_ALIGNMENT_CENTER);
+
+  auto *dragListener = static_cast<UIView::OnDragListener *>(ECGMainPresenter::GetInstance());
+  group_->SetOnDragListener(dragListener);
+
+  group_->Add(titleLabel_);
+  group_->Add(imageView_);
+  group_->Add(contentLabel_);
+}
+
+void ECGInstructionView::InitContactGood() {
+  InitUILabel(titleLabel_, 0, 50, 466, 35, "ecg_contact_good_title", "ECG测量",
+              DEFAULT_VECTOR_FONT_FILENAME, 30);
+  titleLabel_->SetAlign(TEXT_ALIGNMENT_CENTER, TEXT_ALIGNMENT_CENTER);
+  InitImageView(imageView_, 141, 36, 184, 184, "ecg_contact_good", ECG_PATH_CONTACT_GOOD);
+  InitUILabel(contentLabel_, 0, 401, 466, 30, "ecg_contact_good_content", "保持手指不动",
+              DEFAULT_VECTOR_FONT_FILENAME, 30);
+  contentLabel_->SetAlign(TEXT_ALIGNMENT_CENTER, TEXT_ALIGNMENT_CENTER);
+
+  auto *dragListener = static_cast<UIView::OnDragListener *>(ECGMainPresenter::GetInstance());
+  group_->SetOnDragListener(dragListener);
+
+  group_->Add(titleLabel_);
+  group_->Add(imageView_);
+  group_->Add(contentLabel_);
+}
+
+void ECGInstructionView::UpdateForType(ECGInstructionType type) {
+  if (group_ == nullptr) {
+    return;
+  }
+  ClearContent();
+  currentType_ = type;
+  switch (type) {
+    case ECG_INSTRUCTION_TYPE_DISCLAIMER:
+      InitDisclaimer();
+      break;
+    case ECG_INSTRUCTION_TYPE_WAIT_CONTACT:
+      InitWaitContact();
+      break;
+    case ECG_INSTRUCTION_TYPE_CONTACT_GOOD:
+      InitContactGood();
+      break;
+    default:
+      break;
+  }
+  group_->Invalidate();
+}
+
+UIViewGroup *ECGInstructionView::InitView(ECGInstructionType type) {
+  if (group_ == nullptr) {
+    group_ = new UIViewGroup();
+    if (group_ == nullptr) {
+      return nullptr;
+    }
+    group_->SetPosition(0, 0, Screen::GetInstance().GetWidth(), Screen::GetInstance().GetHeight());
+  }
+  UpdateForType(type);
+  return group_;
+}
+
+} // namespace OHOS
diff --git a/code/sdk/application/wearable/n9/nativeapp/nativeui/ecg/src/ECGMainPresenter.cpp b/code/sdk/application/wearable/n9/nativeapp/nativeui/ecg/src/ECGMainPresenter.cpp
new file mode 100644 (file)
index 0000000..adc2fea
--- /dev/null
@@ -0,0 +1,314 @@
+#include "ecg/include/ECGMainPresenter.h"
+#include "ecg/include/ECGEntryView.h"
+#include "ecg/include/ECGHistoryView.h"
+#include "ecg/include/ECGInstructionView.h"
+#include "ecg/include/ECGMeasuringView.h"
+#include "ecg/include/ECGMeasureCompleteView.h"
+#include "ecg/include/ECGModel.h"
+#include "ecg/include/ECGViewClickIds.h"
+#include "NativeRegisterManager.h"
+#include "NativeAbility.h"
+#include "UiConfig.h"
+#include "power_display_service.h"
+#include "wearable_log.h"
+#include <cmsis_os2.h>
+#include <custom_utils.h>
+
+namespace OHOS {
+
+static ECGMainPresenter *g_pECGMainPresenter = nullptr;
+static osTimerId_t g_ecgMeasureTimer = nullptr;
+static osTimerId_t g_ecgContactGoodTimer = nullptr;
+
+REGIST_MENU_PRO(VIEW_ECG, ECGMainView, ECGMainPresenter, PNG_APPLIST_ECG_IMAGE_BIG,
+                PNG_APPLIST_ECG_IMAGE, PNG_APPLIST_ECG_IMAGE, "心电", STR_ECG);
+
+static void ECGMeasureTimerCallback(void *arg) {
+  UNUSED(arg);
+  ECGMeasuringView *view = ECGMeasuringView::GetInstance();
+  if (view == nullptr) {
+    return;
+  }
+  if (view->OnTimerTick()) {
+    ECGMainPresenter *presenter = ECGMainPresenter::GetInstance();
+    ECGMainView *mainView = ECGMainView::GetInstance();
+    if (presenter != nullptr && mainView != nullptr) {
+      presenter->FinishMeasuringFlow(mainView);
+    }
+  }
+}
+
+static void ECGCompleteTimerCallback(void *arg) {
+  UNUSED(arg);
+  ECGMainView *mainView = ECGMainView::GetInstance();
+  if (mainView != nullptr && mainView->GetCurrentViewId() == ECG_MEASURE_COMPLETE_VIEW) {
+    mainView->ChangeView(ECG_MEASURE_COMPLETE_VIEW, ECG_MEASURE_RESULT_VIEW);
+  }
+}
+
+static void ECGContactGoodTimerCallback(void *arg) {
+  UNUSED(arg);
+  ECGMainPresenter *presenter = ECGMainPresenter::GetInstance();
+  ECGMainView *mainView = ECGMainView::GetInstance();
+  if (presenter == nullptr || mainView == nullptr) {
+    return;
+  }
+  if (mainView->GetCurrentViewId() == ECG_INSTRUCTION_VIEW &&
+      mainView->GetInstructionSubType() == ECG_INSTRUCTION_TYPE_CONTACT_GOOD) {
+    mainView->ChangeView(ECG_INSTRUCTION_VIEW, ECG_MEASURING_VIEW);
+  }
+  presenter->StopContactGoodTimer();
+}
+
+ECGMainPresenter::ECGMainPresenter() { g_pECGMainPresenter = this; }
+
+ECGMainPresenter::~ECGMainPresenter() {
+  StopMeasureTimer();
+  StopContactGoodTimer();
+  g_pECGMainPresenter = nullptr;
+}
+
+ECGMainPresenter *ECGMainPresenter::GetInstance() { return g_pECGMainPresenter; }
+
+void ECGMainPresenter::OnStart() { Presenter<ECGMainView>::OnStart(); }
+
+void ECGMainPresenter::OnPause(void) {}
+
+void ECGMainPresenter::OnResume(void) {
+  RootView::GetInstance()->SetOnKeyActListener(this);
+}
+
+void ECGMainPresenter::OnStop(void) {
+  StopMeasureTimer();
+  StopContactGoodTimer();
+}
+
+void ECGMainPresenter::StopContactGoodTimer() {
+  if (g_ecgContactGoodTimer != nullptr) {
+    osTimerStop(g_ecgContactGoodTimer);
+    osTimerDelete(g_ecgContactGoodTimer);
+    g_ecgContactGoodTimer = nullptr;
+  }
+}
+
+bool ECGMainPresenter::IsWaitingContact(ECGMainView *mainView) const {
+  return mainView != nullptr && mainView->GetCurrentViewId() == ECG_INSTRUCTION_VIEW &&
+         mainView->GetInstructionSubType() == ECG_INSTRUCTION_TYPE_WAIT_CONTACT;
+}
+
+void ECGMainPresenter::ShowWaitContact(ECGMainView *mainView) {
+  int16_t hide = mainView->GetCurrentViewId();
+  if (hide == ECG_INSTRUCTION_VIEW) {
+    mainView->ChangeView(ECG_INSTRUCTION_VIEW, ECG_INSTRUCTION_VIEW, ECG_INSTRUCTION_TYPE_WAIT_CONTACT);
+  } else {
+    mainView->ChangeView(hide, ECG_INSTRUCTION_VIEW, ECG_INSTRUCTION_TYPE_WAIT_CONTACT);
+  }
+}
+
+void ECGMainPresenter::ShowContactGood(ECGMainView *mainView) {
+  StopContactGoodTimer();
+  mainView->ChangeView(ECG_INSTRUCTION_VIEW, ECG_INSTRUCTION_VIEW, ECG_INSTRUCTION_TYPE_CONTACT_GOOD);
+  g_ecgContactGoodTimer =
+      osTimerNew((osTimerFunc_t)ECGContactGoodTimerCallback, osTimerOnce, nullptr, nullptr);
+  if (g_ecgContactGoodTimer != nullptr) {
+    osTimerStart(g_ecgContactGoodTimer, 2000);
+  }
+}
+
+void ECGMainPresenter::OnBottomButtonPressed(ECGMainView *mainView) {
+  if (!IsWaitingContact(mainView)) {
+    return;
+  }
+  ShowContactGood(mainView);
+}
+
+void ECGMainPresenter::NavigateBack() {
+  NativeAbility::GetInstance().Back(TransitionType::TRANSITION_BACK_TO_HEXAGONS);
+}
+
+bool ECGMainPresenter::IsFlowSubPage(int16_t viewId) const {
+  return viewId != ECG_ENTRY_VIEW && viewId != ECG_HISTORY_VIEW;
+}
+
+bool ECGMainPresenter::HandleSwipeBack() {
+  StopContactGoodTimer();
+  ECGMainView *mainView = ECGMainView::GetInstance();
+  if (mainView == nullptr) {
+    NavigateBack();
+    return true;
+  }
+  int16_t current = mainView->GetCurrentViewId();
+  if (current == ECG_ENTRY_VIEW) {
+    NavigateBack();
+  } else if (current == ECG_HISTORY_VIEW) {
+    ECGHistoryView *historyView = ECGHistoryView::GetInstance();
+    if (historyView != nullptr && historyView->IsShowingListPage()) {
+      historyView->ShowRecentPage();
+    } else {
+      mainView->ChangeView(ECG_HISTORY_VIEW, ECG_ENTRY_VIEW);
+    }
+  } else if (IsFlowSubPage(current)) {
+    mainView->ChangeView(current, ECG_ENTRY_VIEW);
+  } else {
+    mainView->ChangeView(current, ECG_ENTRY_VIEW);
+  }
+  return true;
+}
+
+void ECGMainPresenter::StartMeasureTimer() {
+  StopMeasureTimer();
+  g_ecgMeasureTimer =
+      osTimerNew((osTimerFunc_t)ECGMeasureTimerCallback, osTimerPeriodic, nullptr, nullptr);
+  if (g_ecgMeasureTimer != nullptr) {
+    osTimerStart(g_ecgMeasureTimer, 200);
+  }
+}
+
+void ECGMainPresenter::StopMeasureTimer() {
+  if (g_ecgMeasureTimer != nullptr) {
+    osTimerStop(g_ecgMeasureTimer);
+    osTimerDelete(g_ecgMeasureTimer);
+    g_ecgMeasureTimer = nullptr;
+  }
+}
+
+void ECGMainPresenter::StartMeasureFlow(ECGMainView *mainView) {
+  ShowWaitContact(mainView);
+}
+
+void ECGMainPresenter::FinishMeasuringFlow(ECGMainView *mainView) {
+  StopMeasureTimer();
+  ECGModel *model = ECGModel::GetInstance();
+  model->SetMeasureResult(76, ECGResultType::SINUS_RHYTHM);
+  model->AddMeasureToHistory();
+  mainView->ChangeView(ECG_MEASURING_VIEW, ECG_MEASURE_COMPLETE_VIEW);
+  osTimerId_t completeTimer =
+      osTimerNew((osTimerFunc_t)ECGCompleteTimerCallback, osTimerOnce, nullptr, nullptr);
+  if (completeTimer != nullptr) {
+    osTimerStart(completeTimer, 3000);
+  }
+}
+
+bool ECGMainPresenter::OnDragStart(UIView &view, const DragEvent &event) {
+  UNUSED(view);
+  UNUSED(event);
+  return false;
+}
+
+bool ECGMainPresenter::OnDrag(UIView &view, const DragEvent &event) {
+  UNUSED(view);
+  UNUSED(event);
+  return false;
+}
+
+bool ECGMainPresenter::OnDragEnd(UIView &view, const DragEvent &event) {
+  ECGMainView *mainView = ECGMainView::GetInstance();
+  if (mainView == nullptr) {
+    return false;
+  }
+  int16_t current = mainView->GetCurrentViewId();
+  uint8_t direction = event.GetDragDirection();
+
+  if (direction == DragEvent::DIRECTION_LEFT_TO_RIGHT) {
+    HandleSwipeBack();
+    return true;
+  }
+  if (current == ECG_ENTRY_VIEW && direction == DragEvent::DIRECTION_RIGHT_TO_LEFT) {
+    mainView->ChangeView(ECG_ENTRY_VIEW, ECG_HISTORY_VIEW);
+    return true;
+  }
+  return false;
+}
+
+void ECGMainPresenter::OnSwipe(UISwipeView &view) { UNUSED(view); }
+
+bool ECGMainPresenter::OnClick(UIView &view, const ClickEvent &event) {
+  UNUSED(event);
+  std::string viewId = view.GetViewId();
+  ECGMainView *mainView = ECGMainView::GetInstance();
+  if (mainView == nullptr) {
+    return true;
+  }
+  int16_t current = mainView->GetCurrentViewId();
+
+  if (viewId == ECG_CLICK_HISTORY) {
+    mainView->ChangeView(ECG_ENTRY_VIEW, ECG_HISTORY_VIEW);
+    return true;
+  }
+  if (viewId == ECG_CLICK_HISTORY_BACK) {
+    mainView->ChangeView(ECG_HISTORY_VIEW, ECG_ENTRY_VIEW);
+    return true;
+  }
+  if (viewId == ECG_CLICK_VIEW_ALL) {
+    ECGHistoryView *historyView = ECGHistoryView::GetInstance();
+    if (historyView != nullptr) {
+      historyView->ShowListPage();
+    }
+    return true;
+  }
+  if (viewId == ECG_CLICK_INSTRUCTION_BACK) {
+    mainView->ChangeView(ECG_INSTRUCTION_VIEW, ECG_ENTRY_VIEW);
+    return true;
+  }
+  if (viewId == ECG_CLICK_INSTRUCTION) {
+    mainView->ChangeView(ECG_ENTRY_VIEW, ECG_INSTRUCTION_VIEW, ECG_INSTRUCTION_TYPE_DISCLAIMER);
+    return true;
+  }
+  if (viewId == ECG_CLICK_EXIT) {
+    mainView->ChangeView(current, ECG_ENTRY_VIEW);
+    return true;
+  }
+  if (viewId == ECG_CLICK_RETRY) {
+    if (current == ECG_ABNORMAL_VIEW) {
+      ShowWaitContact(mainView);
+    }
+    return true;
+  }
+  if (viewId == ECG_CLICK_CONFIRM) {
+    if (current == ECG_MEASURE_RESULT_VIEW) {
+      mainView->ChangeView(ECG_MEASURE_RESULT_VIEW, ECG_ENTRY_VIEW);
+    }
+    return true;
+  }
+  if (viewId == ECG_CLICK_STOP) {
+    StopMeasureTimer();
+    StopContactGoodTimer();
+    if (current == ECG_MEASURING_VIEW) {
+      mainView->ChangeView(ECG_MEASURING_VIEW, ECG_ENTRY_VIEW);
+    } else {
+      mainView->ChangeView(current, ECG_ENTRY_VIEW);
+    }
+    return true;
+  }
+  if (viewId == ECG_CLICK_MEASURE) {
+    if (current == ECG_INSTRUCTION_VIEW &&
+        mainView->GetInstructionSubType() == ECG_INSTRUCTION_TYPE_DISCLAIMER) {
+      ShowWaitContact(mainView);
+      return true;
+    }
+    if (current == ECG_ENTRY_VIEW || current == ECG_HISTORY_VIEW) {
+      StartMeasureFlow(mainView);
+      return true;
+    }
+  }
+  return true;
+}
+
+bool ECGMainPresenter::OnKeyAct(UIView &view, const KeyEvent &event) {
+  UNUSED(view);
+  unsigned short keyID = event.GetKeyId();
+  unsigned short keyState = event.GetState();
+  ECGMainView *mainView = ECGMainView::GetInstance();
+
+  if (keyID == static_cast<uint16_t>(ZliteKeyCode::ZLITE_KEY_FUNC) && keyState == 1 && mainView != nullptr) {
+    OnBottomButtonPressed(mainView);
+    return true;
+  }
+  if (keyID == static_cast<uint16_t>(ZliteKeyCode::ZLITE_KEY_POWER) && keyState == 1 &&
+      uapi_get_screen_state() == 0) {
+    HandleSwipeBack();
+  }
+  return true;
+}
+
+} // namespace OHOS
diff --git a/code/sdk/application/wearable/n9/nativeapp/nativeui/ecg/src/ECGMainView.cpp b/code/sdk/application/wearable/n9/nativeapp/nativeui/ecg/src/ECGMainView.cpp
new file mode 100644 (file)
index 0000000..1d516c3
--- /dev/null
@@ -0,0 +1,285 @@
+#include "ecg/include/ECGMainView.h"
+#include "ecg/include/ECGMainPresenter.h"
+#include "ecg/include/ECGModel.h"
+#include "ecg/include/ECGEntryView.h"
+#include "ecg/include/ECGHistoryView.h"
+#include "ecg/include/ECGInstructionView.h"
+#include "ecg/include/ECGAbnormalView.h"
+#include "ecg/include/ECGMeasuringView.h"
+#include "ecg/include/ECGMeasureCompleteView.h"
+#include "ecg/include/ECGMeasureResultView.h"
+#include "wearable_log.h"
+#include "common/screen.h"
+
+namespace OHOS {
+
+static ECGMainView *g_pECGMainView = nullptr;
+
+ECGMainView::ECGMainView() { g_pECGMainView = this; }
+
+ECGMainView::~ECGMainView() {
+  DeInit();
+  g_pECGMainView = nullptr;
+}
+
+ECGMainView *ECGMainView::GetInstance() { return g_pECGMainView; }
+
+void ECGMainView::DeInit() {
+  delete entryView_;
+  delete historyView_;
+  delete instructionView_;
+  delete abnormalView_;
+  delete measuringView_;
+  delete measureCompleteView_;
+  delete measureResultView_;
+  entryView_ = nullptr;
+  historyView_ = nullptr;
+  instructionView_ = nullptr;
+  abnormalView_ = nullptr;
+  measuringView_ = nullptr;
+  measureCompleteView_ = nullptr;
+  measureResultView_ = nullptr;
+}
+
+void ECGMainView::OnStart() {
+  View<ECGMainPresenter>::OnStart();
+  if (container_ == nullptr) {
+    container_ = new UIViewGroup();
+    if (container_ == nullptr) {
+      return;
+    }
+    container_->SetPosition(0, 0, Screen::GetInstance().GetWidth(),
+                            Screen::GetInstance().GetHeight());
+    container_->SetDraggable(true);
+    container_->SetTouchable(true);
+    container_->SetOnDragListener(presenter_);
+    AddViewToRootContainer(container_);
+  }
+  ECGModel::GetInstance()->LoadData();
+  ChangeView(MAX_ECGVIEW, ECG_ENTRY_VIEW);
+  RootView::GetInstance()->SetOnKeyActListener(
+      static_cast<RootView::OnKeyActListener *>(ECGMainPresenter::GetInstance()));
+  Draw();
+}
+
+void ECGMainView::OnStop() {
+  View<ECGMainPresenter>::OnStop();
+  if (container_ != nullptr) {
+    container_->RemoveAll();
+    delete container_;
+    container_ = nullptr;
+  }
+}
+
+bool ECGMainView::ChangeECGEntryView(int16_t thisView, int16_t type) {
+  UNUSED(type);
+  if (group_[thisView] != nullptr) {
+    return true;
+  }
+  entryView_ = new ECGEntryView();
+  if (entryView_ == nullptr) {
+    return false;
+  }
+  group_[thisView] = entryView_->InitView();
+  if (group_[thisView] == nullptr) {
+    return false;
+  }
+  container_->Add(group_[thisView]);
+  return true;
+}
+
+bool ECGMainView::ChangeECGHistoryView(int16_t thisView, int16_t type) {
+  UNUSED(type);
+  if (group_[thisView] != nullptr) {
+    if (historyView_ != nullptr) {
+      historyView_->RefreshView();
+    }
+    return true;
+  }
+  historyView_ = new ECGHistoryView();
+  if (historyView_ == nullptr) {
+    return false;
+  }
+  group_[thisView] = historyView_->InitView();
+  if (group_[thisView] == nullptr) {
+    return false;
+  }
+  container_->Add(group_[thisView]);
+  return true;
+}
+
+bool ECGMainView::ChangeECGInstructionView(int16_t thisView, int16_t type) {
+  instructionSubType_ = type;
+  if (group_[thisView] != nullptr) {
+    if (instructionView_ != nullptr) {
+      instructionView_->UpdateForType(static_cast<ECGInstructionType>(type));
+    }
+    return true;
+  }
+  instructionView_ = new ECGInstructionView();
+  if (instructionView_ == nullptr) {
+    return false;
+  }
+  group_[thisView] = instructionView_->InitView(static_cast<ECGInstructionType>(type));
+  if (group_[thisView] == nullptr) {
+    return false;
+  }
+  container_->Add(group_[thisView]);
+  return true;
+}
+
+bool ECGMainView::ChangeECGAbnormalView(int16_t thisView, int16_t type) {
+  UNUSED(type);
+  if (group_[thisView] != nullptr) {
+    return true;
+  }
+  abnormalView_ = new ECGAbnormalView();
+  if (abnormalView_ == nullptr) {
+    return false;
+  }
+  group_[thisView] = abnormalView_->InitView();
+  if (group_[thisView] == nullptr) {
+    return false;
+  }
+  container_->Add(group_[thisView]);
+  return true;
+}
+
+bool ECGMainView::ChangeECGMeasuringView(int16_t thisView, int16_t type) {
+  UNUSED(type);
+  if (group_[thisView] != nullptr) {
+    if (measuringView_ != nullptr) {
+      measuringView_->ResetMeasurement();
+    }
+    return true;
+  }
+  measuringView_ = new ECGMeasuringView();
+  if (measuringView_ == nullptr) {
+    return false;
+  }
+  group_[thisView] = measuringView_->InitView();
+  if (group_[thisView] == nullptr) {
+    return false;
+  }
+  container_->Add(group_[thisView]);
+  return true;
+}
+
+bool ECGMainView::ChangeECGMeasureCompleteView(int16_t thisView, int16_t type) {
+  UNUSED(type);
+  if (group_[thisView] != nullptr) {
+    return true;
+  }
+  measureCompleteView_ = new ECGMeasureCompleteView();
+  if (measureCompleteView_ == nullptr) {
+    return false;
+  }
+  group_[thisView] = measureCompleteView_->InitView();
+  if (group_[thisView] == nullptr) {
+    return false;
+  }
+  container_->Add(group_[thisView]);
+  return true;
+}
+
+bool ECGMainView::ChangeECGMeasureResultView(int16_t thisView, int16_t type) {
+  UNUSED(type);
+  if (group_[thisView] != nullptr) {
+    if (measureResultView_ != nullptr) {
+      measureResultView_->UpdateText();
+    }
+    return true;
+  }
+  measureResultView_ = new ECGMeasureResultView();
+  if (measureResultView_ == nullptr) {
+    return false;
+  }
+  group_[thisView] = measureResultView_->InitView();
+  if (group_[thisView] == nullptr) {
+    return false;
+  }
+  container_->Add(group_[thisView]);
+  return true;
+}
+
+struct ECGViewMapper {
+  const int16_t viewId;
+  bool (ECGMainView::*func)(int16_t, int16_t);
+};
+
+static const ECGViewMapper ViewMapper[] = {
+    {ECG_ENTRY_VIEW, &ECGMainView::ChangeECGEntryView},
+    {ECG_HISTORY_VIEW, &ECGMainView::ChangeECGHistoryView},
+    {ECG_INSTRUCTION_VIEW, &ECGMainView::ChangeECGInstructionView},
+    {ECG_ABNORMAL_VIEW, &ECGMainView::ChangeECGAbnormalView},
+    {ECG_MEASURING_VIEW, &ECGMainView::ChangeECGMeasuringView},
+    {ECG_MEASURE_COMPLETE_VIEW, &ECGMainView::ChangeECGMeasureCompleteView},
+    {ECG_MEASURE_RESULT_VIEW, &ECGMainView::ChangeECGMeasureResultView},
+};
+
+bool ECGMainView::ClickFunc(int16_t interface, int16_t type) {
+  int funcSize = sizeof(ViewMapper) / sizeof(ViewMapper[0]);
+  for (int i = 0; i < funcSize; i++) {
+    if (interface == ViewMapper[i].viewId) {
+      return (this->*(ViewMapper[i].func))(interface, type);
+    }
+  }
+  return false;
+}
+
+void ECGMainView::ChangeView(int16_t hideView, int16_t showView, int16_t type) {
+  if (showView >= MAX_ECGVIEW) {
+    return;
+  }
+  if (hideView == showView && showView == ECG_INSTRUCTION_VIEW) {
+    instructionSubType_ = type;
+    if (instructionView_ != nullptr) {
+      instructionView_->UpdateForType(static_cast<ECGInstructionType>(type));
+    }
+    container_->Invalidate();
+    return;
+  }
+  if (hideView != MAX_ECGVIEW) {
+    if (hideView >= MAX_ECGVIEW || group_[hideView] == nullptr) {
+      return;
+    }
+    if (hideView == ECG_MEASURING_VIEW && measuringView_ != nullptr) {
+      measuringView_->StopMeasurement();
+      ECGMainPresenter::GetInstance()->StopMeasureTimer();
+    }
+    if (hideView == ECG_MEASURE_COMPLETE_VIEW && measureCompleteView_ != nullptr) {
+      measureCompleteView_->StopAnimation();
+    }
+    group_[hideView]->SetVisible(false);
+  }
+  if (!ClickFunc(showView, type)) {
+    return;
+  }
+  group_[showView]->SetVisible(true);
+  currentViewId_ = showView;
+  if (showView == ECG_MEASURING_VIEW && measuringView_ != nullptr) {
+    measuringView_->ResetMeasurement();
+    ECGMainPresenter::GetInstance()->StartMeasureTimer();
+  }
+  if (showView == ECG_MEASURE_COMPLETE_VIEW && measureCompleteView_ != nullptr) {
+    measureCompleteView_->StartAnimation();
+  }
+  if (showView == ECG_HISTORY_VIEW && historyView_ != nullptr) {
+    historyView_->RefreshView();
+  }
+  if (showView == ECG_ENTRY_VIEW && entryView_ != nullptr) {
+    entryView_->InitEntryView();
+  }
+  RefreshCurrentView();
+  container_->Invalidate();
+}
+
+void ECGMainView::RefreshCurrentView() {
+  if (currentViewId_ == ECG_MEASURE_RESULT_VIEW && measureResultView_ != nullptr) {
+    measureResultView_->UpdateText();
+  }
+}
+
+int16_t ECGMainView::GetCurrentViewId() const { return currentViewId_; }
+
+} // namespace OHOS
diff --git a/code/sdk/application/wearable/n9/nativeapp/nativeui/ecg/src/ECGMeasureCompleteView.cpp b/code/sdk/application/wearable/n9/nativeapp/nativeui/ecg/src/ECGMeasureCompleteView.cpp
new file mode 100644 (file)
index 0000000..23feedb
--- /dev/null
@@ -0,0 +1,144 @@
+#include "ecg/include/ECGMeasureCompleteView.h"
+#include "common/image_cache_manager.h"
+#include "uicomponents/UIComponents.h"
+#include "wearable_log.h"
+
+namespace OHOS {
+
+static constexpr int16_t ECG_COMPLETE_ANIM_X = 133;
+static constexpr int16_t ECG_COMPLETE_ANIM_Y = 105;
+static constexpr int16_t ECG_COMPLETE_ANIM_W = 200;
+static constexpr int16_t ECG_COMPLETE_ANIM_H = 200;
+static constexpr int16_t ECG_COMPLETE_ANIM_FRAME_COUNT = 40;
+static constexpr int16_t ECG_COMPLETE_ANIM_DELAY = 70;
+
+static const char *kCompleteAnimPaths[ECG_COMPLETE_ANIM_FRAME_COUNT] = {
+    "/user/res/AIVoice/AIVoiceThinking/1.bin",
+    "/user/res/AIVoice/AIVoiceThinking/2.bin",
+    "/user/res/AIVoice/AIVoiceThinking/3.bin",
+    "/user/res/AIVoice/AIVoiceThinking/4.bin",
+    "/user/res/AIVoice/AIVoiceThinking/5.bin",
+    "/user/res/AIVoice/AIVoiceThinking/6.bin",
+    "/user/res/AIVoice/AIVoiceThinking/7.bin",
+    "/user/res/AIVoice/AIVoiceThinking/8.bin",
+    "/user/res/AIVoice/AIVoiceThinking/9.bin",
+    "/user/res/AIVoice/AIVoiceThinking/10.bin",
+    "/user/res/AIVoice/AIVoiceThinking/11.bin",
+    "/user/res/AIVoice/AIVoiceThinking/12.bin",
+    "/user/res/AIVoice/AIVoiceThinking/13.bin",
+    "/user/res/AIVoice/AIVoiceThinking/14.bin",
+    "/user/res/AIVoice/AIVoiceThinking/15.bin",
+    "/user/res/AIVoice/AIVoiceThinking/16.bin",
+    "/user/res/AIVoice/AIVoiceThinking/17.bin",
+    "/user/res/AIVoice/AIVoiceThinking/18.bin",
+    "/user/res/AIVoice/AIVoiceThinking/19.bin",
+    "/user/res/AIVoice/AIVoiceThinking/20.bin",
+    "/user/res/AIVoice/AIVoiceThinking/21.bin",
+    "/user/res/AIVoice/AIVoiceThinking/22.bin",
+    "/user/res/AIVoice/AIVoiceThinking/23.bin",
+    "/user/res/AIVoice/AIVoiceThinking/24.bin",
+    "/user/res/AIVoice/AIVoiceThinking/25.bin",
+    "/user/res/AIVoice/AIVoiceThinking/26.bin",
+    "/user/res/AIVoice/AIVoiceThinking/27.bin",
+    "/user/res/AIVoice/AIVoiceThinking/28.bin",
+    "/user/res/AIVoice/AIVoiceThinking/29.bin",
+    "/user/res/AIVoice/AIVoiceThinking/30.bin",
+    "/user/res/AIVoice/AIVoiceThinking/31.bin",
+    "/user/res/AIVoice/AIVoiceThinking/32.bin",
+    "/user/res/AIVoice/AIVoiceThinking/33.bin",
+    "/user/res/AIVoice/AIVoiceThinking/34.bin",
+    "/user/res/AIVoice/AIVoiceThinking/35.bin",
+    "/user/res/AIVoice/AIVoiceThinking/36.bin",
+    "/user/res/AIVoice/AIVoiceThinking/37.bin",
+    "/user/res/AIVoice/AIVoiceThinking/38.bin",
+    "/user/res/AIVoice/AIVoiceThinking/39.bin",
+    "/user/res/AIVoice/AIVoiceThinking/40.bin",
+};
+
+static ECGMeasureCompleteView *g_instance = nullptr;
+
+ECGMeasureCompleteView::ECGMeasureCompleteView() { g_instance = this; }
+
+ECGMeasureCompleteView::~ECGMeasureCompleteView() {
+  StopAnimation();
+  if (group_ != nullptr) {
+    group_->RemoveAll();
+    delete noteLabel_;
+    delete animatorView_;
+    delete group_;
+    noteLabel_ = nullptr;
+    animatorView_ = nullptr;
+    group_ = nullptr;
+  }
+  UnloadAnimatorFrames();
+  g_instance = nullptr;
+}
+
+ECGMeasureCompleteView *ECGMeasureCompleteView::GetInstance() { return g_instance; }
+
+bool ECGMeasureCompleteView::InitAnimator() {
+  if (animatorInited_) {
+    return animatorView_ != nullptr;
+  }
+  animatorView_ = new UIImageAnimatorView();
+  if (animatorView_ == nullptr) {
+    return false;
+  }
+  for (int16_t i = 0; i < ECG_COMPLETE_ANIM_FRAME_COUNT; i++) {
+    frameInfo_[i].imageInfo =
+        ImageCacheManager::GetInstance().LoadSingleRes(kCompleteAnimPaths[i]);
+    frameInfo_[i].imageType = IMG_SRC_IMAGE_INFO;
+    frameInfo_[i].pos.x = ECG_COMPLETE_ANIM_X;
+    frameInfo_[i].pos.y = ECG_COMPLETE_ANIM_Y;
+    frameInfo_[i].width = ECG_COMPLETE_ANIM_W;
+    frameInfo_[i].height = ECG_COMPLETE_ANIM_H;
+  }
+  animatorView_->SetPosition(ECG_COMPLETE_ANIM_X, ECG_COMPLETE_ANIM_Y, ECG_COMPLETE_ANIM_W,
+                             ECG_COMPLETE_ANIM_H);
+  animatorView_->SetImageAnimatorSrc(frameInfo_, ECG_COMPLETE_ANIM_FRAME_COUNT,
+                                     ECG_COMPLETE_ANIM_DELAY);
+  animatorView_->SetRepeat(true);
+  animatorView_->SetAutoEnable(false);
+  animatorView_->SetResizeMode(UIImageView::FILL);
+  animatorInited_ = true;
+  return true;
+}
+
+void ECGMeasureCompleteView::UnloadAnimatorFrames() {
+  if (!animatorInited_) {
+    return;
+  }
+  for (int16_t i = 0; i < ECG_COMPLETE_ANIM_FRAME_COUNT; i++) {
+    ImageCacheManager::GetInstance().UnloadSingleRes(kCompleteAnimPaths[i]);
+    frameInfo_[i].imageInfo = nullptr;
+  }
+  animatorInited_ = false;
+}
+
+void ECGMeasureCompleteView::StartAnimation() {
+  if (!InitAnimator() || animatorView_ == nullptr) {
+    return;
+  }
+  animatorView_->Start();
+}
+
+void ECGMeasureCompleteView::StopAnimation() {
+  if (animatorView_ != nullptr) {
+    animatorView_->Stop();
+  }
+}
+
+UIViewGroup *ECGMeasureCompleteView::InitView() {
+  group_ = new UIViewGroup();
+  group_->SetPosition(0, 0, Screen::GetInstance().GetWidth(), Screen::GetInstance().GetHeight());
+  if (InitAnimator() && animatorView_ != nullptr) {
+    group_->Add(animatorView_);
+  }
+  InitUILabel(noteLabel_, 0, ECG_COMPLETE_ANIM_Y + ECG_COMPLETE_ANIM_H + 20, 466, 35,
+              "ecg_complete_note", "正在分析数据...", DEFAULT_VECTOR_FONT_FILENAME, 30);
+  noteLabel_->SetAlign(TEXT_ALIGNMENT_CENTER, TEXT_ALIGNMENT_CENTER);
+  group_->Add(noteLabel_);
+  return group_;
+}
+
+} // namespace OHOS
diff --git a/code/sdk/application/wearable/n9/nativeapp/nativeui/ecg/src/ECGMeasureResultView.cpp b/code/sdk/application/wearable/n9/nativeapp/nativeui/ecg/src/ECGMeasureResultView.cpp
new file mode 100644 (file)
index 0000000..259a1b1
--- /dev/null
@@ -0,0 +1,95 @@
+#include "ecg/include/ECGMeasureResultView.h"
+#include "ecg/include/ECGMainPresenter.h"
+#include "ecg/include/ECGModel.h"
+#include "ecg/include/ECGEntryView.h"
+#include "ecg/include/ECGViewClickIds.h"
+#include "uicomponents/UIComponents.h"
+
+namespace OHOS {
+
+static ECGMeasureResultView *g_instance = nullptr;
+
+ECGMeasureResultView::ECGMeasureResultView() { g_instance = this; }
+
+ECGMeasureResultView::~ECGMeasureResultView() {
+  if (group_ != nullptr) {
+    group_->RemoveAll();
+    delete heartIcon_;
+    delete bpmValueLabel_;
+    delete bpmUnitLabel_;
+    delete resultLabel_;
+    delete confirmButton_;
+    delete backgroundIcon_;
+    delete group_;
+    heartIcon_ = nullptr;
+    backgroundIcon_ = nullptr;
+    bpmValueLabel_ = nullptr;
+    bpmUnitLabel_ = nullptr;
+    resultLabel_ = nullptr;
+    backgroundIcon_ = nullptr;
+    confirmButton_ = nullptr;
+    group_ = nullptr;
+  }
+  g_instance = nullptr;
+}
+
+ECGMeasureResultView *ECGMeasureResultView::GetInstance() { return g_instance; }
+
+void ECGMeasureResultView::UpdateText() {
+  ECGModel *model = ECGModel::GetInstance();
+  ECGResultType type = model->GetResultType();
+  uint8_t bpm = model->GetResultBpm();
+
+  if (bpmValueLabel_ != nullptr) {
+    if (type == ECGResultType::INCONCLUSIVE || bpm == 0) {
+      bpmValueLabel_->SetText("--");
+    } else {
+      bpmValueLabel_->SetText(std::to_string(bpm).c_str());
+    }
+  }
+  if (resultLabel_ != nullptr) {
+    resultLabel_->SetText(model->GetResultText(type).c_str());
+    resultLabel_->SetTextColor(model->GetResultColor(type));
+  }
+}
+
+UIViewGroup *ECGMeasureResultView::InitView() {
+  group_ = new UIViewGroup();
+  group_->SetPosition(0, 0, Screen::GetInstance().GetWidth(), Screen::GetInstance().GetHeight());
+  group_->SetOnDragListener(static_cast<UIView::OnDragListener *>(ECGMainPresenter::GetInstance()));
+
+  InitImageView(backgroundIcon_, 0, 43, 466, 380, "ecg_result_background", ECG_PATH_RESULT_BACKGROUND);
+
+  InitImageView(heartIcon_, 206, 100, 54, 50, "ecg_result_heart", ECG_PATH_HEART_ICON);
+
+
+  InitUILabel(bpmValueLabel_, 0, 170, 466, 90, "ecg_result_bpm", "--",
+              DEFAULT_VECTOR_FONT_FILENAME, 72);
+  bpmValueLabel_->SetAlign(TEXT_ALIGNMENT_CENTER, TEXT_ALIGNMENT_CENTER);
+
+  InitUILabel(bpmUnitLabel_, 0, 250, 466, 30, "ecg_result_bpm_unit", "bpm",
+              DEFAULT_VECTOR_FONT_FILENAME, 26);
+  bpmUnitLabel_->SetTextColor(Color::GetColorFromRGBA(0x99, 0x99, 0x99, 0xff));
+  bpmUnitLabel_->SetAlign(TEXT_ALIGNMENT_CENTER, TEXT_ALIGNMENT_CENTER);
+
+  InitUILabel(resultLabel_, 0, 290, 466, 40, "ecg_result_type", "窦性心律",
+              DEFAULT_VECTOR_FONT_FILENAME, 32);
+  resultLabel_->SetAlign(TEXT_ALIGNMENT_CENTER, TEXT_ALIGNMENT_CENTER);
+
+  InitLabelButton(confirmButton_, 93, 330, 280, 90, ECG_CLICK_CONFIRM, "完成", 30);
+  confirmButton_->SetStyle(STYLE_BACKGROUND_COLOR, 0xff191919);
+  confirmButton_->SetStyle(STYLE_BORDER_RADIUS, 45);
+  confirmButton_->SetOnClickListener(static_cast<UIView::OnClickListener *>(ECGMainPresenter::GetInstance()));
+
+  group_->Add(backgroundIcon_);
+  group_->Add(heartIcon_);
+  group_->Add(bpmValueLabel_);
+  group_->Add(bpmUnitLabel_);
+  group_->Add(resultLabel_);
+  group_->Add(confirmButton_);
+
+  UpdateText();
+  return group_;
+}
+
+} // namespace OHOS
diff --git a/code/sdk/application/wearable/n9/nativeapp/nativeui/ecg/src/ECGMeasuringView.cpp b/code/sdk/application/wearable/n9/nativeapp/nativeui/ecg/src/ECGMeasuringView.cpp
new file mode 100644 (file)
index 0000000..6b533c6
--- /dev/null
@@ -0,0 +1,156 @@
+#include "ecg/include/ECGMeasuringView.h"
+#include "ecg/include/ECGMainPresenter.h"
+#include "ecg/include/ECGViewClickIds.h"
+#include "uicomponents/UIComponents.h"
+#include "wearable_log.h"
+#include <cmath>
+
+#ifndef sinf
+#define sinf(x) static_cast<float>(sin(x))
+#endif
+
+namespace OHOS {
+
+static ECGMeasuringView *g_instance = nullptr;
+
+ECGMeasuringView::ECGMeasuringView() { g_instance = this; }
+
+ECGMeasuringView::~ECGMeasuringView() {
+  if (group_ != nullptr) {
+    group_->RemoveAll();
+    delete titleLabel_;
+    delete chart_;
+    delete dataSerial_;
+    delete countdownLabel_;
+    delete hintLabel_;
+    delete group_;
+    titleLabel_ = nullptr;
+    chart_ = nullptr;
+    dataSerial_ = nullptr;
+    countdownLabel_ = nullptr;
+    hintLabel_ = nullptr;
+    group_ = nullptr;
+  }
+  g_instance = nullptr;
+}
+
+ECGMeasuringView *ECGMeasuringView::GetInstance() { return g_instance; }
+
+void ECGMeasuringView::InitChart() {
+  if (chart_ != nullptr) {
+    return;
+  }
+  chart_ = new UIChartPolyline();
+  if (chart_ == nullptr) {
+    return;
+  }
+  chart_->SetPosition(76, 150, 313, 122);
+  chart_->SetStyle(STYLE_BACKGROUND_OPA, 0x00);
+
+  UIXAxis &xAxis = chart_->GetXAxis();
+  xAxis.SetMarkNum(1);
+  xAxis.SetDataRange(0, kWavePointCount - 1);
+  xAxis.SetLineColor(Color::GetColorFromRGBA(0, 0, 0, 0));
+
+  UIYAxis &yAxis = chart_->GetYAxis();
+  yAxis.SetMarkNum(1);
+  yAxis.SetDataRange(0, 220);
+  yAxis.SetLineColor(Color::GetColorFromRGBA(0, 0, 0, 0));
+
+  chart_->SetGradientOpacity(78, 64);
+
+  dataSerial_ = new UIChartDataSerial();
+  if (dataSerial_ != nullptr) {
+    dataSerial_->SetMaxDataCount(kWavePointCount);
+    dataSerial_->SetLineColor(Color::GetColorFromRGBA(0xff, 0x3a, 0x3a, 0xff));
+    dataSerial_->SetFillColor(Color::GetColorFromRGBA(0xff, 0x3a, 0x3a, 0xff));
+    dataSerial_->EnableGradient(true);
+    chart_->AddDataSerial(dataSerial_);
+  }
+}
+
+void ECGMeasuringView::UpdateWaveform() {
+  if (dataSerial_ == nullptr || chart_ == nullptr) {
+    return;
+  }
+  wavePhase_ += 0.35f;
+  for (int i = 0; i < kWavePointCount; i++) {
+    float t = wavePhase_ + static_cast<float>(i) * 0.28f;
+    wavePoints_[i].x = static_cast<int16_t>(i);
+    int16_t base = 110;
+    int16_t v = base + static_cast<int16_t>(35.0f * sinf(t)) +
+                static_cast<int16_t>(18.0f * sinf(t * 4.1f));
+    if (v < 20) {
+      v = 20;
+    } else if (v > 200) {
+      v = 200;
+    }
+    wavePoints_[i].y = v;
+  }
+  dataSerial_->ClearData();
+  dataSerial_->AddPoints(wavePoints_, kWavePointCount);
+  chart_->Invalidate();
+}
+
+void ECGMeasuringView::UpdateCountdownLabel() {
+  if (countdownLabel_ == nullptr) {
+    return;
+  }
+  std::string text = std::to_string(countdownSec_) + "s";
+  countdownLabel_->SetText(text.c_str());
+  countdownLabel_->Invalidate();
+}
+
+void ECGMeasuringView::ResetMeasurement() {
+  countdownSec_ = 30;
+  wavePhase_ = 0.0f;
+  UpdateCountdownLabel();
+  UpdateWaveform();
+}
+
+bool ECGMeasuringView::OnTimerTick() {
+  UpdateWaveform();
+  if (countdownSec_ > 0) {
+    countdownSec_--;
+    UpdateCountdownLabel();
+  }
+  return countdownSec_ <= 0;
+}
+
+void ECGMeasuringView::StopMeasurement() {}
+
+UIViewGroup *ECGMeasuringView::InitView() {
+  group_ = new UIViewGroup();
+  if (group_ == nullptr) {
+    return nullptr;
+  }
+  group_->SetPosition(0, 0, Screen::GetInstance().GetWidth(), Screen::GetInstance().GetHeight());
+  group_->SetOnDragListener(static_cast<UIView::OnDragListener *>(ECGMainPresenter::GetInstance()));
+
+  InitUILabel(titleLabel_, 0, 50, 466, 35, "ecg_measuring_title", "ECG",
+              DEFAULT_VECTOR_FONT_FILENAME, 30);
+  titleLabel_->SetAlign(TEXT_ALIGNMENT_CENTER, TEXT_ALIGNMENT_CENTER);
+
+  InitChart();
+  if (chart_ != nullptr) {
+    group_->Add(chart_);
+  }
+
+  InitUILabel(countdownLabel_, 183, 300, 100, 50, "ecg_measuring_countdown", "30s",
+              DEFAULT_VECTOR_FONT_FILENAME, 40);
+  countdownLabel_->SetAlign(TEXT_ALIGNMENT_CENTER, TEXT_ALIGNMENT_CENTER);
+
+  InitUILabel(hintLabel_, 53, 360, 360, 35, "ecg_measuring_hint",
+              "请保持手指接触电极", DEFAULT_VECTOR_FONT_FILENAME, 26);
+  hintLabel_->SetTextColor(Color::GetColorFromRGBA(0xff, 0x3a, 0x3a, 0xff));
+  hintLabel_->SetAlign(TEXT_ALIGNMENT_CENTER, TEXT_ALIGNMENT_CENTER);
+
+  group_->Add(titleLabel_);
+  group_->Add(countdownLabel_);
+  group_->Add(hintLabel_);
+
+  ResetMeasurement();
+  return group_;
+}
+
+} // namespace OHOS
diff --git a/code/sdk/application/wearable/n9/nativeapp/nativeui/ecg/src/ECGModel.cpp b/code/sdk/application/wearable/n9/nativeapp/nativeui/ecg/src/ECGModel.cpp
new file mode 100644 (file)
index 0000000..227142b
--- /dev/null
@@ -0,0 +1,103 @@
+#include "ecg/include/ECGModel.h"
+#include "securec.h"
+#include "wearable_log.h"
+#include <ctime>
+
+namespace OHOS {
+
+ECGModel::ECGModel() { LoadData(); }
+
+ECGModel::~ECGModel() {}
+
+ECGModel *ECGModel::GetInstance() {
+  static ECGModel instance;
+  return &instance;
+}
+
+void ECGModel::LoadData() {
+  historyRecords_.clear();
+  time_t now = time(nullptr);
+  uint32_t ts = now > 0 ? static_cast<uint32_t>(now) : 0;
+
+  ECGRecord r1{ts - 3600, 76, ECGResultType::SINUS_RHYTHM};
+  ECGRecord r2{ts - 86400, 82, ECGResultType::ATRIAL_FIBRILLATION};
+  ECGRecord r3{ts - 172800, 0, ECGResultType::INCONCLUSIVE};
+  historyRecords_.push_back(r1);
+  historyRecords_.push_back(r2);
+  historyRecords_.push_back(r3);
+  lastRecord_ = r1;
+}
+
+ECGRecord ECGModel::GetLastRecord() const { return lastRecord_; }
+
+bool ECGModel::HasHistory() const { return !historyRecords_.empty(); }
+
+const std::deque<ECGRecord> &ECGModel::GetHistoryRecords() const { return historyRecords_; }
+
+std::string ECGModel::FormatShortTimestamp(uint32_t timestamp) const {
+  if (timestamp == 0) {
+    return "--";
+  }
+  time_t t = static_cast<time_t>(timestamp);
+  tm *timeInfo = localtime(&t);
+  if (timeInfo == nullptr) {
+    return "--";
+  }
+  char buf[32] = {0};
+  if (sprintf_s(buf, sizeof(buf), "%02d-%02d %02d:%02d", timeInfo->tm_mon + 1,
+                timeInfo->tm_mday, timeInfo->tm_hour, timeInfo->tm_min) < 0) {
+    return "--";
+  }
+  return std::string(buf);
+}
+
+std::string ECGModel::GetResultText(ECGResultType type) const {
+  switch (type) {
+    case ECGResultType::SINUS_RHYTHM:
+      return "窦性心律";
+    case ECGResultType::ATRIAL_FIBRILLATION:
+      return "房颤";
+    case ECGResultType::INCONCLUSIVE:
+    default:
+      return "不确定";
+  }
+}
+
+ColorType ECGModel::GetResultColor(ECGResultType type) const {
+  Color32 color;
+  switch (type) {
+    case ECGResultType::SINUS_RHYTHM:
+      color.full = 0xff4fc34c;
+      break;
+    case ECGResultType::ATRIAL_FIBRILLATION:
+      color.full = 0xffe79644;
+      break;
+    case ECGResultType::INCONCLUSIVE:
+    default:
+      color.full = 0xff999999;
+      break;
+  }
+  return color;
+}
+
+void ECGModel::SetMeasureResult(uint8_t bpm, ECGResultType result) {
+  resultBpm_ = bpm;
+  resultType_ = result;
+}
+
+uint8_t ECGModel::GetResultBpm() const { return resultBpm_; }
+
+ECGResultType ECGModel::GetResultType() const { return resultType_; }
+
+void ECGModel::AddMeasureToHistory() {
+  time_t now = time(nullptr);
+  uint32_t ts = now > 0 ? static_cast<uint32_t>(now) : 0;
+  ECGRecord record{ts, resultBpm_, resultType_};
+  historyRecords_.push_front(record);
+  if (historyRecords_.size() > 10) {
+    historyRecords_.pop_back();
+  }
+  lastRecord_ = record;
+}
+
+} // namespace OHOS
diff --git a/code/sdk/files/n9/fs/user/res/bloodpressure/belt_loose.bin b/code/sdk/files/n9/fs/user/res/bloodpressure/belt_loose.bin
new file mode 100644 (file)
index 0000000..6d5a17c
Binary files /dev/null and b/code/sdk/files/n9/fs/user/res/bloodpressure/belt_loose.bin differ
diff --git a/code/sdk/files/n9/fs/user/res/bloodpressure/first.bin b/code/sdk/files/n9/fs/user/res/bloodpressure/first.bin
new file mode 100644 (file)
index 0000000..aa5862d
Binary files /dev/null and b/code/sdk/files/n9/fs/user/res/bloodpressure/first.bin differ
diff --git a/code/sdk/files/n9/fs/user/res/bloodpressure/nonet.bin b/code/sdk/files/n9/fs/user/res/bloodpressure/nonet.bin
new file mode 100644 (file)
index 0000000..168458c
Binary files /dev/null and b/code/sdk/files/n9/fs/user/res/bloodpressure/nonet.bin differ
diff --git a/code/sdk/files/n9/fs/user/res/bloodpressure/seat.bin b/code/sdk/files/n9/fs/user/res/bloodpressure/seat.bin
new file mode 100644 (file)
index 0000000..31614b1
Binary files /dev/null and b/code/sdk/files/n9/fs/user/res/bloodpressure/seat.bin differ
diff --git a/code/sdk/files/n9/fs/user/res/bloodpressure/second.bin b/code/sdk/files/n9/fs/user/res/bloodpressure/second.bin
new file mode 100644 (file)
index 0000000..f1bbffd
Binary files /dev/null and b/code/sdk/files/n9/fs/user/res/bloodpressure/second.bin differ
diff --git a/code/sdk/files/n9/fs/user/res/bloodpressure/vectorgrreen_s.bin b/code/sdk/files/n9/fs/user/res/bloodpressure/vectorgrreen_s.bin
new file mode 100644 (file)
index 0000000..befa76a
Binary files /dev/null and b/code/sdk/files/n9/fs/user/res/bloodpressure/vectorgrreen_s.bin differ
diff --git a/code/sdk/files/n9/fs/user/res/bloodpressure/warning_s.bin b/code/sdk/files/n9/fs/user/res/bloodpressure/warning_s.bin
new file mode 100644 (file)
index 0000000..d3e6abb
Binary files /dev/null and b/code/sdk/files/n9/fs/user/res/bloodpressure/warning_s.bin differ
diff --git a/code/sdk/files/n9/fs/user/res/bloodpressure/wear.bin b/code/sdk/files/n9/fs/user/res/bloodpressure/wear.bin
new file mode 100644 (file)
index 0000000..0949a5d
Binary files /dev/null and b/code/sdk/files/n9/fs/user/res/bloodpressure/wear.bin differ
diff --git a/code/sdk/files/n9/fs/user/res/ecg/background.bin b/code/sdk/files/n9/fs/user/res/ecg/background.bin
new file mode 100644 (file)
index 0000000..f52bce7
Binary files /dev/null and b/code/sdk/files/n9/fs/user/res/ecg/background.bin differ
diff --git a/code/sdk/files/n9/fs/user/res/ecg/contact_good.bin b/code/sdk/files/n9/fs/user/res/ecg/contact_good.bin
new file mode 100644 (file)
index 0000000..fcd7a7c
Binary files /dev/null and b/code/sdk/files/n9/fs/user/res/ecg/contact_good.bin differ
diff --git a/code/sdk/files/n9/fs/user/res/ecg/disclaimer.bin b/code/sdk/files/n9/fs/user/res/ecg/disclaimer.bin
new file mode 100644 (file)
index 0000000..6cc5f7c
Binary files /dev/null and b/code/sdk/files/n9/fs/user/res/ecg/disclaimer.bin differ
diff --git a/code/sdk/files/n9/fs/user/res/ecg/heart_gray.bin b/code/sdk/files/n9/fs/user/res/ecg/heart_gray.bin
new file mode 100644 (file)
index 0000000..b70d5ad
Binary files /dev/null and b/code/sdk/files/n9/fs/user/res/ecg/heart_gray.bin differ
diff --git a/code/sdk/files/n9/fs/user/res/ecg/heart_green_s.bin b/code/sdk/files/n9/fs/user/res/ecg/heart_green_s.bin
new file mode 100644 (file)
index 0000000..ec3b77a
Binary files /dev/null and b/code/sdk/files/n9/fs/user/res/ecg/heart_green_s.bin differ
diff --git a/code/sdk/files/n9/fs/user/res/ecg/heart_red.bin b/code/sdk/files/n9/fs/user/res/ecg/heart_red.bin
new file mode 100644 (file)
index 0000000..19ad753
Binary files /dev/null and b/code/sdk/files/n9/fs/user/res/ecg/heart_red.bin differ
diff --git a/code/sdk/files/n9/fs/user/res/ecg/heart_red_s.bin b/code/sdk/files/n9/fs/user/res/ecg/heart_red_s.bin
new file mode 100644 (file)
index 0000000..5847686
Binary files /dev/null and b/code/sdk/files/n9/fs/user/res/ecg/heart_red_s.bin differ
diff --git a/code/sdk/files/n9/fs/user/res/ecg/wait_contact.bin b/code/sdk/files/n9/fs/user/res/ecg/wait_contact.bin
new file mode 100644 (file)
index 0000000..31c8fe4
Binary files /dev/null and b/code/sdk/files/n9/fs/user/res/ecg/wait_contact.bin differ