介紹
如何實現一個簡單的健康生活應用,主要功能包括:
- 用戶可以創建最多6個健康生活任務(早起,喝水,吃蘋果,每日微笑,刷牙,早睡),并設置任務目標、是否開啟提醒、提醒時間、每周任務頻率。
- 用戶可以在主頁面對設置的健康生活任務進行打卡,其中早起、每日微笑、刷牙和早睡只需打卡一次即可完成任務,喝水、吃蘋果需要根據任務目標量多次打卡完成。
- 主頁可顯示當天的健康生活任務完成進度,當天所有任務都打卡完成后,進度為100%,并且用戶的連續打卡天數加一。
- 當用戶連續打卡天數達到3、7、30、50、73、99天時,可以獲得相應的成就。成就在獲得時會以動畫形式彈出,并可以在“成就”頁面查看。
- 用戶可以查看以前的健康生活任務完成情況。
本應用的運行效果如下圖所示:
相關概念
- [@Observed 和 @ObjectLink]:@Observed適用于類,表示類中的數據變化由UI頁面管理;@ObjectLink應用于被@Observed裝飾類的對象。
- [@Consume 和 @Provide]:@Provide作為數據提供者,可以更新子節點的數據,觸發頁面渲染。@Consume檢測到@Provide數據更新后,會發起當前視圖的重新渲染。
- [Flex]:一個功能強大的容器組件,支持橫向布局,豎向布局,子組件均分和流式換行布局。
- [List]:List是很常用的滾動類容器組件之一,它按照水平或者豎直方向線性排列子組件, List的子組件必須是ListItem,它的寬度默認充滿List的寬度。
- [TimePicker]:TimePicker是選擇時間的滑動選擇器組件,默認以00:00至23:59的時間區創建滑動選擇器。
- [Toggle]:組件提供勾選框樣式、狀態按鈕樣式及開關樣式。
- [后臺代理提醒]:使用后臺代理提醒能力后,應用可以被凍結或退出,計時和彈出提醒的功能將被后臺系統服務代理。
- [關系型數據庫(Relational Database,RDB)]:一種基于關系模型來管理數據的數據庫。
環境搭建
軟件要求
- [DevEco Studio]版本:DevEco Studio 3.1 Release。
- OpenHarmony SDK版本:API version 9。
硬件要求
- 開發板類型:[潤和RK3568開發板]。
- OpenHarmony系統:3.2 Release。
環境搭建
完成本篇Codelab我們首先要完成開發環境的搭建,本示例以RK3568開發板為例,參照以下步驟進行:
- [獲取OpenHarmony系統版本]:標準系統解決方案(二進制)。以3.2 Release版本為例:
- 搭建燒錄環境。
- [完成DevEco Device Tool的安裝]
- [完成RK3568開發板的燒錄]
- 搭建開發環境。
代碼結構解讀
本篇Codelab只對核心代碼進行講解,完整代碼可以直接從gitee獲取。
├─entry/src/main/ets // 代碼區
│ ├─common
│ │ ├─constants
│ │ │ └─CommonConstants.ets // 公共常量
│ │ ├─database
│ │ │ ├─rdb // 數據庫
│ │ │ │ ├─RdbHelper.ets
│ │ │ │ ├─RdbHelperImp.ets
│ │ │ │ ├─RdbUtil.ets
│ │ │ │ └─TableHelper.ets
│ │ │ └─tables // 數據庫接口
│ │ │ ├─DayInfoApi.ets
│ │ │ ├─GlobalInfoApi.ets
│ │ │ └─TaskInfoApi.ets
│ │ └─utils
│ │ ├─BroadCast.ets // 通知
│ │ ├─GlobalContext.ets // 全局上下文
│ │ ├─HealthDataSrcMgr.ets // 數據管理單例
│ │ ├─Logger.ets // 日志類
│ │ └─Utils.ets // 工具類
│ ├─entryability
│ │ └─EntryAbility.ets // 程序入口類
│ ├─model // model
│ │ ├─AchieveModel.ets
│ │ ├─DatabaseModel.ets // 數據庫model
│ │ ├─Mine.ets
│ │ ├─NavItemModel.ets // 菜單欄model
│ │ ├─RdbColumnModel.ets // 數據庫表數據
│ │ ├─TaskInitList.ets
│ │ └─WeekCalendarModel.ets // 日歷model
│ ├─pages
│ │ ├─AdvertisingPage.ets // 廣告頁
│ │ ├─MainPage.ets // 應用主頁面
│ │ ├─MinePage.ets // 我的頁面
│ │ ├─SplashPage.ets // 啟動頁
│ │ ├─TaskEditPage.ets // 任務編輯頁面
│ │ └─TaskListPage.ets // 任務列表頁面
│ ├─service
│ │ └─ReminderAgent.ets // 后臺提醒
│ ├─view
│ │ ├─dialog // 彈窗組件
│ │ │ ├─AchievementDialog.ets // 成就彈窗
│ │ │ ├─CustomDialogView.ets // 自定義彈窗
│ │ │ ├─TaskDetailDialog.ets // 打卡彈窗
│ │ │ ├─TaskDialogView.ets // 任務對話框
│ │ │ ├─TaskSettingDialog.ets // 任務編輯相關彈窗
│ │ │ └─UserPrivacyDialog.ets
│ │ ├─home // 主頁面相關組件
│ │ │ ├─AddBtnComponent.ets // 添加任務按鈕組件
│ │ │ ├─HomeTopComponent.ets // 首頁頂部組件
│ │ │ ├─TaskCardComponent.ets // 任務item組件件
│ │ │ └─WeekCalendarComponent.ets // 日歷組件
│ │ ├─task // 任務相關組件
│ │ │ ├─TaskDetailComponent.ets // 任務編輯詳情組件
│ │ │ ├─TaskEditListItem.ets // 任務編輯行內容
│ │ │ └─TaskListComponent.ets // 任務列表組件
│ │ ├─AchievementComponent.ets // 成就頁面
│ │ ├─BadgeCardComponent.ets // 勛章卡片組件
│ │ ├─BadgePanelComponent.ets // 勛章面板組件
│ │ ├─HealthTextComponent.ets // 自定義text組件
│ │ ├─HomeComponent.ets // 首頁頁面
│ │ ├─ListInfo.ets // 用戶信息列表
│ │ ├─TitleBarComponent.ets // 成就標題組件
│ │ └─UserBaseInfo.ets // 用戶基本信息
│ └─viewmodel // viewmodel
│ ├─AchievementInfo.ets // 成就信息
│ ├─AchievementMapInfo.ets // 成就map信息
│ ├─AchievementViewModel.ets // 成就相關模塊
│ ├─BroadCastCallBackInfo.ets // 通知回調信息
│ ├─CalendarViewModel.ets // 日歷相關模塊
│ ├─CardInfo.ets // 成就卡片信息
│ ├─ColumnInfo.ets // 數據庫表結構
│ ├─CommonConstantsInfo.ets // 公共常量信息
│ ├─DayInfo.ets // 每日信息
│ ├─GlobalInfo.ets // 全局信息
│ ├─HomeViewModel.ets // 首頁相關模塊
│ ├─PublishReminderInfo.ets // 發布提醒信息
│ ├─ReminderInfo.ets // 提醒信息
│ ├─TaskInfo.ets // 任務信息
│ ├─TaskViewModel.ets // 任務設置相關模塊
│ ├─WeekCalendarInfo.ets // 日歷信息
│ └─WeekCalendarMethodInfo.ets // 日歷方法信息
└─entry/src/main/resources // 資源文件夾
`HarmonyOS與OpenHarmony鴻蒙文檔籽料:mau123789是v直接拿`
應用架構分析
本應用的基本架構如下圖所示,數據庫為其他服務提供基礎的用戶數據,主要業務包括:用戶可以查看和編輯自己的健康任務并進行打卡、查看成就。UI層提供了承載上述業務的UI界面。
應用主頁面
本節將介紹如何給應用添加一個啟動頁,設計應用的主界面,以及首頁的界面開發和數據展示。
啟動頁
首先我們需要給應用添加一個啟動頁,啟動頁里我們需要用到一個定時器來實現啟動頁展示固定時間后跳轉應用主頁的功能,效果圖如下:
打開應用時會進入此頁面,具體實現邏輯是:
通過修改/entry/src/main/ets/entryability里的loadContent路徑可以改變應用的入口文件,我們需要把入口文件改為我們寫的SplashPage啟動頁面。
// EntryAbility.ets
windowStage.loadContent('pages/SplashPage', (err, data) = > {
if (err.code) {...}
Logger.info('windowStage','Succeeded in loading the content. Data: ' + JSON.stringify(data));
});
在SplashPage啟動頁的文件里通過首選項來實現是否需要彈“權限管理”的彈窗,如果需要彈窗的情況下,用戶點擊同意權限后通過首選項對用戶的操作做持久化保存。相關代碼如下:
// SplashPage.ets
import data_preferences from '@ohos.data.preferences';
onConfirm() {
let preferences = data_preferences.getPreferences(this.context, H_STORE);
preferences.then((res) = > {
res.put(IS_PRIVACY, true).then(() = > {
res.flush();
Logger.info('SplashPage','isPrivacy is put success');
}).catch((err: Error) = > {
Logger.info('SplashPage','isPrivacy put failed. Cause:' + err);
});
})
this.jumpAdPage();
}
exitApp() {
this.context.terminateSelf();
}
jumpAdPage() {
setTimeout(() = > {
router.replaceUrl({ url: 'pages/AdvertisingPage' });
}, Const.LAUNCHER_DELAY_TIME);
}
aboutToAppear() {
let preferences = data_preferences.getPreferences(this.context, H_STORE);
preferences.then((res) = > {
res.get(IS_PRIVACY, false).then((isPrivate) = > {
if (isPrivate === true) {
this.jumpAdPage();
} else {
this.dialogController.open();
}
});
});
}
APP功能入口
我們需要給APP添加底部菜單欄,用于切換不同的應用模塊,由于各個模塊之間屬于完全獨立的情況,并且不需要每次切換都進行界面的刷新,所以我們用到了Tabs,TabContent組件。
本應用一共有首頁(HomeIndex),成就(AchievementIndex)和我的(MineIndex)三個模塊,分別對應Tabs組件的三個子組件TabContent。
// MainPage.ets
TabContent() {
HomeIndex({ homeStore: $homeStore, editedTaskInfo: $editedTaskInfo, editedTaskID: $editedTaskID })
.borderWidth({ bottom: 1 })
.borderColor($r('app.color.primaryBgColor'))
}
.tabBar(this.TabBuilder(TabId.HOME))
.align(Alignment.Start)
TabContent() {
AchievementIndex()
}
.tabBar(this.TabBuilder(TabId.ACHIEVEMENT))
TabContent() {
MineIndex()
.borderWidth({ bottom: 1 })
.borderColor($r('app.color.primaryBgColor'))
}
.tabBar(this.TabBuilder(TabId.MINE))
首頁
首頁包含了任務信息的所有入口,包含任務列表的展示,任務的編輯和新增,上下滾動的過程中頂部導航欄的漸變,日期的切換以及隨著日期切換界面任務列表跟著同步的功能,效果圖如下:
具體代碼實現我們將在下邊分模塊進行說明:
- 導航欄背景漸變
Scroll滾動的過程中,在它的onScroll方法里我們通過計算它Y軸的偏移量來改變當前界面的@State修飾的naviAlpha變量值,進而改變頂部標題的背景色,代碼實現如下:// HomeComponent.ets // 視圖滾動的過程中處理導航欄的透明度 onScrollAction() { this.yOffset = this.scroller.currentOffset().yOffset; if (this.yOffset > Const.DEFAULT_56) { this.naviAlpha = 1; } else { this.naviAlpha = this.yOffset / Const.DEFAULT_56; } }
- 日歷組件
日歷組件主要用到的是一個橫向滑動的Scroll組件。
// WeekCalendarComponent.ets
build() {
Row() {
Column() {
Row() {...}
Scroll(this.scroller) {
Row() {
ForEach(this.homeStore.dateArr, (item: WeekDateModel, index?: number) = > {
Column() {
Text(item.weekTitle)
.fontColor(sameDate(item.date, this.homeStore.showDate) ? $r('app.color.blueColor') : $r('app.color.titleColor'))
Divider()
.color(sameDate(item.date, this.homeStore.showDate) ? $r('app.color.blueColor') : $r('app.color.white'))
Image(this.getProgressImg(item))
}
.onClick(() = > WeekCalendarMethods.calenderItemClickAction(item, index, this.homeStore))
})
}
}
...
.onScrollEdge((event) = > this.onScrollEdgeAction(event))
}
...
}
...
}
手動滑動頁面時,我們通過在onScrollEnd方法里計算Scroll的偏移量來實現分頁的效果,同時Scroll有提供scrollPage()方法可供我們點擊左右按鈕的時候來進行頁面切換。
// WeekCalendarComponent.ets
import display from '@ohos.display';
...
// scroll滾動停止時通過判斷偏移量進行分頁處理
onScrollEndAction() {
if (this.isPageScroll === false) {
let page = Math.round(this.scroller.currentOffset().xOffset / this.scrollWidth);
page = (this.isLoadMore === true) ? page + 1 : page;
if (this.scroller.currentOffset().xOffset % this.scrollWidth != 0 || this.isLoadMore === true) {
let xOffset = page * this.scrollWidth;
this.scroller.scrollTo({ xOffset, yOffset: 0 } as ScrollTo);
this.isLoadMore = false;
}
this.currentPage = this.homeStore.dateArr.length / Const.WEEK_DAY_NUM - page - 1;
Logger.info('HomeIndex', 'onScrollEnd: page ' + page + ', listLength ' + this.homeStore.dateArr.length);
let dayModel: WeekDateModel = this.homeStore.dateArr[Const.WEEK_DAY_NUM * page+this.homeStore.selectedDay];
Logger.info('HomeIndex', 'currentItem: ' + JSON.stringify(dayModel) + ', selectedDay ' + this.homeStore.selectedDay);
this.homeStore!.setSelectedShowDate(dayModel!.date!.getTime());
}
this.isPageScroll = false;
}
我們在需要在Scroll滑動到左邊邊緣的時候去請求更多的歷史數據以便Scroll能一直滑動,通過Scroll的onScrollEdge方法我們可以判斷它是否已滑到邊緣位置。
// WeekCalendarComponent.ets
onScrollEdgeAction(side: Edge) {
if (side === Edge.Top && this.isPageScroll === false) {
Logger.info('HomeIndex', 'onScrollEdge: currentPage ' + this.currentPage);
if ((this.currentPage + 2) * Const.WEEK_DAY_NUM >= this.homeStore.dateArr.length) {
Logger.info('HomeIndex', 'onScrollEdge: load more data');
let date: Date = new Date(this.homeStore.showDate);
date.setDate(date.getDate() - Const.WEEK_DAY_NUM);
this.homeStore.getPreWeekData(date, () = > {});
this.isLoadMore = true;
}
}
}
homeStore主要是請求數據庫的數據并對數據進行處理進而渲染到界面上。
// HomeViewModel.ets
public getPreWeekData(date: Date, callback: Function) {
let weekCalendarInfo: WeekCalendarInfo = getPreviousWeek(date);
// 請求數據庫數據
DayInfoApi.queryList(weekCalendarInfo.strArr, (res: DayInfo[]) = > {
// 數據處理
...
this.dateArr = weekCalendarInfo.arr.concat(...this.dateArr);
})
}
同時我們還需要知道怎么根據當天的日期計算出本周內的所有日期數據。
// WeekCalendarModel.ets
export function getPreviousWeek(showDate: Date): WeekCalendarInfo {
Logger.debug('WeekCalendarModel', 'get week date by date: ' + showDate.toDateString());
let weekCalendarInfo: WeekCalendarInfo = new WeekCalendarInfo();
let arr: Array< WeekDateModel > = [];
let strArr: Array< string > = [];
let currentDay = showDate.getDay() - 1;
// 由于date的getDay()方法返回的是0-6代表周日到周六,我們界面上展示的周一-周日為一周,所以這里要將getDay()數據偏移一天
let currentDay = showDate.getDay() - 1;
if (showDate.getDay() === 0) {
currentDay = 6;
}
// 將日期設置為當前周第一天的數據(周一)
showDate.setDate(showDate.getDate() - currentDay);
for (let index = WEEK_DAY_NUM; index > 0; index--) {
let tempDate = new Date(showDate);
tempDate.setDate(showDate.getDate() - index);
let dateStr = dateToStr(tempDate);
strArr.push(dateStr);
arr.push(new WeekDateModel(WEEK_TITLES[tempDate.getDay()], dateStr, tempDate));
}
Logger.debug('WeekCalendarModel', JSON.stringify(arr));
weekCalendarInfo.arr = arr;
weekCalendarInfo.strArr = strArr;
return weekCalendarInfo;
}
懸浮按鈕
由于首頁右下角有一個懸浮按鈕,所以首頁整體我們用了一個Stack組件,將右下角的懸浮按鈕和頂部的title放在滾動組件層的上邊。// HomeComponent.ets build() { Stack() { Scroll(this.scroller) { Column() { ... // 上部界面組件 Column() { ForEach(this.homeStore.getTaskListOfDay(), (item: TaskInfo) = > { TaskCard({ taskInfoStr: JSON.stringify(item), clickAction: (isClick: boolean) = > this.taskItemAction(item, isClick) }) ... }, (item: TaskInfo) = > JSON.stringify(item))} } } } .onScroll(() = > { this.onScrollAction() }) // 懸浮按鈕 AddBtn({ clickAction: () = > { this.editTaskAction() } }) // 頂部title Row() { Text($r('app.string.EntryAbility_label')) .titleTextStyle() .fontSize($r('app.float.default_24')) .padding({ left: Const.THOUSANDTH_66 }) } .width(Const.THOUSANDTH_1000) .height(Const.DEFAULT_56) .position({ x: 0, y: 0 }) .backgroundColor(`rgba(${WHITE_COLOR_0X},${WHITE_COLOR_0X},${WHITE_COLOR_0X},${this.naviAlpha})`) CustomDialogView() } .allSize() .backgroundColor($r('app.color.primaryBgColor'))
界面跳轉及傳參
首頁任務列表長按時需要跳轉到對應的任務編輯界面,同時點擊懸浮按鈕時需要跳轉到任務列表頁面。
頁面跳轉需要在頭部引入router。// HomeComponent.ets import router from '@ohos.router';
任務item的點擊事件代碼如下
// HomeComponent.ets taskItemAction(item: TaskInfo, isClick: boolean): void { if (!this.homeStore.checkCurrentDay()) { return; } if (isClick) { // 點擊任務打卡 let callback: CustomDialogCallback = { confirmCallback: (taskTemp: TaskInfo) = > { this.onConfirm(taskTemp) }, cancelCallback: () = > { } }; this.broadCast.emit(BroadCastType.SHOW_TASK_DETAIL_DIALOG, [item, callback]); } else { // 長按編輯任務 let editTaskStr: string = JSON.stringify(TaskMapById[item.taskID - 1]); let editTask: ITaskItem = JSON.parse(editTaskStr); ... router.pushUrl({ url: 'pages/TaskEditPage', params: { params: JSON.stringify(editTask) } }); } }
任務創建與編輯
本節將介紹如何創建和編輯健康生活任務。
功能概述
用戶點擊懸浮按鈕進入任務列表頁,點擊任務列表可進入對應任務編輯的頁面中,對任務進行詳細的設置,之后點擊完成按鈕編輯任務后將返回首頁。實現效果如下圖:
任務列表與編輯任務
這里主要為大家介紹添加任務列表頁的實現、任務編輯的實現、以及具體彈窗設置和編輯完成功能的邏輯實現。
任務列表頁
任務列表頁由包括上部分的標題、返回按鈕以及正中間的任務列表組成。實現效果如圖:
使用Navigation以及List組件構成元素,ForEach遍歷生成具體列表。這里是Navigation構成頁面導航:
// TaskListPage.ets
Navigation() {
Column() {
// 頁面中間的列表
TaskList()
}
.width(Const.THOUSANDTH_1000)
.justifyContent(FlexAlign.Center)
}
.size({ width: Const.THOUSANDTH_1000, height: Const.THOUSANDTH_1000 })
.title(Const.ADD_TASK_TITLE)
.titleMode(NavigationTitleMode.Mini)
列表右側有一個判斷是否開啟的文字標識,點擊某個列表需要跳轉到對應的任務編輯頁里。具體的列表實現如下:
// TaskListComponent.ets
@Component
export default struct TaskList {
...
build() {
List({ space: Const.LIST_ITEM_SPACE }) {
ForEach(this.taskList, (item: ITaskItem) = > {
ListItem() {
Row() {
Row() {
Image(item?.icon)
...
Text(item?.taskName).fontSize(Const.DEFAULT_20).fontColor($r('app.color.titleColor'))
}.width(Const.THOUSANDTH_500)
Blank()
...
// 狀態改變
if (item?.isOpen) {
Text($r('app.string.already_open'))
...
}
Image($r('app.media.ic_right_grey'))
...
}
...
}
...
.onClick(() = > {
router.pushUrl({
url: 'pages/TaskEditPage',
params: {
params: formatParams(item),
}
})
})
...
}, (item: ITaskItem) = > JSON.stringify(item))
}
...
}
}
任務編輯頁
任務編輯頁由上方的“編輯任務”標題以及返回按鈕,主體內容的List配置項和下方的完成按鈕組成,實現效果如圖:
由于每一個配置項功能不相同,且邏輯復雜,故將其拆分為五個獨立的組件。
這是任務編輯頁面,由Navigation和一個自定義組件TaskDetail構成:
// TaskEditPage.ets
Navigation() {
Column() {
TaskDetail()
}
.width(Const.THOUSANDTH_1000)
.height(Const.THOUSANDTH_1000)
}
.size({ width: Const.THOUSANDTH_1000, height: Const.THOUSANDTH_1000 })
.title(Const.EDIT_TASK_TITLE)
.titleMode(NavigationTitleMode.Mini)
自定義組件由List以及其子組件ListItem構成:
// TaskDetailComponent.ets
List({ space: Const.LIST_ITEM_SPACE }) {
ListItem() {
TaskChooseItem()
}
...
ListItem() {
TargetSetItem()
}
...
ListItem() {
OpenRemindItem()
}
...
ListItem() {
RemindTimeItem()
}
...
ListItem() {
FrequencyItem()
}
...
}
.width(Const.THOUSANDTH_940)
其中做了禁用判斷,需要任務打開才可以點擊編輯:
// TaskDetailComponent.ets
.enabled(
this.settingParams?.isOpen
)
一些特殊情況的禁用,如每日微笑、每日刷牙的目標設置不可編輯:
// TaskDetailComponent.ets
.enabled(
this.settingParams?.isOpen
&& this.settingParams?.taskID !== taskType.smile
&& this.settingParams?.taskID !== taskType.brushTeeth
)
提醒時間在開啟提醒打開之后才可以編輯:
// TaskDetailComponent.ets
.enabled(this.settingParams?.isOpen && this.settingParams?.isAlarm)
設置完成之后,點擊完成按鈕,會向數據庫更新現在進行改變的狀態信息,并執行之后的邏輯判斷:
// TaskDetailComponent.ets
addTask(taskInfo, context).then((res: number) = > {
GlobalContext.getContext().setObject('taskListChange', true);
// 成功的狀態,成功后跳轉首頁
router.back({
url: 'pages/MainPage',
params: {
editTask: this.backIndexParams(),
}
})
Logger.info('addTaskFinished', JSON.stringify(res));
}).catch((error: Error) = > {
// 失敗的狀態,失敗后彈出提示,并打印錯誤日志
prompt.showToast({
message: Const.SETTING_FINISH_FAILED_MESSAGE
})
Logger.error('addTaskFailed', JSON.stringify(error));
})
任務編輯彈窗
彈窗由封裝的自定義組件CustomDialogView注冊事件,并在點擊對應的編輯項時進行觸發,從而打開彈窗。
CustomDialogView引入實例并注冊事件:
// TaskDialogView.ets
targetSettingDialog: CustomDialogController = new CustomDialogController({
builder: TargetSettingDialog(),
autoCancel: true,
alignment: DialogAlignment.Bottom,
offset: { dx: Const.ZERO, dy: Const.MINUS_20 }
});
...
// 注冊事件
this.broadCast.on(BroadCastType.SHOW_TARGET_SETTING_DIALOG, () = > {
this.targetSettingDialog.open();
})
點擊對應的編輯項進行觸發:
// TaskDetailComponent.ets
.onClick(() = > {
this.broadCast.emit(
BroadCastType.SHOW_TARGET_SETTING_DIALOG);
})
自定義彈窗的實現:
任務目標設置的彈窗較為特殊,故單獨拿出來說明。
因為任務目標設置有三種類型:
- 早睡早起的時間
- 喝水的量度
- 吃蘋果的個數
如下圖所示:
故根據任務的ID進行區分,將同一彈窗復用:
// TaskSettingDialog.ets
if ([taskType.getup, taskType.sleepEarly].indexOf(this.settingParams?.taskID) > Const.HAS_NO_INDEX) {
TimePicker({
selected: new Date(`${new Date().toDateString()} 8:00:00`),
})
.height(Const.THOUSANDTH_800)
.useMilitaryTime(true)
.onChange((value: TimePickerResult) = > {
this.currentTime = formatTime(value);
})
} else {
TextPicker({ range: this.settingParams?.taskID === taskType.drinkWater ? this.drinkRange : this.appleRange })
.width(Const.THOUSANDTH_900,)
.height(Const.THOUSANDTH_800,)
.onChange((value) = > {
this.currentValue = value?.split(' ')[0];
})
}
彈窗確認的時候將修改好的值賦予該項設置,如不符合規則,將彈出提示:
// TaskSettingDialog.ets
// 校驗規則
compareTime(startTime: string, endTime: string) {
if (returnTimeStamp(this.currentTime) < returnTimeStamp(startTime) ||
returnTimeStamp(this.currentTime) > returnTimeStamp(endTime)) {
prompt.showToast({
message: Const.CHOOSE_TIME_OUT_RANGE
})
return false;
}
return true;
}
// 設置修改項
setTargetValue() {
if (this.settingParams?.taskID === taskType.getup) {
if (!this.compareTime(Const.GET_UP_EARLY_TIME, Const.GET_UP_LATE_TIME)) {
return;
}
this.settingParams.targetValue = this.currentTime;
return;
}
if (this.settingParams?.taskID === taskType.sleepEarly) {
if (!this.compareTime(Const.SLEEP_EARLY_TIME, Const.SLEEP_LATE_TIME)) {
return;
}
this.settingParams.targetValue = this.currentTime;
return;
}
this.settingParams.targetValue = this.currentValue;
}
其余彈窗實現基本類似,這里不再贅述。
后臺代理提醒
健康生活App中提供了任務提醒功能,我們用系統提供的后臺代理提醒reminderAgent接口完成相關的開發。
說明: 后臺代理提醒接口需要在module.json5中申請ohos.permission.PUBLISH_AGENT_REMINDER權限,代碼如下:
// module.json5
"requestPermissions": [
{
"name": "ohos.permission.PUBLISH_AGENT_REMINDER"
}
]
后臺代理提醒entrysrcmainetsserviceReminderAgent.ts文件中提供了發布提醒任務、查詢提醒任務、刪除提醒任務三個接口供任務編輯頁面調用,跟隨任務提醒的開關增加、更改、刪除相關后臺代理提醒,代碼如下:開發前請熟悉鴻蒙開發指導文檔:[gitee.com/li-shizhen-skin/harmony-os/blob/master/README.md
]
// ReminderAgent.ets
import reminderAgent from '@ohos.reminderAgentManager';
import notification from '@ohos.notificationManager';
import preferences from '@ohos.data.preferences';
import Logger from '../common/utils/Logger';
import { CommonConstants as Const } from '../common/constants/CommonConstants';
import ReminderInfo from '../viewmodel/ReminderInfo';
import PublishReminderInfo from '../viewmodel/PublishReminderInfo';
// 發布提醒
function publishReminder(params: PublishReminderInfo, context: Context) {
if (!params) {
Logger.error(Const.REMINDER_AGENT_TAG, 'publishReminder params is empty');
return;
}
let notifyId: string = params.notificationId.toString();
hasPreferencesValue(context, notifyId, (preferences: preferences.Preferences, hasValue: boolean) = > {
if (hasValue) {
preferences.get(notifyId, -1, (error: Error, value: preferences.ValueType) = > {
if (typeof value !== 'number') {
return;
}
if (value >= 0) {
reminderAgent.cancelReminder(value).then(() = > {
processReminderData(params, preferences, notifyId);
}).catch((err: Error) = > {
Logger.error(Const.REMINDER_AGENT_TAG, `cancelReminder err: ${err}`);
});
} else {
Logger.error(Const.REMINDER_AGENT_TAG, 'preferences get value error ' + JSON.stringify(error));
}
});
} else {
processReminderData(params, preferences, notifyId);
}
});
}
// 取消提醒
function cancelReminder(reminderId: number, context: Context) {
if (!reminderId) {
Logger.error(Const.REMINDER_AGENT_TAG, 'cancelReminder reminderId is empty');
return;
}
let reminder: string = reminderId.toString();
hasPreferencesValue(context, reminder, (preferences: preferences.Preferences, hasValue: boolean) = > {
if (!hasValue) {
Logger.error(Const.REMINDER_AGENT_TAG, 'cancelReminder preferences value is empty');
return;
}
getPreferencesValue(preferences, reminder);
});
}
// 可通知ID
function hasNotificationId(params: number) {
if (!params) {
Logger.error(Const.REMINDER_AGENT_TAG, 'hasNotificationId params is undefined');
return;
}
return reminderAgent.getValidReminders().then((reminders) = > {
if (!reminders.length) {
return false;
}
let notificationIdList: Array< number > = [];
for (let i = 0; i < reminders.length; i++) {
let notificationId = reminders[i].notificationId;
if (notificationId) {
notificationIdList.push(notificationId);
}
}
const flag = notificationIdList.indexOf(params);
return flag === -1 ? false : true;
});
}
function hasPreferencesValue(context: Context, hasKey: string, callback: Function) {
let preferencesPromise = preferences.getPreferences(context, Const.H_STORE);
preferencesPromise.then((preferences: preferences.Preferences) = > {
preferences.has(hasKey).then((hasValue: boolean) = > {
callback(preferences, hasValue);
});
});
}
// 進程提醒數據
function processReminderData(params: PublishReminderInfo, preferences: preferences.Preferences, notifyId: string) {
let timer = fetchData(params);
reminderAgent.publishReminder(timer).then((reminderId: number) = > {
putPreferencesValue(preferences, notifyId, reminderId);
}).catch((err: Error) = > {
Logger.error(Const.REMINDER_AGENT_TAG, `publishReminder err: ${err}`);
});
}
// 獲取數據
function fetchData(params: PublishReminderInfo): reminderAgent.ReminderRequestAlarm {
return {
reminderType: reminderAgent.ReminderType.REMINDER_TYPE_ALARM,
hour: params.hour || 0,
minute: params.minute || 0,
daysOfWeek: params.daysOfWeek || [],
wantAgent: {
pkgName: Const.PACKAGE_NAME,
abilityName: Const.ENTRY_ABILITY
},
title: params.title || '',
content: params.content || '',
notificationId: params.notificationId || -1,
slotType: notification.SlotType.SOCIAL_COMMUNICATION
}
}
function putPreferencesValue(preferences: preferences.Preferences, putKey: string, putValue: number) {
preferences.put(putKey, putValue).then(() = > {
preferences.flush();
}).catch((error: Error) = > {
Logger.error(Const.REMINDER_AGENT_TAG, 'preferences put value error ' + JSON.stringify(error));
});
}
function getPreferencesValue(preferences: preferences.Preferences, getKey: string) {
preferences.get(getKey, -1).then((value: preferences.ValueType) = > {
if (typeof value !== 'number') {
return;
}
if (value >= 0) {
reminderAgent.cancelReminder(value).then(() = > {
Logger.info(Const.REMINDER_AGENT_TAG, 'cancelReminder promise success');
}).catch((err: Error) = > {
Logger.error(Const.REMINDER_AGENT_TAG, `cancelReminder err: ${err}`);
});
}
}).catch((error: Error) = > {
Logger.error(Const.REMINDER_AGENT_TAG, 'preferences get value error ' + JSON.stringify(error));
});
}
const reminder = {
publishReminder: publishReminder,
cancelReminder: cancelReminder,
hasNotificationId: hasNotificationId
} as ReminderInfo
export default reminder;
審核編輯 黃宇
-
鴻蒙
+關注
關注
57文章
2370瀏覽量
42902 -
OpenHarmony
+關注
關注
25文章
3728瀏覽量
16398
發布評論請先 登錄
相關推薦
評論